diff --git a/.codecov.yml b/.codecov.yml
new file mode 100644
index 000000000000..337b0b15e740
--- /dev/null
+++ b/.codecov.yml
@@ -0,0 +1,57 @@
+---
+
+codecov:
+ notify:
+ after_n_builds: 9 # Number of test matrix+lint jobs uploading coverage
+ wait_for_ci: false
+
+ require_ci_to_pass: false
+
+ token: >- # repo-scoped, upload-only, needed for stability in PRs from forks
+ 2b8c7a7a-7293-4a00-bf02-19bd55a1389b
+
+comment:
+ require_changes: true
+
+coverage:
+ range: 100..100
+ status:
+ patch:
+ default:
+ target: 100%
+ pytest:
+ target: 100%
+ flags:
+ - pytest
+ typing:
+ flags:
+ - MyPy
+ project:
+ default:
+ target: 75%
+ lib:
+ flags:
+ - pytest
+ paths:
+ - awx/
+ target: 75%
+ tests:
+ flags:
+ - pytest
+ paths:
+ - tests/
+ - >-
+ **/test/
+ - >-
+ **/tests/
+ - >-
+ **/test/**
+ - >-
+ **/tests/**
+ target: 95%
+ typing:
+ flags:
+ - MyPy
+ target: 100%
+
+...
diff --git a/.coveragerc b/.coveragerc
index f9ef3447bfec..14aa98d8f966 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,16 +1,6 @@
-[run]
-source = awx
-branch = True
-omit =
- awx/main/migrations/*
- awx/lib/site-packages/*
-
[report]
# Regexes for lines to exclude from consideration
-exclude_lines =
- # Have to re-enable the standard pragma
- pragma: no cover
-
+exclude_also =
# Don't complain about missing debug-only code:
def __repr__
if self\.debug
@@ -23,7 +13,35 @@ exclude_lines =
if 0:
if __name__ == .__main__.:
-ignore_errors = True
+ ^\s*@pytest\.mark\.xfail
+
+[run]
+branch = True
+# NOTE: `disable_warnings` is needed when `pytest-cov` runs in tandem
+# NOTE: with `pytest-xdist`. These warnings are false negative in this
+# NOTE: context.
+#
+# NOTE: It's `coveragepy` that emits the warnings and previously they
+# NOTE: wouldn't get on the radar of `pytest`'s `filterwarnings`
+# NOTE: mechanism. This changed, however, with `pytest >= 8.4`. And
+# NOTE: since we set `filterwarnings = error`, those warnings are being
+# NOTE: raised as exceptions, cascading into `pytest`'s internals and
+# NOTE: causing tracebacks and crashes of the test sessions.
+#
+# Ref:
+# * https://github.com/pytest-dev/pytest-cov/issues/693
+# * https://github.com/pytest-dev/pytest-cov/pull/695
+# * https://github.com/pytest-dev/pytest-cov/pull/696
+disable_warnings =
+ module-not-measured
+omit =
+ awx/main/migrations/*
+ awx/settings/defaults.py
+ awx/settings/*_defaults.py
+source =
+ .
+source_pkgs =
+ awx
[xml]
output = ./reports/coverage.xml
diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md
index 0164155b8102..31368dcb706b 100644
--- a/.github/CODE_OF_CONDUCT.md
+++ b/.github/CODE_OF_CONDUCT.md
@@ -1,3 +1,3 @@
# Community Code of Conduct
-Please see the official [Ansible Community Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html).
+Please see the official [Ansible Community Code of Conduct](https://docs.ansible.com/projects/ansible/latest/community/code_of_conduct.html).
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index 9cca894822ff..144f4599eb84 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -13,12 +13,14 @@ body:
attributes:
label: Please confirm the following
options:
- - label: I agree to follow this project's [code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html).
+ - label: I agree to follow this project's [code of conduct](https://docs.ansible.com/projects/ansible/latest/community/code_of_conduct.html).
required: true
- label: I have checked the [current issues](https://github.com/ansible/awx/issues) for duplicates.
required: true
- label: I understand that AWX is open source software provided for free and that I might not receive a timely response.
required: true
+ - label: I am **NOT** reporting a (potential) security vulnerability. (These should be emailed to `security@ansible.com` instead.)
+ required: true
- type: textarea
id: summary
@@ -42,6 +44,7 @@ body:
label: Select the relevant components
options:
- label: UI
+ - label: UI (tech preview)
- label: API
- label: Docs
- label: Collection
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index b3e5d26591c8..88a8445f2789 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -5,7 +5,7 @@ contact_links:
url: https://github.com/ansible/awx#get-involved
about: For general debugging or technical support please see the Get Involved section of our readme.
- name: 📝 Ansible Code of Conduct
- url: https://docs.ansible.com/ansible/latest/community/code_of_conduct.html?utm_medium=github&utm_source=issue_template_chooser
+ url: https://docs.ansible.com/projects/ansible/latest/community/code_of_conduct.html?utm_medium=github&utm_source=issue_template_chooser
about: AWX uses the Ansible Code of Conduct; ❤ Be nice to other members of the community. ☮ Behave.
- name: 💼 For Enterprise
url: https://www.ansible.com/products/engine?utm_medium=github&utm_source=issue_template_chooser
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
index 53ba31b9f6d1..d1c81fef755f 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -13,7 +13,7 @@ body:
attributes:
label: Please confirm the following
options:
- - label: I agree to follow this project's [code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html).
+ - label: I agree to follow this project's [code of conduct](https://docs.ansible.com/projects/ansible/latest/community/code_of_conduct.html).
required: true
- label: I have checked the [current issues](https://github.com/ansible/awx/issues) for duplicates.
required: true
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 07d23adf000b..99a30cb41125 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -4,7 +4,8 @@
##### ISSUE TYPE
@@ -16,20 +17,14 @@ the change does.
##### COMPONENT NAME
- API
- - UI
- Collection
- CLI
- Docs
- Other
-##### AWX VERSION
-
-```
-
-```
-##### ADDITIONAL INFORMATION
+##### STEPS TO REPRODUCE AND EXTRA INFO
' reports/coverage.xml
+ echo "Injected PR number ${{ github.event.pull_request.number }} into coverage.xml"
+ fi
+
+ - name: Upload test coverage to Codecov
+ if: >-
+ !cancelled()
+ && steps.make-run.outputs.cov-report-files != ''
+ uses: codecov/codecov-action@v4
+ with:
+ fail_ci_if_error: >-
+ ${{
+ toJSON(env.UPSTREAM_REPOSITORY_ID == github.repository_id)
+ }}
+ files: >-
+ ${{ steps.make-run.outputs.cov-report-files }}
+ flags: >-
+ CI-GHA,
+ pytest,
+ OS-${{
+ runner.os
+ }}
+ token: ${{ secrets.CODECOV_TOKEN }}
+ - name: Upload test results to Codecov
+ if: >-
+ !cancelled()
+ && steps.make-run.outputs.test-result-files != ''
+ uses: codecov/test-results-action@v1
+ with:
+ fail_ci_if_error: >-
+ ${{
+ toJSON(env.UPSTREAM_REPOSITORY_ID == github.repository_id)
+ }}
+ files: >-
+ ${{ steps.make-run.outputs.test-result-files }}
+ flags: >-
+ CI-GHA,
+ pytest,
+ OS-${{
+ runner.os
+ }}
+ token: ${{ secrets.CODECOV_TOKEN }}
+
+ - name: Upload test artifacts
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: ${{ matrix.tests.name }}-artifacts
+ path: reports/coverage.xml
+ retention-days: 5
+
+ - name: >-
+ Upload ${{
+ matrix.tests.coverage-upload-name || 'awx'
+ }} jUnit test reports to the unified dashboard
+ if: >-
+ !cancelled()
+ && steps.make-run.outputs.test-result-files != ''
+ && github.event_name == 'push'
+ && env.UPSTREAM_REPOSITORY_ID == github.repository_id
+ && github.ref_name == github.event.repository.default_branch
+ uses: ansible/gh-action-record-test-results@cd5956ead39ec66351d0779470c8cff9638dd2b8
+ with:
+ aggregation-server-url: ${{ vars.PDE_ORG_RESULTS_AGGREGATOR_UPLOAD_URL }}
+ http-auth-password: >-
+ ${{ secrets.PDE_ORG_RESULTS_UPLOAD_PASSWORD }}
+ http-auth-username: >-
+ ${{ vars.PDE_ORG_RESULTS_AGGREGATOR_UPLOAD_USER }}
+ project-component-name: >-
+ ${{ matrix.tests.coverage-upload-name || 'awx' }}
+ test-result-files: >-
+ ${{ steps.make-run.outputs.test-result-files }}
dev-env:
runs-on: ubuntu-latest
+ timeout-minutes: 60
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
+ with:
+ show-progress: false
+
+ - uses: ./.github/actions/setup-python
+ with:
+ python-version: '3.13'
+
+ - uses: ./.github/actions/run_awx_devel
+ id: awx
+ with:
+ build-ui: false
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ private-github-key: ${{ secrets.PRIVATE_GITHUB_KEY }}
+
+ - name: Run live dev env tests
+ run: docker exec tools_awx_1 /bin/bash -c "make live_test"
- - name: Run smoke test
- run: make github_ci_setup && ansible-playbook tools/docker-compose/ansible/smoke-test.yml -v
+ - uses: ./.github/actions/upload_awx_devel_logs
+ if: always()
+ with:
+ log-filename: live-tests.log
awx-operator:
runs-on: ubuntu-latest
+ timeout-minutes: 60
+ env:
+ DEBUG_OUTPUT_DIR: /tmp/awx_operator_molecule_test
steps:
- name: Checkout awx
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
with:
+ show-progress: false
path: awx
+ - uses: ./awx/.github/actions/setup-ssh-agent
+ with:
+ ssh-private-key: ${{ secrets.PRIVATE_GITHUB_KEY }}
+
- name: Checkout awx-operator
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
with:
+ show-progress: false\
repository: ansible/awx-operator
path: awx-operator
- - name: Get python version from Makefile
- working-directory: awx
- run: echo py_version=`make PYTHON_VERSION` >> $GITHUB_ENV
-
- - name: Install python ${{ env.py_version }}
- uses: actions/setup-python@v2
+ - name: Setup python, referencing action at awx relative path
+ uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
- python-version: ${{ env.py_version }}
+ python-version: '3.12'
- name: Install playbook dependencies
run: |
- python3 -m pip install docker
+ python -m pip install docker
+ - name: Check Python version
+ working-directory: awx
+ run: |
+ make print-PYTHON
+
- name: Build AWX image
working-directory: awx
run: |
- ansible-playbook -v tools/ansible/build.yml \
- -e headless=yes \
- -e awx_image=awx \
- -e awx_image_tag=ci \
- -e ansible_python_interpreter=$(which python3)
+ VERSION=`make version-for-buildyml` make awx-kube-build
+ env:
+ COMPOSE_TAG: ci
+ DEV_DOCKER_TAG_BASE: local
+ HEADLESS: yes
- name: Run test deployment with awx-operator
working-directory: awx-operator
+ id: awx_operator_test
+ timeout-minutes: 60
+ continue-on-error: true
run: |
- python3 -m pip install -r molecule/requirements.txt
- ansible-galaxy collection install -r molecule/requirements.yml
- sudo rm -f $(which kustomize)
- make kustomize
- KUSTOMIZE_PATH=$(readlink -f bin/kustomize) molecule -v test -s kind
+ set +e
+ timeout 15m bash -elc '
+ python -m pip install -r molecule/requirements.txt
+ python -m pip install PyYAML # for awx/tools/scripts/rewrite-awx-operator-requirements.py
+ $(realpath ../awx/tools/scripts/rewrite-awx-operator-requirements.py) molecule/requirements.yml $(realpath ../awx)
+ ansible-galaxy collection install -r molecule/requirements.yml
+ sudo rm -f $(which kustomize)
+ make kustomize
+ KUSTOMIZE_PATH=$(readlink -f bin/kustomize) molecule -v test -s kind -- --skip-tags=replicas
+ '
+ rc=$?
+ if [ $rc -eq 124 ]; then
+ echo "timed_out=true" >> "$GITHUB_OUTPUT"
+ fi
+ exit $rc
env:
- AWX_TEST_IMAGE: awx
+ AWX_TEST_IMAGE: local/awx
AWX_TEST_VERSION: ci
+ AWX_EE_TEST_IMAGE: quay.io/ansible/awx-ee:latest
+ STORE_DEBUG_OUTPUT: true
+
+ - name: Collect awx-operator logs on timeout
+ # Only run on timeout; normal failures should use molecule's built-in log collection.
+ if: steps.awx_operator_test.outputs.timed_out == 'true'
+ run: |
+ mkdir -p "$DEBUG_OUTPUT_DIR"
+ if command -v kind >/dev/null 2>&1; then
+ for cluster in $(kind get clusters 2>/dev/null); do
+ kind export logs "$DEBUG_OUTPUT_DIR/$cluster" --name "$cluster" || true
+ done
+ fi
+ if command -v kubectl >/dev/null 2>&1; then
+ kubectl get all -A -o wide > "$DEBUG_OUTPUT_DIR/kubectl-get-all.txt" || true
+ kubectl get pods -A -o wide > "$DEBUG_OUTPUT_DIR/kubectl-get-pods.txt" || true
+ kubectl describe pods -A > "$DEBUG_OUTPUT_DIR/kubectl-describe-pods.txt" || true
+ fi
+ docker ps -a > "$DEBUG_OUTPUT_DIR/docker-ps.txt" || true
+
+ - name: Upload debug output
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: awx-operator-debug-output
+ path: ${{ env.DEBUG_OUTPUT_DIR }}
+
+ - name: Fail awx-operator check if test deployment failed
+ if: steps.awx_operator_test.outcome != 'success'
+ run: exit 1
collection-sanity:
name: awx_collection sanity
runs-on: ubuntu-latest
+ timeout-minutes: 30
strategy:
fail-fast: false
+ matrix:
+ ansible:
+ - stable-2.17
+ # - devel
steps:
- - uses: actions/checkout@v2
+ - name: Perform sanity testing
+ uses: ansible-community/ansible-test-gh-action@release/v1
+ with:
+ ansible-core-version: ${{ matrix.ansible }}
+ codecov-token: ${{ secrets.CODECOV_TOKEN }}
+ collection-root: awx_collection
+ pre-test-cmd: >-
+ ansible-playbook
+ -i localhost,
+ tools/template_galaxy.yml
+ -e collection_package=awx
+ -e collection_namespace=awx
+ -e collection_version=1.0.0
+ -e '{"awx_template_version": false}'
+ testing-type: sanity
- # The containers that GitHub Actions use have Ansible installed, so upgrade to make sure we have the latest version.
- - name: Upgrade ansible-core
- run: python3 -m pip install --upgrade ansible-core
+ - name: Upload awx jUnit test reports to the unified dashboard
+ if: >-
+ !cancelled()
+ && steps.make-run.outputs.test-result-files != ''
+ && github.event_name == 'push'
+ && env.UPSTREAM_REPOSITORY_ID == github.repository_id
+ && github.ref_name == github.event.repository.default_branch
+ uses: ansible/gh-action-record-test-results@cd5956ead39ec66351d0779470c8cff9638dd2b8
+ with:
+ aggregation-server-url: ${{ vars.PDE_ORG_RESULTS_AGGREGATOR_UPLOAD_URL }}
+ http-auth-password: >-
+ ${{ secrets.PDE_ORG_RESULTS_UPLOAD_PASSWORD }}
+ http-auth-username: >-
+ ${{ vars.PDE_ORG_RESULTS_AGGREGATOR_UPLOAD_USER }}
+ project-component-name: awx
+ test-result-files: >-
+ ${{ steps.make-run.outputs.test-result-files }}
+
+ collection-integration:
+ name: awx_collection integration
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ strategy:
+ fail-fast: false
+ matrix:
+ target-regex:
+ - name: a-h
+ regex: ^[a-h]
+ - name: i-p
+ regex: ^[i-p]
+ - name: r-z0-9
+ regex: ^[r-z0-9]
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ show-progress: false
- - name: Run sanity tests
- run: make test_collection_sanity
+ - uses: ./.github/actions/setup-python
+ with:
+ python-version: '3.13'
+
+ - name: Remove system ansible to avoid conflicts
+ run: |
+ python -m pip uninstall -y ansible ansible-core || true
+
+ - uses: ./.github/actions/run_awx_devel
+ id: awx
+ with:
+ build-ui: false
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ private-github-key: ${{ secrets.PRIVATE_GITHUB_KEY }}
+
+ - name: Install dependencies for running tests
+ run: |
+ python -m pip install -e ./awxkit/
+ python -m pip install -r awx_collection/requirements.txt
+ hash -r # Rehash to pick up newly installed scripts
+
+ - name: Run integration tests
+ id: make-run
+ run: |
+ echo "::remove-matcher owner=python::" # Disable annoying annotations from setup-python
+ echo '[general]' > ~/.tower_cli.cfg
+ echo 'host = https://${{ steps.awx.outputs.ip }}:8043' >> ~/.tower_cli.cfg
+ echo 'username = admin' >> ~/.tower_cli.cfg
+ echo 'password = password' >> ~/.tower_cli.cfg
+ echo 'verify_ssl = false' >> ~/.tower_cli.cfg
+ TARGETS="$(ls awx_collection/tests/integration/targets | grep '${{ matrix.target-regex.regex }}' | tr '\n' ' ')"
+ export PYTHONPATH="$(python -c 'import site; print(":".join(site.getsitepackages()))')${PYTHONPATH:+:$PYTHONPATH}"
+ make COLLECTION_VERSION=100.100.100-git COLLECTION_TEST_TARGET="--requirements $TARGETS" test_collection_integration
env:
- # needed due to cgroupsv2. This is fixed, but a stable release
- # with the fix has not been made yet.
ANSIBLE_TEST_PREFER_PODMAN: 1
+
+ - name: Upload test coverage to Codecov
+ if: >-
+ !cancelled()
+ && steps.make-run.outputs.cov-report-files != ''
+ uses: codecov/codecov-action@v4
+ with:
+ fail_ci_if_error: >-
+ ${{
+ toJSON(env.UPSTREAM_REPOSITORY_ID == github.repository_id)
+ }}
+ files: >-
+ ${{ steps.make-run.outputs.cov-report-files }}
+ flags: >-
+ CI-GHA,
+ ansible-test,
+ integration,
+ OS-${{
+ runner.os
+ }}
+ token: ${{ secrets.CODECOV_TOKEN }}
+
+ # Upload coverage report as artifact
+ - uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: coverage-${{ matrix.target-regex.name }}
+ path: ~/.ansible/collections/ansible_collections/awx/awx/tests/output/coverage/
+ retention-days: 1
+
+ - uses: ./.github/actions/upload_awx_devel_logs
+ if: always()
+ with:
+ log-filename: collection-integration-${{ matrix.target-regex.name }}.log
+
+ collection-integration-coverage-combine:
+ name: combine awx_collection integration coverage
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ needs:
+ - collection-integration
+ strategy:
+ fail-fast: false
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+ show-progress: false
+
+ - uses: ./.github/actions/setup-python
+ with:
+ python-version: '3.13'
+
+ - name: Remove system ansible to avoid conflicts
+ run: |
+ python -m pip uninstall -y ansible ansible-core || true
+
+ - name: Upgrade ansible-core
+ run: python -m pip install --upgrade ansible-core
+
+ - name: Download coverage artifacts
+ uses: actions/download-artifact@v4
+ with:
+ merge-multiple: true
+ path: coverage
+ pattern: coverage-*
+
+ - name: Combine coverage
+ run: |
+ make COLLECTION_VERSION=100.100.100-git install_collection
+ mkdir -p ~/.ansible/collections/ansible_collections/awx/awx/tests/output/coverage
+ cp -rv coverage/* ~/.ansible/collections/ansible_collections/awx/awx/tests/output/coverage/
+ cd ~/.ansible/collections/ansible_collections/awx/awx
+ hash -r # Rehash to pick up newly installed scripts
+ PATH="$(python -c 'import sys; import os; print(os.path.dirname(sys.executable))'):$PATH" ansible-test coverage combine --requirements
+ PATH="$(python -c 'import sys; import os; print(os.path.dirname(sys.executable))'):$PATH" ansible-test coverage html
+ echo '## AWX Collection Integration Coverage' >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ PATH="$(python -c 'import sys; import os; print(os.path.dirname(sys.executable))'):$PATH" ansible-test coverage report >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ echo >> $GITHUB_STEP_SUMMARY
+ echo '## AWX Collection Integration Coverage HTML' >> $GITHUB_STEP_SUMMARY
+ echo 'Download the HTML artifacts to view the coverage report.' >> $GITHUB_STEP_SUMMARY
+
+ - name: Upload coverage report as artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: awx-collection-integration-coverage-html
+ path: ~/.ansible/collections/ansible_collections/awx/awx/tests/output/reports/coverage
diff --git a/.github/workflows/dab-release.yml b/.github/workflows/dab-release.yml
new file mode 100644
index 000000000000..6ebbe05bcecf
--- /dev/null
+++ b/.github/workflows/dab-release.yml
@@ -0,0 +1,57 @@
+---
+name: django-ansible-base requirements update
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: '0 6 * * *' # once an day @ 6 AM
+permissions:
+ pull-requests: write
+ contents: write
+jobs:
+ dab-pin-newest:
+ if: (github.repository_owner == 'ansible' && endsWith(github.repository, 'awx')) || github.event_name != 'schedule'
+ runs-on: ubuntu-latest
+ steps:
+ - id: dab-release
+ name: Get current django-ansible-base release version
+ uses: pozetroninc/github-action-get-latest-release@2a61c339ea7ef0a336d1daa35ef0cb1418e7676c # v0.8.0
+ with:
+ owner: ansible
+ repo: django-ansible-base
+ excludes: prerelease, draft
+
+ - name: Check out respository code
+ uses: actions/checkout@v4
+
+ - id: dab-pinned
+ name: Get current django-ansible-base pinned version
+ run:
+ echo "version=$(requirements/django-ansible-base-pinned-version.sh)" >> "$GITHUB_OUTPUT"
+
+ - name: Update django-ansible-base pinned version to upstream release
+ run:
+ requirements/django-ansible-base-pinned-version.sh -s ${{ steps.dab-release.outputs.release }}
+
+ - name: Create Pull Request
+ uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6
+ with:
+ base: devel
+ branch: bump-django-ansible-base
+ title: Bump django-ansible-base to ${{ steps.dab-release.outputs.release }}
+ body: |
+ ##### SUMMARY
+ Automated .github/workflows/dab-release.yml
+
+ django-ansible-base upstream released version == ${{ steps.dab-release.outputs.release }}
+ requirements_git.txt django-ansible-base pinned version == ${{ steps.dab-pinned.outputs.version }}
+
+ ##### ISSUE TYPE
+ - Bug, Docs Fix or other nominal change
+
+ ##### COMPONENT NAME
+ - API
+
+ commit-message: |
+ Update django-ansible-base version to ${{ steps.dab-pinned.outputs.version }}
+ add-paths:
+ requirements/requirements_git.txt
diff --git a/.github/workflows/devel_images.yml b/.github/workflows/devel_images.yml
index dbc1a4937bee..213d90b8eca9 100644
--- a/.github/workflows/devel_images.yml
+++ b/.github/workflows/devel_images.yml
@@ -2,44 +2,78 @@
name: Build/Push Development Images
env:
LC_ALL: "C.UTF-8" # prevent ERROR: Ansible could not initialize the preferred locale: unsupported locale setting
+ DOCKER_CACHE: "--no-cache" # using the cache will not rebuild git requirements and other things
on:
+ workflow_dispatch:
push:
branches:
- devel
- release_*
+ - feature_*
+ - stable-*
jobs:
- push:
- if: endsWith(github.repository, '/awx') || startsWith(github.ref, 'refs/heads/release_')
+ push-development-images:
runs-on: ubuntu-latest
+ timeout-minutes: 120
permissions:
packages: write
contents: read
+ strategy:
+ fail-fast: false
+ matrix:
+ build-targets:
+ - image-name: awx_devel
+ make-target: docker-compose-buildx
+ - image-name: awx_kube_devel
+ make-target: awx-kube-dev-buildx
+ - image-name: awx
+ make-target: awx-kube-buildx
steps:
- - uses: actions/checkout@v2
- - name: Get python version from Makefile
- run: echo py_version=`make PYTHON_VERSION` >> $GITHUB_ENV
+ - name: Skipping build of awx image for non-awx repository
+ run: |
+ echo "Skipping build of awx image for non-awx repository"
+ exit 0
+ if: matrix.build-targets.image-name == 'awx' && !endsWith(github.repository, '/awx')
- - name: Install python ${{ env.py_version }}
- uses: actions/setup-python@v2
+ - uses: actions/checkout@v4
with:
- python-version: ${{ env.py_version }}
+ show-progress: false
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Set GITHUB_ENV variables
+ run: |
+ echo "DEV_DOCKER_TAG_BASE=ghcr.io/${OWNER,,}" >> $GITHUB_ENV
+ echo "COMPOSE_TAG=${GITHUB_REF##*/}" >> $GITHUB_ENV
+ env:
+ OWNER: '${{ github.repository_owner }}'
+
+ - uses: ./.github/actions/setup-python
- name: Log in to registry
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- - name: Pre-pull image to warm build cache
- run: |
- docker pull ghcr.io/${{ github.repository_owner }}/awx_devel:${GITHUB_REF##*/} || :
- docker pull ghcr.io/${{ github.repository_owner }}/awx_kube_devel:${GITHUB_REF##*/} || :
+ - name: Setup node and npm for the new UI build
+ uses: actions/setup-node@v2
+ with:
+ node-version: '18'
+ if: matrix.build-targets.image-name == 'awx'
- - name: Build images
+ - name: Prebuild new UI for awx image (to speed up build process)
run: |
- DEV_DOCKER_TAG_BASE=ghcr.io/${{ github.repository_owner }} COMPOSE_TAG=${GITHUB_REF##*/} make docker-compose-build
- DEV_DOCKER_TAG_BASE=ghcr.io/${{ github.repository_owner }} COMPOSE_TAG=${GITHUB_REF##*/} make awx-kube-dev-build
+ make ui
+ if: matrix.build-targets.image-name == 'awx'
+
+ - uses: ./.github/actions/setup-ssh-agent
+ with:
+ ssh-private-key: ${{ secrets.PRIVATE_GITHUB_KEY }}
- - name: Push image
+ - name: Build and push AWX devel images
run: |
- docker push ghcr.io/${{ github.repository_owner }}/awx_devel:${GITHUB_REF##*/}
- docker push ghcr.io/${{ github.repository_owner }}/awx_kube_devel:${GITHUB_REF##*/}
+ make ${{ matrix.build-targets.make-target }}
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 000000000000..ec6c9f4a4f2f
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,23 @@
+---
+name: Docsite CI
+on:
+ pull_request:
+jobs:
+ docsite-build:
+ name: docsite test build
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ show-progress: false
+
+ - uses: ./.github/actions/setup-python
+ with:
+ python-version: '3.x'
+
+ - name: install tox
+ run: pip install tox
+
+ - name: Assure docs can be built
+ run: tox -e docs
diff --git a/.github/workflows/e2e_test.yml b/.github/workflows/e2e_test.yml
deleted file mode 100644
index 788042baed97..000000000000
--- a/.github/workflows/e2e_test.yml
+++ /dev/null
@@ -1,109 +0,0 @@
----
-name: E2E Tests
-env:
- LC_ALL: "C.UTF-8" # prevent ERROR: Ansible could not initialize the preferred locale: unsupported locale setting
-
-on:
- pull_request_target:
- types: [labeled]
-jobs:
- e2e-test:
- if: contains(github.event.pull_request.labels.*.name, 'qe:e2e')
- runs-on: ubuntu-latest
- timeout-minutes: 40
- permissions:
- packages: write
- contents: read
- strategy:
- matrix:
- job: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]
-
- steps:
- - uses: actions/checkout@v2
-
- - name: Get python version from Makefile
- run: echo py_version=`make PYTHON_VERSION` >> $GITHUB_ENV
-
- - name: Install python ${{ env.py_version }}
- uses: actions/setup-python@v2
- with:
- python-version: ${{ env.py_version }}
-
- - name: Install system deps
- run: sudo apt-get install -y gettext
-
- - name: Log in to registry
- run: |
- echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
-
- - name: Pre-pull image to warm build cache
- run: |
- docker pull ghcr.io/${{ github.repository_owner }}/awx_devel:${{ github.base_ref }}
-
- - name: Build UI
- run: |
- DEV_DOCKER_TAG_BASE=ghcr.io/${{ github.repository_owner }} COMPOSE_TAG=${{ github.base_ref }} make ui-devel
-
- - name: Start AWX
- run: |
- DEV_DOCKER_TAG_BASE=ghcr.io/${{ github.repository_owner }} COMPOSE_TAG=${{ github.base_ref }} make docker-compose &> make-docker-compose-output.log &
-
- - name: Pull awx_cypress_base image
- run: |
- docker pull quay.io/awx/awx_cypress_base:latest
-
- - name: Checkout test project
- uses: actions/checkout@v2
- with:
- repository: ${{ github.repository_owner }}/tower-qa
- ssh-key: ${{ secrets.QA_REPO_KEY }}
- path: tower-qa
- ref: devel
-
- - name: Build cypress
- run: |
- cd ${{ secrets.E2E_PROJECT }}/ui-tests/awx-pf-tests
- docker build -t awx-pf-tests .
-
- - name: Update default AWX password
- run: |
- while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' -k https://localhost:8043/api/v2/ping/)" != "200" ]]
- do
- echo "Waiting for AWX..."
- sleep 5;
- done
- echo "AWX is up, updating the password..."
- docker exec -i tools_awx_1 sh <<-EOSH
- awx-manage update_password --username=admin --password=password
- EOSH
-
- - name: Run E2E tests
- env:
- CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
- run: |
- export COMMIT_INFO_BRANCH=$GITHUB_HEAD_REF
- export COMMIT_INFO_AUTHOR=$GITHUB_ACTOR
- export COMMIT_INFO_SHA=$GITHUB_SHA
- export COMMIT_INFO_REMOTE=$GITHUB_REPOSITORY_OWNER
- cd ${{ secrets.E2E_PROJECT }}/ui-tests/awx-pf-tests
- AWX_IP=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' tools_awx_1)
- printenv > .env
- echo "Executing tests:"
- docker run \
- --network '_sources_default' \
- --ipc=host \
- --env-file=.env \
- -e CYPRESS_baseUrl="https://$AWX_IP:8043" \
- -e CYPRESS_AWX_E2E_USERNAME=admin \
- -e CYPRESS_AWX_E2E_PASSWORD='password' \
- -e COMMAND="npm run cypress-concurrently-gha" \
- -v /dev/shm:/dev/shm \
- -v $PWD:/e2e \
- -w /e2e \
- awx-pf-tests run --project .
-
- - name: Save AWX logs
- uses: actions/upload-artifact@v2
- with:
- name: AWX-logs-${{ matrix.job }}
- path: make-docker-compose-output.log
diff --git a/.github/workflows/feature_branch_deletion.yml b/.github/workflows/feature_branch_deletion.yml
index 3e574b211b80..0de807aaf4f8 100644
--- a/.github/workflows/feature_branch_deletion.yml
+++ b/.github/workflows/feature_branch_deletion.yml
@@ -2,13 +2,12 @@
name: Feature branch deletion cleanup
env:
LC_ALL: "C.UTF-8" # prevent ERROR: Ansible could not initialize the preferred locale: unsupported locale setting
-on:
- delete:
- branches:
- - feature_**
+on: delete
jobs:
- push:
+ branch_delete:
+ if: ${{ github.event.ref_type == 'branch' && startsWith(github.event.ref, 'feature_') }}
runs-on: ubuntu-latest
+ timeout-minutes: 20
permissions:
packages: write
contents: read
@@ -21,6 +20,4 @@ jobs:
run: |
ansible localhost -c local, -m command -a "{{ ansible_python_interpreter + ' -m pip install boto3'}}"
ansible localhost -c local -m aws_s3 \
- -a "bucket=awx-public-ci-files object=${GITHUB_REF##*/}/schema.json mode=delete permission=public-read"
-
-
+ -a "bucket=awx-public-ci-files object=${{ github.event.repository.name }}/${GITHUB_REF##*/}/schema.json mode=delobj permission=public-read"
diff --git a/.github/workflows/label_issue.yml b/.github/workflows/label_issue.yml
index ead15724bbc0..952685cd5476 100644
--- a/.github/workflows/label_issue.yml
+++ b/.github/workflows/label_issue.yml
@@ -6,14 +6,19 @@ on:
- opened
- reopened
+permissions:
+ contents: write # to fetch code
+ issues: write # to label issues
+
jobs:
triage:
runs-on: ubuntu-latest
+ timeout-minutes: 20
name: Label Issue
steps:
- name: Label Issue
- uses: github/issue-labeler@v2.4.1
+ uses: github/issue-labeler@v3.1
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
not-before: 2021-12-07T07:00:00Z
@@ -22,12 +27,18 @@ jobs:
community:
runs-on: ubuntu-latest
+ timeout-minutes: 20
name: Label Issue - Community
steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-python@v4
+ - uses: actions/checkout@v4
+ with:
+ show-progress: false
+
+ - uses: ./.github/actions/setup-python
+
- name: Install python requests
run: pip install requests
+
- name: Check if user is a member of Ansible org
uses: jannekem/run-python-script-action@v1
id: check_user
diff --git a/.github/workflows/label_pr.yml b/.github/workflows/label_pr.yml
index 8e3f8b81a210..43f1e3a2915c 100644
--- a/.github/workflows/label_pr.yml
+++ b/.github/workflows/label_pr.yml
@@ -7,9 +7,14 @@ on:
- reopened
- synchronize
+permissions:
+ contents: write # to determine modified files (actions/labeler)
+ pull-requests: write # to add labels to PRs (actions/labeler)
+
jobs:
triage:
runs-on: ubuntu-latest
+ timeout-minutes: 20
name: Label PR
steps:
@@ -21,10 +26,17 @@ jobs:
community:
runs-on: ubuntu-latest
+ timeout-minutes: 20
name: Label PR - Community
steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-python@v4
+ - uses: actions/checkout@v4
+ with:
+ show-progress: false
+
+ - uses: ./.github/actions/setup-python
+ with:
+ python-version: '3.x'
+
- name: Install python requests
run: pip install requests
- name: Check if user is a member of Ansible org
diff --git a/.github/workflows/pr_body_check.yml b/.github/workflows/pr_body_check.yml
index 7ddcabd3d64b..9532aa87ede1 100644
--- a/.github/workflows/pr_body_check.yml
+++ b/.github/workflows/pr_body_check.yml
@@ -7,8 +7,10 @@ on:
types: [opened, edited, reopened, synchronize]
jobs:
pr-check:
+ if: github.repository_owner == 'ansible' && endsWith(github.repository, 'awx')
name: Scan PR description for semantic versioning keywords
runs-on: ubuntu-latest
+ timeout-minutes: 20
permissions:
packages: write
contents: read
diff --git a/.github/workflows/promote.yml b/.github/workflows/promote.yml
index 6bd36f010200..ba723c07f975 100644
--- a/.github/workflows/promote.yml
+++ b/.github/workflows/promote.yml
@@ -7,21 +7,36 @@ env:
on:
release:
types: [published]
+ workflow_dispatch:
+ inputs:
+ tag_name:
+ description: 'Name for the tag of the release.'
+ required: true
+permissions:
+ contents: read # to fetch code (actions/checkout)
jobs:
promote:
+ if: endsWith(github.repository, '/awx')
runs-on: ubuntu-latest
+ timeout-minutes: 90
steps:
- - name: Checkout awx
- uses: actions/checkout@v2
+ - name: Set GitHub Env vars for workflow_dispatch event
+ if: ${{ github.event_name == 'workflow_dispatch' }}
+ run: |
+ echo "TAG_NAME=${{ github.event.inputs.tag_name }}" >> $GITHUB_ENV
- - name: Get python version from Makefile
- run: echo py_version=`make PYTHON_VERSION` >> $GITHUB_ENV
+ - name: Set GitHub Env vars if release event
+ if: ${{ github.event_name == 'release' }}
+ run: |
+ echo "TAG_NAME=${{ github.event.release.tag_name }}" >> $GITHUB_ENV
- - name: Install python ${{ env.py_version }}
- uses: actions/setup-python@v2
+ - name: Checkout awx
+ uses: actions/checkout@v4
with:
- python-version: ${{ env.py_version }}
+ show-progress: false
+
+ - uses: ./.github/actions/setup-python
- name: Install dependencies
run: |
@@ -36,14 +51,23 @@ jobs:
if: ${{ github.repository_owner != 'ansible' }}
- name: Build collection and publish to galaxy
+ env:
+ COLLECTION_NAMESPACE: ${{ env.collection_namespace }}
+ COLLECTION_VERSION: ${{ env.TAG_NAME }}
+ COLLECTION_TEMPLATE_VERSION: true
run: |
- COLLECTION_TEMPLATE_VERSION=true COLLECTION_NAMESPACE=${{ env.collection_namespace }} make build_collection
- if [ "$(curl --head -sw '%{http_code}' https://galaxy.ansible.com/download/${{ env.collection_namespace }}-awx-${{ github.event.release.tag_name }}.tar.gz | tail -1)" == "302" ] ; then \
- echo "Galaxy release already done"; \
- else \
+ sudo apt-get install jq
+ make build_collection
+ count=$(curl -s https://galaxy.ansible.com/api/v3/plugin/ansible/search/collection-versions/\?namespace\=${COLLECTION_NAMESPACE}\&name\=awx\&version\=${COLLECTION_VERSION} | jq .meta.count)
+ if [[ "$count" == "1" ]]; then
+ echo "Galaxy release already done";
+ elif [[ "$count" == "0" ]]; then
ansible-galaxy collection publish \
--token=${{ secrets.GALAXY_TOKEN }} \
- awx_collection_build/${{ env.collection_namespace }}-awx-${{ github.event.release.tag_name }}.tar.gz; \
+ awx_collection_build/${COLLECTION_NAMESPACE}-awx-${COLLECTION_VERSION}.tar.gz;
+ else
+ echo "Unexpected count from galaxy search: $count";
+ exit 1;
fi
- name: Set official pypi info
@@ -55,9 +79,11 @@ jobs:
if: ${{ github.repository_owner != 'ansible' }}
- name: Build awxkit and upload to pypi
+ env:
+ SETUPTOOLS_SCM_PRETEND_VERSION: ${{ env.TAG_NAME }}
run: |
git reset --hard
- cd awxkit && python3 setup.py bdist_wheel
+ cd awxkit && python3 setup.py sdist bdist_wheel
twine upload \
-r ${{ env.pypi_repo }} \
-u ${{ secrets.PYPI_USERNAME }} \
@@ -74,11 +100,15 @@ jobs:
- name: Re-tag and promote awx image
run: |
- docker pull ghcr.io/${{ github.repository }}:${{ github.event.release.tag_name }}
- docker tag ghcr.io/${{ github.repository }}:${{ github.event.release.tag_name }} quay.io/${{ github.repository }}:${{ github.event.release.tag_name }}
- docker tag ghcr.io/${{ github.repository }}:${{ github.event.release.tag_name }} quay.io/${{ github.repository }}:latest
- docker push quay.io/${{ github.repository }}:${{ github.event.release.tag_name }}
- docker push quay.io/${{ github.repository }}:latest
- docker pull ghcr.io/${{ github.repository_owner }}/awx-ee:${{ github.event.release.tag_name }}
- docker tag ghcr.io/${{ github.repository_owner }}/awx-ee:${{ github.event.release.tag_name }} quay.io/${{ github.repository_owner }}/awx-ee:${{ github.event.release.tag_name }}
- docker push quay.io/${{ github.repository_owner }}/awx-ee:${{ github.event.release.tag_name }}
+ docker buildx imagetools create \
+ ghcr.io/${{ github.repository }}:${{ env.TAG_NAME }} \
+ --tag quay.io/${{ github.repository }}:${{ env.TAG_NAME }}
+ docker buildx imagetools create \
+ ghcr.io/${{ github.repository }}:${{ env.TAG_NAME }} \
+ --tag quay.io/${{ github.repository }}:latest
+
+ - name: Re-tag and promote awx-ee image
+ run: |
+ docker buildx imagetools create \
+ ghcr.io/${{ github.repository_owner }}/awx-ee:${{ env.TAG_NAME }} \
+ --tag quay.io/${{ github.repository_owner }}/awx-ee:${{ env.TAG_NAME }}
diff --git a/.github/workflows/sonarcloud_pr.yml b/.github/workflows/sonarcloud_pr.yml
new file mode 100644
index 000000000000..380118d7b5c4
--- /dev/null
+++ b/.github/workflows/sonarcloud_pr.yml
@@ -0,0 +1,248 @@
+# SonarCloud Analysis Workflow for awx
+#
+# This workflow runs SonarCloud analysis triggered by CI workflow completion.
+# It is split into two separate jobs for clarity and maintainability:
+#
+# FLOW: CI completes → workflow_run triggers this workflow → appropriate job runs
+#
+# JOB 1: sonar-pr-analysis (for PRs)
+# - Triggered by: workflow_run (CI on pull_request)
+# - Steps: Download coverage → Get PR info → Get changed files → Run SonarCloud PR analysis
+# - Scans: All changed files in the PR (Python, YAML, JSON, etc.)
+# - Quality gate: Focuses on new/changed code in PR only
+#
+# JOB 2: sonar-branch-analysis (for long-lived branches)
+# - Triggered by: workflow_run (CI on push to devel)
+# - Steps: Download coverage → Run SonarCloud branch analysis
+# - Scans: Full codebase
+# - Quality gate: Focuses on overall project health
+#
+# This ensures coverage data is always available from CI before analysis runs.
+#
+# What files are scanned:
+# - All files in the repository that SonarCloud can analyze
+# - Excludes: tests, scripts, dev environments, external collections (see sonar-project.properties)
+
+
+# With much help from:
+# https://community.sonarsource.com/t/how-to-use-sonarcloud-with-a-forked-repository-on-github/7363/30
+# https://community.sonarsource.com/t/how-to-use-sonarcloud-with-a-forked-repository-on-github/7363/32
+name: SonarCloud
+on:
+ workflow_run: # This is triggered by CI being completed.
+ workflows:
+ - CI
+ types:
+ - completed
+permissions: read-all
+jobs:
+ sonar-pr-analysis:
+ name: SonarCloud PR Analysis
+ runs-on: ubuntu-latest
+ if: |
+ github.event.workflow_run.conclusion == 'success' &&
+ github.event.workflow_run.event == 'pull_request' &&
+ github.repository == 'ansible/awx'
+ steps:
+ - uses: actions/checkout@v4
+
+ # Download all individual coverage artifacts from CI workflow
+ - name: Download coverage artifacts
+ uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ workflow: CI
+ run_id: ${{ github.event.workflow_run.id }}
+ pattern: api-test-artifacts
+
+ # Extract PR metadata from workflow_run event
+ - name: Set PR metadata and prepare files for analysis
+ env:
+ COMMIT_SHA: ${{ github.event.workflow_run.head_sha }}
+ REPO_NAME: ${{ github.event.repository.full_name }}
+ HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ # Find all downloaded coverage XML files
+ coverage_files=$(find . -name "coverage.xml" -type f | tr '\n' ',' | sed 's/,$//')
+ echo "Found coverage files: $coverage_files"
+ echo "COVERAGE_PATHS=$coverage_files" >> $GITHUB_ENV
+
+ # Extract PR number from first coverage.xml file found
+ first_coverage=$(find . -name "coverage.xml" -type f | head -1)
+ if [ -f "$first_coverage" ]; then
+ PR_NUMBER=$(grep -m 1 ' host_id
+ # so we can associate emitted events to Host objects
+ self.runner_callback.host_map[hostname] = hv.get('remote_tower_id', '')
+ file_content = '#! /usr/bin/env python3\n# -*- coding: utf-8 -*-\nprint(%r)\n' % json.dumps(script_data)
+ return self.write_private_data_file(private_data_dir, file_name, file_content, sub_dir='inventory', file_permissions=0o700)
+
def build_inventory(self, instance, private_data_dir):
script_params = dict(hostvars=True, towervars=True)
if hasattr(instance, 'job_slice_number'):
script_params['slice_number'] = instance.job_slice_number
script_params['slice_count'] = instance.job_slice_count
- script_data = instance.inventory.get_script_data(**script_params)
- # maintain a list of host_name --> host_id
- # so we can associate emitted events to Host objects
- self.runner_callback.host_map = {hostname: hv.pop('remote_tower_id', '') for hostname, hv in script_data.get('_meta', {}).get('hostvars', {}).items()}
- file_content = '#! /usr/bin/env python3\n# -*- coding: utf-8 -*-\nprint(%r)\n' % json.dumps(script_data)
- return self.write_private_data_file(private_data_dir, 'hosts', file_content, sub_dir='inventory', file_permissions=0o700)
+
+ return self.write_inventory_file(instance.inventory, private_data_dir, 'hosts', script_params)
def build_args(self, instance, private_data_dir, passwords):
raise NotImplementedError
@@ -399,7 +505,7 @@ def acquire_lock(self, project, unified_job_id=None):
except IOError as e:
if e.errno not in (errno.EAGAIN, errno.EACCES):
os.close(self.lock_fd)
- logger.error("I/O error({0}) while trying to aquire lock on file [{1}]: {2}".format(e.errno, lock_path, e.strerror))
+ logger.error("I/O error({0}) while trying to acquire lock on file [{1}]: {2}".format(e.errno, lock_path, e.strerror))
raise
else:
if not emitted_lockfile_log:
@@ -435,20 +541,40 @@ def final_run_hook(self, instance, status, private_data_dir):
Hook for any steps to run after job/task is marked as complete.
"""
instance.log_lifecycle("finalize_run")
- artifact_dir = os.path.join(private_data_dir, 'artifacts', str(self.instance.id))
- collections_info = os.path.join(artifact_dir, 'collections.json')
- ansible_version_file = os.path.join(artifact_dir, 'ansible_version.txt')
-
- if os.path.exists(collections_info):
- with open(collections_info) as ee_json_info:
- ee_collections_info = json.loads(ee_json_info.read())
- instance.installed_collections = ee_collections_info
- instance.save(update_fields=['installed_collections'])
- if os.path.exists(ansible_version_file):
- with open(ansible_version_file) as ee_ansible_info:
- ansible_version_info = ee_ansible_info.readline()
- instance.ansible_version = ansible_version_info
- instance.save(update_fields=['ansible_version'])
+
+ # Run task manager appropriately for speculative dependencies
+ if instance.unifiedjob_blocked_jobs.exists():
+ ScheduleTaskManager().schedule()
+ if instance.spawned_by_workflow:
+ ScheduleWorkflowManager().schedule()
+
+ def should_use_fact_cache(self):
+ return False
+
+ def transition_status(self, pk: int) -> bool:
+ """Atomically transition status to running, if False returned, another process got it"""
+ with transaction.atomic():
+ # Explanation of parts for the fetch:
+ # .values - avoid loading a full object, this is known to lead to deadlocks due to signals
+ # the signals load other related rows which another process may be locking, and happens in practice
+ # of=('self',) - keeps FK tables out of the lock list, another way deadlocks can happen
+ # .get - just load the single job
+ instance_data = UnifiedJob.objects.select_for_update(of=('self',)).values('status', 'cancel_flag').get(pk=pk)
+
+ # If status is not waiting (obtained under lock) then this process does not have clearence to run
+ if instance_data['status'] == 'waiting':
+ if instance_data['cancel_flag']:
+ updated_status = 'canceled'
+ else:
+ updated_status = 'running'
+ # Explanation of the update:
+ # .filter - again, do not load the full object
+ # .update - a bulk update on just that one row, avoid loading unintended data
+ UnifiedJob.objects.filter(pk=pk).update(status=updated_status, start_args='')
+ elif instance_data['status'] == 'running':
+ logger.info(f'Job {pk} is being ran by another process, exiting')
+ return False
+ return True
@with_path_cleanup
@with_signal_handling
@@ -456,21 +582,17 @@ def run(self, pk, **kwargs):
"""
Run the job/task and capture its output.
"""
- self.instance = self.model.objects.get(pk=pk)
- if self.instance.status != 'canceled' and self.instance.cancel_flag:
- self.instance = self.update_model(self.instance.pk, start_args='', status='canceled')
- if self.instance.status not in ACTIVE_STATES:
- # Prevent starting the job if it has been reaped or handled by another process.
- raise RuntimeError(f'Not starting {self.instance.status} task pk={pk} because {self.instance.status} is not a valid active state')
-
- if self.instance.execution_environment_id is None:
- from awx.main.signals import disable_activity_stream
+ if not self.instance: # Used to skip fetch for local runs
+ if not self.transition_status(pk):
+ logger.info(f'Job {pk} is being ran by another process, exiting')
+ return
- with disable_activity_stream():
- self.instance = self.update_model(self.instance.pk, execution_environment=self.instance.resolve_execution_environment())
+ # Load the instance
+ self.instance = self.update_model(pk)
+ if self.instance.status != 'running':
+ logger.error(f'Not starting {self.instance.status} task pk={pk} because its status "{self.instance.status}" is not expected')
+ return
- # self.instance because of the update_model pattern and when it's used in callback handlers
- self.instance = self.update_model(pk, status='running', start_args='') # blank field to remove encrypted passwords
self.instance.websocket_emit_status("running")
status, rc = 'error', None
self.runner_callback.event_ct = 0
@@ -483,12 +605,20 @@ def run(self, pk, **kwargs):
private_data_dir = None
try:
+ if self.instance.execution_environment_id is None:
+ from awx.main.signals import disable_activity_stream
+
+ with disable_activity_stream():
+ self.instance = self.update_model(self.instance.pk, execution_environment=self.instance.resolve_execution_environment())
+
self.instance.send_notification_templates("running")
private_data_dir = self.build_private_data_dir(self.instance)
self.pre_run_hook(self.instance, private_data_dir)
+ evaluate_policy(self.instance)
self.build_project_dir(self.instance, private_data_dir)
self.instance.log_lifecycle("preparing_playbook")
if self.instance.cancel_flag or signal_callback():
+ logger.debug(f'detected pre-run cancel flag for {self.instance.log_format}')
self.instance = self.update_model(self.instance.pk, status='canceled')
if self.instance.status != 'running':
@@ -520,9 +650,13 @@ def run(self, pk, **kwargs):
credentials = self.build_credentials_list(self.instance)
+ container_root = None
+ if settings.IS_K8S and isinstance(self.instance, ProjectUpdate):
+ container_root = private_data_dir
+
for credential in credentials:
if credential:
- credential.credential_type.inject_credential(credential, env, self.safe_cred_env, args, private_data_dir)
+ credential.credential_type.inject_credential(credential, env, self.safe_cred_env, args, private_data_dir, container_root=container_root)
self.runner_callback.safe_env.update(self.safe_cred_env)
@@ -548,7 +682,8 @@ def run(self, pk, **kwargs):
params['module'] = self.build_module_name(self.instance)
params['module_args'] = self.build_module_args(self.instance)
- if getattr(self.instance, 'use_fact_cache', False):
+ # TODO: refactor into a better BasTask method
+ if self.should_use_fact_cache():
# Enable Ansible fact cache.
params['fact_cache_type'] = 'jsonfile'
else:
@@ -606,12 +741,11 @@ def run(self, pk, **kwargs):
elif status == 'canceled':
self.instance = self.update_model(pk)
cancel_flag_value = getattr(self.instance, 'cancel_flag', False)
- if (cancel_flag_value is False) and signal_callback():
+ if cancel_flag_value is False:
self.runner_callback.delay_update(skip_if_already_set=True, job_explanation="Task was canceled due to receiving a shutdown signal.")
status = 'failed'
- elif cancel_flag_value is False:
- self.runner_callback.delay_update(skip_if_already_set=True, job_explanation="The running ansible process received a shutdown signal.")
- status = 'failed'
+ except PolicyEvaluationError as exc:
+ self.runner_callback.delay_update(job_explanation=str(exc), result_traceback=str(exc))
except ReceptorNodeNotFound as exc:
self.runner_callback.delay_update(job_explanation=str(exc))
except Exception:
@@ -637,8 +771,11 @@ def run(self, pk, **kwargs):
# Field host_status_counts is used as a metric to check if event processing is finished
# we send notifications if it is, if not, callback receiver will send them
+ if not self.instance:
+ logger.error(f'Unified job pk={pk} appears to be deleted while running')
+ return
if (self.instance.host_status_counts is not None) or (not self.runner_callback.wrapup_event_dispatched):
- self.instance.send_notification_templates('succeeded' if status == 'successful' else 'failed')
+ events_processed_hook(self.instance)
try:
self.final_run_hook(self.instance, status, private_data_dir)
@@ -686,6 +823,7 @@ def get_sync_needs(self, project, scm_branch=None):
logger.debug(f'Project not available locally, {self.instance.id} will sync with remote')
sync_needs.append(source_update_tag)
+ # Determine whether or not this project sync needs to populate the cache for Ansible content, roles and collections
has_cache = os.path.exists(os.path.join(project.get_cache_path(), project.cache_id))
# Galaxy requirements are not supported for manual projects
if project.scm_type and ((not has_cache) or branch_override):
@@ -732,6 +870,7 @@ def sync_and_copy_without_lock(self, project, private_data_dir, scm_branch=None)
try:
# the job private_data_dir is passed so sync can download roles and collections there
sync_task = RunProjectUpdate(job_private_data_dir=private_data_dir)
+ sync_task.instance = local_project_sync # avoids "waiting" status check, performance
sync_task.run(local_project_sync.id)
local_project_sync.refresh_from_db()
self.instance = self.update_model(self.instance.pk, scm_revision=local_project_sync.scm_revision)
@@ -759,7 +898,7 @@ def sync_and_copy_without_lock(self, project, private_data_dir, scm_branch=None)
def sync_and_copy(self, project, private_data_dir, scm_branch=None):
self.acquire_lock(project, self.instance.id)
-
+ is_commit = False
try:
original_branch = None
failed_reason = project.get_reason_if_failed()
@@ -771,6 +910,7 @@ def sync_and_copy(self, project, private_data_dir, scm_branch=None):
if os.path.exists(project_path):
git_repo = git.Repo(project_path)
if git_repo.head.is_detached:
+ is_commit = True
original_branch = git_repo.head.commit
else:
original_branch = git_repo.active_branch
@@ -782,7 +922,11 @@ def sync_and_copy(self, project, private_data_dir, scm_branch=None):
# for git project syncs, non-default branches can be problems
# restore to branch the repo was on before this run
try:
- original_branch.checkout()
+ if is_commit:
+ git_repo.head.set_commit(original_branch)
+ git_repo.head.reset(index=True, working_tree=True)
+ else:
+ original_branch.checkout()
except Exception:
# this could have failed due to dirty tree, but difficult to predict all cases
logger.exception(f'Failed to restore project repo to prior state after {self.instance.id}')
@@ -790,7 +934,7 @@ def sync_and_copy(self, project, private_data_dir, scm_branch=None):
self.release_lock(project)
-@task(queue=get_local_queuename)
+@task(queue=get_task_queuename)
class RunJob(SourceControlMixin, BaseTask):
"""
Run a job using ansible-playbook.
@@ -892,7 +1036,7 @@ def build_env(self, job, private_data_dir, private_data_files=None):
cred_files = private_data_files.get('credentials', {})
for cloud_cred in job.cloud_credentials:
if cloud_cred and cloud_cred.credential_type.namespace == 'openstack' and cred_files.get(cloud_cred, ''):
- env['OS_CLIENT_CONFIG_FILE'] = to_container_path(cred_files.get(cloud_cred, ''), private_data_dir)
+ env['OS_CLIENT_CONFIG_FILE'] = get_incontainer_path(cred_files.get(cloud_cred, ''), private_data_dir)
for network_cred in job.network_credentials:
env['ANSIBLE_NET_USERNAME'] = network_cred.get_input('username', default='')
@@ -907,10 +1051,15 @@ def build_env(self, job, private_data_dir, private_data_files=None):
if authorize:
env['ANSIBLE_NET_AUTH_PASS'] = network_cred.get_input('authorize_password', default='')
- path_vars = (
- ('ANSIBLE_COLLECTIONS_PATHS', 'collections_paths', 'requirements_collections', '~/.ansible/collections:/usr/share/ansible/collections'),
+ path_vars = [
('ANSIBLE_ROLES_PATH', 'roles_path', 'requirements_roles', '~/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles'),
- )
+ ('ANSIBLE_COLLECTIONS_PATH', 'collections_path', 'requirements_collections', '~/.ansible/collections:/usr/share/ansible/collections'),
+ ]
+
+ if flag_enabled("FEATURE_INDIRECT_NODE_COUNTING_ENABLED"):
+ path_vars.append(
+ ('ANSIBLE_CALLBACK_PLUGINS', 'callback_plugins', 'plugins_path', '~/.ansible/plugins:/plugins/callback:/usr/share/ansible/plugins/callback'),
+ )
config_values = read_ansible_config(os.path.join(private_data_dir, 'project'), list(map(lambda x: x[1], path_vars)))
@@ -927,6 +1076,11 @@ def build_env(self, job, private_data_dir, private_data_files=None):
paths = [os.path.join(CONTAINER_ROOT, folder)] + paths
env[env_key] = os.pathsep.join(paths)
+ if flag_enabled("FEATURE_INDIRECT_NODE_COUNTING_ENABLED"):
+ env['ANSIBLE_CALLBACKS_ENABLED'] = 'indirect_instance_count'
+ if 'callbacks_enabled' in config_values:
+ env['ANSIBLE_CALLBACKS_ENABLED'] += ':' + config_values['callbacks_enabled']
+
return env
def build_args(self, job, private_data_dir, passwords):
@@ -998,6 +1152,9 @@ def build_args(self, job, private_data_dir, passwords):
return args
+ def should_use_fact_cache(self):
+ return self.instance.use_fact_cache
+
def build_playbook_path_relative_to_cwd(self, job, private_data_dir):
return job.playbook
@@ -1063,8 +1220,11 @@ def pre_run_hook(self, job, private_data_dir):
# Fetch "cached" fact data from prior runs and put on the disk
# where ansible expects to find it
- if job.use_fact_cache:
- self.facts_write_time = self.instance.start_job_fact_cache(os.path.join(private_data_dir, 'artifacts', str(job.id), 'fact_cache'))
+ if self.should_use_fact_cache():
+ job.log_lifecycle("start_job_fact_cache")
+ self.hosts_with_facts_cached = start_fact_cache(
+ job.get_hosts_for_fact_cache(), artifacts_dir=os.path.join(private_data_dir, 'artifacts', str(job.id)), inventory_id=job.inventory_id
+ )
def build_project_dir(self, job, private_data_dir):
self.sync_and_copy(job.project, private_data_dir, scm_branch=job.scm_branch)
@@ -1073,15 +1233,17 @@ def post_run_hook(self, job, status):
super(RunJob, self).post_run_hook(job, status)
job.refresh_from_db(fields=['job_env'])
private_data_dir = job.job_env.get('AWX_PRIVATE_DATA_DIR')
- if (not private_data_dir) or (not hasattr(self, 'facts_write_time')):
+ if not private_data_dir:
# If there's no private data dir, that means we didn't get into the
# actual `run()` call; this _usually_ means something failed in
# the pre_run_hook method
return
- if job.use_fact_cache:
- job.finish_job_fact_cache(
- os.path.join(private_data_dir, 'artifacts', str(job.id), 'fact_cache'),
- self.facts_write_time,
+ if self.should_use_fact_cache() and self.runner_callback.artifacts_processed:
+ job.log_lifecycle("finish_job_fact_cache")
+ finish_fact_cache(
+ artifacts_dir=os.path.join(private_data_dir, 'artifacts', str(job.id)),
+ job_id=job.id,
+ inventory_id=job.inventory_id,
)
def final_run_hook(self, job, status, private_data_dir):
@@ -1095,7 +1257,7 @@ def final_run_hook(self, job, status, private_data_dir):
update_inventory_computed_fields.delay(inventory.id)
-@task(queue=get_local_queuename)
+@task(queue=get_task_queuename)
class RunProjectUpdate(BaseTask):
model = ProjectUpdate
event_model = ProjectUpdateEvent
@@ -1242,7 +1404,7 @@ def build_extra_vars_file(self, project_update, private_data_dir):
galaxy_creds_are_defined = project_update.project.organization and project_update.project.organization.galaxy_credentials.exists()
if not galaxy_creds_are_defined and (settings.AWX_ROLES_ENABLED or settings.AWX_COLLECTIONS_ENABLED):
- logger.warning('Galaxy role/collection syncing is enabled, but no ' f'credentials are configured for {project_update.project.organization}.')
+ logger.warning(f'Galaxy role/collection syncing is enabled, but no credentials are configured for {project_update.project.organization}.')
extra_vars.update(
{
@@ -1266,7 +1428,7 @@ def build_extra_vars_file(self, project_update, private_data_dir):
extra_vars['scm_refspec'] = project_update.scm_refspec
elif project_update.project.allow_override:
# If branch is override-able, do extra fetch for all branches
- extra_vars['scm_refspec'] = 'refs/heads/*:refs/remotes/origin/*'
+ extra_vars['scm_refspec'] = '+refs/heads/*:refs/remotes/origin/*'
if project_update.scm_type == 'archive':
# for raw archive, prevent error moving files between volumes
@@ -1356,6 +1518,17 @@ def make_local_copy(project, job_private_data_dir):
shutil.copytree(cache_subpath, dest_subpath, symlinks=True)
logger.debug('{0} {1} prepared {2} from cache'.format(type(project).__name__, project.pk, dest_subpath))
+ if flag_enabled("FEATURE_INDIRECT_NODE_COUNTING_ENABLED"):
+ # copy the special callback (not stdout type) plugin to get list of collections
+ pdd_plugins_path = os.path.join(job_private_data_dir, 'plugins_path')
+ if not os.path.exists(pdd_plugins_path):
+ os.mkdir(pdd_plugins_path)
+ from awx.playbooks import library
+
+ plugin_file_source = os.path.join(library.__path__._path[0], 'indirect_instance_count.py')
+ plugin_file_dest = os.path.join(pdd_plugins_path, 'indirect_instance_count.py')
+ shutil.copyfile(plugin_file_source, plugin_file_dest)
+
def post_run_hook(self, instance, status):
super(RunProjectUpdate, self).post_run_hook(instance, status)
# To avoid hangs, very important to release lock even if errors happen here
@@ -1416,8 +1589,13 @@ def build_execution_environment_params(self, instance, private_data_dir):
)
return params
+ def build_credentials_list(self, project_update):
+ if project_update.scm_type == 'insights' and project_update.credential:
+ return [project_update.credential]
+ return []
+
-@task(queue=get_local_queuename)
+@task(queue=get_task_queuename)
class RunInventoryUpdate(SourceControlMixin, BaseTask):
model = InventoryUpdate
event_model = InventoryUpdateEvent
@@ -1464,8 +1642,6 @@ def build_env(self, inventory_update, private_data_dir, private_data_files=None)
if injector is not None:
env = injector.build_env(inventory_update, env, private_data_dir, private_data_files)
- # All CLOUD_PROVIDERS sources implement as inventory plugin from collection
- env['ANSIBLE_INVENTORY_ENABLED'] = 'auto'
if inventory_update.source == 'scm':
for env_k in inventory_update.source_vars_dict:
@@ -1475,7 +1651,7 @@ def build_env(self, inventory_update, private_data_dir, private_data_files=None)
raise NotImplementedError('Cannot update file sources through the task system.')
if inventory_update.source == 'scm' and inventory_update.source_project_update:
- env_key = 'ANSIBLE_COLLECTIONS_PATHS'
+ env_key = 'ANSIBLE_COLLECTIONS_PATH'
config_setting = 'collections_paths'
folder = 'requirements_collections'
default = '~/.ansible/collections:/usr/share/ansible/collections'
@@ -1493,12 +1669,12 @@ def build_env(self, inventory_update, private_data_dir, private_data_files=None)
paths = [config_values[config_setting]] + paths
paths = [os.path.join(CONTAINER_ROOT, folder)] + paths
env[env_key] = os.pathsep.join(paths)
- if 'ANSIBLE_COLLECTIONS_PATHS' in env:
- paths = env['ANSIBLE_COLLECTIONS_PATHS'].split(':')
+ if 'ANSIBLE_COLLECTIONS_PATH' in env:
+ paths = env['ANSIBLE_COLLECTIONS_PATH'].split(':')
else:
paths = ['~/.ansible/collections', '/usr/share/ansible/collections']
paths.append('/usr/share/automation-controller/collections')
- env['ANSIBLE_COLLECTIONS_PATHS'] = os.pathsep.join(paths)
+ env['ANSIBLE_COLLECTIONS_PATH'] = os.pathsep.join(paths)
return env
@@ -1518,6 +1694,22 @@ def build_args(self, inventory_update, private_data_dir, passwords):
args = ['ansible-inventory', '--list', '--export']
+ # special case for constructed inventories, we pass source inventories from database
+ # these must come in order, and in order _before_ the constructed inventory itself
+ if inventory_update.inventory.kind == 'constructed':
+ inventory_update.log_lifecycle("start_job_fact_cache")
+ for input_inventory in inventory_update.inventory.input_inventories.all():
+ args.append('-i')
+ script_params = dict(hostvars=True, towervars=True)
+ source_inv_path = self.write_inventory_file(input_inventory, private_data_dir, f'hosts_{input_inventory.id}', script_params)
+ args.append(get_incontainer_path(source_inv_path, private_data_dir))
+ # Include any facts from input inventories so they can be used in filters
+ start_fact_cache(
+ input_inventory.hosts.only(*HOST_FACTS_FIELDS),
+ artifacts_dir=os.path.join(private_data_dir, 'artifacts', str(inventory_update.id)),
+ inventory_id=input_inventory.id,
+ )
+
# Add arguments for the source inventory file/script/thing
rel_path = self.pseudo_build_inventory(inventory_update, private_data_dir)
container_location = os.path.join(CONTAINER_ROOT, rel_path)
@@ -1525,6 +1717,11 @@ def build_args(self, inventory_update, private_data_dir, passwords):
args.append('-i')
args.append(container_location)
+ # Added this in order to allow older versions of ansible-inventory https://github.com/ansible/ansible/pull/79596
+ # limit should be usable in ansible-inventory 2.15+
+ if inventory_update.limit:
+ args.append('--limit')
+ args.append(inventory_update.limit)
args.append('--output')
args.append(os.path.join(CONTAINER_ROOT, 'artifacts', str(inventory_update.id), 'output.json'))
@@ -1540,6 +1737,9 @@ def build_args(self, inventory_update, private_data_dir, passwords):
return args
+ def should_use_fact_cache(self):
+ return bool(self.instance.source == 'constructed')
+
def build_inventory(self, inventory_update, private_data_dir):
return None # what runner expects in order to not deal with inventory
@@ -1581,7 +1781,7 @@ def build_project_dir(self, inventory_update, private_data_dir):
if inventory_update.source == 'scm':
if not source_project:
raise RuntimeError('Could not find project to run SCM inventory update from.')
- self.sync_and_copy(source_project, private_data_dir)
+ self.sync_and_copy(source_project, private_data_dir, scm_branch=inventory_update.inventory_source.scm_branch)
else:
# If source is not SCM make an empty project directory, content is built inside inventory folder
super(RunInventoryUpdate, self).build_project_dir(inventory_update, private_data_dir)
@@ -1658,7 +1858,7 @@ def post_run_hook(self, inventory_update, status):
raise PostRunError('Error occured while saving inventory data, see traceback or server logs', status='error', tb=traceback.format_exc())
-@task(queue=get_local_queuename)
+@task(queue=get_task_queuename)
class RunAdHocCommand(BaseTask):
"""
Run an ad hoc command using ansible.
@@ -1811,7 +2011,7 @@ def get_password_prompts(self, passwords={}):
return d
-@task(queue=get_local_queuename)
+@task(queue=get_task_queuename)
class RunSystemJob(BaseTask):
model = SystemJob
event_model = SystemJobEvent
@@ -1831,6 +2031,8 @@ def build_args(self, system_job, private_data_dir, passwords):
if system_job.job_type in ('cleanup_jobs', 'cleanup_activitystream'):
if 'days' in json_vars:
args.extend(['--days', str(json_vars.get('days', 60))])
+ if 'batch_size' in json_vars:
+ args.extend(['--batch-size', str(json_vars['batch_size'])])
if 'dry_run' in json_vars and json_vars['dry_run']:
args.extend(['--dry-run'])
if system_job.job_type == 'cleanup_jobs':
diff --git a/awx/main/tasks/policy.py b/awx/main/tasks/policy.py
new file mode 100644
index 000000000000..e629e919bbdb
--- /dev/null
+++ b/awx/main/tasks/policy.py
@@ -0,0 +1,457 @@
+import json
+import tempfile
+import contextlib
+
+from pprint import pformat
+
+from typing import Optional, Union
+
+from django.conf import settings
+from django.utils.translation import gettext_lazy as _
+from opa_client import OpaClient
+from opa_client.base import BaseClient
+from requests import HTTPError
+from rest_framework import serializers
+from rest_framework import fields
+
+from awx.main import models
+from awx.main.exceptions import PolicyEvaluationError
+
+# Monkey patching opa_client.base.BaseClient to fix retries and timeout settings
+_original_opa_base_client_init = BaseClient.__init__
+
+
+def _opa_base_client_init_fix(
+ self,
+ host: str = "localhost",
+ port: int = 8181,
+ version: str = "v1",
+ ssl: bool = False,
+ cert: Optional[Union[str, tuple]] = None,
+ headers: Optional[dict] = None,
+ retries: int = 2,
+ timeout: float = 1.5,
+):
+ _original_opa_base_client_init(self, host, port, version, ssl, cert, headers)
+ self.retries = retries
+ self.timeout = timeout
+
+
+BaseClient.__init__ = _opa_base_client_init_fix
+
+
+class _TeamSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = models.Team
+ fields = ('id', 'name')
+
+
+class _UserSerializer(serializers.ModelSerializer):
+ teams = serializers.SerializerMethodField()
+
+ class Meta:
+ model = models.User
+ fields = ('id', 'username', 'is_superuser', 'teams')
+
+ def get_teams(self, user: models.User):
+ teams = models.Team.access_qs(user, 'member')
+ return _TeamSerializer(many=True).to_representation(teams)
+
+
+class _ExecutionEnvironmentSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = models.ExecutionEnvironment
+ fields = (
+ 'id',
+ 'name',
+ 'image',
+ 'pull',
+ )
+
+
+class _InstanceGroupSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = models.InstanceGroup
+ fields = (
+ 'id',
+ 'name',
+ 'capacity',
+ 'jobs_running',
+ 'jobs_total',
+ 'max_concurrent_jobs',
+ 'max_forks',
+ )
+
+
+class _InventorySourceSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = models.InventorySource
+ fields = ('id', 'name', 'source', 'status')
+
+
+class _InventorySerializer(serializers.ModelSerializer):
+ inventory_sources = _InventorySourceSerializer(many=True)
+
+ class Meta:
+ model = models.Inventory
+ fields = (
+ 'id',
+ 'name',
+ 'description',
+ 'kind',
+ 'total_hosts',
+ 'total_groups',
+ 'has_inventory_sources',
+ 'total_inventory_sources',
+ 'has_active_failures',
+ 'hosts_with_active_failures',
+ 'inventory_sources',
+ )
+
+
+class _JobTemplateSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = models.JobTemplate
+ fields = (
+ 'id',
+ 'name',
+ 'job_type',
+ )
+
+
+class _WorkflowJobTemplateSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = models.WorkflowJobTemplate
+ fields = (
+ 'id',
+ 'name',
+ 'job_type',
+ )
+
+
+class _WorkflowJobSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = models.WorkflowJob
+ fields = (
+ 'id',
+ 'name',
+ )
+
+
+class _OrganizationSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = models.Organization
+ fields = (
+ 'id',
+ 'name',
+ )
+
+
+class _ProjectSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = models.Project
+ fields = (
+ 'id',
+ 'name',
+ 'status',
+ 'scm_type',
+ 'scm_url',
+ 'scm_branch',
+ 'scm_refspec',
+ 'scm_clean',
+ 'scm_track_submodules',
+ 'scm_delete_on_update',
+ )
+
+
+class _CredentialSerializer(serializers.ModelSerializer):
+ organization = _OrganizationSerializer()
+
+ class Meta:
+ model = models.Credential
+ fields = (
+ 'id',
+ 'name',
+ 'description',
+ 'organization',
+ 'credential_type',
+ 'managed',
+ 'kind',
+ 'cloud',
+ 'kubernetes',
+ )
+
+
+class _LabelSerializer(serializers.ModelSerializer):
+ organization = _OrganizationSerializer()
+
+ class Meta:
+ model = models.Label
+ fields = ('id', 'name', 'organization')
+
+
+class JobSerializer(serializers.ModelSerializer):
+ created_by = _UserSerializer()
+ credentials = _CredentialSerializer(many=True)
+ execution_environment = _ExecutionEnvironmentSerializer()
+ instance_group = _InstanceGroupSerializer()
+ inventory = _InventorySerializer()
+ job_template = _JobTemplateSerializer()
+ labels = _LabelSerializer(many=True)
+ organization = _OrganizationSerializer()
+ project = _ProjectSerializer()
+ extra_vars = fields.SerializerMethodField()
+ hosts_count = fields.SerializerMethodField()
+ workflow_job = fields.SerializerMethodField()
+ workflow_job_template = fields.SerializerMethodField()
+
+ class Meta:
+ model = models.Job
+ fields = (
+ 'id',
+ 'name',
+ 'created',
+ 'created_by',
+ 'credentials',
+ 'execution_environment',
+ 'extra_vars',
+ 'forks',
+ 'hosts_count',
+ 'instance_group',
+ 'inventory',
+ 'job_template',
+ 'job_type',
+ 'job_type_name',
+ 'labels',
+ 'launch_type',
+ 'limit',
+ 'launched_by',
+ 'organization',
+ 'playbook',
+ 'project',
+ 'scm_branch',
+ 'scm_revision',
+ 'workflow_job',
+ 'workflow_job_template',
+ )
+
+ def get_extra_vars(self, obj: models.Job):
+ return json.loads(obj.display_extra_vars())
+
+ def get_hosts_count(self, obj: models.Job):
+ return obj.hosts.count()
+
+ def get_workflow_job(self, obj: models.Job):
+ workflow_job: models.WorkflowJob = obj.get_workflow_job()
+ if workflow_job is None:
+ return None
+ return _WorkflowJobSerializer().to_representation(workflow_job)
+
+ def get_workflow_job_template(self, obj: models.Job):
+ workflow_job: models.WorkflowJob = obj.get_workflow_job()
+ if workflow_job is None:
+ return None
+
+ workflow_job_template: models.WorkflowJobTemplate = workflow_job.workflow_job_template
+ if workflow_job_template is None:
+ return None
+
+ return _WorkflowJobTemplateSerializer().to_representation(workflow_job_template)
+
+
+class OPAResultSerializer(serializers.Serializer):
+ allowed = fields.BooleanField(required=True)
+ violations = fields.ListField(child=fields.CharField())
+
+
+class OPA_AUTH_TYPES:
+ NONE = 'None'
+ TOKEN = 'Token'
+ CERTIFICATE = 'Certificate'
+
+
+@contextlib.contextmanager
+def opa_cert_file():
+ """
+ Context manager that creates temporary certificate files for OPA authentication.
+
+ For mTLS (mutual TLS), we need:
+ - Client certificate and key for client authentication
+ - CA certificate (optional) for server verification
+
+ Returns:
+ tuple: (client_cert_path, verify_path)
+ - client_cert_path: Path to client cert file or None if not using client cert
+ - verify_path: Path to CA cert file, True to use system CA store, or False for no verification
+ """
+ client_cert_temp = None
+ ca_temp = None
+
+ try:
+ # Case 1: Full mTLS with client cert and optional CA cert
+ if settings.OPA_AUTH_TYPE == OPA_AUTH_TYPES.CERTIFICATE:
+ # Create client certificate file (required for mTLS)
+ client_cert_temp = tempfile.NamedTemporaryFile(delete=True, mode='w', suffix=".pem")
+ client_cert_temp.write(settings.OPA_AUTH_CLIENT_CERT)
+ client_cert_temp.write("\n")
+ client_cert_temp.write(settings.OPA_AUTH_CLIENT_KEY)
+ client_cert_temp.write("\n")
+ client_cert_temp.flush()
+
+ # If CA cert is provided, use it for server verification
+ # Otherwise, use system CA store (True)
+ if settings.OPA_AUTH_CA_CERT:
+ ca_temp = tempfile.NamedTemporaryFile(delete=True, mode='w', suffix=".pem")
+ ca_temp.write(settings.OPA_AUTH_CA_CERT)
+ ca_temp.write("\n")
+ ca_temp.flush()
+ verify_path = ca_temp.name
+ else:
+ verify_path = True # Use system CA store
+
+ yield (client_cert_temp.name, verify_path)
+
+ # Case 2: TLS with only server verification (no client cert)
+ elif settings.OPA_SSL:
+ # If CA cert is provided, use it for server verification
+ # Otherwise, use system CA store (True)
+ if settings.OPA_AUTH_CA_CERT:
+ ca_temp = tempfile.NamedTemporaryFile(delete=True, mode='w', suffix=".pem")
+ ca_temp.write(settings.OPA_AUTH_CA_CERT)
+ ca_temp.write("\n")
+ ca_temp.flush()
+ verify_path = ca_temp.name
+ else:
+ verify_path = True # Use system CA store
+
+ yield (None, verify_path)
+
+ # Case 3: No TLS
+ else:
+ yield (None, False)
+
+ finally:
+ # Clean up temporary files
+ if client_cert_temp:
+ client_cert_temp.close()
+ if ca_temp:
+ ca_temp.close()
+
+
+@contextlib.contextmanager
+def opa_client(headers=None):
+ with opa_cert_file() as cert_files:
+ cert, verify = cert_files
+
+ with OpaClient(
+ host=settings.OPA_HOST,
+ port=settings.OPA_PORT,
+ headers=headers,
+ ssl=settings.OPA_SSL,
+ cert=cert,
+ timeout=settings.OPA_REQUEST_TIMEOUT,
+ retries=settings.OPA_REQUEST_RETRIES,
+ ) as client:
+ # Workaround for https://github.com/Turall/OPA-python-client/issues/32
+ # by directly setting cert and verify on requests.session
+ client._session.cert = cert
+ client._session.verify = verify
+
+ yield client
+
+
+def evaluate_policy(instance):
+ # Policy evaluation for Policy as Code feature
+ if not settings.OPA_HOST:
+ return
+
+ if not isinstance(instance, models.Job):
+ return
+
+ instance.log_lifecycle("evaluate_policy")
+
+ input_data = JobSerializer(instance=instance).data
+
+ headers = settings.OPA_AUTH_CUSTOM_HEADERS
+ if settings.OPA_AUTH_TYPE == OPA_AUTH_TYPES.TOKEN:
+ headers.update({'Authorization': 'Bearer {}'.format(settings.OPA_AUTH_TOKEN)})
+
+ if settings.OPA_AUTH_TYPE == OPA_AUTH_TYPES.CERTIFICATE and not settings.OPA_SSL:
+ raise PolicyEvaluationError(_('OPA_AUTH_TYPE=Certificate requires OPA_SSL to be enabled.'))
+
+ cert_settings_missing = []
+
+ if settings.OPA_AUTH_TYPE == OPA_AUTH_TYPES.CERTIFICATE:
+ if not settings.OPA_AUTH_CLIENT_CERT:
+ cert_settings_missing += ['OPA_AUTH_CLIENT_CERT']
+ if not settings.OPA_AUTH_CLIENT_KEY:
+ cert_settings_missing += ['OPA_AUTH_CLIENT_KEY']
+ if not settings.OPA_AUTH_CA_CERT:
+ cert_settings_missing += ['OPA_AUTH_CA_CERT']
+
+ if cert_settings_missing:
+ raise PolicyEvaluationError(_('Following certificate settings are missing for OPA_AUTH_TYPE=Certificate: {}').format(cert_settings_missing))
+
+ query_paths = [
+ ('Organization', instance.organization.opa_query_path),
+ ('Inventory', instance.inventory.opa_query_path),
+ ('Job template', instance.job_template.opa_query_path),
+ ]
+ violations = dict()
+ errors = dict()
+
+ try:
+ with opa_client(headers=headers) as client:
+ for path_type, query_path in query_paths:
+ response = dict()
+ try:
+ if not query_path:
+ continue
+
+ response = client.query_rule(input_data=input_data, package_path=query_path)
+
+ except HTTPError as e:
+ message = _('Call to OPA failed. Exception: {}').format(e)
+ try:
+ error_data = e.response.json()
+ except ValueError:
+ errors[path_type] = message
+ continue
+
+ error_code = error_data.get("code")
+ error_message = error_data.get("message")
+ if error_code or error_message:
+ message = _('Call to OPA failed. Code: {}, Message: {}').format(error_code, error_message)
+ errors[path_type] = message
+ continue
+
+ except Exception as e:
+ errors[path_type] = _('Call to OPA failed. Exception: {}').format(e)
+ continue
+
+ result = response.get('result')
+ if result is None:
+ errors[path_type] = _('Call to OPA did not return a "result" property. The path refers to an undefined document.')
+ continue
+
+ result_serializer = OPAResultSerializer(data=result)
+ if not result_serializer.is_valid():
+ errors[path_type] = _('OPA policy returned invalid result.')
+ continue
+
+ result_data = result_serializer.validated_data
+ if not result_data.get("allowed") and (result_violations := result_data.get("violations")):
+ violations[path_type] = result_violations
+
+ format_results = dict()
+ if any(errors[e] for e in errors):
+ format_results["Errors"] = errors
+
+ if any(violations[v] for v in violations):
+ format_results["Violations"] = violations
+
+ if violations or errors:
+ raise PolicyEvaluationError(pformat(format_results, width=80))
+
+ except Exception as e:
+ raise PolicyEvaluationError(_('This job cannot be executed due to a policy violation or error. See the following details:\n{}').format(e))
diff --git a/awx/main/tasks/receptor.py b/awx/main/tasks/receptor.py
index 006c805943c8..e1ccf4d7c440 100644
--- a/awx/main/tasks/receptor.py
+++ b/awx/main/tasks/receptor.py
@@ -17,6 +17,12 @@
# Runner
import ansible_runner
+# django-ansible-base
+from ansible_base.lib.utils.db import advisory_lock
+
+# Dispatcherd
+from dispatcherd.publish import task
+
# AWX
from awx.main.utils.execution_environments import get_default_pod_spec
from awx.main.exceptions import ReceptorNodeNotFound
@@ -27,9 +33,8 @@
)
from awx.main.constants import MAX_ISOLATED_PATH_COLON_DELIMITER
from awx.main.tasks.signals import signal_state, signal_callback, SignalExit
-from awx.main.models import Instance, InstanceLink, UnifiedJob
-from awx.main.dispatch import get_local_queuename
-from awx.main.dispatch.publish import task
+from awx.main.models import Instance, InstanceLink, UnifiedJob, ReceptorAddress
+from awx.main.dispatch import get_task_queuename
# Receptorctl
from receptorctl.socket_interface import ReceptorControl
@@ -48,6 +53,70 @@ class ReceptorConnectionType(Enum):
STREAMTLS = 2
+"""
+Translate receptorctl messages that come in over stdout into
+structured messages. Currently, these are error messages.
+"""
+
+
+class ReceptorErrorBase:
+ _MESSAGE = 'Receptor Error'
+
+ def __init__(self, node: str = 'N/A', state_name: str = 'N/A'):
+ self.node = node
+ self.state_name = state_name
+
+ def __str__(self):
+ return f"{self.__class__.__name__} '{self._MESSAGE}' on node '{self.node}' with state '{self.state_name}'"
+
+
+class WorkUnitError(ReceptorErrorBase):
+ _MESSAGE = 'unknown work unit '
+
+ def __init__(self, work_unit_id: str, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.work_unit_id = work_unit_id
+
+ def __str__(self):
+ return f"{super().__str__()} work unit id '{self.work_unit_id}'"
+
+
+class WorkUnitCancelError(WorkUnitError):
+ _MESSAGE = 'error cancelling remote unit: unknown work unit '
+
+
+class WorkUnitResultsError(WorkUnitError):
+ _MESSAGE = 'Failed to get results: unknown work unit '
+
+
+class UnknownError(ReceptorErrorBase):
+ _MESSAGE = 'Unknown receptor ctl error'
+
+ def __init__(self, msg, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._MESSAGE = msg
+
+
+class FuzzyError:
+ def __new__(self, e: RuntimeError, node: str, state_name: str):
+ """
+ At the time of writing this comment all of the sub-classes detection
+ is centralized in this parent class. It's like a Router().
+ Someone may find it better to push down the error detection logic into
+ each sub-class.
+ """
+ msg = e.args[0]
+
+ common_startswith = (WorkUnitCancelError, WorkUnitResultsError, WorkUnitError)
+
+ for klass in common_startswith:
+ if msg.startswith(klass._MESSAGE):
+ work_unit_id = msg[len(klass._MESSAGE) :]
+ return klass(work_unit_id, node=node, state_name=state_name)
+
+ return UnknownError(msg, node=node, state_name=state_name)
+
+
def read_receptor_config():
# for K8S deployments, getting a lock is necessary as another process
# may be re-writing the config at this time
@@ -161,22 +230,24 @@ class RemoteJobError(RuntimeError):
pass
-def run_until_complete(node, timing_data=None, **kwargs):
+def run_until_complete(node, timing_data=None, worktype='ansible-runner', ttl='20s', **kwargs):
"""
Runs an ansible-runner work_type on remote node, waits until it completes, then returns stdout.
"""
+
config_data = read_receptor_config()
receptor_ctl = get_receptor_ctl(config_data)
use_stream_tls = getattr(get_conn_type(node, receptor_ctl), 'name', None) == "STREAMTLS"
kwargs.setdefault('tlsclient', get_tls_client(config_data, use_stream_tls))
- kwargs.setdefault('ttl', '20s')
+ if ttl is not None:
+ kwargs['ttl'] = ttl
kwargs.setdefault('payload', '')
if work_signing_enabled(config_data):
kwargs['signwork'] = True
transmit_start = time.time()
- result = receptor_ctl.submit_work(worktype='ansible-runner', node=node, **kwargs)
+ result = receptor_ctl.submit_work(worktype=worktype, node=node, **kwargs)
unit_id = result['unitid']
run_start = time.time()
@@ -184,6 +255,7 @@ def run_until_complete(node, timing_data=None, **kwargs):
timing_data['transmit_timing'] = run_start - transmit_start
run_timing = 0.0
stdout = ''
+ state_name = 'local var never set'
try:
resultfile = receptor_ctl.get_work_results(unit_id)
@@ -204,13 +276,33 @@ def run_until_complete(node, timing_data=None, **kwargs):
stdout = resultfile.read()
stdout = str(stdout, encoding='utf-8')
+ except RuntimeError as e:
+ receptor_e = FuzzyError(e, node, state_name)
+ if type(receptor_e) in (
+ WorkUnitError,
+ WorkUnitResultsError,
+ ):
+ logger.warning(f'While consuming job results: {receptor_e}')
+ else:
+ raise
finally:
if settings.RECEPTOR_RELEASE_WORK:
- res = receptor_ctl.simple_command(f"work release {unit_id}")
- if res != {'released': unit_id}:
- logger.warning(f'Could not confirm release of receptor work unit id {unit_id} from {node}, data: {res}')
-
- receptor_ctl.close()
+ try:
+ res = receptor_ctl.simple_command(f"work release {unit_id}")
+
+ if res != {'released': unit_id}:
+ logger.warning(f'Could not confirm release of receptor work unit id {unit_id} from {node}, data: {res}')
+
+ receptor_ctl.close()
+ except RuntimeError as e:
+ receptor_e = FuzzyError(e, node, state_name)
+ if type(receptor_e) in (
+ WorkUnitError,
+ WorkUnitCancelError,
+ ):
+ logger.warning(f"While releasing work: {receptor_e}")
+ else:
+ logger.error(f"While releasing work: {receptor_e}")
if state_name.lower() == 'failed':
work_detail = status.get('Detail', '')
@@ -274,7 +366,7 @@ def _convert_args_to_cli(vargs):
args = ['cleanup']
for option in ('exclude_strings', 'remove_images'):
if vargs.get(option):
- args.append('--{}={}'.format(option.replace('_', '-'), ' '.join(vargs.get(option))))
+ args.append('--{} {}'.format(option.replace('_', '-'), ' '.join(f'"{item}"' for item in vargs.get(option))))
for option in ('file_pattern', 'image_prune', 'process_isolation_executable', 'grace_period'):
if vargs.get(option) is True:
args.append('--{}'.format(option.replace('_', '-')))
@@ -283,7 +375,7 @@ def _convert_args_to_cli(vargs):
return args
-def worker_cleanup(node_name, vargs, timeout=300.0):
+def worker_cleanup(node_name, vargs):
args = _convert_args_to_cli(vargs)
remote_command = ' '.join(args)
@@ -317,12 +409,40 @@ def run(self):
res = self._run_internal(receptor_ctl)
return res
finally:
- # Make sure to always release the work unit if we established it
- if self.unit_id is not None and settings.RECEPTOR_RELEASE_WORK:
- try:
- receptor_ctl.simple_command(f"work release {self.unit_id}")
- except Exception:
- logger.exception(f"Error releasing work unit {self.unit_id}.")
+ status = getattr(res, 'status', 'error')
+ self._receptor_release_work(receptor_ctl, status)
+
+ def _receptor_release_work(self, receptor_ctl: ReceptorControl, status: str) -> None:
+ """
+ Releases the work unit from Receptor if certain conditions are met.
+ This method checks several conditions before attempting to release the work unit:
+ - If `self.unit_id` is `None`, the method returns immediately.
+ - If the `RECEPTOR_RELEASE_WORK` setting is `False`, the method returns immediately.
+ - If the `RECEPTOR_KEEP_WORK_ON_ERROR` setting is `True` and the status is 'error', the method returns immediately.
+ If none of the above conditions are met, the method attempts to release the work unit using the Receptor control command.
+ If an exception occurs during the release process, it logs an error message.
+ Args:
+ receptor_ctl (ReceptorControl): The Receptor control object used to issue commands.
+ status (str): The status of the work unit, which may affect whether it is released.
+ """
+
+ if self.unit_id is None:
+ logger.debug("No work unit ID to release.")
+ return
+
+ if settings.RECEPTOR_RELEASE_WORK is False:
+ logger.debug(f"RECEPTOR_RELEASE_WORK is False, not releasing work unit {self.unit_id}.")
+ return
+
+ if settings.RECEPTOR_KEEP_WORK_ON_ERROR and status == 'error':
+ logger.debug(f"RECEPTOR_KEEP_WORK_ON_ERROR is True and status is 'error', not releasing work unit {self.unit_id}.")
+ return
+
+ try:
+ logger.debug(f"Released work unit {self.unit_id}.")
+ receptor_ctl.simple_command(f"work release {self.unit_id}")
+ except Exception:
+ logger.exception(f"Error releasing work unit {self.unit_id}.")
def _run_internal(self, receptor_ctl):
# Create a socketpair. Where the left side will be used for writing our payload
@@ -431,16 +551,16 @@ def _run_internal(self, receptor_ctl):
# massive, only ask for last 1000 bytes
startpos = max(stdout_size - 1000, 0)
resultsock, resultfile = receptor_ctl.get_work_results(self.unit_id, startpos=startpos, return_socket=True, return_sockfile=True)
- resultsock.setblocking(False) # this makes resultfile reads non blocking
lines = resultfile.readlines()
receptor_output = b"".join(lines).decode()
if receptor_output:
- self.task.runner_callback.delay_update(result_traceback=receptor_output)
+ self.task.runner_callback.delay_update(result_traceback=f'Worker output:\n{receptor_output}')
elif detail:
- self.task.runner_callback.delay_update(result_traceback=detail)
+ self.task.runner_callback.delay_update(result_traceback=f'Receptor detail:\n{detail}')
else:
logger.warning(f'No result details or output from {self.task.instance.log_format}, status:\n{state_name}')
except Exception:
+ logger.exception(f'Work results error from job id={self.task.instance.id} work_unit={self.task.instance.work_unit_id}')
raise RuntimeError(detail)
return res
@@ -464,6 +584,7 @@ def processor(self, resultfile):
event_handler=self.task.runner_callback.event_handler,
finished_callback=self.task.runner_callback.finished_callback,
status_handler=self.task.runner_callback.status_handler,
+ artifacts_handler=self.task.runner_callback.artifacts_handler,
**self.runner_params,
)
@@ -526,6 +647,10 @@ def pod_definition(self):
pod_spec['spec']['containers'][0]['image'] = ee.image
pod_spec['spec']['containers'][0]['args'] = ['ansible-runner', 'worker', '--private-data-dir=/runner']
+ if settings.AWX_RUNNER_KEEPALIVE_SECONDS:
+ pod_spec['spec']['containers'][0].setdefault('env', [])
+ pod_spec['spec']['containers'][0]['env'].append({'name': 'ANSIBLE_RUNNER_KEEPALIVE_SECONDS', 'value': str(settings.AWX_RUNNER_KEEPALIVE_SECONDS)})
+
# Enforce EE Pull Policy
pull_options = {"always": "Always", "missing": "IfNotPresent", "never": "Never"}
if self.task and self.task.instance.execution_environment:
@@ -635,11 +760,11 @@ def kube_config(self):
#
RECEPTOR_CONFIG_STARTER = (
{'local-only': None},
- {'log-level': 'debug'},
+ {'log-level': settings.RECEPTOR_LOG_LEVEL},
{'node': {'firewallrules': [{'action': 'reject', 'tonode': settings.CLUSTER_HOST_ID, 'toservice': 'control'}]}},
{'control-service': {'service': 'control', 'filename': '/var/run/receptor/receptor.sock', 'permissions': '0660'}},
{'work-command': {'worktype': 'local', 'command': 'ansible-runner', 'params': 'worker', 'allowruntimeparams': True}},
- {'work-signing': {'privatekey': '/etc/receptor/signing/work-private-key.pem', 'tokenexpiration': '1m'}},
+ {'work-signing': {'privatekey': '/etc/receptor/work_private_key.pem', 'tokenexpiration': '1m'}},
{
'work-kubernetes': {
'worktype': 'kubernetes-runtime-auth',
@@ -661,34 +786,58 @@ def kube_config(self):
{
'tls-client': {
'name': 'tlsclient',
- 'rootcas': '/etc/receptor/tls/ca/receptor-ca.crt',
+ 'rootcas': '/etc/receptor/tls/ca/mesh-CA.crt',
'cert': '/etc/receptor/tls/receptor.crt',
'key': '/etc/receptor/tls/receptor.key',
+ 'mintls13': False,
}
},
)
-@task()
-def write_receptor_config():
- lock = FileLock(__RECEPTOR_CONF_LOCKFILE)
- with lock:
- receptor_config = list(RECEPTOR_CONFIG_STARTER)
-
- this_inst = Instance.objects.me()
- instances = Instance.objects.filter(node_type=Instance.Types.EXECUTION)
- existing_peers = {link.target_id for link in InstanceLink.objects.filter(source=this_inst)}
- new_links = []
- for instance in instances:
- peer = {'tcp-peer': {'address': f'{instance.hostname}:{instance.listener_port}', 'tls': 'tlsclient'}}
+def should_update_config(new_config):
+ '''
+ checks that the list of instances matches the list of
+ tcp-peers in the config
+ '''
+
+ current_config = read_receptor_config() # this gets receptor conf lock
+ for config_entry in current_config:
+ if config_entry not in new_config:
+ logger.warning(f"{config_entry} should not be in receptor config. Updating.")
+ return True
+ for config_entry in new_config:
+ if config_entry not in current_config:
+ logger.warning(f"{config_entry} missing from receptor config. Updating.")
+ return True
+
+ return False
+
+
+def generate_config_data():
+ # returns two values
+ # receptor config - based on current database peers
+ # should_update - If True, receptor_config differs from the receptor conf file on disk
+ addresses = ReceptorAddress.objects.filter(peers_from_control_nodes=True)
+
+ receptor_config = list(RECEPTOR_CONFIG_STARTER)
+ for address in addresses:
+ if address.get_peer_type():
+ peer = {
+ f'{address.get_peer_type()}': {
+ 'address': f'{address.get_full_address()}',
+ 'tls': 'tlsclient',
+ }
+ }
receptor_config.append(peer)
- if instance.id not in existing_peers:
- new_links.append(InstanceLink(source=this_inst, target=instance, link_state=InstanceLink.States.ADDING))
+ else:
+ logger.warning(f"Receptor address {address} has unsupported peer type, skipping.")
+ should_update = should_update_config(receptor_config)
+ return receptor_config, should_update
- InstanceLink.objects.bulk_create(new_links)
- with open(__RECEPTOR_CONF, 'w') as file:
- yaml.dump(receptor_config, file, default_flow_style=False)
+def reload_receptor():
+ logger.warning("Receptor config changed, reloading receptor")
# This needs to be outside of the lock because this function itself will acquire the lock.
receptor_ctl = get_receptor_ctl()
@@ -704,14 +853,34 @@ def write_receptor_config():
else:
raise RuntimeError("Receptor reload failed")
- links = InstanceLink.objects.filter(source=this_inst, target__in=instances, link_state=InstanceLink.States.ADDING)
- links.update(link_state=InstanceLink.States.ESTABLISHED)
-
-@task(queue=get_local_queuename)
+@task(on_duplicate='queue_one')
+def write_receptor_config():
+ """
+ This task runs async on each control node, K8S only.
+ It is triggered whenever remote is added or removed, or if peers_from_control_nodes
+ is flipped.
+ It is possible for write_receptor_config to be called multiple times.
+ For example, if new instances are added in quick succession.
+ To prevent that case, each control node first grabs a DB advisory lock, specific
+ to just that control node (i.e. multiple control nodes can run this function
+ at the same time, since it only writes the local receptor config file)
+ """
+ with advisory_lock(f"{settings.CLUSTER_HOST_ID}_write_receptor_config", wait=True):
+ # Config file needs to be updated
+ receptor_config, should_update = generate_config_data()
+ if should_update:
+ lock = FileLock(__RECEPTOR_CONF_LOCKFILE)
+ with lock:
+ with open(__RECEPTOR_CONF, 'w') as file:
+ yaml.dump(receptor_config, file, default_flow_style=False)
+ reload_receptor()
+
+
+@task(queue=get_task_queuename, on_duplicate='discard')
def remove_deprovisioned_node(hostname):
InstanceLink.objects.filter(source__hostname=hostname).update(link_state=InstanceLink.States.REMOVING)
- InstanceLink.objects.filter(target__hostname=hostname).update(link_state=InstanceLink.States.REMOVING)
+ InstanceLink.objects.filter(target__instance__hostname=hostname).update(link_state=InstanceLink.States.REMOVING)
node_jobs = UnifiedJob.objects.filter(
execution_node=hostname,
@@ -725,6 +894,3 @@ def remove_deprovisioned_node(hostname):
# This will as a side effect also delete the InstanceLinks that are tied to it.
Instance.objects.filter(hostname=hostname).delete()
-
- # Update the receptor configs for all of the control-plane.
- write_receptor_config.apply_async(queue='tower_broadcast_all')
diff --git a/awx/main/tasks/signals.py b/awx/main/tasks/signals.py
index 95610548b991..a1607bfc9960 100644
--- a/awx/main/tasks/signals.py
+++ b/awx/main/tasks/signals.py
@@ -2,7 +2,6 @@
import functools
import logging
-
logger = logging.getLogger('awx.main.tasks.signals')
@@ -14,33 +13,50 @@ class SignalExit(Exception):
class SignalState:
+ # SIGTERM: Sent by supervisord to process group on shutdown
+ # SIGUSR1: The dispatcherd cancel signal
+ signals = (signal.SIGTERM, signal.SIGINT, signal.SIGUSR1)
+
def reset(self):
- self.sigterm_flag = False
- self.is_active = False
- self.original_sigterm = None
- self.original_sigint = None
+ for for_signal in self.signals:
+ self.signal_flags[for_signal] = False
+ self.original_methods[for_signal] = None
+
+ self.is_active = False # for nested context managers
self.raise_exception = False
def __init__(self):
+ self.signal_flags = {}
+ self.original_methods = {}
self.reset()
- def set_flag(self, *args):
- """Method to pass into the python signal.signal method to receive signals"""
- self.sigterm_flag = True
+ def raise_if_needed(self):
if self.raise_exception:
self.raise_exception = False # so it is not raised a second time in error handling
raise SignalExit()
+ def set_signal_flag(self, *args, for_signal=None):
+ self.signal_flags[for_signal] = True
+ logger.info(f'Processed signal {for_signal}, set exit flag')
+ self.raise_if_needed()
+
def connect_signals(self):
- self.original_sigterm = signal.getsignal(signal.SIGTERM)
- self.original_sigint = signal.getsignal(signal.SIGINT)
- signal.signal(signal.SIGTERM, self.set_flag)
- signal.signal(signal.SIGINT, self.set_flag)
+ for for_signal in self.signals:
+ self.original_methods[for_signal] = signal.getsignal(for_signal)
+ signal.signal(for_signal, lambda *args, for_signal=for_signal: self.set_signal_flag(*args, for_signal=for_signal))
self.is_active = True
def restore_signals(self):
- signal.signal(signal.SIGTERM, self.original_sigterm)
- signal.signal(signal.SIGINT, self.original_sigint)
+ for for_signal in self.signals:
+ original_method = self.original_methods[for_signal]
+ signal.signal(for_signal, original_method)
+ # if we got a signal while context manager was active, call parent methods.
+ if self.signal_flags[for_signal]:
+ if callable(original_method):
+ try:
+ original_method()
+ except Exception as exc:
+ logger.info(f'Error processing original {for_signal} signal, error: {str(exc)}')
self.reset()
@@ -48,12 +64,12 @@ def restore_signals(self):
def signal_callback():
- return signal_state.sigterm_flag
+ return any(signal_state.signal_flags[for_signal] for for_signal in signal_state.signals)
def with_signal_handling(f):
"""
- Change signal handling to make signal_callback return True in event of SIGTERM or SIGINT.
+ Change signal handling to make signal_callback return True in event of SIGTERM, SIGINT, or SIGUSR1.
"""
@functools.wraps(f)
diff --git a/awx/main/tasks/system.py b/awx/main/tasks/system.py
index 482c168af210..fda46b296849 100644
--- a/awx/main/tasks/system.py
+++ b/awx/main/tasks/system.py
@@ -1,74 +1,78 @@
# Python
-from collections import namedtuple
import functools
import importlib
+import itertools
import json
import logging
import os
-from io import StringIO
-from contextlib import redirect_stdout
import shutil
import time
-from distutils.version import LooseVersion as Version
-from datetime import datetime
+from collections import namedtuple
+from contextlib import redirect_stdout
+from packaging.version import Version
+from io import StringIO
+
+# dispatcherd
+from dispatcherd.factories import get_control_from_settings
+from dispatcherd.publish import task
+
+# Runner
+import ansible_runner.cleanup
+import psycopg
+from ansible_base.lib.utils.db import advisory_lock
+
+# django-ansible-base
+from ansible_base.resource_registry.tasks.sync import SyncExecutor
+
+# Django-CRUM
+from crum import impersonate
+
+# dateutil
+from dateutil.parser import parse as parse_date
# Django
from django.conf import settings
-from django.db import transaction, DatabaseError, IntegrityError
+from django.contrib.auth.models import User
+from django.core.cache import cache
+from django.core.exceptions import ObjectDoesNotExist
+from django.db import DatabaseError, IntegrityError, connection, transaction
from django.db.models.fields.related import ForeignKey
-from django.utils.timezone import now, timedelta
+from django.db.models.query import QuerySet
from django.utils.encoding import smart_str
-from django.contrib.auth.models import User
+from django.utils.timezone import now, timedelta
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext_noop
-from django.core.cache import cache
-from django.core.exceptions import ObjectDoesNotExist
-
-# Django-CRUM
-from crum import impersonate
-
-# Runner
-import ansible_runner.cleanup
-
-# dateutil
-from dateutil.parser import parse as parse_date
+# Django flags
+from flags.state import flag_enabled
+from rest_framework.exceptions import PermissionDenied
# AWX
from awx import __version__ as awx_application_version
+from awx.conf import settings_registry
+from awx.main import analytics
from awx.main.access import access_registry
+from awx.main.analytics.subsystem_metrics import DispatcherMetrics
+from awx.main.constants import ACTIVE_STATES, ERROR_STATES
+from awx.main.consumers import emit_channel_notification
+from awx.main.dispatch import get_task_queuename, reaper
from awx.main.models import (
- Schedule,
- TowerScheduleState,
Instance,
InstanceGroup,
- UnifiedJob,
- Notification,
Inventory,
- SmartInventoryMembership,
Job,
+ Notification,
+ Schedule,
+ SmartInventoryMembership,
+ TowerScheduleState,
+ UnifiedJob,
+ convert_jsonfields,
)
-from awx.main.constants import ACTIVE_STATES
-from awx.main.dispatch.publish import task
-from awx.main.dispatch import get_local_queuename, reaper
-from awx.main.utils.common import (
- get_type_for_model,
- ignore_inventory_computed_fields,
- ignore_inventory_group_removal,
- ScheduleWorkflowManager,
- ScheduleTaskManager,
-)
-
-from awx.main.utils.external_logging import reconfigure_rsyslog
+from awx.main.tasks.helpers import is_run_threshold_reached
+from awx.main.tasks.host_indirect import save_indirect_host_entries
+from awx.main.tasks.receptor import administrative_workunit_reaper, get_receptor_ctl, worker_cleanup, worker_info, write_receptor_config
+from awx.main.utils.common import ignore_inventory_computed_fields, ignore_inventory_group_removal
from awx.main.utils.reload import stop_local_services
-from awx.main.utils.pglock import advisory_lock
-from awx.main.tasks.receptor import get_receptor_ctl, worker_info, worker_cleanup, administrative_workunit_reaper, write_receptor_config
-from awx.main.consumers import emit_channel_notification
-from awx.main import analytics
-from awx.conf import settings_registry
-from awx.main.analytics.subsystem_metrics import Metrics
-
-from rest_framework.exceptions import PermissionDenied
logger = logging.getLogger('awx.main.tasks.system')
@@ -79,19 +83,32 @@
'''
-def dispatch_startup():
+def _run_dispatch_startup_common():
+ """
+ Execute the common startup initialization steps.
+ This includes updating schedules, syncing instance membership, and starting
+ local reaping and resetting metrics.
+ """
startup_logger = logging.getLogger('awx.main.tasks')
# TODO: Enable this on VM installs
if settings.IS_K8S:
- write_receptor_config()
+ try:
+ write_receptor_config()
+ except Exception:
+ logger.exception("Failed to write receptor config, skipping.")
+
+ try:
+ convert_jsonfields()
+ except Exception:
+ logger.exception("Failed JSON field conversion, skipping.")
- startup_logger.debug("Syncing Schedules")
+ startup_logger.debug("Syncing schedules")
for sch in Schedule.objects.all():
try:
sch.update_computed_fields()
except Exception:
- logger.exception("Failed to rebuild schedule {}.".format(sch))
+ logger.exception("Failed to rebuild schedule %s.", sch)
#
# When the dispatcher starts, if the instance cannot be found in the database,
@@ -109,30 +126,95 @@ def dispatch_startup():
# no-op.
#
apply_cluster_membership_policies()
- cluster_node_heartbeat()
+ cluster_node_heartbeat(None)
reaper.startup_reaping()
- reaper.reap_waiting(grace_period=0)
- m = Metrics()
+ m = DispatcherMetrics()
m.reset_values()
- # Update Tower's rsyslog.conf file based on loggins settings in the db
- reconfigure_rsyslog()
+
+def _dispatcherd_dispatch_startup():
+ """
+ New dispatcherd branch for startup: uses the control API to re-submit waiting jobs.
+ """
+ logger.debug("Dispatcherd enabled: dispatching waiting jobs via control channel")
+ from awx.main.tasks.jobs import dispatch_waiting_jobs
+
+ dispatch_waiting_jobs.apply_async(queue=get_task_queuename())
+
+
+def dispatch_startup():
+ """
+ System initialization at startup.
+ First, execute the common logic.
+ Then, re-submit waiting jobs via the control API.
+ """
+ _run_dispatch_startup_common()
+ _dispatcherd_dispatch_startup()
def inform_cluster_of_shutdown():
+ """
+ Clean system shutdown that marks the current instance offline.
+ Relies on dispatcherd's built-in cleanup.
+ """
try:
- this_inst = Instance.objects.get(hostname=settings.CLUSTER_HOST_ID)
- this_inst.mark_offline(update_last_seen=True, errors=_('Instance received normal shutdown signal'))
- try:
- reaper.reap_waiting(this_inst, grace_period=0)
- except Exception:
- logger.exception('failed to reap waiting jobs for {}'.format(this_inst.hostname))
- logger.warning('Normal shutdown signal for instance {}, removed self from capacity pool.'.format(this_inst.hostname))
- except Exception:
- logger.exception('Encountered problem with normal shutdown signal.')
+ inst = Instance.objects.get(hostname=settings.CLUSTER_HOST_ID)
+ inst.mark_offline(update_last_seen=True, errors=_('Instance received normal shutdown signal'))
+ except Instance.DoesNotExist:
+ logger.exception("Cluster host not found: %s", settings.CLUSTER_HOST_ID)
+ return
+
+ logger.debug("No extra reaping required for instance %s", inst.hostname)
+ logger.warning("Normal shutdown processed for instance %s; instance removed from capacity pool.", inst.hostname)
+
+
+@task(queue=get_task_queuename, timeout=3600 * 5)
+def migrate_jsonfield(table, pkfield, columns):
+ batchsize = 10000
+ with advisory_lock(f'json_migration_{table}', wait=False) as acquired:
+ if not acquired:
+ return
+
+ from django.db.migrations.executor import MigrationExecutor
+
+ # If Django is currently running migrations, wait until it is done.
+ while True:
+ executor = MigrationExecutor(connection)
+ if not executor.migration_plan(executor.loader.graph.leaf_nodes()):
+ break
+ time.sleep(120)
+
+ logger.warning(f"Migrating json fields for {table}: {', '.join(columns)}")
+
+ with connection.cursor() as cursor:
+ for i in itertools.count(0, batchsize):
+ # Are there even any rows in the table beyond this point?
+ cursor.execute(f"select count(1) from {table} where {pkfield} >= %s limit 1;", (i,))
+ if not cursor.fetchone()[0]:
+ break
+
+ column_expr = ', '.join(f"{colname} = {colname}_old::jsonb" for colname in columns)
+ # If any of the old columns have non-null values, the data needs to be cast and copied over.
+ empty_expr = ' or '.join(f"{colname}_old is not null" for colname in columns)
+ cursor.execute( # Only clobber the new fields if there is non-null data in the old ones.
+ f"""
+ update {table}
+ set {column_expr}
+ where {pkfield} >= %s and {pkfield} < %s
+ and {empty_expr};
+ """,
+ (i, i + batchsize),
+ )
+ rows = cursor.rowcount
+ logger.debug(f"Batch {i} to {i + batchsize} copied on {table}, {rows} rows affected.")
+ column_expr = ', '.join(f"DROP COLUMN {column}_old" for column in columns)
+ cursor.execute(f"ALTER TABLE {table} {column_expr};")
-@task(queue=get_local_queuename)
+ logger.warning(f"Migration of {table} to jsonb is finished.")
+
+
+@task(queue=get_task_queuename, timeout=3600, on_duplicate='queue_one')
def apply_cluster_membership_policies():
from awx.main.signals import disable_activity_stream
@@ -244,8 +326,10 @@ def apply_cluster_membership_policies():
logger.debug('Cluster policy computation finished in {} seconds'.format(time.time() - started_compute))
-@task(queue='tower_broadcast_all')
-def handle_setting_changes(setting_keys):
+@task(queue='tower_settings_change', timeout=600)
+def clear_setting_cache(setting_keys):
+ # log that cache is being cleared
+ logger.info(f"clear_setting_cache of keys {setting_keys}")
orig_len = len(setting_keys)
for i in range(orig_len):
for dependent_key in settings_registry.get_dependent_settings(setting_keys[i]):
@@ -254,11 +338,13 @@ def handle_setting_changes(setting_keys):
logger.debug('cache delete_many(%r)', cache_keys)
cache.delete_many(cache_keys)
- if any([setting.startswith('LOG_AGGREGATOR') for setting in setting_keys]):
- reconfigure_rsyslog()
+ if 'LOG_AGGREGATOR_LEVEL' in setting_keys:
+ ctl = get_control_from_settings()
+ ctl.queuename = get_task_queuename()
+ ctl.control('set_log_level', data={'level': settings.LOG_AGGREGATOR_LEVEL})
-@task(queue='tower_broadcast_all')
+@task(queue='tower_broadcast_all', timeout=600)
def delete_project_files(project_path):
# TODO: possibly implement some retry logic
lock_file = project_path + '.lock'
@@ -286,7 +372,7 @@ def profile_sql(threshold=1, minutes=1):
logger.error('SQL QUERIES >={}s ENABLED FOR {} MINUTE(S)'.format(threshold, minutes))
-@task(queue=get_local_queuename)
+@task(queue=get_task_queuename, timeout=1800)
def send_notifications(notification_list, job_id=None):
if not isinstance(notification_list, list):
raise TypeError("notification_list should be of type list")
@@ -317,20 +403,27 @@ def send_notifications(notification_list, job_id=None):
logger.exception('Error saving notification {} result.'.format(notification.id))
-@task(queue=get_local_queuename)
-def gather_analytics():
- from awx.conf.models import Setting
- from rest_framework.fields import DateTimeField
+def events_processed_hook(unified_job):
+ """This method is intended to be called for every unified job
+ after the playbook_on_stats/EOF event is processed and final status is saved
+ Either one of these events could happen before the other, or there may be no events"""
+ unified_job.send_notification_templates('succeeded' if unified_job.status == 'successful' else 'failed')
+ if isinstance(unified_job, Job) and flag_enabled("FEATURE_INDIRECT_NODE_COUNTING_ENABLED"):
+ if unified_job.event_queries_processed is True:
+ # If this is called from callback receiver, it likely does not have updated model data
+ # a refresh now is formally robust
+ unified_job.refresh_from_db(fields=['event_queries_processed'])
+ if unified_job.event_queries_processed is False:
+ save_indirect_host_entries.delay(unified_job.id)
- last_gather = Setting.objects.filter(key='AUTOMATION_ANALYTICS_LAST_GATHER').first()
- last_time = DateTimeField().to_internal_value(last_gather.value) if last_gather and last_gather.value else None
- gather_time = now()
- if not last_time or ((gather_time - last_time).total_seconds() > settings.AUTOMATION_ANALYTICS_GATHER_INTERVAL):
+@task(queue=get_task_queuename, timeout=3600 * 5, on_duplicate='discard')
+def gather_analytics():
+ if is_run_threshold_reached(getattr(settings, 'AUTOMATION_ANALYTICS_LAST_GATHER', None), settings.AUTOMATION_ANALYTICS_GATHER_INTERVAL):
analytics.gather()
-@task(queue=get_local_queuename)
+@task(queue=get_task_queuename, timeout=600, on_duplicate='queue_one')
def purge_old_stdout_files():
nowtime = time.time()
for f in os.listdir(settings.JOBOUTPUT_ROOT):
@@ -339,70 +432,71 @@ def purge_old_stdout_files():
logger.debug("Removing {}".format(os.path.join(settings.JOBOUTPUT_ROOT, f)))
-def _cleanup_images_and_files(**kwargs):
- if settings.IS_K8S:
- return
- this_inst = Instance.objects.me()
- runner_cleanup_kwargs = this_inst.get_cleanup_task_kwargs(**kwargs)
- if runner_cleanup_kwargs:
- stdout = ''
- with StringIO() as buffer:
- with redirect_stdout(buffer):
- ansible_runner.cleanup.run_cleanup(runner_cleanup_kwargs)
- stdout = buffer.getvalue()
- if '(changed: True)' in stdout:
- logger.info(f'Performed local cleanup with kwargs {kwargs}, output:\n{stdout}')
-
- # if we are the first instance alphabetically, then run cleanup on execution nodes
- checker_instance = (
- Instance.objects.filter(node_type__in=['hybrid', 'control'], node_state=Instance.States.READY, enabled=True, capacity__gt=0)
- .order_by('-hostname')
- .first()
- )
- if checker_instance and this_inst.hostname == checker_instance.hostname:
- for inst in Instance.objects.filter(node_type='execution', node_state=Instance.States.READY, enabled=True, capacity__gt=0):
- runner_cleanup_kwargs = inst.get_cleanup_task_kwargs(**kwargs)
- if not runner_cleanup_kwargs:
- continue
- try:
- stdout = worker_cleanup(inst.hostname, runner_cleanup_kwargs)
- if '(changed: True)' in stdout:
- logger.info(f'Performed cleanup on execution node {inst.hostname} with output:\n{stdout}')
- except RuntimeError:
- logger.exception(f'Error running cleanup on execution node {inst.hostname}')
+class CleanupImagesAndFiles:
+ @classmethod
+ def get_first_control_instance(cls) -> Instance | None:
+ return (
+ Instance.objects.filter(node_type__in=['hybrid', 'control'], node_state=Instance.States.READY, enabled=True, capacity__gt=0)
+ .order_by('-hostname')
+ .first()
+ )
+ @classmethod
+ def get_execution_instances(cls) -> QuerySet[Instance]:
+ return Instance.objects.filter(node_type='execution', node_state=Instance.States.READY, enabled=True, capacity__gt=0)
-@task(queue='tower_broadcast_all')
+ @classmethod
+ def run_local(cls, this_inst: Instance, **kwargs):
+ if settings.IS_K8S:
+ return
+ runner_cleanup_kwargs = this_inst.get_cleanup_task_kwargs(**kwargs)
+ if runner_cleanup_kwargs:
+ stdout = ''
+ with StringIO() as buffer:
+ with redirect_stdout(buffer):
+ ansible_runner.cleanup.run_cleanup(runner_cleanup_kwargs)
+ stdout = buffer.getvalue()
+ if '(changed: True)' in stdout:
+ logger.info(f'Performed local cleanup with kwargs {kwargs}, output:\n{stdout}')
+
+ @classmethod
+ def run_remote(cls, this_inst: Instance, **kwargs):
+ # if we are the first instance alphabetically, then run cleanup on execution nodes
+ checker_instance = cls.get_first_control_instance()
+
+ if checker_instance and this_inst.hostname == checker_instance.hostname:
+ for inst in cls.get_execution_instances():
+ runner_cleanup_kwargs = inst.get_cleanup_task_kwargs(**kwargs)
+ if not runner_cleanup_kwargs:
+ continue
+ try:
+ stdout = worker_cleanup(inst.hostname, runner_cleanup_kwargs)
+ if '(changed: True)' in stdout:
+ logger.info(f'Performed cleanup on execution node {inst.hostname} with output:\n{stdout}')
+ except RuntimeError:
+ logger.exception(f'Error running cleanup on execution node {inst.hostname}')
+
+ @classmethod
+ def run(cls, **kwargs):
+ if settings.IS_K8S:
+ return
+ this_inst = Instance.objects.me()
+ cls.run_local(this_inst, **kwargs)
+ cls.run_remote(this_inst, **kwargs)
+
+
+@task(queue='tower_broadcast_all', timeout=3600)
def handle_removed_image(remove_images=None):
"""Special broadcast invocation of this method to handle case of deleted EE"""
- _cleanup_images_and_files(remove_images=remove_images, file_pattern='')
+ CleanupImagesAndFiles.run(remove_images=remove_images, file_pattern='')
-@task(queue=get_local_queuename)
+@task(queue=get_task_queuename, timeout=3600, on_duplicate='queue_one')
def cleanup_images_and_files():
- _cleanup_images_and_files()
-
-
-@task(queue=get_local_queuename)
-def cluster_node_health_check(node):
- """
- Used for the health check endpoint, refreshes the status of the instance, but must be ran on target node
- """
- if node == '':
- logger.warning('Local health check incorrectly called with blank string')
- return
- elif node != settings.CLUSTER_HOST_ID:
- logger.warning(f'Local health check for {node} incorrectly sent to {settings.CLUSTER_HOST_ID}')
- return
- try:
- this_inst = Instance.objects.me()
- except Instance.DoesNotExist:
- logger.warning(f'Instance record for {node} missing, could not check capacity.')
- return
- this_inst.local_health_check()
+ CleanupImagesAndFiles.run(image_prune=True)
-@task(queue=get_local_queuename)
+@task(queue=get_task_queuename, timeout=600, on_duplicate='queue_one')
def execution_node_health_check(node):
if node == '':
logger.warning('Remote health check incorrectly called with blank string')
@@ -424,7 +518,6 @@ def execution_node_health_check(node):
data = worker_info(node)
prior_capacity = instance.capacity
-
instance.save_health_data(
version='ansible-runner-' + data.get('runner_version', '???'),
cpu=data.get('cpu_count', 0),
@@ -445,12 +538,44 @@ def execution_node_health_check(node):
return data
-def inspect_execution_nodes(instance_list):
- with advisory_lock('inspect_execution_nodes_lock', wait=False):
+def inspect_established_receptor_connections(mesh_status):
+ '''
+ Flips link state from ADDING to ESTABLISHED
+ If the InstanceLink source and target match the entries
+ in Known Connection Costs, flip to Established.
+ '''
+ from awx.main.models import InstanceLink
+
+ all_links = InstanceLink.objects.filter(link_state=InstanceLink.States.ADDING)
+ if not all_links.exists():
+ return
+ active_receptor_conns = mesh_status['KnownConnectionCosts']
+ update_links = []
+ for link in all_links:
+ if link.link_state != InstanceLink.States.REMOVING:
+ if link.target.instance.hostname in active_receptor_conns.get(link.source.hostname, {}):
+ if link.link_state is not InstanceLink.States.ESTABLISHED:
+ link.link_state = InstanceLink.States.ESTABLISHED
+ update_links.append(link)
+
+ InstanceLink.objects.bulk_update(update_links, ['link_state'])
+
+
+def inspect_execution_and_hop_nodes(instance_list):
+ with advisory_lock('inspect_execution_and_hop_nodes_lock', wait=False):
node_lookup = {inst.hostname: inst for inst in instance_list}
+ try:
+ ctl = get_receptor_ctl()
+ except FileNotFoundError:
+ logger.error('Receptor daemon not running, skipping execution node check')
+ return
+ try:
+ mesh_status = ctl.simple_command('status')
+ except ValueError as exc:
+ logger.error(f'Error running receptorctl status command, error: {str(exc)}')
+ return
- ctl = get_receptor_ctl()
- mesh_status = ctl.simple_command('status')
+ inspect_established_receptor_connections(mesh_status)
nowtime = now()
workers = mesh_status['Advertisements']
@@ -496,8 +621,81 @@ def inspect_execution_nodes(instance_list):
execution_node_health_check.apply_async([hostname])
-@task(queue=get_local_queuename, bind_kwargs=['dispatch_time', 'worker_tasks'])
-def cluster_node_heartbeat(dispatch_time=None, worker_tasks=None):
+@task(queue=get_task_queuename, bind=True)
+def cluster_node_heartbeat(binder):
+ """
+ Dispatcherd implementation.
+ Uses Control API to get running tasks.
+ """
+
+ # Run common instance management logic
+ this_inst, instance_list, lost_instances = _heartbeat_instance_management()
+ if this_inst is None:
+ return # Early return case from instance management
+
+ # Check versions
+ _heartbeat_check_versions(this_inst, instance_list)
+
+ # Handle lost instances
+ _heartbeat_handle_lost_instances(lost_instances, this_inst)
+
+ # Get running tasks using dispatcherd API
+ if binder is None:
+ logger.debug("Heartbeat finished in startup.")
+ return
+ active_task_ids = _get_active_task_ids_from_dispatcherd(binder)
+ if active_task_ids is None:
+ logger.warning("No active task IDs retrieved from dispatcherd, skipping reaper")
+ return # Failed to get task IDs, don't attempt reaping
+
+ # Run local reaper using tasks from dispatcherd
+ ref_time = now() # No dispatch_time in dispatcherd version
+ logger.debug(f"Running reaper with {len(active_task_ids)} excluded UUIDs")
+ reaper.reap(instance=this_inst, excluded_uuids=active_task_ids, ref_time=ref_time)
+ # If waiting jobs are hanging out, resubmit them
+ if UnifiedJob.objects.filter(controller_node=settings.CLUSTER_HOST_ID, status='waiting').exists():
+ from awx.main.tasks.jobs import dispatch_waiting_jobs
+
+ dispatch_waiting_jobs.apply_async(queue=get_task_queuename())
+
+
+def _get_active_task_ids_from_dispatcherd(binder):
+ """
+ Retrieve active task IDs from the dispatcherd control API.
+
+ Returns:
+ list: List of active task UUIDs
+ None: If there was an error retrieving the data
+ """
+ active_task_ids = []
+ try:
+
+ logger.debug("Querying dispatcherd API for running tasks")
+ data = binder.control('running')
+
+ # Extract UUIDs from the running data
+ # Process running data: first item is a dict with node_id and task entries
+ data.pop('node_id', None)
+
+ # Extract task UUIDs from data structure
+ for task_key, task_value in data.items():
+ if isinstance(task_value, dict) and 'uuid' in task_value:
+ active_task_ids.append(task_value['uuid'])
+ logger.debug(f"Found active task with UUID: {task_value['uuid']}")
+ elif isinstance(task_key, str):
+ # Handle case where UUID might be the key
+ active_task_ids.append(task_key)
+ logger.debug(f"Found active task with key: {task_key}")
+
+ logger.debug(f"Retrieved {len(active_task_ids)} active task IDs from dispatcherd")
+ return active_task_ids
+ except Exception:
+ logger.exception("Failed to get running tasks from dispatcherd")
+ return None
+
+
+def _heartbeat_instance_management():
+ """Common logic for heartbeat instance management."""
logger.debug("Cluster node heartbeat task.")
nowtime = now()
instance_list = list(Instance.objects.filter(node_state__in=(Instance.States.READY, Instance.States.UNAVAILABLE, Instance.States.INSTALLED)))
@@ -509,7 +707,7 @@ def cluster_node_heartbeat(dispatch_time=None, worker_tasks=None):
this_inst = inst
break
- inspect_execution_nodes(instance_list)
+ inspect_execution_and_hop_nodes(instance_list)
for inst in list(instance_list):
if inst == this_inst:
@@ -524,20 +722,26 @@ def cluster_node_heartbeat(dispatch_time=None, worker_tasks=None):
this_inst.local_health_check()
if startup_event and this_inst.capacity != 0:
logger.warning(f'Rejoining the cluster as instance {this_inst.hostname}. Prior last_seen {last_last_seen}')
- return
+ return None, None, None # Early return case
elif not last_last_seen:
logger.warning(f'Instance does not have recorded last_seen, updating to {nowtime}')
elif (nowtime - last_last_seen) > timedelta(seconds=settings.CLUSTER_NODE_HEARTBEAT_PERIOD + 2):
logger.warning(f'Heartbeat skew - interval={(nowtime - last_last_seen).total_seconds():.4f}, expected={settings.CLUSTER_NODE_HEARTBEAT_PERIOD}')
else:
if settings.AWX_AUTO_DEPROVISION_INSTANCES:
- (changed, this_inst) = Instance.objects.register(ip_address=os.environ.get('MY_POD_IP'), node_type='control', uuid=settings.SYSTEM_UUID)
+ changed, this_inst = Instance.objects.register(ip_address=os.environ.get('MY_POD_IP'), node_type='control', node_uuid=settings.SYSTEM_UUID)
if changed:
logger.warning(f'Recreated instance record {this_inst.hostname} after unexpected removal')
this_inst.local_health_check()
else:
- raise RuntimeError("Cluster Host Not Found: {}".format(settings.CLUSTER_HOST_ID))
- # IFF any node has a greater version than we do, then we'll shutdown services
+ logger.error("Cluster Host Not Found: {}".format(settings.CLUSTER_HOST_ID))
+ return None, None, None
+
+ return this_inst, instance_list, lost_instances
+
+
+def _heartbeat_check_versions(this_inst, instance_list):
+ """Check versions across instances and determine if shutdown is needed."""
for other_inst in instance_list:
if other_inst.node_type in ('execution', 'hop'):
continue
@@ -554,13 +758,18 @@ def cluster_node_heartbeat(dispatch_time=None, worker_tasks=None):
stop_local_services(communicate=False)
raise RuntimeError("Shutting down.")
+
+def _heartbeat_handle_lost_instances(lost_instances, this_inst):
+ """Handle lost instances by reaping their running jobs and marking them offline."""
for other_inst in lost_instances:
try:
+ # Any jobs marked as running will be marked as error
explanation = "Job reaped due to instance shutdown"
reaper.reap(other_inst, job_explanation=explanation)
- reaper.reap_waiting(other_inst, grace_period=0, job_explanation=explanation)
+ # Any jobs that were waiting to be processed by this node will be handed back to task manager
+ UnifiedJob.objects.filter(status='waiting', controller_node=other_inst.hostname).update(status='pending', controller_node='', execution_node='')
except Exception:
- logger.exception('failed to reap jobs for {}'.format(other_inst.hostname))
+ logger.exception('failed to re-process jobs for lost instance {}'.format(other_inst.hostname))
try:
if settings.AWX_AUTO_DEPROVISION_INSTANCES and other_inst.node_type == "control":
deprovision_hostname = other_inst.hostname
@@ -571,22 +780,21 @@ def cluster_node_heartbeat(dispatch_time=None, worker_tasks=None):
logger.error("Host {} last checked in at {}, marked as lost.".format(other_inst.hostname, other_inst.last_seen))
except DatabaseError as e:
- if 'did not affect any rows' in str(e):
- logger.debug('Another instance has marked {} as lost'.format(other_inst.hostname))
+ cause = e.__cause__
+ if cause and hasattr(cause, 'sqlstate'):
+ sqlstate = cause.sqlstate
+ sqlstate_str = psycopg.errors.lookup(sqlstate)
+ logger.debug('SQL Error state: {} - {}'.format(sqlstate, sqlstate_str))
+
+ if sqlstate == psycopg.errors.NoData:
+ logger.debug('Another instance has marked {} as lost'.format(other_inst.hostname))
+ else:
+ logger.exception("Error marking {} as lost.".format(other_inst.hostname))
else:
- logger.exception('Error marking {} as lost'.format(other_inst.hostname))
-
- # Run local reaper
- if worker_tasks is not None:
- active_task_ids = []
- for task_list in worker_tasks.values():
- active_task_ids.extend(task_list)
- reaper.reap(instance=this_inst, excluded_uuids=active_task_ids)
- if max(len(task_list) for task_list in worker_tasks.values()) <= 1:
- reaper.reap_waiting(instance=this_inst, excluded_uuids=active_task_ids, ref_time=datetime.fromisoformat(dispatch_time))
+ logger.exception('No SQL state available. Error marking {} as lost'.format(other_inst.hostname))
-@task(queue=get_local_queuename)
+@task(queue=get_task_queuename, timeout=1800, on_duplicate='queue_one')
def awx_receptor_workunit_reaper():
"""
When an AWX job is launched via receptor, files such as status, stdin, and stdout are created
@@ -609,11 +817,21 @@ def awx_receptor_workunit_reaper():
if not settings.RECEPTOR_RELEASE_WORK:
return
logger.debug("Checking for unreleased receptor work units")
- receptor_ctl = get_receptor_ctl()
- receptor_work_list = receptor_ctl.simple_command("work list")
+ try:
+ receptor_ctl = get_receptor_ctl()
+ except FileNotFoundError:
+ logger.info('Receptorctl sockfile not found for workunit reaper, doing nothing')
+ return
+ try:
+ receptor_work_list = receptor_ctl.simple_command("work list")
+ except ValueError as exc:
+ logger.info(f'Error getting work list for workunit reaper, error: {str(exc)}')
+ return
unit_ids = [id for id in receptor_work_list]
jobs_with_unreleased_receptor_units = UnifiedJob.objects.filter(work_unit_id__in=unit_ids).exclude(status__in=ACTIVE_STATES)
+ if settings.RECEPTOR_KEEP_WORK_ON_ERROR:
+ jobs_with_unreleased_receptor_units = jobs_with_unreleased_receptor_units.exclude(status__in=ERROR_STATES)
for job in jobs_with_unreleased_receptor_units:
logger.debug(f"{job.log_format} is not active, reaping receptor work unit {job.work_unit_id}")
receptor_ctl.simple_command(f"work cancel {job.work_unit_id}")
@@ -622,7 +840,7 @@ def awx_receptor_workunit_reaper():
administrative_workunit_reaper(receptor_work_list)
-@task(queue=get_local_queuename)
+@task(queue=get_task_queuename, timeout=1800, on_duplicate='queue_one')
def awx_k8s_reaper():
if not settings.RECEPTOR_RELEASE_WORK:
return
@@ -633,7 +851,10 @@ def awx_k8s_reaper():
logger.debug("Checking for orphaned k8s pods for {}.".format(group))
pods = PodManager.list_active_jobs(group)
time_cutoff = now() - timedelta(seconds=settings.K8S_POD_REAPER_GRACE_PERIOD)
- for job in UnifiedJob.objects.filter(pk__in=pods.keys(), finished__lte=time_cutoff).exclude(status__in=ACTIVE_STATES):
+ reap_job_candidates = UnifiedJob.objects.filter(pk__in=pods.keys(), finished__lte=time_cutoff).exclude(status__in=ACTIVE_STATES)
+ if settings.RECEPTOR_KEEP_WORK_ON_ERROR:
+ reap_job_candidates = reap_job_candidates.exclude(status__in=ERROR_STATES)
+ for job in reap_job_candidates:
logger.debug('{} is no longer active, reaping orphaned k8s pod'.format(job.log_format))
try:
pm = PodManager(job)
@@ -642,9 +863,10 @@ def awx_k8s_reaper():
logger.exception("Failed to delete orphaned pod {} from {}".format(job.log_format, group))
-@task(queue=get_local_queuename)
+@task(queue=get_task_queuename, timeout=3600 * 5, on_duplicate='discard')
def awx_periodic_scheduler():
- with advisory_lock('awx_periodic_scheduler_lock', wait=False) as acquired:
+ lock_session_timeout_milliseconds = settings.TASK_MANAGER_LOCK_TIMEOUT * 1000
+ with advisory_lock('awx_periodic_scheduler_lock', lock_session_timeout_milliseconds=lock_session_timeout_milliseconds, wait=False) as acquired:
if acquired is False:
logger.debug("Not running periodic scheduler, another task holds lock")
return
@@ -691,76 +913,29 @@ def awx_periodic_scheduler():
continue
if not can_start:
new_unified_job.status = 'failed'
- new_unified_job.job_explanation = gettext_noop(
- "Scheduled job could not start because it \
- was not in the right state or required manual credentials"
- )
+ new_unified_job.job_explanation = gettext_noop("Scheduled job could not start because it \
+ was not in the right state or required manual credentials")
new_unified_job.save(update_fields=['status', 'job_explanation'])
new_unified_job.websocket_emit_status("failed")
emit_channel_notification('schedules-changed', dict(id=schedule.id, group_name="schedules"))
- state.save()
-
-
-def schedule_manager_success_or_error(instance):
- if instance.unifiedjob_blocked_jobs.exists():
- ScheduleTaskManager().schedule()
- if instance.spawned_by_workflow:
- ScheduleWorkflowManager().schedule()
-
-
-@task(queue=get_local_queuename)
-def handle_work_success(task_actual):
- try:
- instance = UnifiedJob.get_instance_by_type(task_actual['type'], task_actual['id'])
- except ObjectDoesNotExist:
- logger.warning('Missing {} `{}` in success callback.'.format(task_actual['type'], task_actual['id']))
- return
- if not instance:
- return
- schedule_manager_success_or_error(instance)
-@task(queue=get_local_queuename)
-def handle_work_error(task_actual):
- try:
- instance = UnifiedJob.get_instance_by_type(task_actual['type'], task_actual['id'])
- except ObjectDoesNotExist:
- logger.warning('Missing {} `{}` in error callback.'.format(task_actual['type'], task_actual['id']))
- return
- if not instance:
- return
-
- subtasks = instance.get_jobs_fail_chain() # reverse of dependent_jobs mostly
- logger.debug(f'Executing error task id {task_actual["id"]}, subtasks: {[subtask.id for subtask in subtasks]}')
-
- deps_of_deps = {}
-
- for subtask in subtasks:
- if subtask.celery_task_id != instance.celery_task_id and not subtask.cancel_flag and not subtask.status in ('successful', 'failed'):
- # If there are multiple in the dependency chain, A->B->C, and this was called for A, blame B for clarity
- blame_job = deps_of_deps.get(subtask.id, instance)
- subtask.status = 'failed'
- subtask.failed = True
- if not subtask.job_explanation:
- subtask.job_explanation = 'Previous Task Failed: {"job_type": "%s", "job_name": "%s", "job_id": "%s"}' % (
- get_type_for_model(type(blame_job)),
- blame_job.name,
- blame_job.id,
- )
- subtask.save()
- subtask.websocket_emit_status("failed")
-
- for sub_subtask in subtask.get_jobs_fail_chain():
- deps_of_deps[sub_subtask.id] = subtask
-
- # We only send 1 job complete message since all the job completion message
- # handling does is trigger the scheduler. If we extend the functionality of
- # what the job complete message handler does then we may want to send a
- # completion event for each job here.
- schedule_manager_success_or_error(instance)
+@task(queue=get_task_queuename, timeout=3600)
+def handle_failure_notifications(task_ids):
+ """A task-ified version of the method that sends notifications."""
+ found_task_ids = set()
+ for instance in UnifiedJob.objects.filter(id__in=task_ids):
+ found_task_ids.add(instance.id)
+ try:
+ instance.send_notification_templates('failed')
+ except Exception:
+ logger.exception(f'Error preparing notifications for task {instance.id}')
+ deleted_tasks = set(task_ids) - found_task_ids
+ if deleted_tasks:
+ logger.warning(f'Could not send notifications for {deleted_tasks} because they were not found in the database')
-@task(queue=get_local_queuename)
+@task(queue=get_task_queuename, timeout=3600 * 5)
def update_inventory_computed_fields(inventory_id):
"""
Signal handler and wrapper around inventory.update_computed_fields to
@@ -774,10 +949,19 @@ def update_inventory_computed_fields(inventory_id):
try:
i.update_computed_fields()
except DatabaseError as e:
- if 'did not affect any rows' in str(e):
- logger.debug('Exiting duplicate update_inventory_computed_fields task.')
- return
- raise
+ # https://github.com/django/django/blob/eff21d8e7a1cb297aedf1c702668b590a1b618f3/django/db/models/base.py#L1105
+ # django raises DatabaseError("Forced update did not affect any rows.")
+
+ # if sqlstate is set then there was a database error and otherwise will re-raise that error
+ cause = e.__cause__
+ if cause and hasattr(cause, 'sqlstate'):
+ sqlstate = cause.sqlstate
+ sqlstate_str = psycopg.errors.lookup(sqlstate)
+ logger.error('SQL Error state: {} - {}'.format(sqlstate, sqlstate_str))
+ raise
+
+ # otherwise
+ logger.debug('Exiting duplicate update_inventory_computed_fields task.')
def update_smart_memberships_for_inventory(smart_inventory):
@@ -801,7 +985,7 @@ def update_smart_memberships_for_inventory(smart_inventory):
return False
-@task(queue=get_local_queuename)
+@task(queue=get_task_queuename, timeout=3600, on_duplicate='queue_one')
def update_host_smart_inventory_memberships():
smart_inventories = Inventory.objects.filter(kind='smart', host_filter__isnull=False, pending_deletion=False)
changed_inventories = set([])
@@ -817,7 +1001,7 @@ def update_host_smart_inventory_memberships():
smart_inventory.update_computed_fields()
-@task(queue=get_local_queuename)
+@task(queue=get_task_queuename, timeout=3600 * 5)
def delete_inventory(inventory_id, user_id, retries=5):
# Delete inventory as user
if user_id is None:
@@ -829,10 +1013,7 @@ def delete_inventory(inventory_id, user_id, retries=5):
user = None
with ignore_inventory_computed_fields(), ignore_inventory_group_removal(), impersonate(user):
try:
- i = Inventory.objects.get(id=inventory_id)
- for host in i.hosts.iterator():
- host.job_events_as_primary_host.update(host=None)
- i.delete()
+ Inventory.objects.get(id=inventory_id).delete()
emit_channel_notification('inventories-status_changed', {'group_name': 'inventories', 'inventory_id': inventory_id, 'status': 'deleted'})
logger.debug('Deleted inventory {} as user {}.'.format(inventory_id, user_id))
except Inventory.DoesNotExist:
@@ -882,16 +1063,9 @@ def _reconstruct_relationships(copy_mapping):
new_obj.save()
-@task(queue=get_local_queuename)
-def deep_copy_model_obj(model_module, model_name, obj_pk, new_obj_pk, user_pk, uuid, permission_check_func=None):
- sub_obj_list = cache.get(uuid)
- if sub_obj_list is None:
- logger.error('Deep copy {} from {} to {} failed unexpectedly.'.format(model_name, obj_pk, new_obj_pk))
- return
-
+@task(queue=get_task_queuename, timeout=600)
+def deep_copy_model_obj(model_module, model_name, obj_pk, new_obj_pk, user_pk, permission_check_func=None):
logger.debug('Deep copy {} from {} to {}.'.format(model_name, obj_pk, new_obj_pk))
- from awx.api.generics import CopyAPIView
- from awx.main.signals import disable_activity_stream
model = getattr(importlib.import_module(model_module), model_name, None)
if model is None:
@@ -903,6 +1077,28 @@ def deep_copy_model_obj(model_module, model_name, obj_pk, new_obj_pk, user_pk, u
except ObjectDoesNotExist:
logger.warning("Object or user no longer exists.")
return
+
+ o2m_to_preserve = {}
+ fields_to_preserve = set(getattr(model, 'FIELDS_TO_PRESERVE_AT_COPY', []))
+
+ for field in model._meta.get_fields():
+ if field.name in fields_to_preserve:
+ if field.one_to_many:
+ try:
+ field_val = getattr(obj, field.name)
+ except AttributeError:
+ continue
+ o2m_to_preserve[field.name] = field_val
+
+ sub_obj_list = []
+ for o2m in o2m_to_preserve:
+ for sub_obj in o2m_to_preserve[o2m].all():
+ sub_model = type(sub_obj)
+ sub_obj_list.append((sub_model.__module__, sub_model.__name__, sub_obj.pk))
+
+ from awx.api.generics import CopyAPIView
+ from awx.main.signals import disable_activity_stream
+
with transaction.atomic(), ignore_inventory_computed_fields(), disable_activity_stream():
copy_mapping = {}
for sub_obj_setup in sub_obj_list:
@@ -920,3 +1116,27 @@ def deep_copy_model_obj(model_module, model_name, obj_pk, new_obj_pk, user_pk, u
permission_check_func(creater, copy_mapping.values())
if isinstance(new_obj, Inventory):
update_inventory_computed_fields.delay(new_obj.id)
+
+
+@task(queue=get_task_queuename, timeout=3600, on_duplicate='discard')
+def periodic_resource_sync():
+ if not getattr(settings, 'RESOURCE_SERVER', None):
+ logger.debug("Skipping periodic resource_sync, RESOURCE_SERVER not configured")
+ return
+
+ with advisory_lock('periodic_resource_sync', wait=False) as acquired:
+ if acquired is False:
+ logger.debug("Not running periodic_resource_sync, another task holds lock")
+ return
+ logger.debug("Running periodic resource sync")
+
+ executor = SyncExecutor()
+ executor.run()
+ for key, item_list in executor.results.items():
+ if not item_list or key == 'noop':
+ continue
+ # Log creations and conflicts
+ if len(item_list) > 10 and settings.LOG_AGGREGATOR_LEVEL != 'DEBUG':
+ logger.info(f'Periodic resource sync {key}, first 10 items:\n{item_list[:10]}')
+ else:
+ logger.info(f'Periodic resource sync {key}:\n{item_list}')
diff --git a/awx/main/tests/README.md b/awx/main/tests/README.md
new file mode 100644
index 000000000000..f6aac6418eec
--- /dev/null
+++ b/awx/main/tests/README.md
@@ -0,0 +1,42 @@
+## Test Environments
+
+Several of the subfolders of `awx/main/tests/` indicate a different required _environment_
+where you can run the tests. Those folders are:
+
+ - `functional/` - requires a test database and no other services running
+ - `live/` - must run in `tools_awx_1` container launched by `make docker-compose`
+ - `unit/` - does not require a test database or any active services
+
+### Functional and unit test environment
+
+The functional and unit tests have an invocation in `make test`,
+and this attaches several other things like schema that piggybacks on requests.
+These tests are ran from the root AWX folder.
+
+#### Functional tests
+
+Only tests in the `functional/` folder should use the `@pytest.mark.django_db` decorator.
+This is the only difference between the functional and unit folders,
+the test environment is otherwise the same for both.
+
+Functional tests use a sqlite3 database, so the postgres service is not necessary.
+
+### Live tests
+
+The live tests have an invocation in `make live_test` which will change
+directory before running, which is required to pick up a different pytest
+configuration.
+
+This will use the postges container from `make docker-compose` for the database,
+and will disable the pytest-django features of running with a test database
+and running tests in transactions.
+This means that any changes done in the course of the test could potentially
+be seen in your browser via the API or UI, and anything the test fails
+to clean up will remain in the database.
+
+### Folders that should not contain tests
+
+ - `data/` - just files other tests use
+ - `docs/` - utilities for schema generation
+ - `factories/` - general utilities
+ - `manual/` - python files to be ran directly
diff --git a/awx/main/tests/conftest.py b/awx/main/tests/conftest.py
index 28565901b06a..71e9637ed9e8 100644
--- a/awx/main/tests/conftest.py
+++ b/awx/main/tests/conftest.py
@@ -140,11 +140,6 @@ def rf(persisted):
return rf
-@pytest.fixture
-def job_with_secret_key_unit(job_with_secret_key_factory):
- return job_with_secret_key_factory(persisted=False)
-
-
@pytest.fixture
def workflow_job_template_factory():
return create_workflow_job_template
@@ -216,6 +211,25 @@ def event_qs(self):
@pytest.fixture
def mock_me():
+ "Allows Instance.objects.me() to work without touching the database"
me_mock = mock.MagicMock(return_value=Instance(id=1, hostname=settings.CLUSTER_HOST_ID, uuid='00000000-0000-0000-0000-000000000000'))
with mock.patch.object(Instance.objects, 'me', me_mock):
yield
+
+
+@pytest.fixture
+def me_inst():
+ "Inserts an instance to the database for Instance.objects.me(), and goes ahead and mocks it in"
+ inst = Instance.objects.create(hostname='local_node', uuid='00000000-0000-0000-0000-000000000000')
+ me_mock = mock.MagicMock(return_value=inst)
+ with mock.patch.object(Instance.objects, 'me', me_mock):
+ yield inst
+
+
+@pytest.fixture(scope="session", autouse=True)
+def load_all_credentials():
+ with mock.patch('awx.main.models.credential.detect_server_product_name', return_value='NOT_AWX'):
+ from awx.main.models.credential import load_credentials
+
+ load_credentials()
+ yield
diff --git a/awx/main/tests/data/ansible_utils/playbooks/valid/hello_world.yml b/awx/main/tests/data/ansible_utils/playbooks/valid/hello_world.yml
index 80d56debc40a..7aff8dbf9ef4 100644
--- a/awx/main/tests/data/ansible_utils/playbooks/valid/hello_world.yml
+++ b/awx/main/tests/data/ansible_utils/playbooks/valid/hello_world.yml
@@ -3,5 +3,5 @@
hosts: all
tasks:
- name: Hello Message
- debug:
+ ansible.builtin.debug:
msg: "Hello World!"
diff --git a/awx/main/tests/data/inventory/plugins/azure_rm/env.json b/awx/main/tests/data/inventory/plugins/azure_rm/env.json
index 9ad6db311ec0..b2627d437d7c 100644
--- a/awx/main/tests/data/inventory/plugins/azure_rm/env.json
+++ b/awx/main/tests/data/inventory/plugins/azure_rm/env.json
@@ -1,9 +1,8 @@
{
"ANSIBLE_JINJA2_NATIVE": "True",
- "ANSIBLE_TRANSFORM_INVALID_GROUP_CHARS": "never",
"AZURE_CLIENT_ID": "fooo",
"AZURE_CLOUD_ENVIRONMENT": "fooo",
"AZURE_SECRET": "fooo",
"AZURE_SUBSCRIPTION_ID": "fooo",
"AZURE_TENANT": "fooo"
-}
\ No newline at end of file
+}
diff --git a/awx/main/tests/data/inventory/plugins/controller/env.json b/awx/main/tests/data/inventory/plugins/controller/env.json
index cc7a5d1ffa7d..8a13483b0462 100644
--- a/awx/main/tests/data/inventory/plugins/controller/env.json
+++ b/awx/main/tests/data/inventory/plugins/controller/env.json
@@ -1,5 +1,4 @@
{
- "ANSIBLE_TRANSFORM_INVALID_GROUP_CHARS": "never",
"TOWER_HOST": "https://foo.invalid",
"TOWER_PASSWORD": "fooo",
"TOWER_USERNAME": "fooo",
@@ -9,5 +8,12 @@
"CONTROLLER_PASSWORD": "fooo",
"CONTROLLER_USERNAME": "fooo",
"CONTROLLER_OAUTH_TOKEN": "",
- "CONTROLLER_VERIFY_SSL": "False"
-}
\ No newline at end of file
+ "CONTROLLER_VERIFY_SSL": "False",
+ "AAP_HOSTNAME": "https://foo.invalid",
+ "AAP_PASSWORD": "fooo",
+ "AAP_USERNAME": "fooo",
+ "AAP_VALIDATE_CERTS": "False",
+ "CONTROLLER_REQUEST_TIMEOUT": "fooo",
+ "AAP_REQUEST_TIMEOUT": "fooo",
+ "AAP_TOKEN": ""
+}
diff --git a/awx/main/tests/data/inventory/plugins/ec2/env.json b/awx/main/tests/data/inventory/plugins/ec2/env.json
index 77cedd003eb2..2d1fea36ac12 100644
--- a/awx/main/tests/data/inventory/plugins/ec2/env.json
+++ b/awx/main/tests/data/inventory/plugins/ec2/env.json
@@ -1,8 +1,7 @@
{
"ANSIBLE_JINJA2_NATIVE": "True",
- "ANSIBLE_TRANSFORM_INVALID_GROUP_CHARS": "never",
"AWS_ACCESS_KEY_ID": "fooo",
"AWS_SECRET_ACCESS_KEY": "fooo",
"AWS_SECURITY_TOKEN": "fooo",
"AWS_SESSION_TOKEN": "fooo"
-}
\ No newline at end of file
+}
diff --git a/awx/main/tests/data/inventory/plugins/gce/env.json b/awx/main/tests/data/inventory/plugins/gce/env.json
index 4c87c078eb88..13970e2356d2 100644
--- a/awx/main/tests/data/inventory/plugins/gce/env.json
+++ b/awx/main/tests/data/inventory/plugins/gce/env.json
@@ -1,6 +1,5 @@
{
"ANSIBLE_JINJA2_NATIVE": "True",
- "ANSIBLE_TRANSFORM_INVALID_GROUP_CHARS": "never",
"GCE_CREDENTIALS_FILE_PATH": "{{ file_reference }}",
"GOOGLE_APPLICATION_CREDENTIALS": "{{ file_reference }}",
"GCP_AUTH_KIND": "serviceaccount",
diff --git a/awx/main/tests/data/inventory/plugins/insights/env.json b/awx/main/tests/data/inventory/plugins/insights/env.json
index 46eb0a34e7bb..bbece6a48fcf 100644
--- a/awx/main/tests/data/inventory/plugins/insights/env.json
+++ b/awx/main/tests/data/inventory/plugins/insights/env.json
@@ -1,5 +1,6 @@
{
- "ANSIBLE_TRANSFORM_INVALID_GROUP_CHARS": "never",
"INSIGHTS_USER": "fooo",
- "INSIGHTS_PASSWORD": "fooo"
-}
\ No newline at end of file
+ "INSIGHTS_PASSWORD": "fooo",
+ "INSIGHTS_CLIENT_ID": "fooo",
+ "INSIGHTS_CLIENT_SECRET": "fooo"
+}
diff --git a/awx/main/tests/data/inventory/plugins/openshift_virtualization/env.json b/awx/main/tests/data/inventory/plugins/openshift_virtualization/env.json
new file mode 100644
index 000000000000..a44de77b88cb
--- /dev/null
+++ b/awx/main/tests/data/inventory/plugins/openshift_virtualization/env.json
@@ -0,0 +1,5 @@
+{
+ "K8S_AUTH_HOST": "https://foo.invalid",
+ "K8S_AUTH_API_KEY": "fooo",
+ "K8S_AUTH_VERIFY_SSL": "False"
+}
diff --git a/awx/main/tests/data/inventory/plugins/openstack/env.json b/awx/main/tests/data/inventory/plugins/openstack/env.json
index 88dfb239c373..21e151c38bb3 100644
--- a/awx/main/tests/data/inventory/plugins/openstack/env.json
+++ b/awx/main/tests/data/inventory/plugins/openstack/env.json
@@ -1,4 +1,3 @@
{
- "ANSIBLE_TRANSFORM_INVALID_GROUP_CHARS": "never",
"OS_CLIENT_CONFIG_FILE": "{{ file_reference }}"
-}
\ No newline at end of file
+}
diff --git a/awx/main/tests/data/inventory/plugins/rhv/env.json b/awx/main/tests/data/inventory/plugins/rhv/env.json
index 08477df16913..1030a591bf47 100644
--- a/awx/main/tests/data/inventory/plugins/rhv/env.json
+++ b/awx/main/tests/data/inventory/plugins/rhv/env.json
@@ -1,7 +1,6 @@
{
- "ANSIBLE_TRANSFORM_INVALID_GROUP_CHARS": "never",
"OVIRT_INI_PATH": "{{ file_reference }}",
"OVIRT_PASSWORD": "fooo",
"OVIRT_URL": "https://foo.invalid",
"OVIRT_USERNAME": "fooo"
-}
\ No newline at end of file
+}
diff --git a/awx/main/tests/data/inventory/plugins/satellite6/env.json b/awx/main/tests/data/inventory/plugins/satellite6/env.json
index 102abee70b10..482d2ae5057c 100644
--- a/awx/main/tests/data/inventory/plugins/satellite6/env.json
+++ b/awx/main/tests/data/inventory/plugins/satellite6/env.json
@@ -1,6 +1,5 @@
{
- "ANSIBLE_TRANSFORM_INVALID_GROUP_CHARS": "never",
"FOREMAN_PASSWORD": "fooo",
"FOREMAN_SERVER": "https://foo.invalid",
"FOREMAN_USER": "fooo"
-}
\ No newline at end of file
+}
diff --git a/awx/main/tests/data/inventory/plugins/terraform/env.json b/awx/main/tests/data/inventory/plugins/terraform/env.json
new file mode 100644
index 000000000000..49e8282433e8
--- /dev/null
+++ b/awx/main/tests/data/inventory/plugins/terraform/env.json
@@ -0,0 +1,3 @@
+{
+ "GOOGLE_BACKEND_CREDENTIALS": "{{ file_reference }}"
+}
diff --git a/awx/main/tests/data/inventory/plugins/vmware/env.json b/awx/main/tests/data/inventory/plugins/vmware/env.json
index 97563377c050..6321c24f2632 100644
--- a/awx/main/tests/data/inventory/plugins/vmware/env.json
+++ b/awx/main/tests/data/inventory/plugins/vmware/env.json
@@ -1,7 +1,6 @@
{
- "ANSIBLE_TRANSFORM_INVALID_GROUP_CHARS": "never",
"VMWARE_HOST": "https://foo.invalid",
"VMWARE_PASSWORD": "fooo",
"VMWARE_USER": "fooo",
"VMWARE_VALIDATE_CERTS": "False"
-}
\ No newline at end of file
+}
diff --git a/awx/main/tests/data/projects/README.md b/awx/main/tests/data/projects/README.md
new file mode 100644
index 000000000000..26be7e425c27
--- /dev/null
+++ b/awx/main/tests/data/projects/README.md
@@ -0,0 +1,41 @@
+# Project data for live tests
+
+Each folder in this directory is usable as source for a project or role or collection,
+which is used in tests, particularly the "awx/main/tests/live" tests.
+
+Although these are not git repositories, test fixtures will make copies,
+and in the coppied folders, run `git init` type commands, turning them into
+git repos. This is done in the locations
+
+ - `/var/lib/awx/projects`
+ - `/tmp/live_tests`
+
+These can then be referenced for manual projects or git via the `file://` protocol.
+
+## debug
+
+This is the simplest possible case with 1 playbook with 1 debug task.
+
+## with_requirements
+
+This has a playbook that runs a task that uses a role.
+
+The role project is referenced in the `roles/requirements.yml` file.
+
+### role_requirement
+
+This is the source for the role that the `with_requirements` project uses.
+
+## test_host_query
+
+This has a playbook that runs a task from a custom collection module which
+is registered for the host query feature.
+
+The collection is referenced in its `collections/requirements.yml` file.
+
+### host_query
+
+This can act as source code for a collection that enables host/event querying.
+
+It has a `meta/event_query.yml` file, which may provide you an example of how
+to implement this in your own collection.
diff --git a/awx/main/tests/data/projects/debug/debug.yml b/awx/main/tests/data/projects/debug/debug.yml
new file mode 100644
index 000000000000..f4fdcb2f0e51
--- /dev/null
+++ b/awx/main/tests/data/projects/debug/debug.yml
@@ -0,0 +1,6 @@
+---
+- hosts: all
+ gather_facts: false
+ connection: local
+ tasks:
+ - debug: msg='hello'
diff --git a/awx/main/tests/data/projects/debug/sleep.yml b/awx/main/tests/data/projects/debug/sleep.yml
new file mode 100644
index 000000000000..5ae223f29dc2
--- /dev/null
+++ b/awx/main/tests/data/projects/debug/sleep.yml
@@ -0,0 +1,9 @@
+---
+- hosts: all
+ gather_facts: false
+ connection: local
+ vars:
+ sleep_interval: 5
+ tasks:
+ - name: sleep for a specified interval
+ command: sleep '{{ sleep_interval }}'
diff --git a/awx/main/tests/data/projects/facts/clear.yml b/awx/main/tests/data/projects/facts/clear.yml
new file mode 100644
index 000000000000..140463d6c8fc
--- /dev/null
+++ b/awx/main/tests/data/projects/facts/clear.yml
@@ -0,0 +1,7 @@
+---
+
+- hosts: all
+ gather_facts: false
+ connection: local
+ tasks:
+ - meta: clear_facts
diff --git a/awx/main/tests/data/projects/facts/gather.yml b/awx/main/tests/data/projects/facts/gather.yml
new file mode 100644
index 000000000000..4b5592dd8b3e
--- /dev/null
+++ b/awx/main/tests/data/projects/facts/gather.yml
@@ -0,0 +1,17 @@
+---
+
+- hosts: all
+ vars:
+ extra_value: ""
+ gather_facts: false
+ connection: local
+ tasks:
+ - name: set a custom fact
+ set_fact:
+ foo: "bar{{ extra_value }}"
+ bar:
+ a:
+ b:
+ - "c"
+ - "d"
+ cacheable: true
diff --git a/awx/main/tests/data/projects/facts/no_op.yml b/awx/main/tests/data/projects/facts/no_op.yml
new file mode 100644
index 000000000000..05e5b244d7d1
--- /dev/null
+++ b/awx/main/tests/data/projects/facts/no_op.yml
@@ -0,0 +1,9 @@
+---
+
+- hosts: all
+ gather_facts: false
+ connection: local
+ vars:
+ msg: 'hello'
+ tasks:
+ - debug: var=msg
diff --git a/awx/main/tests/data/projects/host_query/extensions/audit/event_query.yml b/awx/main/tests/data/projects/host_query/extensions/audit/event_query.yml
new file mode 100644
index 000000000000..a10586b90e22
--- /dev/null
+++ b/awx/main/tests/data/projects/host_query/extensions/audit/event_query.yml
@@ -0,0 +1,4 @@
+---
+demo.query.example:
+ query: >-
+ {name: .name, canonical_facts: {host_name: .direct_host_name}, facts: {device_type: .device_type}}
diff --git a/awx/main/tests/data/projects/host_query/galaxy.yml b/awx/main/tests/data/projects/host_query/galaxy.yml
new file mode 100644
index 000000000000..a69203d41643
--- /dev/null
+++ b/awx/main/tests/data/projects/host_query/galaxy.yml
@@ -0,0 +1,19 @@
+---
+authors:
+ - AWX Project Contributors
+dependencies: {}
+description: Indirect host counting example repo. Not for use in production.
+documentation: https://github.com/ansible/awx
+homepage: https://github.com/ansible/awx
+issues: https://github.com/ansible/awx
+license:
+ - GPL-3.0-or-later
+name: query
+namespace: demo
+readme: README.md
+repository: https://github.com/ansible/awx
+tags:
+ - demo
+ - testing
+ - host_counting
+version: 0.0.1
diff --git a/awx/main/tests/data/projects/host_query/plugins/modules/example.py b/awx/main/tests/data/projects/host_query/plugins/modules/example.py
new file mode 100644
index 000000000000..c1427c1d52bd
--- /dev/null
+++ b/awx/main/tests/data/projects/host_query/plugins/modules/example.py
@@ -0,0 +1,78 @@
+#!/usr/bin/python
+
+# Same licensing as AWX
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: example
+
+short_description: Module for specific live tests
+
+version_added: "2.0.0"
+
+description: This module is part of a test collection in local source.
+
+options:
+ host_name:
+ description: Name to return as the host name.
+ required: false
+ type: str
+
+author:
+ - AWX Live Tests
+'''
+
+EXAMPLES = r'''
+- name: Test with defaults
+ demo.query.example:
+
+- name: Test with custom host name
+ demo.query.example:
+ host_name: foo_host
+'''
+
+RETURN = r'''
+direct_host_name:
+ description: The name of the host, this will be collected with the feature.
+ type: str
+ returned: always
+ sample: 'foo_host'
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+
+
+def run_module():
+ module_args = dict(
+ host_name=dict(type='str', required=False, default='foo_host_default'),
+ )
+
+ result = dict(
+ changed=False,
+ other_data='sample_string',
+ )
+
+ module = AnsibleModule(argument_spec=module_args, supports_check_mode=True)
+
+ if module.check_mode:
+ module.exit_json(**result)
+
+ result['direct_host_name'] = module.params['host_name']
+ result['nested_host_name'] = {'host_name': module.params['host_name']}
+ result['name'] = 'vm-foo'
+
+ # non-cononical facts
+ result['device_type'] = 'Fake Host'
+
+ module.exit_json(**result)
+
+
+def main():
+ run_module()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/awx/main/tests/data/projects/inventory_vars/inventory_var_deleted_in_source.ini b/awx/main/tests/data/projects/inventory_vars/inventory_var_deleted_in_source.ini
new file mode 100644
index 000000000000..8dfebcd50291
--- /dev/null
+++ b/awx/main/tests/data/projects/inventory_vars/inventory_var_deleted_in_source.ini
@@ -0,0 +1,3 @@
+[all:vars]
+a=value_a
+b=value_b
diff --git a/awx/main/tests/data/projects/role_requirement/meta/main.yml b/awx/main/tests/data/projects/role_requirement/meta/main.yml
new file mode 100644
index 000000000000..25563e6d68c8
--- /dev/null
+++ b/awx/main/tests/data/projects/role_requirement/meta/main.yml
@@ -0,0 +1,19 @@
+---
+galaxy_info:
+ author: "For Test"
+ company: AWX
+ license: MIT
+ min_ansible_version: 1.4
+ platforms:
+ - name: EL
+ versions:
+ - 8
+ - 9
+ - name: Fedora
+ versions:
+ - 39
+ - 40
+ - 41
+ categories:
+ - stuff
+dependencies: []
diff --git a/awx/main/tests/data/projects/role_requirement/tasks/main.yml b/awx/main/tests/data/projects/role_requirement/tasks/main.yml
new file mode 100644
index 000000000000..2dbafc91cfb7
--- /dev/null
+++ b/awx/main/tests/data/projects/role_requirement/tasks/main.yml
@@ -0,0 +1,4 @@
+---
+- name: debug variable
+ debug:
+ msg: "1234567890"
diff --git a/awx/main/tests/data/projects/test_host_query/collections/requirements.yml b/awx/main/tests/data/projects/test_host_query/collections/requirements.yml
new file mode 100644
index 000000000000..17e176ae3991
--- /dev/null
+++ b/awx/main/tests/data/projects/test_host_query/collections/requirements.yml
@@ -0,0 +1,5 @@
+---
+collections:
+ - name: 'file:///tmp/live_tests/host_query'
+ type: git
+ version: devel
diff --git a/awx/main/tests/data/projects/test_host_query/run_task.yml b/awx/main/tests/data/projects/test_host_query/run_task.yml
new file mode 100644
index 000000000000..2d23555c6385
--- /dev/null
+++ b/awx/main/tests/data/projects/test_host_query/run_task.yml
@@ -0,0 +1,8 @@
+---
+- hosts: all
+ gather_facts: false
+ connection: local
+ tasks:
+ - demo.query.example:
+ register: result
+ - debug: var=result
diff --git a/awx/main/tests/data/projects/with_requirements/roles/requirements.yml b/awx/main/tests/data/projects/with_requirements/roles/requirements.yml
new file mode 100644
index 000000000000..b4eb43576f61
--- /dev/null
+++ b/awx/main/tests/data/projects/with_requirements/roles/requirements.yml
@@ -0,0 +1,3 @@
+---
+- name: role_requirement
+ src: git+file:///tmp/live_tests/role_requirement
diff --git a/awx/main/tests/data/projects/with_requirements/use_requirement.yml b/awx/main/tests/data/projects/with_requirements/use_requirement.yml
new file mode 100644
index 000000000000..7907d662d272
--- /dev/null
+++ b/awx/main/tests/data/projects/with_requirements/use_requirement.yml
@@ -0,0 +1,7 @@
+---
+- hosts: all
+ connection: local
+ gather_facts: false
+ tasks:
+ - include_role:
+ name: role_requirement
diff --git a/awx/main/tests/data/sleep_task.py b/awx/main/tests/data/sleep_task.py
new file mode 100644
index 000000000000..8582a73c794c
--- /dev/null
+++ b/awx/main/tests/data/sleep_task.py
@@ -0,0 +1,55 @@
+import time
+import logging
+
+from dispatcherd.publish import task
+
+from django.db import connection
+
+from awx.main.dispatch import get_task_queuename
+
+from ansible_base.lib.utils.db import advisory_lock
+
+logger = logging.getLogger(__name__)
+
+
+@task(queue=get_task_queuename)
+def sleep_task(seconds=10, log=False):
+ if log:
+ logger.info('starting sleep_task')
+ time.sleep(seconds)
+ if log:
+ logger.info('finished sleep_task')
+
+
+@task()
+def sleep_break_connection(seconds=0.2):
+ """
+ Interact with the database in an intentionally breaking way.
+ After this finishes, queries made by this connection are expected to error
+ with "the connection is closed"
+ This is obviously a problem for any task that comes afterwards.
+ So this is used to break things so that the fixes may be demonstrated.
+ """
+ with connection.cursor() as cursor:
+ cursor.execute(f"SET idle_session_timeout = '{seconds / 2}s';")
+
+ logger.info(f'sleeping for {seconds}s > {seconds / 2}s session timeout')
+ time.sleep(seconds)
+
+ for i in range(1, 3):
+ logger.info(f'\nRunning query number {i}')
+ try:
+ with connection.cursor() as cursor:
+ cursor.execute("SELECT 1;")
+ logger.info(' query worked, not expected')
+ except Exception as exc:
+ logger.info(f' query errored as expected\ntype: {type(exc)}\nstr: {str(exc)}')
+
+ logger.info(f'Connection present: {bool(connection.connection)}, reports closed: {getattr(connection.connection, "closed", "not_found")}')
+
+
+@task()
+def advisory_lock_exception():
+ time.sleep(0.2) # so it can fill up all the workers... hacky for now
+ with advisory_lock('advisory_lock_exception', lock_session_timeout_milliseconds=20):
+ raise RuntimeError('this is an intentional error')
diff --git a/awx/main/tests/docs/conftest.py b/awx/main/tests/docs/conftest.py
index bd0cf1c99ff2..7ec4273627e3 100644
--- a/awx/main/tests/docs/conftest.py
+++ b/awx/main/tests/docs/conftest.py
@@ -1,13 +1,8 @@
from awx.main.tests.functional.conftest import * # noqa
+import os
+import pytest
-def pytest_addoption(parser):
- parser.addoption("--release", action="store", help="a release version number, e.g., 3.3.0")
-
-
-def pytest_generate_tests(metafunc):
- # This is called for every test. Only get/set command line arguments
- # if the argument is specified in the list of test "fixturenames".
- option_value = metafunc.config.option.release
- if 'release' in metafunc.fixturenames and option_value is not None:
- metafunc.parametrize("release", [option_value])
+@pytest.fixture()
+def release():
+ return os.environ.get('VERSION_TARGET', '')
diff --git a/awx/main/tests/docs/test_swagger_generation.py b/awx/main/tests/docs/test_swagger_generation.py
index 658d8ad2d4b5..39b8b8408ae7 100644
--- a/awx/main/tests/docs/test_swagger_generation.py
+++ b/awx/main/tests/docs/test_swagger_generation.py
@@ -7,7 +7,6 @@
from django.utils.functional import Promise
from django.utils.encoding import force_str
-from openapi_codec.encode import generate_swagger_object
import pytest
from awx.api.versioning import drf_reverse
@@ -43,12 +42,12 @@ class TestSwaggerGeneration:
@pytest.fixture(autouse=True, scope='function')
def _prepare(self, get, admin):
if not self.__class__.JSON:
- url = drf_reverse('api:swagger_view') + '?format=openapi'
+ # drf-spectacular returns OpenAPI schema directly from schema endpoint
+ url = drf_reverse('api:schema-json') + '?format=json'
response = get(url, user=admin)
- data = generate_swagger_object(response.data)
+ data = response.data
if response.has_header('X-Deprecated-Paths'):
data['deprecated_paths'] = json.loads(response['X-Deprecated-Paths'])
- data.update(response.accepted_renderer.get_customizations() or {})
data['host'] = None
data['schemes'] = ['https']
@@ -60,12 +59,21 @@ def _prepare(self, get, admin):
# change {version} in paths to the actual default API version (e.g., v2)
revised_paths[path.replace('{version}', settings.REST_FRAMEWORK['DEFAULT_VERSION'])] = node
for method in node:
+ # Ignore any parameters methods, these cause issues because it can come as an array instead of a dict
+ # Which causes issues in the last for loop in here
+ if method == 'parameters':
+ continue
+
if path in deprecated_paths:
node[method]['deprecated'] = True
if 'description' in node[method]:
# Pop off the first line and use that as the summary
lines = node[method]['description'].splitlines()
- node[method]['summary'] = lines.pop(0).strip('#:')
+ # If there was a description then set the summary as the description, otherwise make something up
+ if lines:
+ node[method]['summary'] = lines.pop(0).strip('#:')
+ else:
+ node[method]['summary'] = f'No Description for {method} on {path}'
node[method]['description'] = '\n'.join(lines)
# remove the required `version` parameter
@@ -80,7 +88,7 @@ def test_sanity(self, release, request):
JSON['info']['version'] = release
if not request.config.getoption('--genschema'):
- JSON['modified'] = datetime.datetime.utcnow().isoformat()
+ JSON['modified'] = datetime.datetime.now(datetime.UTC).isoformat()
# Make some basic assertions about the rendered JSON so we can
# be sure it doesn't break across DRF upgrades and view/serializer
@@ -90,13 +98,13 @@ def test_sanity(self, release, request):
# The number of API endpoints changes over time, but let's just check
# for a reasonable number here; if this test starts failing, raise/lower the bounds
paths = JSON['paths']
- assert 250 < len(paths) < 350
- assert list(paths['/api/'].keys()) == ['get']
- assert list(paths['/api/v2/'].keys()) == ['get']
- assert list(sorted(paths['/api/v2/credentials/'].keys())) == ['get', 'post']
- assert list(sorted(paths['/api/v2/credentials/{id}/'].keys())) == ['delete', 'get', 'patch', 'put']
- assert list(paths['/api/v2/settings/'].keys()) == ['get']
- assert list(paths['/api/v2/settings/{category_slug}/'].keys()) == ['get', 'put', 'patch', 'delete']
+ assert 250 < len(paths) < 400
+ assert set(list(paths['/api/'].keys())) == set(['get', 'parameters'])
+ assert set(list(paths['/api/v2/'].keys())) == set(['get', 'parameters'])
+ assert set(list(sorted(paths['/api/v2/credentials/'].keys()))) == set(['get', 'post', 'parameters'])
+ assert set(list(sorted(paths['/api/v2/credentials/{id}/'].keys()))) == set(['delete', 'get', 'patch', 'put', 'parameters'])
+ assert set(list(paths['/api/v2/settings/'].keys())) == set(['get', 'parameters'])
+ assert set(list(paths['/api/v2/settings/{category_slug}/'].keys())) == set(['get', 'put', 'patch', 'delete', 'parameters'])
@pytest.mark.parametrize(
'path',
@@ -162,4 +170,8 @@ def teardown_class(cls):
data = re.sub(r'[0-9]{4}-[0-9]{2}-[0-9]{2}(T|\s)[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]+(Z|\+[0-9]{2}:[0-9]{2})?', r'2018-02-01T08:00:00.000000Z', data)
data = re.sub(r'''(\s+"client_id": ")([a-zA-Z0-9]{40})("\,\s*)''', r'\1xxxx\3', data)
data = re.sub(r'"action_node": "[^"]+"', '"action_node": "awx"', data)
+
+ # replace uuids to prevent needless diffs
+ pattern = r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
+ data = re.sub(pattern, r'00000000-0000-0000-0000-000000000000', data)
f.write(data)
diff --git a/awx/main/tests/factories/fixtures.py b/awx/main/tests/factories/fixtures.py
index 27556d6efe47..6f9a3263ac7f 100644
--- a/awx/main/tests/factories/fixtures.py
+++ b/awx/main/tests/factories/fixtures.py
@@ -1,6 +1,9 @@
import json
from django.contrib.auth.models import User
+from django.core.exceptions import ValidationError
+
+from unittest import mock
from awx.main.models import (
Organization,
@@ -20,6 +23,7 @@
WorkflowJobNode,
WorkflowJobTemplateNode,
)
+from awx.main.models.inventory import HostMetric, HostMetricSummaryMonthly
# mk methods should create only a single object of a single type.
# they should also have the option of being persisted or not.
@@ -95,11 +99,19 @@ def mk_user(name, is_superuser=False, organization=None, team=None, persisted=Tr
def mk_project(name, organization=None, description=None, persisted=True):
description = description or '{}-description'.format(name)
- project = Project(name=name, description=description, playbook_files=['helloworld.yml', 'alt-helloworld.yml'])
+ project = Project(
+ name=name,
+ description=description,
+ playbook_files=['helloworld.yml', 'alt-helloworld.yml'],
+ scm_type='git',
+ scm_url='https://foo.invalid',
+ scm_revision='1234567890123456789012345678901234567890',
+ scm_update_on_launch=False,
+ )
if organization is not None:
project.organization = organization
if persisted:
- project.save()
+ project.save(skip_update=True)
return project
@@ -248,3 +260,42 @@ def mk_workflow_job_node(unified_job_template=None, success_nodes=None, failure_
if persisted:
workflow_node.save()
return workflow_node
+
+
+def mk_host_metric(hostname, first_automation, last_automation=None, last_deleted=None, deleted=False, persisted=True):
+ ok, idx = False, 1
+ while not ok:
+ try:
+ with mock.patch("django.utils.timezone.now") as mock_now:
+ mock_now.return_value = first_automation
+ metric = HostMetric(
+ hostname=hostname or f"host-{first_automation}-{idx}",
+ first_automation=first_automation,
+ last_automation=last_automation or first_automation,
+ last_deleted=last_deleted,
+ deleted=deleted,
+ )
+ metric.validate_unique()
+ if persisted:
+ metric.save()
+ ok = True
+ except ValidationError as e:
+ # Repeat create for auto-generated hostname
+ if not hostname and e.message_dict.get('hostname', None):
+ idx += 1
+ else:
+ raise e
+
+
+def mk_host_metric_summary(date, license_consumed=0, license_capacity=0, hosts_added=0, hosts_deleted=0, indirectly_managed_hosts=0, persisted=True):
+ summary = HostMetricSummaryMonthly(
+ date=date,
+ license_consumed=license_consumed,
+ license_capacity=license_capacity,
+ hosts_added=hosts_added,
+ hosts_deleted=hosts_deleted,
+ indirectly_managed_hosts=indirectly_managed_hosts,
+ )
+ if persisted:
+ summary.save()
+ return summary
diff --git a/awx/main/tests/functional/__init__.py b/awx/main/tests/functional/__init__.py
index 07d89e97390b..3139dcf4e720 100644
--- a/awx/main/tests/functional/__init__.py
+++ b/awx/main/tests/functional/__init__.py
@@ -59,8 +59,7 @@ def app_post_migration(sender, app_config, **kwargs):
elif tblname == 'main_systemjobevent':
unique_columns = "system_job_id integer NOT NULL"
- cur.execute(
- f"""CREATE TABLE _unpartitioned_{tblname} (
+ cur.execute(f"""CREATE TABLE _unpartitioned_{tblname} (
id bigint NOT NULL,
created timestamp with time zone NOT NULL,
modified timestamp with time zone NOT NULL,
@@ -72,8 +71,7 @@ def app_post_migration(sender, app_config, **kwargs):
uuid character varying(1024) NOT NULL,
verbosity integer NOT NULL,
{unique_columns});
- """
- )
+ """)
if settings.DATABASES['default']['ENGINE'] == 'django.db.backends.sqlite3':
diff --git a/awx/main/tests/functional/analytics/test_collectors.py b/awx/main/tests/functional/analytics/test_collectors.py
index 0fed6e9c156c..4dcb9cd3c3ec 100644
--- a/awx/main/tests/functional/analytics/test_collectors.py
+++ b/awx/main/tests/functional/analytics/test_collectors.py
@@ -2,8 +2,8 @@
import tempfile
import os
import re
-import shutil
import csv
+from io import StringIO
from django.utils.timezone import now
from datetime import timedelta
@@ -20,15 +20,16 @@
)
-@pytest.fixture
-def sqlite_copy_expert(request):
- # copy_expert is postgres-specific, and SQLite doesn't support it; mock its
- # behavior to test that it writes a file that contains stdout from events
- path = tempfile.mkdtemp(prefix="copied_tables")
+class MockCopy:
+ headers = None
+ results = None
+ sent_data = False
- def write_stdout(self, sql, fd):
+ def __init__(self, sql, parent_connection):
# Would be cool if we instead properly disected the SQL query and verified
# it that way. But instead, we just take the naive approach here.
+ self.results = None
+ self.headers = None
sql = sql.strip()
assert sql.startswith("COPY (")
assert sql.endswith(") TO STDOUT WITH CSV HEADER")
@@ -51,29 +52,49 @@ def write_stdout(self, sql, fd):
elif not line.endswith(","):
sql_new[-1] = sql_new[-1].rstrip(",")
sql = "\n".join(sql_new)
+ parent_connection.execute(sql)
+ self.results = parent_connection.fetchall()
+ self.headers = [i[0] for i in parent_connection.description]
+
+ def read(self):
+ if not self.sent_data:
+ mem_file = StringIO()
+ csv_handle = csv.writer(
+ mem_file,
+ delimiter=",",
+ quoting=csv.QUOTE_ALL,
+ escapechar="\\",
+ lineterminator="\n",
+ )
+ if self.headers:
+ csv_handle.writerow(self.headers)
+ if self.results:
+ csv_handle.writerows(self.results)
+ self.sent_data = True
+ return memoryview((mem_file.getvalue()).encode())
+ return None
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ pass
+
- self.execute(sql)
- results = self.fetchall()
- headers = [i[0] for i in self.description]
+@pytest.fixture
+def sqlite_copy(request, mocker):
+ # copy is postgres-specific, and SQLite doesn't support it; mock its
+ # behavior to test that it writes a file that contains stdout from events
- csv_handle = csv.writer(
- fd,
- delimiter=",",
- quoting=csv.QUOTE_ALL,
- escapechar="\\",
- lineterminator="\n",
- )
- csv_handle.writerow(headers)
- csv_handle.writerows(results)
+ def write_stdout(self, sql):
+ mock_copy = MockCopy(sql, self)
+ return mock_copy
- setattr(SQLiteCursorWrapper, "copy_expert", write_stdout)
- request.addfinalizer(lambda: shutil.rmtree(path))
- request.addfinalizer(lambda: delattr(SQLiteCursorWrapper, "copy_expert"))
- return path
+ mocker.patch.object(SQLiteCursorWrapper, 'copy', write_stdout, create=True)
@pytest.mark.django_db
-def test_copy_tables_unified_job_query(sqlite_copy_expert, project, inventory, job_template):
+def test_copy_tables_unified_job_query(sqlite_copy, project, inventory, job_template):
"""
Ensure that various unified job types are in the output of the query.
"""
@@ -127,7 +148,7 @@ def workflow_job(states=["new", "new", "new", "new", "new"]):
@pytest.mark.django_db
-def test_copy_tables_workflow_job_node_query(sqlite_copy_expert, workflow_job):
+def test_copy_tables_workflow_job_node_query(sqlite_copy, workflow_job):
time_start = now() - timedelta(hours=9)
with tempfile.TemporaryDirectory() as tmpdir:
diff --git a/awx/main/tests/functional/analytics/test_core.py b/awx/main/tests/functional/analytics/test_core.py
index e37f30d26bf1..a2c525c83670 100644
--- a/awx/main/tests/functional/analytics/test_core.py
+++ b/awx/main/tests/functional/analytics/test_core.py
@@ -2,11 +2,13 @@
import json
import os
import tarfile
+import tempfile
from unittest import mock
import pytest
from django.conf import settings
-from awx.main.analytics import gather, register
+from django.test.utils import override_settings
+from awx.main.analytics import gather, register, ship
@register('example', '1.0')
@@ -57,3 +59,115 @@ def test_gather(mock_valid_license):
os.remove(tgz)
except Exception:
pass
+
+
+@pytest.fixture
+def temp_analytic_tar():
+ # Create a temporary file and yield its path
+ with tempfile.NamedTemporaryFile(delete=False) as temp_file:
+ temp_file.write(b"data")
+ temp_file_path = temp_file.name
+ yield temp_file_path
+ # Clean up the temporary file after the test
+ os.remove(temp_file_path)
+
+
+@pytest.fixture
+def mock_analytic_post():
+ # Patch the Session.post method to return a mock response with status_code 200
+ with mock.patch('awx.main.analytics.core.requests.Session.post', return_value=mock.Mock(status_code=200)) as mock_post:
+ yield mock_post
+
+
+@pytest.mark.parametrize(
+ "setting_map, expected_result, expected_auth",
+ [
+ # Valid Red Hat credentials
+ (
+ {
+ 'REDHAT_USERNAME': 'redhat_user',
+ 'REDHAT_PASSWORD': 'redhat_pass', # NOSONAR
+ 'SUBSCRIPTIONS_CLIENT_ID': '',
+ 'SUBSCRIPTIONS_CLIENT_SECRET': '',
+ },
+ True,
+ ('redhat_user', 'redhat_pass'),
+ ),
+ # Valid Subscription credentials with no Red Hat credentials
+ (
+ {
+ 'REDHAT_USERNAME': None,
+ 'REDHAT_PASSWORD': None,
+ 'SUBSCRIPTIONS_CLIENT_ID': 'subs_user',
+ 'SUBSCRIPTIONS_CLIENT_SECRET': 'subs_pass', # NOSONAR
+ },
+ True,
+ ('subs_user', 'subs_pass'),
+ ),
+ # Valid Subscription credentials with empty Red Hat credentials
+ (
+ {
+ 'REDHAT_USERNAME': '',
+ 'REDHAT_PASSWORD': '',
+ 'SUBSCRIPTIONS_CLIENT_ID': 'subs_user',
+ 'SUBSCRIPTIONS_CLIENT_SECRET': 'subs_pass', # NOSONAR
+ },
+ True,
+ ('subs_user', 'subs_pass'),
+ ),
+ # No credentials
+ (
+ {
+ 'REDHAT_USERNAME': '',
+ 'REDHAT_PASSWORD': '',
+ 'SUBSCRIPTIONS_CLIENT_ID': '',
+ 'SUBSCRIPTIONS_CLIENT_SECRET': '',
+ },
+ False,
+ None, # No request should be made
+ ),
+ # Mixed credentials
+ (
+ {
+ 'REDHAT_USERNAME': '',
+ 'REDHAT_PASSWORD': 'redhat_pass', # NOSONAR
+ 'SUBSCRIPTIONS_CLIENT_ID': 'subs_user',
+ 'SUBSCRIPTIONS_CLIENT_SECRET': '',
+ },
+ False,
+ None, # Invalid, no request should be made
+ ),
+ ],
+)
+@pytest.mark.django_db
+def test_ship_credential(setting_map, expected_result, expected_auth, temp_analytic_tar, mock_analytic_post):
+ with override_settings(**setting_map):
+ result = ship(temp_analytic_tar)
+
+ assert result == expected_result
+ if expected_auth:
+ mock_analytic_post.assert_called_once()
+ assert mock_analytic_post.call_args[1]['auth'] == expected_auth
+ else:
+ mock_analytic_post.assert_not_called()
+
+
+@pytest.mark.django_db
+def test_gather_cleanup_on_auth_failure(mock_valid_license, temp_analytic_tar):
+ settings.INSIGHTS_TRACKING_STATE = True
+ settings.AUTOMATION_ANALYTICS_URL = 'https://example.com/api'
+ settings.REDHAT_USERNAME = 'test_user'
+ settings.REDHAT_PASSWORD = 'test_password'
+
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.tar.gz') as temp_file:
+ temp_file_path = temp_file.name
+
+ try:
+ with mock.patch('awx.main.analytics.core.ship', return_value=False):
+ with mock.patch('awx.main.analytics.core.package', return_value=temp_file_path):
+ gather(module=importlib.import_module(__name__), collection_type='scheduled')
+
+ assert not os.path.exists(temp_file_path), "Temp file was not cleaned up after ship failure"
+ finally:
+ if os.path.exists(temp_file_path):
+ os.remove(temp_file_path)
diff --git a/awx/main/tests/functional/analytics/test_counts.py b/awx/main/tests/functional/analytics/test_counts.py
index dd38c9ef3147..015467692d5b 100644
--- a/awx/main/tests/functional/analytics/test_counts.py
+++ b/awx/main/tests/functional/analytics/test_counts.py
@@ -66,7 +66,7 @@ def test_database_counts(organization_factory, job_template_factory, workflow_jo
@pytest.mark.django_db
def test_inventory_counts(organization_factory, inventory_factory):
- (inv1, inv2, inv3) = [inventory_factory(f"inv-{i}") for i in range(3)]
+ inv1, inv2, inv3 = [inventory_factory(f"inv-{i}") for i in range(3)]
s1 = inv1.inventory_sources.create(name="src1", source="ec2")
s2 = inv1.inventory_sources.create(name="src2", source="file")
diff --git a/awx/main/tests/functional/analytics/test_metrics.py b/awx/main/tests/functional/analytics/test_metrics.py
index 6192d4e9bd94..2652db85b7c3 100644
--- a/awx/main/tests/functional/analytics/test_metrics.py
+++ b/awx/main/tests/functional/analytics/test_metrics.py
@@ -1,10 +1,12 @@
import pytest
+from django.test import RequestFactory
from prometheus_client.parser import text_string_to_metric_families
+from rest_framework.request import Request
from awx.main import models
from awx.main.analytics.metrics import metrics
+from awx.main.analytics.dispatcherd_metrics import get_dispatcherd_metrics
from awx.api.versioning import reverse
-from awx.main.models.rbac import Role
EXPECTED_VALUES = {
'awx_system_info': 1.0,
@@ -31,6 +33,7 @@
'awx_license_instance_free': 0,
'awx_pending_jobs_total': 0,
'awx_database_connections_total': 1,
+ 'awx_license_expiry': 0,
}
@@ -49,7 +52,7 @@ def test_metrics_counts(organization_factory, job_template_factory, workflow_job
for gauge in gauges:
for sample in gauge.samples:
# name, label, value, timestamp, exemplar
- name, _, value, _, _ = sample
+ name, _, value, _, _, _ = sample
assert EXPECTED_VALUES[name] == value
@@ -66,7 +69,6 @@ def test_metrics_permissions(get, admin, org_admin, alice, bob, organization):
organization.auditor_role.members.add(bob)
assert get(get_metrics_view_db_only(), user=bob).status_code == 403
- Role.singleton('system_auditor').members.add(bob)
bob.is_system_auditor = True
assert get(get_metrics_view_db_only(), user=bob).status_code == 200
@@ -78,3 +80,55 @@ def test_metrics_http_methods(get, post, patch, put, options, admin):
assert patch(get_metrics_view_db_only(), user=admin).status_code == 405
assert post(get_metrics_view_db_only(), user=admin).status_code == 405
assert options(get_metrics_view_db_only(), user=admin).status_code == 200
+
+
+class DummyMetricsResponse:
+ def __init__(self, payload):
+ self._payload = payload
+
+ def read(self):
+ return self._payload
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc, tb):
+ return False
+
+
+def test_dispatcherd_metrics_node_filter_match(mocker, settings):
+ settings.CLUSTER_HOST_ID = "awx-1"
+ payload = b'# HELP test_metric A test metric\n# TYPE test_metric gauge\ntest_metric 1\n'
+
+ def fake_urlopen(url, timeout=1.0):
+ return DummyMetricsResponse(payload)
+
+ mocker.patch('urllib.request.urlopen', fake_urlopen)
+
+ request = Request(RequestFactory().get('/api/v2/metrics/', {'node': 'awx-1'}))
+
+ assert get_dispatcherd_metrics(request) == payload.decode('utf-8')
+
+
+def test_dispatcherd_metrics_node_filter_excludes_local(mocker, settings):
+ settings.CLUSTER_HOST_ID = "awx-1"
+
+ def fake_urlopen(*args, **kwargs):
+ raise AssertionError("urlopen should not be called when node filter excludes local node")
+
+ mocker.patch('urllib.request.urlopen', fake_urlopen)
+
+ request = Request(RequestFactory().get('/api/v2/metrics/', {'node': 'awx-2'}))
+
+ assert get_dispatcherd_metrics(request) == ''
+
+
+def test_dispatcherd_metrics_metric_filter_excludes_unrelated(mocker):
+ def fake_urlopen(*args, **kwargs):
+ raise AssertionError("urlopen should not be called when metric filter excludes dispatcherd metrics")
+
+ mocker.patch('urllib.request.urlopen', fake_urlopen)
+
+ request = Request(RequestFactory().get('/api/v2/metrics/', {'metric': 'awx_system_info'}))
+
+ assert get_dispatcherd_metrics(request) == ''
diff --git a/awx/main/tests/functional/api/test_activity_streams.py b/awx/main/tests/functional/api/test_activity_streams.py
index 961fd02f8004..e66276b3cbf9 100644
--- a/awx/main/tests/functional/api/test_activity_streams.py
+++ b/awx/main/tests/functional/api/test_activity_streams.py
@@ -109,7 +109,8 @@ def test_stream_queryset_hides_shows_items(
settings.ACTIVITY_STREAM_ENABLED = True
# this user is not in any organizations and should not see any resource activity
no_access_user = user('no-access-user', False)
- queryset = ActivityStreamAccess(no_access_user).get_queryset()
+ access = ActivityStreamAccess(no_access_user)
+ queryset = access.get_queryset()
assert not queryset.filter(project__pk=project.pk)
assert not queryset.filter(credential__pk=org_credential.pk)
@@ -120,9 +121,11 @@ def test_stream_queryset_hides_shows_items(
assert not queryset.filter(host__pk=host.pk)
assert not queryset.filter(team__pk=team.pk)
assert not queryset.filter(notification_template__pk=notification_template.pk)
+ assert not access.can_read(activity_stream_entry)
# Organization admin should be able to see most things in the ActivityStream
- queryset = ActivityStreamAccess(org_admin).get_queryset()
+ access = ActivityStreamAccess(org_admin)
+ queryset = access.get_queryset()
assert queryset.filter(project__pk=project.pk, operation='create').count() == 1
assert queryset.filter(credential__pk=org_credential.pk, operation='create').count() == 1
@@ -133,6 +136,7 @@ def test_stream_queryset_hides_shows_items(
assert queryset.filter(host__pk=host.pk, operation='create').count() == 1
assert queryset.filter(team__pk=team.pk, operation='create').count() == 1
assert queryset.filter(notification_template__pk=notification_template.pk, operation='create').count() == 1
+ assert access.can_read(activity_stream_entry)
@pytest.mark.django_db
diff --git a/awx/main/tests/functional/api/test_adhoc.py b/awx/main/tests/functional/api/test_adhoc.py
index 983e45029c40..b6b0b7c7470b 100644
--- a/awx/main/tests/functional/api/test_adhoc.py
+++ b/awx/main/tests/functional/api/test_adhoc.py
@@ -3,7 +3,6 @@
from awx.api.versioning import reverse
-
"""
def run_test_ad_hoc_command(self, **kwargs):
# Post to list to start a new ad hoc command.
diff --git a/awx/main/tests/functional/api/test_analytics.py b/awx/main/tests/functional/api/test_analytics.py
new file mode 100644
index 000000000000..52fdba74e376
--- /dev/null
+++ b/awx/main/tests/functional/api/test_analytics.py
@@ -0,0 +1,259 @@
+import pytest
+import requests
+from unittest import mock
+from awx.api.views.analytics import AnalyticsGenericView, MissingSettings, AUTOMATION_ANALYTICS_API_URL_PATH, ERROR_MISSING_USER, ERROR_MISSING_PASSWORD
+from django.test.utils import override_settings
+from django.test import RequestFactory
+from rest_framework import status
+
+from awx.main.utils import get_awx_version
+from django.utils import translation
+
+
+class TestAnalyticsGenericView:
+ @pytest.mark.parametrize(
+ "existing_headers,expected_headers",
+ [
+ ({}, {}),
+ ({'Hey': 'There'}, {}), # We don't forward just any headers
+ ({'Content-Type': 'text/html', 'Content-Length': '12'}, {'Content-Type': 'text/html', 'Content-Length': '12'}),
+ # Requests will auto-add the following headers (so we don't need to test them): 'Accept-Encoding', 'User-Agent', 'Accept'
+ ],
+ )
+ def test__request_headers(self, existing_headers, expected_headers):
+ expected_headers['X-Rh-Analytics-Source'] = 'controller'
+ expected_headers['X-Rh-Analytics-Source-Version'] = get_awx_version()
+ expected_headers['Accept-Language'] = translation.get_language()
+
+ request = requests.session()
+ request.headers.update(existing_headers)
+ assert set(expected_headers.items()).issubset(set(AnalyticsGenericView._request_headers(request).items()))
+
+ @pytest.mark.parametrize(
+ "path,expected_path",
+ [
+ ('A/B', f'{AUTOMATION_ANALYTICS_API_URL_PATH}/A/B'),
+ ('B', f'{AUTOMATION_ANALYTICS_API_URL_PATH}/B'),
+ ('/a/b/c/analytics/reports/my_slug', f'{AUTOMATION_ANALYTICS_API_URL_PATH}/reports/my_slug'),
+ ('/a/b/c/analytics/', f'{AUTOMATION_ANALYTICS_API_URL_PATH}/'),
+ ('/a/b/c/analytics', f'{AUTOMATION_ANALYTICS_API_URL_PATH}//a/b/c/analytics'), # Because there is no ending / on analytics we get a weird condition
+ ('/a/b/c/analytics/', f'{AUTOMATION_ANALYTICS_API_URL_PATH}/'),
+ ],
+ )
+ @pytest.mark.django_db
+ def test__get_analytics_path(self, path, expected_path):
+ assert AnalyticsGenericView._get_analytics_path(path) == expected_path
+
+ @pytest.mark.django_db
+ def test__get_analytics_url_no_url(self):
+ with override_settings(AUTOMATION_ANALYTICS_URL=None):
+ with pytest.raises(MissingSettings):
+ agw = AnalyticsGenericView()
+ agw._get_analytics_url('A')
+
+ @pytest.mark.parametrize(
+ "request_path,ending_url",
+ [
+ ('A', 'A'),
+ ('A/B', 'A/B'),
+ ('A/B/analytics/', ''), # we split on analytics but because there is nothing after
+ ('A/B/analytics/report', 'report'),
+ ('A/B/analytics/report/slug', 'report/slug'),
+ ],
+ )
+ @pytest.mark.django_db
+ def test__get_analytics_url(self, request_path, ending_url):
+ base_url = 'http://testing'
+ with override_settings(AUTOMATION_ANALYTICS_URL=base_url):
+ agw = AnalyticsGenericView()
+ assert agw._get_analytics_url(request_path) == f'{base_url}{AUTOMATION_ANALYTICS_API_URL_PATH}/{ending_url}'
+
+ @pytest.mark.parametrize(
+ "setting_name,setting_value,raises",
+ [
+ ('INSIGHTS_TRACKING_STATE', None, True),
+ ('INSIGHTS_TRACKING_STATE', False, True),
+ ('INSIGHTS_TRACKING_STATE', True, False),
+ ('INSIGHTS_TRACKING_STATE', 'Steve', False),
+ ('INSIGHTS_TRACKING_STATE', 1, False),
+ ('INSIGHTS_TRACKING_STATE', '', True),
+ ],
+ )
+ @pytest.mark.django_db
+ def test__get_setting(self, setting_name, setting_value, raises):
+ with override_settings(**{setting_name: setting_value}):
+ if raises:
+ with pytest.raises(MissingSettings):
+ AnalyticsGenericView._get_setting(setting_name, False, None)
+ else:
+ assert AnalyticsGenericView._get_setting(setting_name, False, None) == setting_value
+
+ @pytest.mark.parametrize(
+ "settings_map, expected_auth, expected_error_keyword",
+ [
+ # Test case 1: Valid Red Hat credentials
+ (
+ {
+ 'INSIGHTS_TRACKING_STATE': True,
+ 'REDHAT_USERNAME': 'redhat_user',
+ 'REDHAT_PASSWORD': 'redhat_pass', # NOSONAR
+ 'SUBSCRIPTIONS_CLIENT_ID': '',
+ 'SUBSCRIPTIONS_CLIENT_SECRET': '',
+ },
+ ('redhat_user', 'redhat_pass'),
+ None,
+ ),
+ # Test case 2: Valid Subscription credentials
+ (
+ {
+ 'INSIGHTS_TRACKING_STATE': True,
+ 'REDHAT_USERNAME': '',
+ 'REDHAT_PASSWORD': '',
+ 'SUBSCRIPTIONS_CLIENT_ID': 'subs_user',
+ 'SUBSCRIPTIONS_CLIENT_SECRET': 'subs_pass', # NOSONAR
+ },
+ ('subs_user', 'subs_pass'),
+ None,
+ ),
+ # Test case 3: No credentials
+ (
+ {
+ 'INSIGHTS_TRACKING_STATE': True,
+ 'REDHAT_USERNAME': '',
+ 'REDHAT_PASSWORD': '',
+ 'SUBSCRIPTIONS_CLIENT_ID': '',
+ 'SUBSCRIPTIONS_CLIENT_SECRET': '',
+ },
+ None,
+ ERROR_MISSING_USER,
+ ),
+ # Test case 4: Both credentials
+ (
+ {
+ 'INSIGHTS_TRACKING_STATE': True,
+ 'REDHAT_USERNAME': 'redhat_user',
+ 'REDHAT_PASSWORD': 'redhat_pass', # NOSONAR
+ 'SUBSCRIPTIONS_CLIENT_ID': 'subs_user',
+ 'SUBSCRIPTIONS_CLIENT_SECRET': 'subs_pass', # NOSONAR
+ },
+ ('redhat_user', 'redhat_pass'),
+ None,
+ ),
+ # Test case 5: Missing password
+ (
+ {
+ 'INSIGHTS_TRACKING_STATE': True,
+ 'REDHAT_USERNAME': '',
+ 'REDHAT_PASSWORD': '',
+ 'SUBSCRIPTIONS_CLIENT_ID': 'subs_user', # NOSONAR
+ 'SUBSCRIPTIONS_CLIENT_SECRET': '',
+ },
+ None,
+ ERROR_MISSING_PASSWORD,
+ ),
+ ],
+ )
+ @pytest.mark.django_db
+ def test__send_to_analytics_credentials(self, settings_map, expected_auth, expected_error_keyword):
+ """
+ Test _send_to_analytics with various combinations of credentials.
+ """
+ with override_settings(**settings_map):
+ request = RequestFactory().post('/some/path')
+ view = AnalyticsGenericView()
+
+ if expected_auth:
+ with mock.patch('awx.api.views.analytics.OIDCClient') as mock_oidc_client:
+ # Configure the mock OIDCClient instance and its make_request method
+ mock_client_instance = mock.Mock()
+ mock_oidc_client.return_value = mock_client_instance
+ mock_client_instance.make_request.return_value = mock.Mock(status_code=200)
+
+ analytic_url = view._get_analytics_url(request.path)
+ response = view._send_to_analytics(request, 'POST')
+
+ # Assertions
+ # Assert OIDCClient instantiation
+ expected_client_id, expected_client_secret = expected_auth
+ mock_oidc_client.assert_called_once_with(expected_client_id, expected_client_secret)
+
+ # Assert make_request call
+ mock_client_instance.make_request.assert_called_once_with(
+ 'POST',
+ analytic_url,
+ headers=mock.ANY,
+ verify=mock.ANY,
+ params=mock.ANY,
+ json=mock.ANY,
+ timeout=mock.ANY,
+ )
+ assert response.status_code == 200
+ else:
+ # Test when settings are missing and MissingSettings is raised
+ response = view._send_to_analytics(request, 'POST')
+
+ # # Assert that _error_response is called when MissingSettings is raised
+ # mock_error_response.assert_called_once_with(expected_error_keyword, remote=False)
+ assert response.status_code == status.HTTP_403_FORBIDDEN
+ assert response.data['error']['keyword'] == expected_error_keyword
+
+ @pytest.mark.django_db
+ @pytest.mark.parametrize(
+ "settings_map, expected_auth",
+ [
+ # Test case 1: Username and password should be used for basic auth
+ (
+ {
+ 'INSIGHTS_TRACKING_STATE': True,
+ 'REDHAT_USERNAME': 'redhat_user',
+ 'REDHAT_PASSWORD': 'redhat_pass', # NOSONAR
+ 'SUBSCRIPTIONS_CLIENT_ID': '',
+ 'SUBSCRIPTIONS_CLIENT_SECRET': '',
+ },
+ ('redhat_user', 'redhat_pass'),
+ ),
+ # Test case 2: Client ID and secret should be used for basic auth
+ (
+ {
+ 'INSIGHTS_TRACKING_STATE': True,
+ 'REDHAT_USERNAME': '',
+ 'REDHAT_PASSWORD': '',
+ 'SUBSCRIPTIONS_CLIENT_ID': 'subs_user',
+ 'SUBSCRIPTIONS_CLIENT_SECRET': 'subs_pass', # NOSONAR
+ },
+ None,
+ ),
+ ],
+ )
+ def test__send_to_analytics_fallback_to_basic_auth(self, settings_map, expected_auth):
+ """
+ Test _send_to_analytics with basic auth fallback.
+ """
+ with override_settings(**settings_map):
+ request = RequestFactory().post('/some/path')
+ view = AnalyticsGenericView()
+
+ with mock.patch('awx.api.views.analytics.OIDCClient') as mock_oidc_client, mock.patch(
+ 'awx.api.views.analytics.AnalyticsGenericView._base_auth_request'
+ ) as mock_base_auth_request:
+ # Configure the mock OIDCClient instance and its make_request method
+ mock_client_instance = mock.Mock()
+ mock_oidc_client.return_value = mock_client_instance
+ mock_client_instance.make_request.side_effect = requests.RequestException("Incorrect credentials")
+
+ analytic_url = view._get_analytics_url(request.path)
+ view._send_to_analytics(request, 'POST')
+
+ if expected_auth:
+ # assert mock_base_auth_request called with expected_auth
+ mock_base_auth_request.assert_called_once_with(
+ request,
+ 'POST',
+ analytic_url,
+ expected_auth[0],
+ expected_auth[1],
+ mock.ANY,
+ )
+ else:
+ # assert mock_base_auth_request not called
+ mock_base_auth_request.assert_not_called()
diff --git a/awx/main/tests/functional/test_api_generics.py b/awx/main/tests/functional/api/test_api_generics.py
similarity index 100%
rename from awx/main/tests/functional/test_api_generics.py
rename to awx/main/tests/functional/api/test_api_generics.py
diff --git a/awx/main/tests/functional/api/test_application_name.py b/awx/main/tests/functional/api/test_application_name.py
new file mode 100644
index 000000000000..da7cda210ca0
--- /dev/null
+++ b/awx/main/tests/functional/api/test_application_name.py
@@ -0,0 +1,23 @@
+import pytest
+from awx.settings.application_name import get_service_name, set_application_name
+
+
+@pytest.mark.parametrize(
+ 'argv,result',
+ (
+ ([], None),
+ (['-m'], None),
+ (['-m', 'python'], None),
+ (['-m', 'python', 'manage'], None),
+ (['-m', 'python', 'manage', 'a'], 'a'),
+ (['-m', 'python', 'manage', 'b', 'a'], 'b'),
+ (['-m', 'python', 'manage', 'run_something', 'b', 'a'], 'something'),
+ ),
+)
+def test_get_service_name(argv, result):
+ assert get_service_name(argv) == result
+
+
+@pytest.mark.parametrize('DATABASES,CLUSTER_ID,function', (({}, 12, ''), ({'default': {'ENGINE': 'sqllite3'}}, 12, '')))
+def test_set_application_name(DATABASES, CLUSTER_ID, function):
+ set_application_name(DATABASES, CLUSTER_ID, function)
diff --git a/awx/main/tests/functional/api/test_auth.py b/awx/main/tests/functional/api/test_auth.py
index d9ac588de323..49a9c7640df2 100644
--- a/awx/main/tests/functional/api/test_auth.py
+++ b/awx/main/tests/functional/api/test_auth.py
@@ -1,12 +1,16 @@
import pytest
from django.contrib import auth
+from django.http import JsonResponse
+
from django.test import Client
from rest_framework.test import APIRequestFactory
-from awx.api.generics import LoggedLoginView
-from awx.api.versioning import drf_reverse
+import awx.api.generics
+from rest_framework.reverse import reverse as drf_reverse
+
+from pytest_mock import MockerFixture
@pytest.mark.django_db
@@ -21,6 +25,25 @@ def test_invalid_login():
request = factory.post(url, data)
request.user = anon
- response = LoggedLoginView.as_view()(request)
+ response = awx.api.generics.LoggedLoginView.as_view()(request)
+
+ assert response.status_code == 401
+
+@pytest.mark.django_db
+def test_invalid_post(mocker: MockerFixture, monkeypatch: pytest.MonkeyPatch):
+ url = drf_reverse('api:login')
+ factory = APIRequestFactory()
+ request = factory.post(url)
+
+ is_proxied_request_mock = mocker.Mock(
+ autospec=True,
+ name='is_proxied_request',
+ return_value=True,
+ )
+ monkeypatch.setattr(awx.api.generics, 'is_proxied_request', is_proxied_request_mock)
+ response = awx.api.generics.LoggedLoginView.as_view()(request)
+
+ assert isinstance(response, JsonResponse)
+ assert b'Please log in via Platform Authentication.' in response.content
assert response.status_code == 401
diff --git a/awx/main/tests/functional/api/test_create_attach_views.py b/awx/main/tests/functional/api/test_create_attach_views.py
index b22ec089122f..7b92f82f5076 100644
--- a/awx/main/tests/functional/api/test_create_attach_views.py
+++ b/awx/main/tests/functional/api/test_create_attach_views.py
@@ -9,8 +9,8 @@ def test_user_role_view_access(rando, inventory, mocker, post):
role_pk = inventory.admin_role.pk
data = {"id": role_pk}
mock_access = mocker.MagicMock(can_attach=mocker.MagicMock(return_value=False))
- with mocker.patch('awx.main.access.RoleAccess', return_value=mock_access):
- post(url=reverse('api:user_roles_list', kwargs={'pk': rando.pk}), data=data, user=rando, expect=403)
+ mocker.patch('awx.main.access.RoleAccess', return_value=mock_access)
+ post(url=reverse('api:user_roles_list', kwargs={'pk': rando.pk}), data=data, user=rando, expect=403)
mock_access.can_attach.assert_called_once_with(inventory.admin_role, rando, 'members', data, skip_sub_obj_read_check=False)
@@ -21,8 +21,8 @@ def test_team_role_view_access(rando, team, inventory, mocker, post):
role_pk = inventory.admin_role.pk
data = {"id": role_pk}
mock_access = mocker.MagicMock(can_attach=mocker.MagicMock(return_value=False))
- with mocker.patch('awx.main.access.RoleAccess', return_value=mock_access):
- post(url=reverse('api:team_roles_list', kwargs={'pk': team.pk}), data=data, user=rando, expect=403)
+ mocker.patch('awx.main.access.RoleAccess', return_value=mock_access)
+ post(url=reverse('api:team_roles_list', kwargs={'pk': team.pk}), data=data, user=rando, expect=403)
mock_access.can_attach.assert_called_once_with(inventory.admin_role, team, 'member_role.parents', data, skip_sub_obj_read_check=False)
@@ -33,8 +33,8 @@ def test_role_team_view_access(rando, team, inventory, mocker, post):
role_pk = inventory.admin_role.pk
data = {"id": team.pk}
mock_access = mocker.MagicMock(return_value=False, __name__='mocked')
- with mocker.patch('awx.main.access.RoleAccess.can_attach', mock_access):
- post(url=reverse('api:role_teams_list', kwargs={'pk': role_pk}), data=data, user=rando, expect=403)
+ mocker.patch('awx.main.access.RoleAccess.can_attach', mock_access)
+ post(url=reverse('api:role_teams_list', kwargs={'pk': role_pk}), data=data, user=rando, expect=403)
mock_access.assert_called_once_with(inventory.admin_role, team, 'member_role.parents', data, skip_sub_obj_read_check=False)
diff --git a/awx/main/tests/functional/api/test_credential.py b/awx/main/tests/functional/api/test_credential.py
index 52814f3655ea..7956ebed4fc1 100644
--- a/awx/main/tests/functional/api/test_credential.py
+++ b/awx/main/tests/functional/api/test_credential.py
@@ -12,25 +12,13 @@
EXAMPLE_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----\nxyz==\n-----END PRIVATE KEY-----'
EXAMPLE_ENCRYPTED_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nxyz==\n-----END PRIVATE KEY-----'
-
-@pytest.mark.django_db
-def test_idempotent_credential_type_setup():
- assert CredentialType.objects.count() == 0
- CredentialType.setup_tower_managed_defaults()
- total = CredentialType.objects.count()
- assert total > 0
-
- CredentialType.setup_tower_managed_defaults()
- assert CredentialType.objects.count() == total
-
-
#
# user credential creation
#
@pytest.mark.django_db
-def test_create_user_credential_via_credentials_list(post, get, alice, credentialtype_ssh):
+def test_create_user_credential_via_credentials_list(post, get, alice, credentialtype_ssh, setup_managed_roles):
params = {
'credential_type': 1,
'inputs': {'username': 'someusername'},
@@ -77,11 +65,11 @@ def test_credential_validation_error_with_multiple_owner_fields(post, admin, ali
}
response = post(reverse('api:credential_list'), params, admin)
assert response.status_code == 400
- assert response.data['detail'][0] == ("Only one of 'user', 'team', or 'organization' should be provided, " "received organization, team, user fields.")
+ assert response.data['detail'][0] == ("Only one of 'user', 'team', or 'organization' should be provided, received organization, team, user fields.")
@pytest.mark.django_db
-def test_create_user_credential_via_user_credentials_list(post, get, alice, credentialtype_ssh):
+def test_create_user_credential_via_user_credentials_list(post, get, alice, credentialtype_ssh, setup_managed_roles):
params = {
'credential_type': 1,
'inputs': {'username': 'someusername'},
@@ -299,6 +287,72 @@ def test_sa_grant_private_credential_to_team_through_role_teams(post, credential
assert response.status_code == 400
+@pytest.mark.django_db
+def test_grant_credential_to_team_different_organization_through_role_teams(post, get, credential, organizations, admin, org_admin, team, team_member):
+ # # Test that credential from different org can be assigned to team by a superuser through role_teams_list endpoint
+ orgs = organizations(2)
+ credential.organization = orgs[0]
+ credential.save()
+ team.organization = orgs[1]
+ team.save()
+
+ # Non-superuser (org_admin) trying cross-org assignment should be denied
+ response = post(reverse('api:role_teams_list', kwargs={'pk': credential.use_role.id}), {'id': team.id}, org_admin)
+ assert response.status_code == 400
+ assert (
+ "You cannot grant a team access to a credential in a different organization. Only superusers can grant cross-organization credential access to teams"
+ in response.data['msg']
+ )
+
+ # Superuser (admin) can do cross-org assignment
+ response = post(reverse('api:role_teams_list', kwargs={'pk': credential.use_role.id}), {'id': team.id}, admin)
+ assert response.status_code == 204
+
+ assert credential.use_role in team.member_role.children.all()
+ assert team_member in credential.read_role
+ assert team_member in credential.use_role
+ assert team_member not in credential.admin_role
+
+
+@pytest.mark.django_db
+def test_grant_credential_to_team_different_organization(post, get, credential, organizations, admin, org_admin, team, team_member):
+ # Test that credential from different org can be assigned to team by a superuser
+ orgs = organizations(2)
+ credential.organization = orgs[0]
+ credential.save()
+ team.organization = orgs[1]
+ team.save()
+
+ # Non-superuser (org_admin, ...) trying cross-org assignment should be denied
+ response = post(reverse('api:team_roles_list', kwargs={'pk': team.id}), {'id': credential.use_role.id}, org_admin)
+ assert response.status_code == 400
+ assert (
+ "You cannot grant a team access to a credential in a different organization. Only superusers can grant cross-organization credential access to teams"
+ in response.data['msg']
+ )
+
+ # Superuser (system admin) can do cross-org assignment
+ response = post(reverse('api:team_roles_list', kwargs={'pk': team.id}), {'id': credential.use_role.id}, admin)
+ assert response.status_code == 204
+
+ assert credential.use_role in team.member_role.children.all()
+
+ assert team_member in credential.read_role
+ assert team_member in credential.use_role
+ assert team_member not in credential.admin_role
+
+ # Team member can see the credential in API
+ response = get(reverse('api:team_credentials_list', kwargs={'pk': team.id}), team_member)
+ assert response.status_code == 200
+ assert response.data['count'] == 1
+ assert response.data['results'][0]['id'] == credential.id
+
+ # Team member can see the credential in general credentials API
+ response = get(reverse('api:credential_list'), team_member)
+ assert response.status_code == 200
+ assert any(cred['id'] == credential.id for cred in response.data['results'])
+
+
@pytest.mark.django_db
def test_sa_grant_private_credential_to_team_through_team_roles(post, credential, admin, team):
# not even a system admin can grant a private cred to a team though
@@ -385,10 +439,9 @@ def test_list_created_org_credentials(post, get, organization, org_admin, org_me
@pytest.mark.django_db
def test_list_cannot_order_by_encrypted_field(post, get, organization, org_admin, credentialtype_ssh, order_by):
for i, password in enumerate(('abc', 'def', 'xyz')):
- response = post(reverse('api:credential_list'), {'organization': organization.id, 'name': 'C%d' % i, 'password': password}, org_admin)
+ post(reverse('api:credential_list'), {'organization': organization.id, 'name': 'C%d' % i, 'password': password}, org_admin, expect=400)
- response = get(reverse('api:credential_list'), org_admin, QUERY_STRING='order_by=%s' % order_by, status=400)
- assert response.status_code == 400
+ get(reverse('api:credential_list'), org_admin, QUERY_STRING='order_by=%s' % order_by, expect=400)
@pytest.mark.django_db
@@ -399,8 +452,7 @@ def test_inputs_cannot_contain_extra_fields(get, post, organization, admin, cred
'credential_type': credentialtype_ssh.pk,
'inputs': {'invalid_field': 'foo'},
}
- response = post(reverse('api:credential_list'), params, admin)
- assert response.status_code == 400
+ response = post(reverse('api:credential_list'), params, admin, expect=400)
assert "'invalid_field' was unexpected" in response.data['inputs'][0]
@@ -925,7 +977,7 @@ def _change_credential_type():
response = _change_credential_type()
assert response.status_code == 400
- expected = ['You cannot change the credential type of the credential, ' 'as it may break the functionality of the resources using it.']
+ expected = ['You cannot change the credential type of the credential, as it may break the functionality of the resources using it.']
assert response.data['credential_type'] == expected
response = patch(reverse('api:credential_detail', kwargs={'pk': cred.pk}), {'name': 'Worst credential ever'}, admin)
@@ -962,7 +1014,7 @@ def _change_credential_type():
response = _change_credential_type()
assert response.status_code == 400
- expected = ['You cannot change the credential type of the credential, ' 'as it may break the functionality of the resources using it.']
+ expected = ['You cannot change the credential type of the credential, as it may break the functionality of the resources using it.']
assert response.data['credential_type'] == expected
response = patch(reverse('api:credential_detail', kwargs={'pk': cred.pk}), {'name': 'Worst credential ever'}, admin)
@@ -994,7 +1046,7 @@ def _change_credential_type():
response = _change_credential_type()
assert response.status_code == 400
- expected = ['You cannot change the credential type of the credential, ' 'as it may break the functionality of the resources using it.']
+ expected = ['You cannot change the credential type of the credential, as it may break the functionality of the resources using it.']
assert response.data['credential_type'] == expected
response = patch(reverse('api:credential_detail', kwargs={'pk': cred.pk}), {'name': 'Worst credential ever'}, admin)
@@ -1238,6 +1290,30 @@ def test_custom_credential_type_create(get, post, organization, admin):
assert decrypt_field(cred, 'api_token') == 'secret'
+@pytest.mark.django_db
+def test_galaxy_create_ok(post, organization, admin):
+ params = {
+ 'credential_type': 1,
+ 'name': 'Galaxy credential',
+ 'inputs': {
+ 'url': 'https://galaxy.ansible.com',
+ 'token': 'some_galaxy_token',
+ },
+ }
+ galaxy = CredentialType.defaults['galaxy_api_token']()
+ galaxy.save()
+ params['user'] = admin.id
+ params['credential_type'] = galaxy.pk
+ response = post(reverse('api:credential_list'), params, admin)
+ assert response.status_code == 201
+
+ assert Credential.objects.count() == 1
+ cred = Credential.objects.all()[:1].get()
+ assert cred.credential_type == galaxy
+ assert cred.inputs['url'] == 'https://galaxy.ansible.com'
+ assert decrypt_field(cred, 'token') == 'some_galaxy_token'
+
+
#
# misc xfail conditions
#
diff --git a/awx/main/tests/functional/api/test_deprecated_credential_assignment.py b/awx/main/tests/functional/api/test_deprecated_credential_assignment.py
index 266218c0cb23..02937c184514 100644
--- a/awx/main/tests/functional/api/test_deprecated_credential_assignment.py
+++ b/awx/main/tests/functional/api/test_deprecated_credential_assignment.py
@@ -6,12 +6,6 @@
from awx.api.versioning import reverse
-@pytest.fixture
-def ec2_source(inventory, project):
- with mock.patch('awx.main.models.unified_jobs.UnifiedJobTemplate.update'):
- return inventory.inventory_sources.create(name='some_source', source='ec2', source_project=project)
-
-
@pytest.fixture
def job_template(job_template, project, inventory):
job_template.playbook = 'helloworld.yml'
diff --git a/awx/main/tests/functional/api/test_events.py b/awx/main/tests/functional/api/test_events.py
index 34ecf4d691cc..ebc8900cae84 100644
--- a/awx/main/tests/functional/api/test_events.py
+++ b/awx/main/tests/functional/api/test_events.py
@@ -73,6 +73,7 @@ def test_job_job_events_children_summary(get, organization_factory, job_template
job_id=job.pk, uuid='uuid3', parent_uuid='uuid2', event="playbook_on_task_start", counter=3, stdout='a' * 1024, job_created=job.created
).save()
JobEvent.create_from_data(job_id=job.pk, uuid='uuid4', parent_uuid='', event='verbose', counter=4, stdout='a' * 1024, job_created=job.created).save()
+
JobEvent.create_from_data(
job_id=job.pk, uuid='uuid5', parent_uuid='uuid1', event="playbook_on_play_start", counter=5, stdout='a' * 1024, job_created=job.created
).save()
@@ -131,3 +132,46 @@ def test_job_job_events_children_summary_is_tree(get, organization_factory, job_
assert response.data["meta_event_nested_uuid"] == {}
assert response.data["event_processing_finished"] == True
assert response.data["is_tree"] == False
+
+
+@pytest.mark.django_db
+def test_job_job_events_children_summary_empty_event(get, organization_factory, job_template_factory):
+ objs = organization_factory("org", superusers=['admin'])
+ jt = job_template_factory("jt", organization=objs.organization, inventory='test_inv', project='test_proj').job_template
+ job = jt.create_unified_job()
+ url = reverse('api:job_job_events_children_summary', kwargs={'pk': job.pk})
+ response = get(url, user=objs.superusers.admin, expect=200)
+ assert response.data["event_processing_finished"] == False
+ '''
+ E1
+ E2
+ E3
+ E4 (verbose)
+ E5
+ '''
+ JobEvent.create_from_data(
+ job_id=job.pk, uuid='uuid1', parent_uuid='', event="playbook_on_start", counter=1, stdout='a' * 1024, job_created=job.created
+ ).save()
+ JobEvent.create_from_data(
+ job_id=job.pk, uuid='uuid2', parent_uuid='uuid1', event="playbook_on_play_start", counter=2, stdout='a' * 1024, job_created=job.created
+ ).save()
+ JobEvent.create_from_data(
+ job_id=job.pk, uuid='uuid3', parent_uuid='uuid2', event="playbook_on_task_start", counter=3, stdout='a' * 1024, job_created=job.created
+ ).save()
+ JobEvent.create_from_data(job_id=job.pk, uuid='uuid4', parent_uuid='', event='verbose', counter=4, stdout='a' * 1024, job_created=job.created).save()
+
+ JobEvent.create_from_data(job_id=job.pk, uuid='uuid4', parent_uuid='', event='', counter=5, stdout='a' * 1024, job_created=job.created).save()
+
+ JobEvent.create_from_data(
+ job_id=job.pk, uuid='uuid5', parent_uuid='uuid1', event="playbook_on_play_start", counter=6, stdout='a' * 1024, job_created=job.created
+ ).save()
+
+ job.emitted_events = job.get_event_queryset().count()
+ job.status = "successful"
+ job.save()
+ url = reverse('api:job_job_events_children_summary', kwargs={'pk': job.pk})
+ response = get(url, user=objs.superusers.admin, expect=200)
+ assert response.data["children_summary"] == {1: {"rowNumber": 0, "numChildren": 4}, 2: {"rowNumber": 1, "numChildren": 2}}
+ assert response.data["meta_event_nested_uuid"] == {4: "uuid2"}
+ assert response.data["event_processing_finished"] == True
+ assert response.data["is_tree"] == True
diff --git a/awx/main/tests/functional/api/test_generic.py b/awx/main/tests/functional/api/test_generic.py
index 0c064f23a40c..87571c8eeabe 100644
--- a/awx/main/tests/functional/api/test_generic.py
+++ b/awx/main/tests/functional/api/test_generic.py
@@ -1,22 +1,30 @@
import pytest
+from unittest import mock
from awx.api.versioning import reverse
+from django.test.utils import override_settings
+
+from ansible_base.jwt_consumer.common.util import generate_x_trusted_proxy_header
+from ansible_base.lib.testing.fixtures import rsa_keypair_factory, rsa_keypair # noqa: F401; pylint: disable=unused-import
+
+
+class HeaderTrackingMiddleware(object):
+ def __init__(self):
+ self.environ = {}
+
+ def process_request(self, request):
+ pass
+
+ def process_response(self, request, response):
+ self.environ = request.environ
+
@pytest.mark.django_db
def test_proxy_ip_allowed(get, patch, admin):
url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'system'})
patch(url, user=admin, data={'REMOTE_HOST_HEADERS': ['HTTP_X_FROM_THE_LOAD_BALANCER', 'REMOTE_ADDR', 'REMOTE_HOST']})
- class HeaderTrackingMiddleware(object):
- environ = {}
-
- def process_request(self, request):
- pass
-
- def process_response(self, request, response):
- self.environ = request.environ
-
# By default, `PROXY_IP_ALLOWED_LIST` is disabled, so custom `REMOTE_HOST_HEADERS`
# should just pass through
middleware = HeaderTrackingMiddleware()
@@ -45,6 +53,51 @@ def process_response(self, request, response):
assert middleware.environ['HTTP_X_FROM_THE_LOAD_BALANCER'] == 'some-actual-ip'
+@pytest.mark.django_db
+class TestTrustedProxyAllowListIntegration:
+ @pytest.fixture
+ def url(self, patch, admin):
+ url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'system'})
+ patch(url, user=admin, data={'REMOTE_HOST_HEADERS': ['HTTP_X_FROM_THE_LOAD_BALANCER', 'REMOTE_ADDR', 'REMOTE_HOST']})
+ patch(url, user=admin, data={'PROXY_IP_ALLOWED_LIST': ['my.proxy.example.org']})
+ return url
+
+ @pytest.fixture
+ def middleware(self):
+ return HeaderTrackingMiddleware()
+
+ def test_x_trusted_proxy_valid_signature(self, get, admin, rsa_keypair, url, middleware): # noqa: F811
+ # Headers should NOT get deleted
+ headers = {
+ 'HTTP_X_TRUSTED_PROXY': generate_x_trusted_proxy_header(rsa_keypair.private),
+ 'HTTP_X_FROM_THE_LOAD_BALANCER': 'some-actual-ip',
+ }
+ with mock.patch('ansible_base.jwt_consumer.common.cache.JWTCache.get_key_from_cache', lambda self: None):
+ with override_settings(ANSIBLE_BASE_JWT_KEY=rsa_keypair.public, PROXY_IP_ALLOWED_LIST=[]):
+ get(url, user=admin, middleware=middleware, **headers)
+ assert middleware.environ['HTTP_X_FROM_THE_LOAD_BALANCER'] == 'some-actual-ip'
+
+ def test_x_trusted_proxy_invalid_signature(self, get, admin, url, patch, middleware):
+ # Headers should NOT get deleted
+ headers = {
+ 'HTTP_X_TRUSTED_PROXY': 'DEAD-BEEF',
+ 'HTTP_X_FROM_THE_LOAD_BALANCER': 'some-actual-ip',
+ }
+ with override_settings(PROXY_IP_ALLOWED_LIST=[]):
+ get(url, user=admin, middleware=middleware, **headers)
+ assert middleware.environ['HTTP_X_FROM_THE_LOAD_BALANCER'] == 'some-actual-ip'
+
+ def test_x_trusted_proxy_invalid_signature_valid_proxy(self, get, admin, url, middleware):
+ # A valid explicit proxy SHOULD result in sensitive headers NOT being deleted, regardless of the trusted proxy signature results
+ headers = {
+ 'HTTP_X_TRUSTED_PROXY': 'DEAD-BEEF',
+ 'REMOTE_ADDR': 'my.proxy.example.org',
+ 'HTTP_X_FROM_THE_LOAD_BALANCER': 'some-actual-ip',
+ }
+ get(url, user=admin, middleware=middleware, **headers)
+ assert middleware.environ['HTTP_X_FROM_THE_LOAD_BALANCER'] == 'some-actual-ip'
+
+
@pytest.mark.django_db
class TestDeleteViews:
def test_sublist_delete_permission_check(self, inventory_source, host, rando, delete):
diff --git a/awx/main/tests/functional/api/test_instance.py b/awx/main/tests/functional/api/test_instance.py
index b9ec4d2ab088..c2e18b765dae 100644
--- a/awx/main/tests/functional/api/test_instance.py
+++ b/awx/main/tests/functional/api/test_instance.py
@@ -1,3 +1,5 @@
+from unittest import mock
+
import pytest
from awx.api.versioning import reverse
@@ -5,7 +7,9 @@
from awx.main.models.ha import Instance
from django.test.utils import override_settings
+from django.http import HttpResponse
+from rest_framework import status
INSTANCE_KWARGS = dict(hostname='example-host', cpu=6, node_type='execution', memory=36000000000, cpu_capacity=6, mem_capacity=42)
@@ -84,5 +88,14 @@ def test_custom_hostname_regex(post, admin_user):
"hostname": value[0],
"node_type": "execution",
"node_state": "installed",
+ "peers": [],
}
post(url=url, user=admin_user, data=data, expect=value[1])
+
+
+def test_instance_install_bundle(get, admin_user, system_auditor):
+ instance = Instance.objects.create(**INSTANCE_KWARGS)
+ url = reverse('api:instance_install_bundle', kwargs={'pk': instance.pk})
+ with mock.patch('awx.api.views.instance_install_bundle.InstanceInstallBundle.get', return_value=HttpResponse({'test': 'data'}, status=status.HTTP_200_OK)):
+ get(url=url, user=admin_user, expect=200)
+ get(url=url, user=system_auditor, expect=403)
diff --git a/awx/main/tests/functional/api/test_instance_group.py b/awx/main/tests/functional/api/test_instance_group.py
index aa8204c6dae9..5bc56940c980 100644
--- a/awx/main/tests/functional/api/test_instance_group.py
+++ b/awx/main/tests/functional/api/test_instance_group.py
@@ -32,13 +32,6 @@ def fn(hostname, node_type):
return fn
-@pytest.fixture
-def instance_group(job_factory):
- ig = InstanceGroup(name="east")
- ig.save()
- return ig
-
-
@pytest.fixture
def containerized_instance_group(instance_group, kube_credential):
ig = InstanceGroup(name="container")
diff --git a/awx/main/tests/functional/api/test_instance_peers.py b/awx/main/tests/functional/api/test_instance_peers.py
new file mode 100644
index 000000000000..1ce6f843bd78
--- /dev/null
+++ b/awx/main/tests/functional/api/test_instance_peers.py
@@ -0,0 +1,608 @@
+import pytest
+import yaml
+from unittest import mock
+
+from awx.api.versioning import reverse
+from awx.main.models import Instance, ReceptorAddress
+from awx.api.views.instance_install_bundle import generate_group_vars_all_yml
+
+
+def has_peer(group_vars, peer):
+ peers = group_vars.get('receptor_peers', [])
+ for p in peers:
+ if p['address'] == peer:
+ return True
+ return False
+
+
+@pytest.mark.django_db
+class TestPeers:
+ @pytest.fixture(autouse=True)
+ def configure_settings(self, settings):
+ settings.IS_K8S = True
+
+ @pytest.mark.parametrize('node_type', ['hop', 'execution'])
+ def test_peering_to_self(self, node_type, admin_user, patch):
+ """
+ cannot peer to self
+ """
+ instance = Instance.objects.create(hostname='abc', node_type=node_type)
+ addr = ReceptorAddress.objects.create(instance=instance, address='abc', canonical=True)
+ resp = patch(
+ url=reverse('api:instance_detail', kwargs={'pk': instance.pk}),
+ data={"hostname": "abc", "node_type": node_type, "peers": [addr.id]},
+ user=admin_user,
+ expect=400,
+ )
+ assert 'Instance cannot peer to its own address.' in str(resp.data)
+
+ @pytest.mark.parametrize('node_type', ['control', 'hybrid', 'hop', 'execution'])
+ def test_creating_node(self, node_type, admin_user, post):
+ """
+ can only add hop and execution nodes via API
+ """
+ resp = post(
+ url=reverse('api:instance_list'),
+ data={"hostname": "abc", "node_type": node_type},
+ user=admin_user,
+ expect=400 if node_type in ['control', 'hybrid'] else 201,
+ )
+ if resp.status_code == 400:
+ assert 'Can only create execution or hop nodes.' in str(resp.data)
+
+ def test_changing_node_type(self, admin_user, patch):
+ """
+ cannot change node type
+ """
+ hop = Instance.objects.create(hostname='abc', node_type="hop")
+ resp = patch(
+ url=reverse('api:instance_detail', kwargs={'pk': hop.pk}),
+ data={"node_type": "execution"},
+ user=admin_user,
+ expect=400,
+ )
+ assert 'Cannot change node type.' in str(resp.data)
+
+ @pytest.mark.parametrize(
+ 'payload_port, payload_peers_from, initial_port, initial_peers_from',
+ [
+ (-1, -1, None, None),
+ (-1, -1, 27199, False),
+ (-1, -1, 27199, True),
+ (None, -1, None, None),
+ (None, False, None, None),
+ (-1, False, None, None),
+ (27199, True, 27199, True),
+ (27199, False, 27199, False),
+ (27199, -1, 27199, True),
+ (27199, -1, 27199, False),
+ (-1, True, 27199, True),
+ (-1, False, 27199, False),
+ ],
+ )
+ def test_no_op(self, payload_port, payload_peers_from, initial_port, initial_peers_from, admin_user, patch):
+ node = Instance.objects.create(hostname='abc', node_type='hop')
+ if initial_port is not None:
+ ReceptorAddress.objects.create(address=node.hostname, port=initial_port, canonical=True, peers_from_control_nodes=initial_peers_from, instance=node)
+
+ assert ReceptorAddress.objects.filter(instance=node).count() == 1
+ else:
+ assert ReceptorAddress.objects.filter(instance=node).count() == 0
+
+ data = {'enabled': True} # Just to have something to post.
+ if payload_port != -1:
+ data['listener_port'] = payload_port
+ if payload_peers_from != -1:
+ data['peers_from_control_nodes'] = payload_peers_from
+
+ patch(
+ url=reverse('api:instance_detail', kwargs={'pk': node.pk}),
+ data=data,
+ user=admin_user,
+ expect=200,
+ )
+
+ assert ReceptorAddress.objects.filter(instance=node).count() == (0 if initial_port is None else 1)
+ if initial_port is not None:
+ ra = ReceptorAddress.objects.get(instance=node, canonical=True)
+ assert ra.port == initial_port
+ assert ra.peers_from_control_nodes == initial_peers_from
+
+ @pytest.mark.parametrize(
+ 'payload_port, payload_peers_from',
+ [
+ (27199, True),
+ (27199, False),
+ (27199, -1),
+ ],
+ )
+ def test_creates_canonical_address(self, payload_port, payload_peers_from, admin_user, patch):
+ node = Instance.objects.create(hostname='abc', node_type='hop')
+ assert ReceptorAddress.objects.filter(instance=node).count() == 0
+
+ data = {'enabled': True} # Just to have something to post.
+ if payload_port != -1:
+ data['listener_port'] = payload_port
+ if payload_peers_from != -1:
+ data['peers_from_control_nodes'] = payload_peers_from
+
+ patch(
+ url=reverse('api:instance_detail', kwargs={'pk': node.pk}),
+ data=data,
+ user=admin_user,
+ expect=200,
+ )
+
+ assert ReceptorAddress.objects.filter(instance=node).count() == 1
+ ra = ReceptorAddress.objects.get(instance=node, canonical=True)
+ assert ra.port == payload_port
+ assert ra.peers_from_control_nodes == (payload_peers_from if payload_peers_from != -1 else False)
+
+ @pytest.mark.parametrize(
+ 'payload_port, payload_peers_from, initial_port, initial_peers_from',
+ [
+ (None, False, 27199, True),
+ (None, -1, 27199, True),
+ (None, False, 27199, False),
+ (None, -1, 27199, False),
+ ],
+ )
+ def test_deletes_canonical_address(self, payload_port, payload_peers_from, initial_port, initial_peers_from, admin_user, patch):
+ node = Instance.objects.create(hostname='abc', node_type='hop')
+ ReceptorAddress.objects.create(address=node.hostname, port=initial_port, canonical=True, peers_from_control_nodes=initial_peers_from, instance=node)
+
+ assert ReceptorAddress.objects.filter(instance=node).count() == 1
+
+ data = {'enabled': True} # Just to have something to post.
+ if payload_port != -1:
+ data['listener_port'] = payload_port
+ if payload_peers_from != -1:
+ data['peers_from_control_nodes'] = payload_peers_from
+
+ patch(
+ url=reverse('api:instance_detail', kwargs={'pk': node.pk}),
+ data=data,
+ user=admin_user,
+ expect=200,
+ )
+
+ assert ReceptorAddress.objects.filter(instance=node).count() == 0
+
+ @pytest.mark.parametrize(
+ 'payload_port, payload_peers_from, initial_port, initial_peers_from',
+ [
+ (27199, True, 27199, False),
+ (27199, False, 27199, True),
+ (-1, True, 27199, False),
+ (-1, False, 27199, True),
+ ],
+ )
+ def test_updates_canonical_address(self, payload_port, payload_peers_from, initial_port, initial_peers_from, admin_user, patch):
+ node = Instance.objects.create(hostname='abc', node_type='hop')
+ ReceptorAddress.objects.create(address=node.hostname, port=initial_port, canonical=True, peers_from_control_nodes=initial_peers_from, instance=node)
+
+ assert ReceptorAddress.objects.filter(instance=node).count() == 1
+
+ data = {'enabled': True} # Just to have something to post.
+ if payload_port != -1:
+ data['listener_port'] = payload_port
+ if payload_peers_from != -1:
+ data['peers_from_control_nodes'] = payload_peers_from
+
+ patch(
+ url=reverse('api:instance_detail', kwargs={'pk': node.pk}),
+ data=data,
+ user=admin_user,
+ expect=200,
+ )
+
+ assert ReceptorAddress.objects.filter(instance=node).count() == 1
+ ra = ReceptorAddress.objects.get(instance=node, canonical=True)
+ assert ra.port == initial_port # At the present time, changing ports is not allowed
+ assert ra.peers_from_control_nodes == payload_peers_from
+
+ @pytest.mark.parametrize(
+ 'payload_port, payload_peers_from, initial_port, initial_peers_from, error_msg',
+ [
+ (-1, True, None, None, "Cannot enable peers_from_control_nodes"),
+ (None, True, None, None, "Cannot enable peers_from_control_nodes"),
+ (None, True, 21799, True, "Cannot enable peers_from_control_nodes"),
+ (None, True, 21799, False, "Cannot enable peers_from_control_nodes"),
+ (21800, -1, 21799, True, "Cannot change listener port"),
+ (21800, True, 21799, True, "Cannot change listener port"),
+ (21800, False, 21799, True, "Cannot change listener port"),
+ (21800, -1, 21799, False, "Cannot change listener port"),
+ (21800, True, 21799, False, "Cannot change listener port"),
+ (21800, False, 21799, False, "Cannot change listener port"),
+ ],
+ )
+ def test_canonical_address_validation_error(self, payload_port, payload_peers_from, initial_port, initial_peers_from, error_msg, admin_user, patch):
+ node = Instance.objects.create(hostname='abc', node_type='hop')
+ if initial_port is not None:
+ ReceptorAddress.objects.create(address=node.hostname, port=initial_port, canonical=True, peers_from_control_nodes=initial_peers_from, instance=node)
+
+ assert ReceptorAddress.objects.filter(instance=node).count() == 1
+ else:
+ assert ReceptorAddress.objects.filter(instance=node).count() == 0
+
+ data = {'enabled': True} # Just to have something to post.
+ if payload_port != -1:
+ data['listener_port'] = payload_port
+ if payload_peers_from != -1:
+ data['peers_from_control_nodes'] = payload_peers_from
+
+ resp = patch(
+ url=reverse('api:instance_detail', kwargs={'pk': node.pk}),
+ data=data,
+ user=admin_user,
+ expect=400,
+ )
+
+ assert error_msg in str(resp.data)
+
+ def test_changing_managed_listener_port(self, admin_user, patch):
+ """
+ if instance is managed, cannot change listener port at all
+ """
+ hop = Instance.objects.create(hostname='abc', node_type="hop", managed=True)
+ resp = patch(
+ url=reverse('api:instance_detail', kwargs={'pk': hop.pk}),
+ data={"listener_port": 5678},
+ user=admin_user,
+ expect=400, # cannot set port
+ )
+ assert 'Cannot change listener port for managed nodes.' in str(resp.data)
+ ReceptorAddress.objects.create(instance=hop, address='hop', port=27199, canonical=True)
+ resp = patch(
+ url=reverse('api:instance_detail', kwargs={'pk': hop.pk}),
+ data={"listener_port": None},
+ user=admin_user,
+ expect=400, # cannot unset port
+ )
+ assert 'Cannot change listener port for managed nodes.' in str(resp.data)
+
+ def test_bidirectional_peering(self, admin_user, patch):
+ """
+ cannot peer to node that is already to peered to it
+ if A -> B, then disallow B -> A
+ """
+ hop1 = Instance.objects.create(hostname='hop1', node_type='hop')
+ hop1addr = ReceptorAddress.objects.create(instance=hop1, address='hop1', canonical=True)
+ hop2 = Instance.objects.create(hostname='hop2', node_type='hop')
+ hop2addr = ReceptorAddress.objects.create(instance=hop2, address='hop2', canonical=True)
+ hop1.peers.add(hop2addr)
+ resp = patch(
+ url=reverse('api:instance_detail', kwargs={'pk': hop2.pk}),
+ data={"peers": [hop1addr.id]},
+ user=admin_user,
+ expect=400,
+ )
+ assert 'Instance hop1 is already peered to this instance.' in str(resp.data)
+
+ def test_multiple_peers_same_instance(self, admin_user, patch):
+ """
+ cannot peer to more than one address of the same instance
+ """
+ hop1 = Instance.objects.create(hostname='hop1', node_type='hop')
+ hop1addr1 = ReceptorAddress.objects.create(instance=hop1, address='hop1', canonical=True)
+ hop1addr2 = ReceptorAddress.objects.create(instance=hop1, address='hop1alternate')
+ hop2 = Instance.objects.create(hostname='hop2', node_type='hop')
+ resp = patch(
+ url=reverse('api:instance_detail', kwargs={'pk': hop2.pk}),
+ data={"peers": [hop1addr1.id, hop1addr2.id]},
+ user=admin_user,
+ expect=400,
+ )
+ assert 'Cannot peer to the same instance more than once.' in str(resp.data)
+
+ @pytest.mark.parametrize('node_type', ['control', 'hybrid'])
+ def test_changing_peers_control_nodes(self, node_type, admin_user, patch):
+ """
+ for control nodes, peers field should not be
+ modified directly via patch.
+ """
+ control = Instance.objects.create(hostname='abc', node_type=node_type, managed=True)
+ hop1 = Instance.objects.create(hostname='hop1', node_type='hop')
+ hop1addr = ReceptorAddress.objects.create(instance=hop1, address='hop1', peers_from_control_nodes=True, canonical=True)
+ hop2 = Instance.objects.create(hostname='hop2', node_type='hop')
+ hop2addr = ReceptorAddress.objects.create(instance=hop2, address='hop2', canonical=True)
+ assert [hop1addr] == list(control.peers.all()) # only hop1addr should be peered
+ resp = patch(
+ url=reverse('api:instance_detail', kwargs={'pk': control.pk}),
+ data={"peers": [hop2addr.id]},
+ user=admin_user,
+ expect=400, # cannot add peers manually
+ )
+ assert 'Setting peers manually for managed nodes is not allowed.' in str(resp.data)
+
+ patch(
+ url=reverse('api:instance_detail', kwargs={'pk': control.pk}),
+ data={"peers": [hop1addr.id]},
+ user=admin_user,
+ expect=200, # patching with current peers list should be okay
+ )
+ resp = patch(
+ url=reverse('api:instance_detail', kwargs={'pk': control.pk}),
+ data={"peers": []},
+ user=admin_user,
+ expect=400, # cannot remove peers directly
+ )
+ assert 'Setting peers manually for managed nodes is not allowed.' in str(resp.data)
+
+ patch(
+ url=reverse('api:instance_detail', kwargs={'pk': control.pk}),
+ data={},
+ user=admin_user,
+ expect=200, # patching without data should be fine too
+ )
+ # patch hop2
+ patch(
+ url=reverse('api:instance_detail', kwargs={'pk': hop2.pk}),
+ data={"peers_from_control_nodes": True},
+ user=admin_user,
+ expect=200,
+ )
+ assert {hop1addr, hop2addr} == set(control.peers.all()) # hop1 and hop2 should now be peered from control node
+
+ def test_changing_hostname(self, admin_user, patch):
+ """
+ cannot change hostname
+ """
+ hop = Instance.objects.create(hostname='hop', node_type='hop')
+ resp = patch(
+ url=reverse('api:instance_detail', kwargs={'pk': hop.pk}),
+ data={"hostname": "hop2"},
+ user=admin_user,
+ expect=400,
+ )
+
+ assert 'Cannot change hostname.' in str(resp.data)
+
+ def test_changing_node_state(self, admin_user, patch):
+ """
+ only allow setting to deprovisioning
+ """
+ hop = Instance.objects.create(hostname='hop', node_type='hop', node_state='installed')
+ patch(
+ url=reverse('api:instance_detail', kwargs={'pk': hop.pk}),
+ data={"node_state": "deprovisioning"},
+ user=admin_user,
+ expect=200,
+ )
+ resp = patch(
+ url=reverse('api:instance_detail', kwargs={'pk': hop.pk}),
+ data={"node_state": "ready"},
+ user=admin_user,
+ expect=400,
+ )
+ assert "Can only change instances to the 'deprovisioning' state." in str(resp.data)
+
+ def test_changing_managed_node_state(self, admin_user, patch):
+ """
+ cannot change node state of managed node
+ """
+ hop = Instance.objects.create(hostname='hop', node_type='hop', managed=True)
+ resp = patch(
+ url=reverse('api:instance_detail', kwargs={'pk': hop.pk}),
+ data={"node_state": "deprovisioning"},
+ user=admin_user,
+ expect=400,
+ )
+
+ assert 'Cannot deprovision managed nodes.' in str(resp.data)
+
+ def test_changing_managed_peers_from_control_nodes(self, admin_user, patch):
+ """
+ cannot change peers_from_control_nodes of managed node
+ """
+ hop = Instance.objects.create(hostname='hop', node_type='hop', managed=True)
+ ReceptorAddress.objects.create(instance=hop, address='hop', peers_from_control_nodes=True, canonical=True)
+ resp = patch(
+ url=reverse('api:instance_detail', kwargs={'pk': hop.pk}),
+ data={"peers_from_control_nodes": False},
+ user=admin_user,
+ expect=400,
+ )
+
+ assert 'Cannot change peers_from_control_nodes for managed nodes.' in str(resp.data)
+
+ hop.peers_from_control_nodes = False
+ hop.save()
+
+ resp = patch(
+ url=reverse('api:instance_detail', kwargs={'pk': hop.pk}),
+ data={"peers_from_control_nodes": False},
+ user=admin_user,
+ expect=400,
+ )
+
+ assert 'Cannot change peers_from_control_nodes for managed nodes.' in str(resp.data)
+
+ @pytest.mark.parametrize('node_type', ['control', 'hybrid'])
+ def test_control_node_automatically_peers(self, node_type):
+ """
+ a new control node should automatically
+ peer to hop
+
+ peer to hop should be removed if hop is deleted
+ """
+
+ hop = Instance.objects.create(hostname='hop', node_type='hop')
+ hopaddr = ReceptorAddress.objects.create(instance=hop, address='hop', peers_from_control_nodes=True, canonical=True)
+ control = Instance.objects.create(hostname='abc', node_type=node_type)
+ assert hopaddr in control.peers.all()
+ hop.delete()
+ assert not control.peers.exists()
+
+ @pytest.mark.parametrize('node_type', ['control', 'hybrid'])
+ def test_control_node_retains_other_peers(self, node_type):
+ """
+ if a new node comes online, other peer relationships should
+ remain intact
+ """
+ hop1 = Instance.objects.create(hostname='hop1', node_type='hop')
+ hop2 = Instance.objects.create(hostname='hop2', node_type='hop')
+ hop2addr = ReceptorAddress.objects.create(instance=hop2, address='hop2', canonical=True)
+ hop1.peers.add(hop2addr)
+
+ # a control node is added
+ Instance.objects.create(hostname='control', node_type=node_type)
+
+ assert hop1.peers.exists()
+
+ def test_reverse_peers(self, admin_user, get):
+ """
+ if hop1 peers to hop2, hop1 should
+ be in hop2's reverse_peers list
+ """
+ hop1 = Instance.objects.create(hostname='hop1', node_type='hop')
+ hop2 = Instance.objects.create(hostname='hop2', node_type='hop')
+ hop2addr = ReceptorAddress.objects.create(instance=hop2, address='hop2', canonical=True)
+ hop1.peers.add(hop2addr)
+
+ resp = get(
+ url=reverse('api:instance_detail', kwargs={'pk': hop2.pk}),
+ user=admin_user,
+ expect=200,
+ )
+
+ assert hop1.pk in resp.data['reverse_peers']
+
+ def test_group_vars(self):
+ """
+ control > hop1 > hop2 < execution
+ """
+ control = Instance.objects.create(hostname='control', node_type='control')
+ hop1 = Instance.objects.create(hostname='hop1', node_type='hop')
+ ReceptorAddress.objects.create(instance=hop1, address='hop1', peers_from_control_nodes=True, port=6789, canonical=True)
+
+ hop2 = Instance.objects.create(hostname='hop2', node_type='hop')
+ hop2addr = ReceptorAddress.objects.create(instance=hop2, address='hop2', peers_from_control_nodes=False, port=6789, canonical=True)
+
+ execution = Instance.objects.create(hostname='execution', node_type='execution')
+ ReceptorAddress.objects.create(instance=execution, address='execution', peers_from_control_nodes=False, port=6789, canonical=True)
+
+ execution.peers.add(hop2addr)
+ hop1.peers.add(hop2addr)
+
+ control_vars = yaml.safe_load(generate_group_vars_all_yml(control))
+ hop1_vars = yaml.safe_load(generate_group_vars_all_yml(hop1))
+ hop2_vars = yaml.safe_load(generate_group_vars_all_yml(hop2))
+ execution_vars = yaml.safe_load(generate_group_vars_all_yml(execution))
+
+ # control group vars assertions
+ assert has_peer(control_vars, 'hop1:6789')
+ assert not has_peer(control_vars, 'hop2:6789')
+ assert not has_peer(control_vars, 'execution:6789')
+ assert not control_vars.get('receptor_listener', False)
+
+ # hop1 group vars assertions
+ assert has_peer(hop1_vars, 'hop2:6789')
+ assert not has_peer(hop1_vars, 'execution:6789')
+ assert hop1_vars.get('receptor_listener', False)
+
+ # hop2 group vars assertions
+ assert not has_peer(hop2_vars, 'hop1:6789')
+ assert not has_peer(hop2_vars, 'execution:6789')
+ assert hop2_vars.get('receptor_listener', False)
+ assert hop2_vars.get('receptor_peers', []) == []
+
+ # execution group vars assertions
+ assert has_peer(execution_vars, 'hop2:6789')
+ assert not has_peer(execution_vars, 'hop1:6789')
+ assert execution_vars.get('receptor_listener', False)
+
+ def test_write_receptor_config_called(self):
+ """
+ Assert that write_receptor_config is called
+ when certain instances are created, or if
+ peers_from_control_nodes changes.
+ In general, write_receptor_config should only
+ be called when necessary, as it will reload
+ receptor backend connections which is not trivial.
+ """
+ with mock.patch('awx.main.models.ha.schedule_write_receptor_config') as write_method:
+ # new control instance but nothing to peer to (no)
+ control = Instance.objects.create(hostname='control1', node_type='control')
+ write_method.assert_not_called()
+
+ # new address with peers_from_control_nodes False (no)
+ hop1 = Instance.objects.create(hostname='hop1', node_type='hop')
+ hop1addr = ReceptorAddress.objects.create(instance=hop1, address='hop1', peers_from_control_nodes=False, canonical=True)
+ hop1.delete()
+ write_method.assert_not_called()
+
+ # new address with peers_from_control_nodes True (yes)
+ hop1 = Instance.objects.create(hostname='hop1', node_type='hop')
+ hop1addr = ReceptorAddress.objects.create(instance=hop1, address='hop1', peers_from_control_nodes=True, canonical=True)
+ write_method.assert_called()
+ write_method.reset_mock()
+
+ # new control instance but with something to peer to (yes)
+ Instance.objects.create(hostname='control2', node_type='control')
+ write_method.assert_called()
+ write_method.reset_mock()
+
+ # new address with peers_from_control_nodes False and peered to another hop node (no)
+ hop2 = Instance.objects.create(hostname='hop2', node_type='hop')
+ ReceptorAddress.objects.create(instance=hop2, address='hop2', peers_from_control_nodes=False, canonical=True)
+ hop2.peers.add(hop1addr)
+ hop2.delete()
+ write_method.assert_not_called()
+
+ # changing peers_from_control_nodes to False (yes)
+ hop1addr.peers_from_control_nodes = False
+ hop1addr.save()
+ write_method.assert_called()
+ write_method.reset_mock()
+
+ # deleting address that has peers_from_control_nodes to False (no)
+ hop1.delete() # cascade deletes to hop1addr
+ write_method.assert_not_called()
+
+ # deleting control nodes (no)
+ control.delete()
+ write_method.assert_not_called()
+
+ def test_write_receptor_config_data(self):
+ """
+ Assert the correct peers are included in data that will
+ be written to receptor.conf
+ """
+ from awx.main.tasks.receptor import RECEPTOR_CONFIG_STARTER
+
+ with mock.patch('awx.main.tasks.receptor.read_receptor_config', return_value=list(RECEPTOR_CONFIG_STARTER)):
+ from awx.main.tasks.receptor import generate_config_data
+
+ _, should_update = generate_config_data()
+ assert not should_update
+
+ # not peered, so config file should not be updated
+ for i in range(3):
+ inst = Instance.objects.create(hostname=f"exNo-{i}", node_type='execution')
+ ReceptorAddress.objects.create(instance=inst, address=f"exNo-{i}", port=6789, peers_from_control_nodes=False, canonical=True)
+ _, should_update = generate_config_data()
+ assert not should_update
+
+ # peered, so config file should be updated
+ expected_peers = []
+ for i in range(3):
+ expected_peers.append(f"hop-{i}:6789")
+ inst = Instance.objects.create(hostname=f"hop-{i}", node_type='hop')
+ ReceptorAddress.objects.create(instance=inst, address=f"hop-{i}", port=6789, peers_from_control_nodes=True, canonical=True)
+
+ for i in range(3):
+ expected_peers.append(f"exYes-{i}:6789")
+ inst = Instance.objects.create(hostname=f"exYes-{i}", node_type='execution')
+ ReceptorAddress.objects.create(instance=inst, address=f"exYes-{i}", port=6789, peers_from_control_nodes=True, canonical=True)
+
+ new_config, should_update = generate_config_data()
+ assert should_update
+
+ peers = []
+ for entry in new_config:
+ for key, value in entry.items():
+ if key == "tcp-peer":
+ peers.append(value['address'])
+
+ assert set(expected_peers) == set(peers)
diff --git a/awx/main/tests/functional/api/test_inventory.py b/awx/main/tests/functional/api/test_inventory.py
index 80357a22f96c..da19d62e66f7 100644
--- a/awx/main/tests/functional/api/test_inventory.py
+++ b/awx/main/tests/functional/api/test_inventory.py
@@ -8,6 +8,7 @@
from awx.api.versioning import reverse
from awx.main.models import InventorySource, Inventory, ActivityStream
+from awx.main.utils.inventory_vars import update_group_variables
@pytest.fixture
@@ -17,15 +18,6 @@ def scm_inventory(inventory, project):
return inventory
-@pytest.fixture
-def factory_scm_inventory(inventory, project):
- def fn(**kwargs):
- with mock.patch('awx.main.models.unified_jobs.UnifiedJobTemplate.update'):
- return inventory.inventory_sources.create(source_project=project, overwrite_vars=True, source='scm', **kwargs)
-
- return fn
-
-
@pytest.mark.django_db
def test_inventory_source_notification_on_cloud_only(get, post, inventory_source_factory, user, notification_template):
u = user('admin', True)
@@ -471,6 +463,26 @@ def test_validating_credential_type(self, organization, inventory, admin_user, p
assert 'Cloud-based inventory sources (such as ec2)' in r.data['credential'][0]
assert 'require credentials for the matching cloud service' in r.data['credential'][0]
+ def test_credential_dict_value_returns_400(self, inventory, admin_user, put):
+ """Passing a dict for the credential field should return 400, not 500.
+
+ Reproduces a bug where int() raises TypeError on non-scalar types
+ (dict, list) which was uncaught, resulting in a 500 Internal Server Error.
+ """
+ inv_src = InventorySource.objects.create(name='test-src', inventory=inventory, source='ec2')
+ r = put(
+ url=reverse('api:inventory_source_detail', kwargs={'pk': inv_src.pk}),
+ data={
+ 'name': 'test-src',
+ 'inventory': inventory.pk,
+ 'source': 'ec2',
+ 'credential': {'username': 'admin', 'password': 'secret'},
+ },
+ user=admin_user,
+ expect=400,
+ )
+ assert r.status_code == 400
+
def test_vault_credential_not_allowed(self, project, inventory, vault_credential, admin_user, post):
"""Vault credentials cannot be associated via the deprecated field"""
# TODO: when feature is added, add tests to use the related credentials
@@ -529,6 +541,20 @@ def test_credentials_relationship_mapping(self, project, inventory, organization
patch(url=inv_src.get_absolute_url(), data={'credential': aws_cred.pk}, expect=200, user=admin_user)
assert list(inv_src.credentials.values_list('id', flat=True)) == [aws_cred.pk]
+ @pytest.mark.skip(reason="Delay until AAP-53978 completed")
+ def test_vmware_cred_create_esxi_source(self, inventory, admin_user, organization, post, get):
+ """Test that a vmware esxi source can be added with a vmware credential"""
+ from awx.main.models.credential import Credential, CredentialType
+
+ vmware = CredentialType.defaults['vmware']()
+ vmware.save()
+ vmware_cred = Credential.objects.create(credential_type=vmware, name="bar", organization=organization)
+ inv_src = InventorySource.objects.create(inventory=inventory, name='foobar', source='vmware_esxi')
+ r = post(url=reverse('api:inventory_source_credentials_list', kwargs={'pk': inv_src.pk}), data={'id': vmware_cred.pk}, expect=204, user=admin_user)
+ g = get(inv_src.get_absolute_url(), admin_user)
+ assert r.status_code == 204
+ assert g.data['credential'] == vmware_cred.pk
+
@pytest.mark.django_db
class TestControlledBySCM:
@@ -594,3 +620,346 @@ def test_adding_inv_src_without_proj_access_prohibited(self, post, project, inve
rando,
expect=403,
)
+
+
+@pytest.mark.django_db
+class TestConstructedInventory:
+ @pytest.fixture
+ def constructed_inventory(self, organization):
+ return Inventory.objects.create(name='constructed-test-inventory', kind='constructed', organization=organization)
+
+ def test_get_constructed_inventory(self, constructed_inventory, admin_user, get):
+ inv_src = constructed_inventory.inventory_sources.first()
+ inv_src.update_cache_timeout = 53
+ inv_src.save(update_fields=['update_cache_timeout'])
+ r = get(url=reverse('api:constructed_inventory_detail', kwargs={'pk': constructed_inventory.pk}), user=admin_user, expect=200)
+ assert r.data['update_cache_timeout'] == 53
+
+ def test_patch_constructed_inventory(self, constructed_inventory, admin_user, patch):
+ inv_src = constructed_inventory.inventory_sources.first()
+ assert inv_src.update_cache_timeout == 0
+ assert inv_src.limit == ''
+ r = patch(
+ url=reverse('api:constructed_inventory_detail', kwargs={'pk': constructed_inventory.pk}),
+ data=dict(update_cache_timeout=54, limit='foobar'),
+ user=admin_user,
+ expect=200,
+ )
+ assert r.data['update_cache_timeout'] == 54
+ inv_src = constructed_inventory.inventory_sources.first()
+ assert inv_src.update_cache_timeout == 54
+ assert inv_src.limit == 'foobar'
+
+ def test_patch_constructed_inventory_generated_source_limits_editable_fields(self, constructed_inventory, admin_user, project, patch):
+ inv_src = constructed_inventory.inventory_sources.first()
+ r = patch(
+ url=inv_src.get_absolute_url(),
+ data={
+ 'source': 'scm',
+ 'source_project': project.pk,
+ 'source_path': '',
+ 'source_vars': 'plugin: a.b.c',
+ },
+ expect=400,
+ user=admin_user,
+ )
+ assert str(r.data['error'][0]) == "Cannot change field 'source' on a constructed inventory source."
+
+ # Make sure it didn't get updated before we got the error
+ inv_src_after_err = constructed_inventory.inventory_sources.first()
+ assert inv_src.id == inv_src_after_err.id
+ assert inv_src.source == inv_src_after_err.source
+ assert inv_src.source_project == inv_src_after_err.source_project
+ assert inv_src.source_path == inv_src_after_err.source_path
+ assert inv_src.source_vars == inv_src_after_err.source_vars
+
+ def test_patch_constructed_inventory_generated_source_allows_source_vars_edit(self, constructed_inventory, admin_user, patch):
+ inv_src = constructed_inventory.inventory_sources.first()
+ patch(
+ url=inv_src.get_absolute_url(),
+ data={
+ 'source_vars': 'plugin: a.b.c',
+ },
+ expect=200,
+ user=admin_user,
+ )
+
+ inv_src_after_patch = constructed_inventory.inventory_sources.first()
+
+ # sanity checks
+ assert inv_src.id == inv_src_after_patch.id
+ assert inv_src.source == 'constructed'
+ assert inv_src_after_patch.source == 'constructed'
+ assert inv_src.source_vars == ''
+
+ assert inv_src_after_patch.source_vars == 'plugin: a.b.c'
+
+ def test_create_constructed_inventory(self, constructed_inventory, admin_user, post, organization):
+ r = post(
+ url=reverse('api:constructed_inventory_list'),
+ data=dict(name='constructed-inventory-just-created', kind='constructed', organization=organization.id, update_cache_timeout=55, limit='foobar'),
+ user=admin_user,
+ expect=201,
+ )
+ pk = r.data['id']
+ constructed_inventory = Inventory.objects.get(pk=pk)
+ inv_src = constructed_inventory.inventory_sources.first()
+ assert inv_src.update_cache_timeout == 55
+ assert inv_src.limit == 'foobar'
+
+ def test_get_absolute_url_for_constructed_inventory(self, constructed_inventory, admin_user, get):
+ """
+ If we are using the normal inventory API endpoint to look at a
+ constructed inventory, then we should get a normal inventory API route
+ back. If we are accessing it via the special constructed inventory
+ endpoint, then we should get that back.
+ """
+
+ url_const = reverse('api:constructed_inventory_detail', kwargs={'pk': constructed_inventory.pk})
+ url_inv = reverse('api:inventory_detail', kwargs={'pk': constructed_inventory.pk})
+
+ const_r = get(url=url_const, user=admin_user, expect=200)
+ inv_r = get(url=url_inv, user=admin_user, expect=200)
+ assert const_r.data['url'] == url_const
+ assert inv_r.data['url'] == url_inv
+ assert inv_r.data['url'] != const_r.data['url']
+ assert inv_r.data['related']['constructed_url'] == url_const
+ assert const_r.data['related']['constructed_url'] == url_const
+
+
+@pytest.mark.django_db
+class TestInventoryAllVariables:
+
+ @staticmethod
+ def simulate_update_from_source(inv_src, variables_dict, overwrite_vars=True):
+ """
+ Update `inventory` with variables `variables_dict` from source
+ `inv_src`.
+ """
+ # Perform an update from source the same way it is done in
+ # `inventory_import.Command._update_inventory`.
+ new_vars = update_group_variables(
+ group_id=None, # `None` denotes the 'all' group (which doesn't have a pk).
+ newvars=variables_dict,
+ dbvars=inv_src.inventory.variables_dict,
+ invsrc_id=inv_src.id,
+ inventory_id=inv_src.inventory.id,
+ overwrite_vars=overwrite_vars,
+ )
+ inv_src.inventory.variables = json.dumps(new_vars)
+ inv_src.inventory.save(update_fields=["variables"])
+ return new_vars
+
+ def update_and_verify(self, inv_src, new_vars, expect=None, overwrite_vars=True, teststep=None):
+ """
+ Helper: Update from source and verify the new inventory variables.
+
+ :param inv_src: An inventory source object with its inventory property
+ set to the inventory fixture of the called.
+ :param dict new_vars: The variables of the inventory source `inv_src`.
+ :param dict expect: (optional) The expected variables state of the
+ inventory after the update. If not set or None, expect `new_vars`.
+ :param bool overwrite_vars: The status of the inventory source option
+ 'overwrite variables'. Default is `True`.
+ :raise AssertionError: If the inventory does not contain the expected
+ variables after the update.
+ """
+ self.simulate_update_from_source(inv_src, new_vars, overwrite_vars=overwrite_vars)
+ if teststep is not None:
+ assert inv_src.inventory.variables_dict == (expect if expect is not None else new_vars), f"Test step {teststep}"
+ else:
+ assert inv_src.inventory.variables_dict == (expect if expect is not None else new_vars)
+
+ def test_set_variables_through_inventory_details_update(self, inventory, patch, admin_user):
+ """
+ Set an inventory variable by changing the inventory details, simulating
+ a user edit.
+ """
+ # a: x
+ patch(url=reverse('api:inventory_detail', kwargs={'pk': inventory.pk}), data={'variables': 'a: x'}, user=admin_user, expect=200)
+ inventory.refresh_from_db()
+ assert inventory.variables_dict == {"a": "x"}
+
+ def test_variables_set_by_user_persist_update_from_src(self, inventory, inventory_source, patch, admin_user):
+ """
+ Verify the special behavior that a variable which originates from a user
+ edit (instead of a source update), is not removed from the inventory
+ when a source update with overwrite_vars=True does not contain that
+ variable. This behavior is considered special because a variable which
+ originates from a source would actually be deleted.
+
+ In addition, verify that an existing variable which was set by a user
+ edit can be overwritten by a source update.
+ """
+ # Set two variables via user edit.
+ patch(
+ url=reverse('api:inventory_detail', kwargs={'pk': inventory.pk}),
+ data={'variables': '{"a": "a_from_user", "b": "b_from_user"}'},
+ user=admin_user,
+ expect=200,
+ )
+ inventory.refresh_from_db()
+ assert inventory.variables_dict == {'a': 'a_from_user', 'b': 'b_from_user'}
+ # Update from a source which contains only one of the two variables from
+ # the previous update.
+ self.simulate_update_from_source(inventory_source, {'a': 'a_from_source'})
+ # Verify inventory variables.
+ assert inventory.variables_dict == {'a': 'a_from_source', 'b': 'b_from_user'}
+
+ def test_variables_set_through_src_get_removed_on_update_from_same_src(self, inventory, inventory_source, patch, admin_user):
+ """
+ Verify that a variable which originates from a source update, is removed
+ from the inventory when a source update with overwrite_vars=True does
+ not contain that variable.
+
+ In addition, verify that an existing variable which was set by a user
+ edit can be overwritten by a source update.
+ """
+ # Set two variables via update from source.
+ self.simulate_update_from_source(inventory_source, {'a': 'a_from_source', 'b': 'b_from_source'})
+ # Verify inventory variables.
+ assert inventory.variables_dict == {'a': 'a_from_source', 'b': 'b_from_source'}
+ # Update from the same source which now contains only one of the two
+ # variables from the previous update.
+ self.simulate_update_from_source(inventory_source, {'b': 'b_from_source'})
+ # Verify the variable has been deleted from the inventory.
+ assert inventory.variables_dict == {'b': 'b_from_source'}
+
+ def test_overwrite_variables_through_inventory_details_update(self, inventory, patch, admin_user):
+ """
+ Set and update the inventory variables multiple times by changing the
+ inventory details via api, simulating user edits.
+
+ Any variables update by means of an inventory details update shall
+ overwright all existing inventory variables.
+ """
+ # a: x
+ patch(url=reverse('api:inventory_detail', kwargs={'pk': inventory.pk}), data={'variables': 'a: x'}, user=admin_user, expect=200)
+ inventory.refresh_from_db()
+ assert inventory.variables_dict == {"a": "x"}
+ # a: x2
+ patch(url=reverse('api:inventory_detail', kwargs={'pk': inventory.pk}), data={'variables': 'a: x2'}, user=admin_user, expect=200)
+ inventory.refresh_from_db()
+ assert inventory.variables_dict == {"a": "x2"}
+ # b: y
+ patch(url=reverse('api:inventory_detail', kwargs={'pk': inventory.pk}), data={'variables': 'b: y'}, user=admin_user, expect=200)
+ inventory.refresh_from_db()
+ assert inventory.variables_dict == {"b": "y"}
+
+ def test_inventory_group_variables_internal_data(self, inventory, patch, admin_user):
+ """
+ Basic verification of how variable updates are stored internally.
+
+ .. Warning::
+
+ This test verifies a specific implementation of the inventory
+ variables update business logic. It may deliver false negatives if
+ the implementation changes.
+ """
+ # x: a
+ patch(url=reverse('api:inventory_detail', kwargs={'pk': inventory.pk}), data={'variables': 'a: x'}, user=admin_user, expect=200)
+ igv = inventory.inventory_group_variables.first()
+ assert igv.variables == {'a': [[-1, 'x']]}
+ # b: y
+ patch(url=reverse('api:inventory_detail', kwargs={'pk': inventory.pk}), data={'variables': 'b: y'}, user=admin_user, expect=200)
+ igv = inventory.inventory_group_variables.first()
+ assert igv.variables == {'b': [[-1, 'y']]}
+
+ def test_update_then_user_change(self, inventory, patch, admin_user, inventory_source):
+ """
+ 1. Update inventory vars by means of an inventory source update.
+ 2. Update inventory vars by editing the inventory details (aka a 'user
+ update'), thereby changing variables values and deleting variables
+ from the inventory.
+
+ .. Warning::
+
+ This test partly relies on a specific implementation of the
+ inventory variables update business logic. It may deliver false
+ negatives if the implementation changes.
+ """
+ assert inventory_source.inventory_id == inventory.pk # sanity
+ # ---- Test step 1: Set variables by updating from an inventory source.
+ self.simulate_update_from_source(inventory_source, {'foo': 'foo_from_source', 'bar': 'bar_from_source'})
+ # Verify inventory variables.
+ assert inventory.variables_dict == {'foo': 'foo_from_source', 'bar': 'bar_from_source'}
+ # Verify internal storage of variables data. Note that this is
+ # implementation specific
+ assert inventory.inventory_group_variables.count() == 1
+ igv = inventory.inventory_group_variables.first()
+ assert igv.variables == {'foo': [[inventory_source.id, 'foo_from_source']], 'bar': [[inventory_source.id, 'bar_from_source']]}
+ # ---- Test step 2: Change the variables by editing the inventory details.
+ patch(url=reverse('api:inventory_detail', kwargs={'pk': inventory.pk}), data={'variables': 'foo: foo_from_user'}, user=admin_user, expect=200)
+ inventory.refresh_from_db()
+ # Verify that variable `foo` contains the new value, and that variable
+ # `bar` has been deleted from the inventory.
+ assert inventory.variables_dict == {"foo": "foo_from_user"}
+ # Verify internal storage of variables data. Note that this is
+ # implementation specific
+ inventory.inventory_group_variables.count() == 1
+ igv = inventory.inventory_group_variables.first()
+ assert igv.variables == {'foo': [[-1, 'foo_from_user']]}
+
+ def test_monotonic_deletions(self, inventory, patch, admin_user):
+ """
+ Verify the variables history logic for monotonic deletions.
+
+ Monotonic in this context means that the variables are deleted in the
+ reverse order of their creation.
+
+ 1. Set inventory variable x: 0, expect INV={x: 0}
+
+ (The following steps use overwrite_variables=False)
+
+ 2. Update from source A={x: 1}, expect INV={x: 1}
+ 3. Update from source B={x: 2}, expect INV={x: 2}
+ 4. Update from source B={}, expect INV={x: 1}
+ 5. Update from source A={}, expect INV={x: 0}
+ """
+ inv_src_a = InventorySource.objects.create(name="inv-src-A", inventory=inventory, source="ec2")
+ inv_src_b = InventorySource.objects.create(name="inv-src-B", inventory=inventory, source="ec2")
+ # Test step 1:
+ patch(url=reverse('api:inventory_detail', kwargs={'pk': inventory.pk}), data={'variables': 'x: 0'}, user=admin_user, expect=200)
+ inventory.refresh_from_db()
+ assert inventory.variables_dict == {"x": 0}
+ # Test step 2: Source A overwrites value of var x
+ self.update_and_verify(inv_src_a, {"x": 1}, teststep=2)
+ # Test step 3: Source A overwrites value of var x
+ self.update_and_verify(inv_src_b, {"x": 2}, teststep=3)
+ # Test step 4: Value of var x from source A reappears
+ self.update_and_verify(inv_src_b, {}, expect={"x": 1}, teststep=4)
+ # Test step 5: Value of var x from initial user edit reappears
+ self.update_and_verify(inv_src_a, {}, expect={"x": 0}, teststep=5)
+
+ def test_interleaved_deletions(self, inventory, patch, admin_user, inventory_source):
+ """
+ Verify the variables history logic for interleaved deletions.
+
+ Interleaved in this context means that the variables are deleted in a
+ different order than the sequence of their creation.
+
+ 1. Set inventory variable x: 0, expect INV={x: 0}
+ 2. Update from source A={x: 1}, expect INV={x: 1}
+ 3. Update from source B={x: 2}, expect INV={x: 2}
+ 4. Update from source C={x: 3}, expect INV={x: 3}
+ 5. Update from source B={}, expect INV={x: 3}
+ 6. Update from source C={}, expect INV={x: 1}
+ """
+ inv_src_a = InventorySource.objects.create(name="inv-src-A", inventory=inventory, source="ec2")
+ inv_src_b = InventorySource.objects.create(name="inv-src-B", inventory=inventory, source="ec2")
+ inv_src_c = InventorySource.objects.create(name="inv-src-C", inventory=inventory, source="ec2")
+ # Test step 1. Set inventory variable x: 0
+ patch(url=reverse('api:inventory_detail', kwargs={'pk': inventory.pk}), data={'variables': 'x: 0'}, user=admin_user, expect=200)
+ inventory.refresh_from_db()
+ assert inventory.variables_dict == {"x": 0}
+ # Test step 2: Source A overwrites value of var x
+ self.update_and_verify(inv_src_a, {"x": 1}, teststep=2)
+ # Test step 3: Source B overwrites value of var x
+ self.update_and_verify(inv_src_b, {"x": 2}, teststep=3)
+ # Test step 4: Source C overwrites value of var x
+ self.update_and_verify(inv_src_c, {"x": 3}, teststep=4)
+ # Test step 5: Value of var x from source C remains unchanged
+ self.update_and_verify(inv_src_b, {}, expect={"x": 3}, teststep=5)
+ # Test step 6: Value of var x from source A reappears, because the
+ # latest update from source B did not contain var x.
+ self.update_and_verify(inv_src_c, {}, expect={"x": 1}, teststep=6)
diff --git a/awx/main/tests/functional/api/test_job.py b/awx/main/tests/functional/api/test_job.py
index 53e31e5981a6..8398c9b8c26f 100644
--- a/awx/main/tests/functional/api/test_job.py
+++ b/awx/main/tests/functional/api/test_job.py
@@ -51,6 +51,16 @@ def test_job_relaunch_permission_denied_response(post, get, inventory, project,
r = post(reverse('api:job_relaunch', kwargs={'pk': job.pk}), {}, jt_user, expect=201)
+@pytest.mark.django_db
+def test_label_sublist(get, admin_user, organization):
+ job = Job.objects.create()
+ label = Label.objects.create(organization=organization, name='Steve')
+ job.labels.add(label)
+ r = get(url=reverse('api:job_label_list', kwargs={'pk': job.pk}), user=admin_user, expect=200)
+ assert r.data['count'] == 1
+ assert r.data['results'].pop()['id'] == label.id
+
+
@pytest.mark.django_db
def test_job_relaunch_prompts_not_accepted_response(post, get, inventory, project, credential, net_credential, machine_credential):
jt = JobTemplate.objects.create(name='testjt', inventory=inventory, project=project)
@@ -200,6 +210,39 @@ def test_disallowed_http_update_methods(put, patch, post, inventory, project, ad
patch(url=reverse('api:job_detail', kwargs={'pk': job.pk}), data={}, user=admin_user, expect=405)
+@pytest.mark.django_db
+@pytest.mark.parametrize(
+ "job_type",
+ [
+ 'run',
+ 'check',
+ ],
+)
+def test_job_relaunch_with_job_type(post, inventory, project, machine_credential, admin_user, job_type):
+ # Create a job template
+ jt = JobTemplate.objects.create(name='testjt', inventory=inventory, project=project)
+
+ # Set initial job type
+ init_job_type = 'check' if job_type == 'run' else 'run'
+
+ # Create a job instance
+ job = jt.create_unified_job(_eager_fields={'job_type': init_job_type})
+
+ # Perform the POST request
+ url = reverse('api:job_relaunch', kwargs={'pk': job.pk})
+ r = post(url=url, data={'job_type': job_type}, user=admin_user, expect=201)
+
+ # Assert that the response status code is 201 (Created)
+ assert r.status_code == 201
+
+ # Retrieve the newly created job from the response
+ new_job_id = r.data.get('id')
+ new_job = Job.objects.get(id=new_job_id)
+
+ # Assert that the new job has the correct job type
+ assert new_job.job_type == job_type
+
+
class TestControllerNode:
@pytest.fixture
def project_update(self, project):
@@ -214,7 +257,7 @@ def adhoc(self, inventory):
return AdHocCommand.objects.create(inventory=inventory)
@pytest.mark.django_db
- def test_field_controller_node_exists(self, sqlite_copy_expert, admin_user, job, project_update, inventory_update, adhoc, get, system_job_factory):
+ def test_field_controller_node_exists(self, sqlite_copy, admin_user, job, project_update, inventory_update, adhoc, get, system_job_factory):
system_job = system_job_factory()
r = get(reverse('api:unified_job_list') + '?id={}'.format(job.id), admin_user, expect=200)
diff --git a/awx/main/tests/functional/api/test_job_runtime_params.py b/awx/main/tests/functional/api/test_job_runtime_params.py
index f477a66ed945..95b55e15b601 100644
--- a/awx/main/tests/functional/api/test_job_runtime_params.py
+++ b/awx/main/tests/functional/api/test_job_runtime_params.py
@@ -38,11 +38,6 @@ def runtime_data(organization, credentialtype_ssh):
)
-@pytest.fixture
-def job_with_links(machine_credential, inventory):
- return Job.objects.create(name='existing-job', credential=machine_credential, inventory=inventory)
-
-
@pytest.fixture
def job_template_prompts(project, inventory, machine_credential):
def rf(on_off):
@@ -131,11 +126,11 @@ def test_job_ignore_unprompted_vars(runtime_data, job_template_prompts, post, ad
mock_job = mocker.MagicMock(spec=Job, id=968, **runtime_data)
- with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job):
- with mocker.patch('awx.api.serializers.JobSerializer.to_representation'):
- response = post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), runtime_data, admin_user, expect=201)
- assert JobTemplate.create_unified_job.called
- assert JobTemplate.create_unified_job.call_args == ()
+ mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job)
+ mocker.patch('awx.api.serializers.JobSerializer.to_representation')
+ response = post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), runtime_data, admin_user, expect=201)
+ assert JobTemplate.create_unified_job.called
+ assert JobTemplate.create_unified_job.call_args == ()
# Check that job is serialized correctly
job_id = response.data['job']
@@ -167,12 +162,12 @@ def test_job_accept_prompted_vars(runtime_data, job_template_prompts, post, admi
mock_job = mocker.MagicMock(spec=Job, id=968, **runtime_data)
- with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job):
- with mocker.patch('awx.api.serializers.JobSerializer.to_representation'):
- response = post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), runtime_data, admin_user, expect=201)
- assert JobTemplate.create_unified_job.called
- called_with = data_to_internal(runtime_data)
- JobTemplate.create_unified_job.assert_called_with(**called_with)
+ mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job)
+ mocker.patch('awx.api.serializers.JobSerializer.to_representation')
+ response = post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), runtime_data, admin_user, expect=201)
+ assert JobTemplate.create_unified_job.called
+ called_with = data_to_internal(runtime_data)
+ JobTemplate.create_unified_job.assert_called_with(**called_with)
job_id = response.data['job']
assert job_id == 968
@@ -187,11 +182,11 @@ def test_job_accept_empty_tags(job_template_prompts, post, admin_user, mocker):
mock_job = mocker.MagicMock(spec=Job, id=968)
- with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job):
- with mocker.patch('awx.api.serializers.JobSerializer.to_representation'):
- post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), {'job_tags': '', 'skip_tags': ''}, admin_user, expect=201)
- assert JobTemplate.create_unified_job.called
- assert JobTemplate.create_unified_job.call_args == ({'job_tags': '', 'skip_tags': ''},)
+ mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job)
+ mocker.patch('awx.api.serializers.JobSerializer.to_representation')
+ post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), {'job_tags': '', 'skip_tags': ''}, admin_user, expect=201)
+ assert JobTemplate.create_unified_job.called
+ assert JobTemplate.create_unified_job.call_args == ({'job_tags': '', 'skip_tags': ''},)
mock_job.signal_start.assert_called_once()
@@ -203,14 +198,14 @@ def test_slice_timeout_forks_need_int(job_template_prompts, post, admin_user, mo
mock_job = mocker.MagicMock(spec=Job, id=968)
- with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job):
- with mocker.patch('awx.api.serializers.JobSerializer.to_representation'):
- response = post(
- reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), {'timeout': '', 'job_slice_count': '', 'forks': ''}, admin_user, expect=400
- )
- assert 'forks' in response.data and response.data['forks'][0] == 'A valid integer is required.'
- assert 'job_slice_count' in response.data and response.data['job_slice_count'][0] == 'A valid integer is required.'
- assert 'timeout' in response.data and response.data['timeout'][0] == 'A valid integer is required.'
+ mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job)
+ mocker.patch('awx.api.serializers.JobSerializer.to_representation')
+ response = post(
+ reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), {'timeout': '', 'job_slice_count': '', 'forks': ''}, admin_user, expect=400
+ )
+ assert 'forks' in response.data and response.data['forks'][0] == 'A valid integer is required.'
+ assert 'job_slice_count' in response.data and response.data['job_slice_count'][0] == 'A valid integer is required.'
+ assert 'timeout' in response.data and response.data['timeout'][0] == 'A valid integer is required.'
@pytest.mark.django_db
@@ -244,12 +239,12 @@ def test_job_accept_prompted_vars_null(runtime_data, job_template_prompts_null,
mock_job = mocker.MagicMock(spec=Job, id=968, **runtime_data)
- with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job):
- with mocker.patch('awx.api.serializers.JobSerializer.to_representation'):
- response = post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), runtime_data, rando, expect=201)
- assert JobTemplate.create_unified_job.called
- expected_call = data_to_internal(runtime_data)
- assert JobTemplate.create_unified_job.call_args == (expected_call,)
+ mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job)
+ mocker.patch('awx.api.serializers.JobSerializer.to_representation')
+ response = post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), runtime_data, rando, expect=201)
+ assert JobTemplate.create_unified_job.called
+ expected_call = data_to_internal(runtime_data)
+ assert JobTemplate.create_unified_job.call_args == (expected_call,)
job_id = response.data['job']
assert job_id == 968
@@ -641,18 +636,18 @@ def test_job_launch_unprompted_vars_with_survey(mocker, survey_spec_factory, job
job_template.survey_spec = survey_spec_factory('survey_var')
job_template.save()
- with mocker.patch('awx.main.access.BaseAccess.check_license'):
- mock_job = mocker.MagicMock(spec=Job, id=968, extra_vars={"job_launch_var": 3, "survey_var": 4})
- with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job):
- with mocker.patch('awx.api.serializers.JobSerializer.to_representation', return_value={}):
- response = post(
- reverse('api:job_template_launch', kwargs={'pk': job_template.pk}),
- dict(extra_vars={"job_launch_var": 3, "survey_var": 4}),
- admin_user,
- expect=201,
- )
- assert JobTemplate.create_unified_job.called
- assert JobTemplate.create_unified_job.call_args == ({'extra_vars': {'survey_var': 4}},)
+ mocker.patch('awx.main.access.BaseAccess.check_license')
+ mock_job = mocker.MagicMock(spec=Job, id=968, extra_vars={"job_launch_var": 3, "survey_var": 4})
+ mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job)
+ mocker.patch('awx.api.serializers.JobSerializer.to_representation', return_value={})
+ response = post(
+ reverse('api:job_template_launch', kwargs={'pk': job_template.pk}),
+ dict(extra_vars={"job_launch_var": 3, "survey_var": 4}),
+ admin_user,
+ expect=201,
+ )
+ assert JobTemplate.create_unified_job.called
+ assert JobTemplate.create_unified_job.call_args == ({'extra_vars': {'survey_var': 4}},)
job_id = response.data['job']
assert job_id == 968
@@ -670,22 +665,22 @@ def test_callback_accept_prompted_extra_var(mocker, survey_spec_factory, job_tem
job_template.survey_spec = survey_spec_factory('survey_var')
job_template.save()
- with mocker.patch('awx.main.access.BaseAccess.check_license'):
- mock_job = mocker.MagicMock(spec=Job, id=968, extra_vars={"job_launch_var": 3, "survey_var": 4})
- with mocker.patch.object(UnifiedJobTemplate, 'create_unified_job', return_value=mock_job):
- with mocker.patch('awx.api.serializers.JobSerializer.to_representation', return_value={}):
- with mocker.patch('awx.api.views.JobTemplateCallback.find_matching_hosts', return_value=[host]):
- post(
- reverse('api:job_template_callback', kwargs={'pk': job_template.pk}),
- dict(extra_vars={"job_launch_var": 3, "survey_var": 4}, host_config_key="foo"),
- admin_user,
- expect=201,
- format='json',
- )
- assert UnifiedJobTemplate.create_unified_job.called
- call_args = UnifiedJobTemplate.create_unified_job.call_args[1]
- call_args.pop('_eager_fields', None) # internal purposes
- assert call_args == {'extra_vars': {'survey_var': 4, 'job_launch_var': 3}, 'limit': 'single-host'}
+ mocker.patch('awx.main.access.BaseAccess.check_license')
+ mock_job = mocker.MagicMock(spec=Job, id=968, extra_vars={"job_launch_var": 3, "survey_var": 4})
+ mocker.patch.object(UnifiedJobTemplate, 'create_unified_job', return_value=mock_job)
+ mocker.patch('awx.api.serializers.JobSerializer.to_representation', return_value={})
+ mocker.patch('awx.api.views.JobTemplateCallback.find_matching_hosts', return_value=[host])
+ post(
+ reverse('api:job_template_callback', kwargs={'pk': job_template.pk}),
+ dict(extra_vars={"job_launch_var": 3, "survey_var": 4}, host_config_key="foo"),
+ admin_user,
+ expect=201,
+ format='json',
+ )
+ assert UnifiedJobTemplate.create_unified_job.called
+ call_args = UnifiedJobTemplate.create_unified_job.call_args[1]
+ call_args.pop('_eager_fields', None) # internal purposes
+ assert call_args == {'extra_vars': {'survey_var': 4, 'job_launch_var': 3}, 'limit': 'single-host'}
mock_job.signal_start.assert_called_once()
@@ -697,22 +692,22 @@ def test_callback_ignore_unprompted_extra_var(mocker, survey_spec_factory, job_t
job_template.host_config_key = "foo"
job_template.save()
- with mocker.patch('awx.main.access.BaseAccess.check_license'):
- mock_job = mocker.MagicMock(spec=Job, id=968, extra_vars={"job_launch_var": 3, "survey_var": 4})
- with mocker.patch.object(UnifiedJobTemplate, 'create_unified_job', return_value=mock_job):
- with mocker.patch('awx.api.serializers.JobSerializer.to_representation', return_value={}):
- with mocker.patch('awx.api.views.JobTemplateCallback.find_matching_hosts', return_value=[host]):
- post(
- reverse('api:job_template_callback', kwargs={'pk': job_template.pk}),
- dict(extra_vars={"job_launch_var": 3, "survey_var": 4}, host_config_key="foo"),
- admin_user,
- expect=201,
- format='json',
- )
- assert UnifiedJobTemplate.create_unified_job.called
- call_args = UnifiedJobTemplate.create_unified_job.call_args[1]
- call_args.pop('_eager_fields', None) # internal purposes
- assert call_args == {'limit': 'single-host'}
+ mocker.patch('awx.main.access.BaseAccess.check_license')
+ mock_job = mocker.MagicMock(spec=Job, id=968, extra_vars={"job_launch_var": 3, "survey_var": 4})
+ mocker.patch.object(UnifiedJobTemplate, 'create_unified_job', return_value=mock_job)
+ mocker.patch('awx.api.serializers.JobSerializer.to_representation', return_value={})
+ mocker.patch('awx.api.views.JobTemplateCallback.find_matching_hosts', return_value=[host])
+ post(
+ reverse('api:job_template_callback', kwargs={'pk': job_template.pk}),
+ dict(extra_vars={"job_launch_var": 3, "survey_var": 4}, host_config_key="foo"),
+ admin_user,
+ expect=201,
+ format='json',
+ )
+ assert UnifiedJobTemplate.create_unified_job.called
+ call_args = UnifiedJobTemplate.create_unified_job.call_args[1]
+ call_args.pop('_eager_fields', None) # internal purposes
+ assert call_args == {'limit': 'single-host'}
mock_job.signal_start.assert_called_once()
@@ -725,9 +720,9 @@ def test_callback_find_matching_hosts(mocker, get, job_template_prompts, admin_u
job_template.save()
host_with_alias = Host(name='localhost', inventory=job_template.inventory)
host_with_alias.save()
- with mocker.patch('awx.main.access.BaseAccess.check_license'):
- r = get(reverse('api:job_template_callback', kwargs={'pk': job_template.pk}), user=admin_user, expect=200)
- assert tuple(r.data['matching_hosts']) == ('localhost',)
+ mocker.patch('awx.main.access.BaseAccess.check_license')
+ r = get(reverse('api:job_template_callback', kwargs={'pk': job_template.pk}), user=admin_user, expect=200)
+ assert tuple(r.data['matching_hosts']) == ('localhost',)
@pytest.mark.django_db
@@ -738,6 +733,6 @@ def test_callback_extra_var_takes_priority_over_host_name(mocker, get, job_templ
job_template.save()
host_with_alias = Host(name='localhost', variables={'ansible_host': 'foobar'}, inventory=job_template.inventory)
host_with_alias.save()
- with mocker.patch('awx.main.access.BaseAccess.check_license'):
- r = get(reverse('api:job_template_callback', kwargs={'pk': job_template.pk}), user=admin_user, expect=200)
- assert not r.data['matching_hosts']
+ mocker.patch('awx.main.access.BaseAccess.check_license')
+ r = get(reverse('api:job_template_callback', kwargs={'pk': job_template.pk}), user=admin_user, expect=200)
+ assert not r.data['matching_hosts']
diff --git a/awx/main/tests/functional/api/test_job_template.py b/awx/main/tests/functional/api/test_job_template.py
index dc6ce0c7a008..4cdf43d37b9f 100644
--- a/awx/main/tests/functional/api/test_job_template.py
+++ b/awx/main/tests/functional/api/test_job_template.py
@@ -1,17 +1,23 @@
import pytest
+from unittest import mock
# AWX
from awx.api.serializers import JobTemplateSerializer
from awx.api.versioning import reverse
-from awx.main.models import Job, JobTemplate, CredentialType, WorkflowJobTemplate, Organization, Project
+from awx.main.models import Job, JobTemplate, CredentialType, WorkflowJobTemplate, Organization, Project, Inventory
from awx.main.migrations import _save_password_keys as save_password_keys
# Django
from django.apps import apps
+from django.test.utils import override_settings
# DRF
from rest_framework.exceptions import ValidationError
+# DAB
+from ansible_base.jwt_consumer.common.util import generate_x_trusted_proxy_header
+from ansible_base.lib.testing.fixtures import rsa_keypair_factory, rsa_keypair # noqa: F401; pylint: disable=unused-import
+
@pytest.mark.django_db
@pytest.mark.parametrize(
@@ -353,3 +359,129 @@ def test_job_template_branch_prompt_error(project, inventory, post, admin_user):
expect=400,
)
assert 'Project does not allow overriding branch' in str(r.data['ask_scm_branch_on_launch'])
+
+
+@pytest.mark.django_db
+def test_job_template_missing_inventory(project, inventory, admin_user, post):
+ jt = JobTemplate.objects.create(
+ name='test-jt', inventory=inventory, ask_inventory_on_launch=True, project=project, playbook='helloworld.yml', host_config_key='abcd'
+ )
+ Inventory.objects.get(pk=inventory.pk).delete()
+ r = post(
+ url=reverse('api:job_template_callback', kwargs={'pk': jt.pk}),
+ data={'host_config_key': 'abcd'},
+ user=admin_user,
+ expect=400,
+ )
+ assert r.status_code == 400
+ assert "Cannot start automatically, an inventory is required." in str(r.data)
+
+
+@pytest.mark.django_db
+class TestJobTemplateCallbackProxyIntegration:
+ """
+ Test the interaction of provision job template callback feature and:
+ settings.PROXY_IP_ALLOWED_LIST
+ x-trusted-proxy http header
+ """
+
+ @pytest.fixture
+ def job_template(self, inventory, project):
+ jt = JobTemplate.objects.create(name='test-jt', inventory=inventory, project=project, playbook='helloworld.yml', host_config_key='abcd')
+ return jt
+
+ @override_settings(REMOTE_HOST_HEADERS=['HTTP_X_FROM_THE_LOAD_BALANCER', 'REMOTE_ADDR', 'REMOTE_HOST'], PROXY_IP_ALLOWED_LIST=['my.proxy.example.org'])
+ def test_host_not_found(self, job_template, admin_user, post, rsa_keypair): # noqa: F811
+ job_template.inventory.hosts.create(name='foobar')
+
+ headers = {
+ 'HTTP_X_FROM_THE_LOAD_BALANCER': 'baz',
+ 'REMOTE_HOST': 'baz',
+ 'REMOTE_ADDR': 'baz',
+ }
+ r = post(
+ url=reverse('api:job_template_callback', kwargs={'pk': job_template.pk}), data={'host_config_key': 'abcd'}, user=admin_user, expect=400, **headers
+ )
+ assert r.data['msg'] == 'No matching host could be found!'
+
+ @pytest.mark.parametrize(
+ 'headers, expected',
+ (
+ pytest.param(
+ {
+ 'HTTP_X_FROM_THE_LOAD_BALANCER': 'foobar',
+ 'REMOTE_HOST': 'my.proxy.example.org',
+ },
+ 201,
+ ),
+ pytest.param(
+ {
+ 'HTTP_X_FROM_THE_LOAD_BALANCER': 'foobar',
+ 'REMOTE_HOST': 'not-my-proxy.org',
+ },
+ 400,
+ ),
+ ),
+ )
+ @override_settings(REMOTE_HOST_HEADERS=['HTTP_X_FROM_THE_LOAD_BALANCER', 'REMOTE_ADDR', 'REMOTE_HOST'], PROXY_IP_ALLOWED_LIST=['my.proxy.example.org'])
+ def test_proxy_ip_allowed_list(self, job_template, admin_user, post, headers, expected): # noqa: F811
+ job_template.inventory.hosts.create(name='my.proxy.example.org')
+
+ post(
+ url=reverse('api:job_template_callback', kwargs={'pk': job_template.pk}),
+ data={'host_config_key': 'abcd'},
+ user=admin_user,
+ expect=expected,
+ **headers
+ )
+
+ @override_settings(REMOTE_HOST_HEADERS=['HTTP_X_FROM_THE_LOAD_BALANCER', 'REMOTE_ADDR', 'REMOTE_HOST'], PROXY_IP_ALLOWED_LIST=[])
+ def test_no_proxy_trust_all_headers(self, job_template, admin_user, post):
+ job_template.inventory.hosts.create(name='foobar')
+
+ headers = {
+ 'HTTP_X_FROM_THE_LOAD_BALANCER': 'foobar',
+ 'REMOTE_ADDR': 'bar',
+ 'REMOTE_HOST': 'baz',
+ }
+ post(url=reverse('api:job_template_callback', kwargs={'pk': job_template.pk}), data={'host_config_key': 'abcd'}, user=admin_user, expect=201, **headers)
+
+ @override_settings(REMOTE_HOST_HEADERS=['HTTP_X_FROM_THE_LOAD_BALANCER', 'REMOTE_ADDR', 'REMOTE_HOST'], PROXY_IP_ALLOWED_LIST=['my.proxy.example.org'])
+ def test_trusted_proxy(self, job_template, admin_user, post, rsa_keypair): # noqa: F811
+ job_template.inventory.hosts.create(name='foobar')
+
+ headers = {
+ 'HTTP_X_TRUSTED_PROXY': generate_x_trusted_proxy_header(rsa_keypair.private),
+ 'HTTP_X_FROM_THE_LOAD_BALANCER': 'foobar, my.proxy.example.org',
+ }
+
+ with mock.patch('ansible_base.jwt_consumer.common.cache.JWTCache.get_key_from_cache', lambda self: None):
+ with override_settings(ANSIBLE_BASE_JWT_KEY=rsa_keypair.public):
+ post(
+ url=reverse('api:job_template_callback', kwargs={'pk': job_template.pk}),
+ data={'host_config_key': 'abcd'},
+ user=admin_user,
+ expect=201,
+ **headers
+ )
+
+ @override_settings(REMOTE_HOST_HEADERS=['HTTP_X_FROM_THE_LOAD_BALANCER', 'REMOTE_ADDR', 'REMOTE_HOST'], PROXY_IP_ALLOWED_LIST=['my.proxy.example.org'])
+ def test_trusted_proxy_host_not_found(self, job_template, admin_user, post, rsa_keypair): # noqa: F811
+ job_template.inventory.hosts.create(name='foobar')
+
+ headers = {
+ 'HTTP_X_TRUSTED_PROXY': generate_x_trusted_proxy_header(rsa_keypair.private),
+ 'HTTP_X_FROM_THE_LOAD_BALANCER': 'baz, my.proxy.example.org',
+ 'REMOTE_ADDR': 'bar',
+ 'REMOTE_HOST': 'baz',
+ }
+
+ with mock.patch('ansible_base.jwt_consumer.common.cache.JWTCache.get_key_from_cache', lambda self: None):
+ with override_settings(ANSIBLE_BASE_JWT_KEY=rsa_keypair.public):
+ post(
+ url=reverse('api:job_template_callback', kwargs={'pk': job_template.pk}),
+ data={'host_config_key': 'abcd'},
+ user=admin_user,
+ expect=400,
+ **headers
+ )
diff --git a/awx/main/tests/functional/api/test_license_cache_clearing.py b/awx/main/tests/functional/api/test_license_cache_clearing.py
new file mode 100644
index 000000000000..08b8e3513822
--- /dev/null
+++ b/awx/main/tests/functional/api/test_license_cache_clearing.py
@@ -0,0 +1,191 @@
+import pytest
+from unittest.mock import patch, MagicMock
+
+from awx.api.versioning import reverse
+
+
+# Generated by Cursor (claude-4-sonnet)
+@pytest.mark.django_db
+class TestLicenseCacheClearing:
+ """Test cache clearing for LICENSE setting changes"""
+
+ def test_license_from_manifest_clears_cache(self, admin_user, post):
+ """Test that posting a manifest to /api/v2/config/ clears the LICENSE cache"""
+
+ # Mock the licenser and clear_setting_cache
+ with patch('awx.api.views.root.get_licenser') as mock_get_licenser, patch('awx.api.views.root.validate_entitlement_manifest') as mock_validate, patch(
+ 'awx.api.views.root.clear_setting_cache'
+ ) as mock_clear_cache, patch('django.db.connection.on_commit') as mock_on_commit:
+
+ # Set up mock license data
+ mock_license_data = {'valid_key': True, 'license_type': 'enterprise', 'instance_count': 100, 'subscription_name': 'Test Enterprise License'}
+
+ # Mock the validation and license processing
+ mock_validate.return_value = [{'some': 'manifest_data'}]
+ mock_licenser = MagicMock()
+ mock_licenser.license_from_manifest.return_value = mock_license_data
+ mock_get_licenser.return_value = mock_licenser
+
+ # Prepare the request data (base64 encoded manifest)
+ manifest_data = {'manifest': 'ZmFrZS1tYW5pZmVzdC1kYXRh'} # base64 for "fake-manifest-data"
+
+ # Make the POST request
+ url = reverse('api:api_v2_config_view')
+ response = post(url, manifest_data, admin_user, expect=200)
+
+ # Verify the response
+ assert response.data == mock_license_data
+
+ # Verify license_from_manifest was called
+ mock_licenser.license_from_manifest.assert_called_once()
+
+ # Verify on_commit was called (may be multiple times due to other settings)
+ assert mock_on_commit.call_count >= 1
+
+ # Execute all on_commit callbacks to trigger cache clearing
+ for call_args in mock_on_commit.call_args_list:
+ callback = call_args[0][0]
+ callback()
+
+ # Verify that clear_setting_cache.delay was called with ['LICENSE']
+ mock_clear_cache.delay.assert_any_call(['LICENSE'])
+
+ def test_config_delete_clears_cache(self, admin_user, delete):
+ """Test that DELETE /api/v2/config/ clears the LICENSE cache"""
+
+ with patch('awx.api.views.root.clear_setting_cache') as mock_clear_cache, patch('django.db.connection.on_commit') as mock_on_commit:
+
+ # Make the DELETE request
+ url = reverse('api:api_v2_config_view')
+ delete(url, admin_user, expect=204)
+
+ # Verify on_commit was called at least once
+ assert mock_on_commit.call_count >= 1
+
+ # Execute all on_commit callbacks to trigger cache clearing
+ for call_args in mock_on_commit.call_args_list:
+ callback = call_args[0][0]
+ callback()
+
+ mock_clear_cache.delay.assert_called_once_with(['LICENSE'])
+
+ def test_attach_view_clears_cache(self, admin_user, post):
+ """Test that posting to /api/v2/config/attach/ clears the LICENSE cache"""
+
+ with patch('awx.api.views.root.get_licenser') as mock_get_licenser, patch('awx.api.views.root.clear_setting_cache') as mock_clear_cache, patch(
+ 'django.db.connection.on_commit'
+ ) as mock_on_commit, patch('awx.api.views.root.settings') as mock_settings:
+
+ # Set up subscription credentials in settings
+ mock_settings.SUBSCRIPTIONS_CLIENT_ID = 'test-client-id'
+ mock_settings.SUBSCRIPTIONS_CLIENT_SECRET = 'test-client-secret'
+
+ # Set up mock licenser with validated subscriptions
+ mock_licenser = MagicMock()
+ subscription_data = {'subscription_id': 'test-subscription-123', 'valid_key': False, 'license_type': 'enterprise', 'instance_count': 50}
+ mock_licenser.validate_rh.return_value = [subscription_data]
+ mock_get_licenser.return_value = mock_licenser
+
+ # Prepare request data
+ request_data = {'subscription_id': 'test-subscription-123'}
+
+ # Make the POST request
+ url = reverse('api:api_v2_attach_view')
+ response = post(url, request_data, admin_user, expect=200)
+
+ # Verify the response includes valid_key=True
+ assert response.data['valid_key'] is True
+ assert response.data['subscription_id'] == 'test-subscription-123'
+
+ # Verify settings.LICENSE was set
+ expected_license = subscription_data.copy()
+ expected_license['valid_key'] = True
+ assert mock_settings.LICENSE == expected_license
+
+ # Verify cache clearing was scheduled
+ mock_on_commit.assert_called_once()
+ call_args = mock_on_commit.call_args[0][0] # Get the lambda function
+
+ # Execute the lambda to verify it calls clear_setting_cache
+ call_args()
+ mock_clear_cache.delay.assert_called_once_with(['LICENSE'])
+
+ def test_attach_view_subscription_not_found_no_cache_clear(self, admin_user, post):
+ """Test that attach view doesn't clear cache when subscription is not found"""
+
+ with patch('awx.api.views.root.get_licenser') as mock_get_licenser, patch('awx.api.views.root.clear_setting_cache') as mock_clear_cache, patch(
+ 'django.db.connection.on_commit'
+ ) as mock_on_commit:
+
+ # Set up mock licenser with different subscription
+ mock_licenser = MagicMock()
+ subscription_data = {'subscription_id': 'different-subscription-456', 'valid_key': False, 'license_type': 'enterprise'} # Different ID
+ mock_licenser.validate_rh.return_value = [subscription_data]
+ mock_get_licenser.return_value = mock_licenser
+
+ # Request data with non-matching subscription ID
+ request_data = {
+ 'subscription_id': 'test-subscription-123', # This won't match
+ }
+
+ # Make the POST request
+ url = reverse('api:api_v2_attach_view')
+ response = post(url, request_data, admin_user, expect=400)
+
+ # Verify error response
+ assert 'error' in response.data
+
+ # Verify cache clearing was NOT called (no matching subscription)
+ mock_on_commit.assert_not_called()
+ mock_clear_cache.delay.assert_not_called()
+
+ def test_manifest_validation_error_no_cache_clear(self, admin_user, post):
+ """Test that config view doesn't clear cache when manifest validation fails"""
+
+ with patch('awx.api.views.root.validate_entitlement_manifest') as mock_validate, patch(
+ 'awx.api.views.root.clear_setting_cache'
+ ) as mock_clear_cache, patch('django.db.connection.on_commit') as mock_on_commit:
+
+ # Mock validation to raise ValueError
+ mock_validate.side_effect = ValueError("Invalid manifest")
+
+ # Prepare request data
+ manifest_data = {'manifest': 'aW52YWxpZC1tYW5pZmVzdA=='} # base64 for "invalid-manifest"
+
+ # Make the POST request
+ url = reverse('api:api_v2_config_view')
+ response = post(url, manifest_data, admin_user, expect=400)
+
+ # Verify error response
+ assert response.data['error'] == 'Invalid manifest'
+
+ # Verify cache clearing was NOT called (validation failed)
+ mock_on_commit.assert_not_called()
+ mock_clear_cache.delay.assert_not_called()
+
+ def test_license_processing_error_no_cache_clear(self, admin_user, post):
+ """Test that config view doesn't clear cache when license processing fails"""
+
+ with patch('awx.api.views.root.get_licenser') as mock_get_licenser, patch('awx.api.views.root.validate_entitlement_manifest') as mock_validate, patch(
+ 'awx.api.views.root.clear_setting_cache'
+ ) as mock_clear_cache, patch('django.db.connection.on_commit') as mock_on_commit:
+
+ # Mock validation to succeed but license processing to fail
+ mock_validate.return_value = [{'some': 'manifest_data'}]
+ mock_licenser = MagicMock()
+ mock_licenser.license_from_manifest.side_effect = Exception("License processing failed")
+ mock_get_licenser.return_value = mock_licenser
+
+ # Prepare request data
+ manifest_data = {'manifest': 'ZmFrZS1tYW5pZmVzdA=='} # base64 for "fake-manifest"
+
+ # Make the POST request
+ url = reverse('api:api_v2_config_view')
+ response = post(url, manifest_data, admin_user, expect=400)
+
+ # Verify error response
+ assert response.data['error'] == 'Invalid License'
+
+ # Verify cache clearing was NOT called (license processing failed)
+ mock_on_commit.assert_not_called()
+ mock_clear_cache.delay.assert_not_called()
diff --git a/awx/main/tests/functional/api/test_licensing.py b/awx/main/tests/functional/api/test_licensing.py
new file mode 100644
index 000000000000..a752f0d3f2be
--- /dev/null
+++ b/awx/main/tests/functional/api/test_licensing.py
@@ -0,0 +1,244 @@
+from unittest.mock import patch, MagicMock
+
+import pytest
+from awx.api.versioning import reverse
+from rest_framework import status
+
+
+@pytest.mark.django_db
+class TestApiV2SubscriptionView:
+ """Test cases for the /api/v2/config/subscriptions/ endpoint"""
+
+ def test_basic_auth(self, post, admin):
+ """Test POST with subscriptions_username and subscriptions_password calls validate_rh with basic_auth=True"""
+ data = {'subscriptions_username': 'test_user', 'subscriptions_password': 'test_password'}
+
+ with patch('awx.api.views.root.get_licenser') as mock_get_licenser:
+ mock_licenser = MagicMock()
+ mock_licenser.validate_rh.return_value = []
+ mock_get_licenser.return_value = mock_licenser
+
+ response = post(reverse('api:api_v2_subscription_view'), data, admin)
+
+ assert response.status_code == status.HTTP_200_OK
+ mock_licenser.validate_rh.assert_called_once_with('test_user', 'test_password', True)
+
+ def test_service_account(self, post, admin):
+ """Test POST with subscriptions_client_id and subscriptions_client_secret calls validate_rh with basic_auth=False"""
+ data = {'subscriptions_client_id': 'test_client_id', 'subscriptions_client_secret': 'test_client_secret'}
+
+ with patch('awx.api.views.root.get_licenser') as mock_get_licenser:
+ mock_licenser = MagicMock()
+ mock_licenser.validate_rh.return_value = []
+ mock_get_licenser.return_value = mock_licenser
+
+ response = post(reverse('api:api_v2_subscription_view'), data, admin)
+
+ assert response.status_code == status.HTTP_200_OK
+ mock_licenser.validate_rh.assert_called_once_with('test_client_id', 'test_client_secret', False)
+
+ def test_encrypted_password_basic_auth(self, post, admin, settings):
+ """Test POST with $encrypted$ password uses settings value for basic auth"""
+ data = {'subscriptions_username': 'test_user', 'subscriptions_password': '$encrypted$'}
+
+ settings.SUBSCRIPTIONS_PASSWORD = 'actual_password_from_settings'
+
+ with patch('awx.api.views.root.get_licenser') as mock_get_licenser:
+ mock_licenser = MagicMock()
+ mock_licenser.validate_rh.return_value = []
+ mock_get_licenser.return_value = mock_licenser
+
+ response = post(reverse('api:api_v2_subscription_view'), data, admin)
+
+ assert response.status_code == status.HTTP_200_OK
+ mock_licenser.validate_rh.assert_called_once_with('test_user', 'actual_password_from_settings', True)
+
+ def test_encrypted_client_secret_service_account(self, post, admin, settings):
+ """Test POST with $encrypted$ client_secret uses settings value for service_account"""
+ data = {'subscriptions_client_id': 'test_client_id', 'subscriptions_client_secret': '$encrypted$'}
+
+ settings.SUBSCRIPTIONS_CLIENT_SECRET = 'actual_secret_from_settings'
+
+ with patch('awx.api.views.root.get_licenser') as mock_get_licenser:
+ mock_licenser = MagicMock()
+ mock_licenser.validate_rh.return_value = []
+ mock_get_licenser.return_value = mock_licenser
+
+ response = post(reverse('api:api_v2_subscription_view'), data, admin)
+
+ assert response.status_code == status.HTTP_200_OK
+ mock_licenser.validate_rh.assert_called_once_with('test_client_id', 'actual_secret_from_settings', False)
+
+ def test_missing_username_returns_error(self, post, admin):
+ """Test POST with missing username returns 400 error"""
+ data = {'subscriptions_password': 'test_password'}
+
+ response = post(reverse('api:api_v2_subscription_view'), data, admin)
+
+ assert response.status_code == status.HTTP_400_BAD_REQUEST
+ assert 'Missing subscription credentials' in response.data['error']
+
+ def test_missing_password_returns_error(self, post, admin, settings):
+ """Test POST with missing password returns 400 error"""
+ data = {'subscriptions_username': 'test_user'}
+ settings.SUBSCRIPTIONS_PASSWORD = None
+
+ response = post(reverse('api:api_v2_subscription_view'), data, admin)
+
+ assert response.status_code == status.HTTP_400_BAD_REQUEST
+ assert 'Missing subscription credentials' in response.data['error']
+
+ def test_missing_client_id_returns_error(self, post, admin):
+ """Test POST with missing client_id returns 400 error"""
+ data = {'subscriptions_client_secret': 'test_secret'}
+
+ response = post(reverse('api:api_v2_subscription_view'), data, admin)
+
+ assert response.status_code == status.HTTP_400_BAD_REQUEST
+ assert 'Missing subscription credentials' in response.data['error']
+
+ def test_missing_client_secret_returns_error(self, post, admin, settings):
+ """Test POST with missing client_secret returns 400 error"""
+ data = {'subscriptions_client_id': 'test_client_id'}
+ settings.SUBSCRIPTIONS_CLIENT_SECRET = None
+
+ response = post(reverse('api:api_v2_subscription_view'), data, admin)
+
+ assert response.status_code == status.HTTP_400_BAD_REQUEST
+ assert 'Missing subscription credentials' in response.data['error']
+
+ def test_empty_username_returns_error(self, post, admin):
+ """Test POST with empty username returns 400 error"""
+ data = {'subscriptions_username': '', 'subscriptions_password': 'test_password'}
+
+ response = post(reverse('api:api_v2_subscription_view'), data, admin)
+
+ assert response.status_code == status.HTTP_400_BAD_REQUEST
+ assert 'Missing subscription credentials' in response.data['error']
+
+ def test_empty_password_returns_error(self, post, admin, settings):
+ """Test POST with empty password returns 400 error"""
+ data = {'subscriptions_username': 'test_user', 'subscriptions_password': ''}
+ settings.SUBSCRIPTIONS_PASSWORD = None
+
+ response = post(reverse('api:api_v2_subscription_view'), data, admin)
+
+ assert response.status_code == status.HTTP_400_BAD_REQUEST
+ assert 'Missing subscription credentials' in response.data['error']
+
+ def test_non_superuser_permission_denied(self, post, rando):
+ """Test that non-superuser cannot access the endpoint"""
+ data = {'subscriptions_username': 'test_user', 'subscriptions_password': 'test_password'}
+
+ response = post(reverse('api:api_v2_subscription_view'), data, rando)
+
+ assert response.status_code == status.HTTP_403_FORBIDDEN
+
+ def test_settings_updated_on_successful_basic_auth(self, post, admin, settings):
+ """Test that settings are updated when basic auth validation succeeds"""
+ data = {'subscriptions_username': 'new_username', 'subscriptions_password': 'new_password'}
+
+ with patch('awx.api.views.root.get_licenser') as mock_get_licenser:
+ mock_licenser = MagicMock()
+ mock_licenser.validate_rh.return_value = []
+ mock_get_licenser.return_value = mock_licenser
+
+ response = post(reverse('api:api_v2_subscription_view'), data, admin)
+
+ assert response.status_code == status.HTTP_200_OK
+ assert settings.SUBSCRIPTIONS_USERNAME == 'new_username'
+ assert settings.SUBSCRIPTIONS_PASSWORD == 'new_password'
+
+ def test_settings_updated_on_successful_service_account(self, post, admin, settings):
+ """Test that settings are updated when service account validation succeeds"""
+ data = {'subscriptions_client_id': 'new_client_id', 'subscriptions_client_secret': 'new_client_secret'}
+
+ with patch('awx.api.views.root.get_licenser') as mock_get_licenser:
+ mock_licenser = MagicMock()
+ mock_licenser.validate_rh.return_value = []
+ mock_get_licenser.return_value = mock_licenser
+
+ response = post(reverse('api:api_v2_subscription_view'), data, admin)
+
+ assert response.status_code == status.HTTP_200_OK
+ assert settings.SUBSCRIPTIONS_CLIENT_ID == 'new_client_id'
+ assert settings.SUBSCRIPTIONS_CLIENT_SECRET == 'new_client_secret'
+
+ def test_validate_rh_exception_handling(self, post, admin):
+ """Test that exceptions from validate_rh are properly handled"""
+ data = {'subscriptions_username': 'test_user', 'subscriptions_password': 'test_password'}
+
+ with patch('awx.api.views.root.get_licenser') as mock_get_licenser:
+ mock_licenser = MagicMock()
+ mock_licenser.validate_rh.side_effect = Exception("Connection error")
+ mock_get_licenser.return_value = mock_licenser
+
+ response = post(reverse('api:api_v2_subscription_view'), data, admin)
+
+ assert response.status_code == status.HTTP_400_BAD_REQUEST
+
+ def test_mixed_credentials_prioritizes_client_id(self, post, admin):
+ """Test that when both username and client_id are provided, client_id takes precedence"""
+ data = {
+ 'subscriptions_username': 'test_user',
+ 'subscriptions_password': 'test_password',
+ 'subscriptions_client_id': 'test_client_id',
+ 'subscriptions_client_secret': 'test_client_secret',
+ }
+
+ with patch('awx.api.views.root.get_licenser') as mock_get_licenser:
+ mock_licenser = MagicMock()
+ mock_licenser.validate_rh.return_value = []
+ mock_get_licenser.return_value = mock_licenser
+
+ response = post(reverse('api:api_v2_subscription_view'), data, admin)
+
+ assert response.status_code == status.HTTP_200_OK
+ # Should use service account (basic_auth=False) since client_id is present
+ mock_licenser.validate_rh.assert_called_once_with('test_client_id', 'test_client_secret', False)
+
+ def test_basic_auth_clears_service_account_settings(self, post, admin, settings):
+ """Test that setting basic auth credentials clears service account settings"""
+ # Pre-populate service account settings
+ settings.SUBSCRIPTIONS_CLIENT_ID = 'existing_client_id'
+ settings.SUBSCRIPTIONS_CLIENT_SECRET = 'existing_client_secret'
+
+ data = {'subscriptions_username': 'test_user', 'subscriptions_password': 'test_password'}
+
+ with patch('awx.api.views.root.get_licenser') as mock_get_licenser:
+ mock_licenser = MagicMock()
+ mock_licenser.validate_rh.return_value = []
+ mock_get_licenser.return_value = mock_licenser
+
+ response = post(reverse('api:api_v2_subscription_view'), data, admin)
+
+ assert response.status_code == status.HTTP_200_OK
+ # Basic auth settings should be set
+ assert settings.SUBSCRIPTIONS_USERNAME == 'test_user'
+ assert settings.SUBSCRIPTIONS_PASSWORD == 'test_password'
+ # Service account settings should be cleared
+ assert settings.SUBSCRIPTIONS_CLIENT_ID == ""
+ assert settings.SUBSCRIPTIONS_CLIENT_SECRET == ""
+
+ def test_service_account_clears_basic_auth_settings(self, post, admin, settings):
+ """Test that setting service account credentials clears basic auth settings"""
+ # Pre-populate basic auth settings
+ settings.SUBSCRIPTIONS_USERNAME = 'existing_username'
+ settings.SUBSCRIPTIONS_PASSWORD = 'existing_password'
+
+ data = {'subscriptions_client_id': 'test_client_id', 'subscriptions_client_secret': 'test_client_secret'}
+
+ with patch('awx.api.views.root.get_licenser') as mock_get_licenser:
+ mock_licenser = MagicMock()
+ mock_licenser.validate_rh.return_value = []
+ mock_get_licenser.return_value = mock_licenser
+
+ response = post(reverse('api:api_v2_subscription_view'), data, admin)
+
+ assert response.status_code == status.HTTP_200_OK
+ # Service account settings should be set
+ assert settings.SUBSCRIPTIONS_CLIENT_ID == 'test_client_id'
+ assert settings.SUBSCRIPTIONS_CLIENT_SECRET == 'test_client_secret'
+ # Basic auth settings should be cleared
+ assert settings.SUBSCRIPTIONS_USERNAME == ""
+ assert settings.SUBSCRIPTIONS_PASSWORD == ""
diff --git a/awx/main/tests/functional/api/test_notifications.py b/awx/main/tests/functional/api/test_notifications.py
index 92f604519109..431065396d5f 100644
--- a/awx/main/tests/functional/api/test_notifications.py
+++ b/awx/main/tests/functional/api/test_notifications.py
@@ -153,3 +153,13 @@ def test_post_org_approval_notification(get, post, admin, notification_template,
response = get(url, admin)
assert response.status_code == 200
assert len(response.data['results']) == 1
+
+
+@pytest.mark.django_db
+def test_post_wfj_notification(get, post, admin, workflow_job, notification):
+ workflow_job.notifications.add(notification)
+ workflow_job.save()
+ url = reverse("api:workflow_job_notifications_list", kwargs={'pk': workflow_job.pk})
+ response = get(url, admin)
+ assert response.status_code == 200
+ assert len(response.data['results']) == 1
diff --git a/awx/main/tests/functional/api/test_oauth.py b/awx/main/tests/functional/api/test_oauth.py
deleted file mode 100644
index 4387f06b9caa..000000000000
--- a/awx/main/tests/functional/api/test_oauth.py
+++ /dev/null
@@ -1,342 +0,0 @@
-import base64
-import json
-import time
-
-import pytest
-
-from django.db import connection
-from django.test.utils import override_settings
-from django.utils.encoding import smart_str, smart_bytes
-
-from awx.main.utils.encryption import decrypt_value, get_encryption_key
-from awx.api.versioning import reverse, drf_reverse
-from awx.main.models.oauth import OAuth2Application as Application, OAuth2AccessToken as AccessToken
-from awx.main.tests.functional import immediate_on_commit
-from awx.sso.models import UserEnterpriseAuth
-from oauth2_provider.models import RefreshToken
-
-
-@pytest.mark.django_db
-def test_personal_access_token_creation(oauth_application, post, alice):
- url = drf_reverse('api:oauth_authorization_root_view') + 'token/'
- resp = post(
- url,
- data='grant_type=password&username=alice&password=alice&scope=read',
- content_type='application/x-www-form-urlencoded',
- HTTP_AUTHORIZATION='Basic ' + smart_str(base64.b64encode(smart_bytes(':'.join([oauth_application.client_id, oauth_application.client_secret])))),
- )
- resp_json = smart_str(resp._container[0])
- assert 'access_token' in resp_json
- assert 'scope' in resp_json
- assert 'refresh_token' in resp_json
-
-
-@pytest.mark.django_db
-@pytest.mark.parametrize('allow_oauth, status', [(True, 201), (False, 403)])
-def test_token_creation_disabled_for_external_accounts(oauth_application, post, alice, allow_oauth, status):
- UserEnterpriseAuth(user=alice, provider='radius').save()
- url = drf_reverse('api:oauth_authorization_root_view') + 'token/'
-
- with override_settings(RADIUS_SERVER='example.org', ALLOW_OAUTH2_FOR_EXTERNAL_USERS=allow_oauth):
- resp = post(
- url,
- data='grant_type=password&username=alice&password=alice&scope=read',
- content_type='application/x-www-form-urlencoded',
- HTTP_AUTHORIZATION='Basic ' + smart_str(base64.b64encode(smart_bytes(':'.join([oauth_application.client_id, oauth_application.client_secret])))),
- status=status,
- )
- if allow_oauth:
- assert AccessToken.objects.count() == 1
- else:
- assert 'OAuth2 Tokens cannot be created by users associated with an external authentication provider' in smart_str(resp.content) # noqa
- assert AccessToken.objects.count() == 0
-
-
-@pytest.mark.django_db
-def test_existing_token_enabled_for_external_accounts(oauth_application, get, post, admin):
- UserEnterpriseAuth(user=admin, provider='radius').save()
- url = drf_reverse('api:oauth_authorization_root_view') + 'token/'
- with override_settings(RADIUS_SERVER='example.org', ALLOW_OAUTH2_FOR_EXTERNAL_USERS=True):
- resp = post(
- url,
- data='grant_type=password&username=admin&password=admin&scope=read',
- content_type='application/x-www-form-urlencoded',
- HTTP_AUTHORIZATION='Basic ' + smart_str(base64.b64encode(smart_bytes(':'.join([oauth_application.client_id, oauth_application.client_secret])))),
- status=201,
- )
- token = json.loads(resp.content)['access_token']
- assert AccessToken.objects.count() == 1
-
- with immediate_on_commit():
- resp = get(drf_reverse('api:user_me_list', kwargs={'version': 'v2'}), HTTP_AUTHORIZATION='Bearer ' + token, status=200)
- assert json.loads(resp.content)['results'][0]['username'] == 'admin'
-
- with override_settings(RADIUS_SERVER='example.org', ALLOW_OAUTH2_FOR_EXTERNAL_USER=False):
- with immediate_on_commit():
- resp = get(drf_reverse('api:user_me_list', kwargs={'version': 'v2'}), HTTP_AUTHORIZATION='Bearer ' + token, status=200)
- assert json.loads(resp.content)['results'][0]['username'] == 'admin'
-
-
-@pytest.mark.django_db
-def test_pat_creation_no_default_scope(oauth_application, post, admin):
- # tests that the default scope is overriden
- url = reverse('api:o_auth2_token_list')
- response = post(
- url,
- {
- 'description': 'test token',
- 'scope': 'read',
- 'application': oauth_application.pk,
- },
- admin,
- )
- assert response.data['scope'] == 'read'
-
-
-@pytest.mark.django_db
-def test_pat_creation_no_scope(oauth_application, post, admin):
- url = reverse('api:o_auth2_token_list')
- response = post(
- url,
- {
- 'description': 'test token',
- 'application': oauth_application.pk,
- },
- admin,
- )
- assert response.data['scope'] == 'write'
-
-
-@pytest.mark.django_db
-def test_oauth2_application_create(admin, organization, post):
- response = post(
- reverse('api:o_auth2_application_list'),
- {
- 'name': 'test app',
- 'organization': organization.pk,
- 'client_type': 'confidential',
- 'authorization_grant_type': 'password',
- },
- admin,
- expect=201,
- )
- assert 'modified' in response.data
- assert 'updated' not in response.data
- created_app = Application.objects.get(client_id=response.data['client_id'])
- assert created_app.name == 'test app'
- assert created_app.skip_authorization is False
- assert created_app.redirect_uris == ''
- assert created_app.client_type == 'confidential'
- assert created_app.authorization_grant_type == 'password'
- assert created_app.organization == organization
-
-
-@pytest.mark.django_db
-def test_oauth2_validator(admin, oauth_application, post):
- post(
- reverse('api:o_auth2_application_list'),
- {
- 'name': 'Write App Token',
- 'application': oauth_application.pk,
- 'scope': 'Write',
- },
- admin,
- expect=400,
- )
-
-
-@pytest.mark.django_db
-def test_oauth_application_update(oauth_application, organization, patch, admin, alice):
- patch(
- reverse('api:o_auth2_application_detail', kwargs={'pk': oauth_application.pk}),
- {
- 'name': 'Test app with immutable grant type and user',
- 'organization': organization.pk,
- 'redirect_uris': 'http://localhost/api/',
- 'authorization_grant_type': 'password',
- 'skip_authorization': True,
- },
- admin,
- expect=200,
- )
- updated_app = Application.objects.get(client_id=oauth_application.client_id)
- assert updated_app.name == 'Test app with immutable grant type and user'
- assert updated_app.redirect_uris == 'http://localhost/api/'
- assert updated_app.skip_authorization is True
- assert updated_app.authorization_grant_type == 'password'
- assert updated_app.organization == organization
-
-
-@pytest.mark.django_db
-def test_oauth_application_encryption(admin, organization, post):
- response = post(
- reverse('api:o_auth2_application_list'),
- {
- 'name': 'test app',
- 'organization': organization.pk,
- 'client_type': 'confidential',
- 'authorization_grant_type': 'password',
- },
- admin,
- expect=201,
- )
- pk = response.data.get('id')
- secret = response.data.get('client_secret')
- with connection.cursor() as cursor:
- encrypted = cursor.execute('SELECT client_secret FROM main_oauth2application WHERE id={}'.format(pk)).fetchone()[0]
- assert encrypted.startswith('$encrypted$')
- assert decrypt_value(get_encryption_key('value', pk=None), encrypted) == secret
-
-
-@pytest.mark.django_db
-def test_oauth_token_create(oauth_application, get, post, admin):
- response = post(reverse('api:o_auth2_application_token_list', kwargs={'pk': oauth_application.pk}), {'scope': 'read'}, admin, expect=201)
- assert 'modified' in response.data and response.data['modified'] is not None
- assert 'updated' not in response.data
- token = AccessToken.objects.get(token=response.data['token'])
- refresh_token = RefreshToken.objects.get(token=response.data['refresh_token'])
- assert token.application == oauth_application
- assert refresh_token.application == oauth_application
- assert token.user == admin
- assert refresh_token.user == admin
- assert refresh_token.access_token == token
- assert token.scope == 'read'
- response = get(reverse('api:o_auth2_application_token_list', kwargs={'pk': oauth_application.pk}), admin, expect=200)
- assert response.data['count'] == 1
- response = get(reverse('api:o_auth2_application_detail', kwargs={'pk': oauth_application.pk}), admin, expect=200)
- assert response.data['summary_fields']['tokens']['count'] == 1
- assert response.data['summary_fields']['tokens']['results'][0] == {'id': token.pk, 'scope': token.scope, 'token': '************'}
-
- response = post(reverse('api:o_auth2_token_list'), {'scope': 'read', 'application': oauth_application.pk}, admin, expect=201)
- assert response.data['refresh_token']
- response = post(
- reverse('api:user_authorized_token_list', kwargs={'pk': admin.pk}), {'scope': 'read', 'application': oauth_application.pk}, admin, expect=201
- )
- assert response.data['refresh_token']
- response = post(reverse('api:application_o_auth2_token_list', kwargs={'pk': oauth_application.pk}), {'scope': 'read'}, admin, expect=201)
- assert response.data['refresh_token']
-
-
-@pytest.mark.django_db
-def test_oauth_token_update(oauth_application, post, patch, admin):
- response = post(reverse('api:o_auth2_application_token_list', kwargs={'pk': oauth_application.pk}), {'scope': 'read'}, admin, expect=201)
- token = AccessToken.objects.get(token=response.data['token'])
- patch(reverse('api:o_auth2_token_detail', kwargs={'pk': token.pk}), {'scope': 'write'}, admin, expect=200)
- token = AccessToken.objects.get(token=token.token)
- assert token.scope == 'write'
-
-
-@pytest.mark.django_db
-def test_oauth_token_delete(oauth_application, post, delete, get, admin):
- response = post(reverse('api:o_auth2_application_token_list', kwargs={'pk': oauth_application.pk}), {'scope': 'read'}, admin, expect=201)
- token = AccessToken.objects.get(token=response.data['token'])
- delete(reverse('api:o_auth2_token_detail', kwargs={'pk': token.pk}), admin, expect=204)
- assert AccessToken.objects.count() == 0
- assert RefreshToken.objects.count() == 1
- response = get(reverse('api:o_auth2_application_token_list', kwargs={'pk': oauth_application.pk}), admin, expect=200)
- assert response.data['count'] == 0
- response = get(reverse('api:o_auth2_application_detail', kwargs={'pk': oauth_application.pk}), admin, expect=200)
- assert response.data['summary_fields']['tokens']['count'] == 0
-
-
-@pytest.mark.django_db
-def test_oauth_application_delete(oauth_application, post, delete, admin):
- post(reverse('api:o_auth2_application_token_list', kwargs={'pk': oauth_application.pk}), {'scope': 'read'}, admin, expect=201)
- delete(reverse('api:o_auth2_application_detail', kwargs={'pk': oauth_application.pk}), admin, expect=204)
- assert Application.objects.filter(client_id=oauth_application.client_id).count() == 0
- assert RefreshToken.objects.filter(application=oauth_application).count() == 0
- assert AccessToken.objects.filter(application=oauth_application).count() == 0
-
-
-@pytest.mark.django_db
-def test_oauth_list_user_tokens(oauth_application, post, get, admin, alice):
- for user in (admin, alice):
- url = reverse('api:o_auth2_token_list', kwargs={'pk': user.pk})
- post(url, {'scope': 'read'}, user, expect=201)
- response = get(url, admin, expect=200)
- assert response.data['count'] == 1
-
-
-@pytest.mark.django_db
-def test_refresh_accesstoken(oauth_application, post, get, delete, admin):
- response = post(reverse('api:o_auth2_application_token_list', kwargs={'pk': oauth_application.pk}), {'scope': 'read'}, admin, expect=201)
- assert AccessToken.objects.count() == 1
- assert RefreshToken.objects.count() == 1
- token = AccessToken.objects.get(token=response.data['token'])
- refresh_token = RefreshToken.objects.get(token=response.data['refresh_token'])
-
- refresh_url = drf_reverse('api:oauth_authorization_root_view') + 'token/'
- response = post(
- refresh_url,
- data='grant_type=refresh_token&refresh_token=' + refresh_token.token,
- content_type='application/x-www-form-urlencoded',
- HTTP_AUTHORIZATION='Basic ' + smart_str(base64.b64encode(smart_bytes(':'.join([oauth_application.client_id, oauth_application.client_secret])))),
- )
- assert RefreshToken.objects.filter(token=refresh_token).exists()
- original_refresh_token = RefreshToken.objects.get(token=refresh_token)
- assert token not in AccessToken.objects.all()
- assert AccessToken.objects.count() == 1
- # the same RefreshToken remains but is marked revoked
- assert RefreshToken.objects.count() == 2
- new_token = json.loads(response._container[0])['access_token']
- new_refresh_token = json.loads(response._container[0])['refresh_token']
- assert AccessToken.objects.filter(token=new_token).count() == 1
- # checks that RefreshTokens are rotated (new RefreshToken issued)
- assert RefreshToken.objects.filter(token=new_refresh_token).count() == 1
- assert original_refresh_token.revoked # is not None
-
-
-@pytest.mark.django_db
-def test_refresh_token_expiration_is_respected(oauth_application, post, get, delete, admin):
- response = post(reverse('api:o_auth2_application_token_list', kwargs={'pk': oauth_application.pk}), {'scope': 'read'}, admin, expect=201)
- assert AccessToken.objects.count() == 1
- assert RefreshToken.objects.count() == 1
- refresh_token = RefreshToken.objects.get(token=response.data['refresh_token'])
- refresh_url = drf_reverse('api:oauth_authorization_root_view') + 'token/'
- short_lived = {'ACCESS_TOKEN_EXPIRE_SECONDS': 1, 'AUTHORIZATION_CODE_EXPIRE_SECONDS': 1, 'REFRESH_TOKEN_EXPIRE_SECONDS': 1}
- time.sleep(1)
- with override_settings(OAUTH2_PROVIDER=short_lived):
- response = post(
- refresh_url,
- data='grant_type=refresh_token&refresh_token=' + refresh_token.token,
- content_type='application/x-www-form-urlencoded',
- HTTP_AUTHORIZATION='Basic ' + smart_str(base64.b64encode(smart_bytes(':'.join([oauth_application.client_id, oauth_application.client_secret])))),
- )
- assert response.status_code == 403
- assert b'The refresh token has expired.' in response.content
- assert RefreshToken.objects.filter(token=refresh_token).exists()
- assert AccessToken.objects.count() == 1
- assert RefreshToken.objects.count() == 1
-
-
-@pytest.mark.django_db
-def test_revoke_access_then_refreshtoken(oauth_application, post, get, delete, admin):
- response = post(reverse('api:o_auth2_application_token_list', kwargs={'pk': oauth_application.pk}), {'scope': 'read'}, admin, expect=201)
- token = AccessToken.objects.get(token=response.data['token'])
- refresh_token = RefreshToken.objects.get(token=response.data['refresh_token'])
- assert AccessToken.objects.count() == 1
- assert RefreshToken.objects.count() == 1
-
- token.revoke()
- assert AccessToken.objects.count() == 0
- assert RefreshToken.objects.count() == 1
- assert not refresh_token.revoked
-
- refresh_token.revoke()
- assert AccessToken.objects.count() == 0
- assert RefreshToken.objects.count() == 1
-
-
-@pytest.mark.django_db
-def test_revoke_refreshtoken(oauth_application, post, get, delete, admin):
- response = post(reverse('api:o_auth2_application_token_list', kwargs={'pk': oauth_application.pk}), {'scope': 'read'}, admin, expect=201)
- refresh_token = RefreshToken.objects.get(token=response.data['refresh_token'])
- assert AccessToken.objects.count() == 1
- assert RefreshToken.objects.count() == 1
-
- refresh_token.revoke()
- assert AccessToken.objects.count() == 0
- # the same RefreshToken is recycled
- new_refresh_token = RefreshToken.objects.all().first()
- assert refresh_token == new_refresh_token
- assert new_refresh_token.revoked
diff --git a/awx/main/tests/functional/api/test_organizations.py b/awx/main/tests/functional/api/test_organizations.py
index f86963ecc64b..af2f918ba0c5 100644
--- a/awx/main/tests/functional/api/test_organizations.py
+++ b/awx/main/tests/functional/api/test_organizations.py
@@ -329,3 +329,21 @@ def test_galaxy_credential_association(alice, admin, organization, post, get):
'Public Galaxy 4',
'Public Galaxy 5',
]
+
+
+@pytest.mark.django_db
+def test_org_admin_credential_count(org_admin, admin, organization, post, get):
+ galaxy = CredentialType.defaults['galaxy_api_token']()
+ galaxy.save()
+
+ for i in range(3):
+ cred = Credential.objects.create(credential_type=galaxy, name=f'test_{i}', inputs={'url': 'https://galaxy.ansible.com/'})
+ url = reverse('api:organization_galaxy_credentials_list', kwargs={'pk': organization.pk})
+ post(url, {'associate': True, 'id': cred.pk}, user=admin, expect=204)
+ # org admin should see all associated galaxy credentials
+ resp = get(url, user=org_admin)
+ assert resp.data['count'] == 3
+ # removing one to validate new count
+ post(url, {'disassociate': True, 'id': Credential.objects.get(name='test_1').pk}, user=admin, expect=204)
+ resp_new = get(url, user=org_admin)
+ assert resp_new.data['count'] == 2
diff --git a/awx/main/tests/functional/api/test_rbac_displays.py b/awx/main/tests/functional/api/test_rbac_displays.py
index 8178da672c05..22d9ec9990a9 100644
--- a/awx/main/tests/functional/api/test_rbac_displays.py
+++ b/awx/main/tests/functional/api/test_rbac_displays.py
@@ -165,8 +165,8 @@ def _assert_one_in_list(self, data, sublist='direct_access'):
def test_access_list_direct_access_capability(self, inventory, rando, get, mocker, mock_access_method):
inventory.admin_role.members.add(rando)
- with mocker.patch.object(access_registry[Role], 'can_unattach', mock_access_method):
- response = get(reverse('api:inventory_access_list', kwargs={'pk': inventory.id}), rando)
+ mocker.patch.object(access_registry[Role], 'can_unattach', mock_access_method)
+ response = get(reverse('api:inventory_access_list', kwargs={'pk': inventory.id}), rando)
mock_access_method.assert_called_once_with(inventory.admin_role, rando, 'members', **self.extra_kwargs)
self._assert_one_in_list(response.data)
@@ -174,8 +174,8 @@ def test_access_list_direct_access_capability(self, inventory, rando, get, mocke
assert direct_access_list[0]['role']['user_capabilities']['unattach'] == 'foobar'
def test_access_list_indirect_access_capability(self, inventory, organization, org_admin, get, mocker, mock_access_method):
- with mocker.patch.object(access_registry[Role], 'can_unattach', mock_access_method):
- response = get(reverse('api:inventory_access_list', kwargs={'pk': inventory.id}), org_admin)
+ mocker.patch.object(access_registry[Role], 'can_unattach', mock_access_method)
+ response = get(reverse('api:inventory_access_list', kwargs={'pk': inventory.id}), org_admin)
mock_access_method.assert_called_once_with(organization.admin_role, org_admin, 'members', **self.extra_kwargs)
self._assert_one_in_list(response.data, sublist='indirect_access')
@@ -185,8 +185,8 @@ def test_access_list_indirect_access_capability(self, inventory, organization, o
def test_access_list_team_direct_access_capability(self, inventory, team, team_member, get, mocker, mock_access_method):
team.member_role.children.add(inventory.admin_role)
- with mocker.patch.object(access_registry[Role], 'can_unattach', mock_access_method):
- response = get(reverse('api:inventory_access_list', kwargs={'pk': inventory.id}), team_member)
+ mocker.patch.object(access_registry[Role], 'can_unattach', mock_access_method)
+ response = get(reverse('api:inventory_access_list', kwargs={'pk': inventory.id}), team_member)
mock_access_method.assert_called_once_with(inventory.admin_role, team.member_role, 'parents', **self.extra_kwargs)
self._assert_one_in_list(response.data)
@@ -198,8 +198,8 @@ def test_access_list_team_direct_access_capability(self, inventory, team, team_m
def test_team_roles_unattach(mocker, team, team_member, inventory, mock_access_method, get):
team.member_role.children.add(inventory.admin_role)
- with mocker.patch.object(access_registry[Role], 'can_unattach', mock_access_method):
- response = get(reverse('api:team_roles_list', kwargs={'pk': team.id}), team_member)
+ mocker.patch.object(access_registry[Role], 'can_unattach', mock_access_method)
+ response = get(reverse('api:team_roles_list', kwargs={'pk': team.id}), team_member)
# Did we assess whether team_member can remove team's permission to the inventory?
mock_access_method.assert_called_once_with(inventory.admin_role, team.member_role, 'parents', skip_sub_obj_read_check=True, data={})
@@ -212,8 +212,8 @@ def test_user_roles_unattach(mocker, organization, alice, bob, mock_access_metho
organization.member_role.members.add(alice)
organization.member_role.members.add(bob)
- with mocker.patch.object(access_registry[Role], 'can_unattach', mock_access_method):
- response = get(reverse('api:user_roles_list', kwargs={'pk': alice.id}), bob)
+ mocker.patch.object(access_registry[Role], 'can_unattach', mock_access_method)
+ response = get(reverse('api:user_roles_list', kwargs={'pk': alice.id}), bob)
# Did we assess whether bob can remove alice's permission to the inventory?
mock_access_method.assert_called_once_with(organization.member_role, alice, 'members', skip_sub_obj_read_check=True, data={})
diff --git a/awx/main/tests/functional/api/test_role.py b/awx/main/tests/functional/api/test_role.py
index cec31d9d7ede..68ce8855feab 100644
--- a/awx/main/tests/functional/api/test_role.py
+++ b/awx/main/tests/functional/api/test_role.py
@@ -3,17 +3,6 @@
from awx.api.versioning import reverse
-@pytest.mark.django_db
-def test_admin_visible_to_orphaned_users(get, alice):
- names = set()
-
- response = get(reverse('api:role_list'), user=alice)
- for item in response.data['results']:
- names.add(item['name'])
- assert 'System Auditor' in names
- assert 'System Administrator' in names
-
-
@pytest.mark.django_db
@pytest.mark.parametrize('role,code', [('member_role', 400), ('admin_role', 400), ('inventory_admin_role', 204)])
@pytest.mark.parametrize('reversed', [True, False])
diff --git a/awx/main/tests/functional/api/test_schedules.py b/awx/main/tests/functional/api/test_schedules.py
index c1cd0779c35f..cb49a53d22a5 100644
--- a/awx/main/tests/functional/api/test_schedules.py
+++ b/awx/main/tests/functional/api/test_schedules.py
@@ -8,7 +8,6 @@
from awx.main.models import JobTemplate, Schedule
from awx.main.utils.encryption import decrypt_value, get_encryption_key
-
RRULE_EXAMPLE = 'DTSTART:20151117T050000Z RRULE:FREQ=DAILY;INTERVAL=1;COUNT=1'
diff --git a/awx/main/tests/functional/api/test_serializers.py b/awx/main/tests/functional/api/test_serializers.py
new file mode 100644
index 000000000000..ab31e186e9c4
--- /dev/null
+++ b/awx/main/tests/functional/api/test_serializers.py
@@ -0,0 +1,75 @@
+import pytest
+
+from django.test.utils import override_settings
+
+from rest_framework.serializers import ValidationError
+
+from awx.api.serializers import UserSerializer
+from django.contrib.auth.models import User
+
+
+@pytest.mark.parametrize(
+ "password,min_length,min_digits,min_upper,min_special,expect_error",
+ [
+ # Test length
+ ("a", 1, 0, 0, 0, False),
+ ("a", 2, 0, 0, 0, True),
+ ("aa", 2, 0, 0, 0, False),
+ ("aaabcDEF123$%^", 2, 0, 0, 0, False),
+ # Test digits
+ ("a", 0, 1, 0, 0, True),
+ ("1", 0, 1, 0, 0, False),
+ ("1", 0, 2, 0, 0, True),
+ ("12", 0, 2, 0, 0, False),
+ ("12abcDEF123$%^", 0, 2, 0, 0, False),
+ # Test upper
+ ("a", 0, 0, 1, 0, True),
+ ("A", 0, 0, 1, 0, False),
+ ("A", 0, 0, 2, 0, True),
+ ("AB", 0, 0, 2, 0, False),
+ ("ABabcDEF123$%^", 0, 0, 2, 0, False),
+ # Test special
+ ("a", 0, 0, 0, 1, True),
+ ("!", 0, 0, 0, 1, False),
+ ("!", 0, 0, 0, 2, True),
+ ("!@", 0, 0, 0, 2, False),
+ ("!@abcDEF123$%^", 0, 0, 0, 2, False),
+ ],
+)
+@pytest.mark.django_db
+def test_validate_password_rules(password, min_length, min_digits, min_upper, min_special, expect_error):
+ user_serializer = UserSerializer()
+
+ # First test password with no params, this should always pass
+ try:
+ user_serializer.validate_password(password)
+ except ValidationError:
+ assert False, f"Password {password} should not have validation issue if no params are used"
+
+ with override_settings(
+ LOCAL_PASSWORD_MIN_LENGTH=min_length, LOCAL_PASSWORD_MIN_DIGITS=min_digits, LOCAL_PASSWORD_MIN_UPPER=min_upper, LOCAL_PASSWORD_MIN_SPECIAL=min_special
+ ):
+ if expect_error:
+ with pytest.raises(ValidationError):
+ user_serializer.validate_password(password)
+ else:
+ try:
+ user_serializer.validate_password(password)
+ except ValidationError:
+ assert False, "validate_password raised an unexpected exception"
+
+
+@pytest.mark.django_db
+def test_validate_password_too_long():
+ password_max_length = User._meta.get_field('password').max_length
+ password = "x" * password_max_length
+
+ user_serializer = UserSerializer()
+ try:
+ user_serializer.validate_password(password)
+ except ValidationError:
+ assert False, f"Password {password} should not have validation"
+
+ password = f"{password}x"
+ with pytest.raises(ValidationError):
+ user_serializer.validate_password(password)
diff --git a/awx/main/tests/functional/api/test_settings.py b/awx/main/tests/functional/api/test_settings.py
index a1ae7398a5bf..e096637dc28c 100644
--- a/awx/main/tests/functional/api/test_settings.py
+++ b/awx/main/tests/functional/api/test_settings.py
@@ -7,10 +7,8 @@
# AWX
from awx.api.versioning import reverse
-from awx.conf.models import Setting
from awx.conf.registry import settings_registry
-
TEST_GIF_LOGO = 'data:image/gif;base64,R0lGODlhIQAjAPIAAP//////AP8AAMzMAJmZADNmAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJCgAHACwAAAAAIQAjAAADo3i63P4wykmrvTjrzZsxXfR94WMQBFh6RECuixHMLyzPQ13ewZCvow9OpzEAjIBj79cJJmU+FceIVEZ3QRozxBttmyOBwPBtisdX4Bha3oxmS+llFIPHQXQKkiSEXz9PeklHBzx3hYNyEHt4fmmAhHp8Nz45KgV5FgWFOFEGmwWbGqEfniChohmoQZ+oqRiZDZhEgk81I4mwg4EKVbxzrDHBEAkAIfkECQoABwAsAAAAACEAIwAAA6V4utz+MMpJq724GpP15p1kEAQYQmOwnWjgrmxjuMEAx8rsDjZ+fJvdLWQAFAHGWo8FRM54JqIRmYTigDrDMqZTbbbMj0CgjTLHZKvPQH6CTx+a2vKR0XbbOsoZ7SphG057gjl+c0dGgzeGNiaBiSgbBQUHBV08NpOVlkMSk0FKjZuURHiiOJxQnSGfQJuoEKREejK0dFRGjoiQt7iOuLx0rgxYEQkAIfkECQoABwAsAAAAACEAIwAAA7h4utxnxslJDSGR6nrz/owxYB64QUEwlGaVqlB7vrAJscsd3Lhy+wBArGEICo3DUFH4QDqK0GMy51xOgcGlEAfJ+iAFie62chR+jYKaSAuQGOqwJp7jGQRDuol+F/jxZWsyCmoQfwYwgoM5Oyg1i2w0A2WQIW2TPYOIkleQmy+UlYygoaIPnJmapKmqKiusMmSdpjxypnALtrcHioq3ury7hGm3dnVosVpMWFmwREZbddDOSsjVswcJACH5BAkKAAcALAAAAAAhACMAAAOxeLrc/jDKSZUxNS9DCNYV54HURQwfGRlDEFwqdLVuGjOsW9/Odb0wnsUAKBKNwsMFQGwyNUHckVl8bqI4o43lA26PNkv1S9DtNuOeVirw+aTI3qWAQwnud1vhLSnQLS0GeFF+GoVKNF0fh4Z+LDQ6Bn5/MTNmL0mAl2E3j2aclTmRmYCQoKEDiaRDKFhJez6UmbKyQowHtzy1uEl8DLCnEktrQ2PBD1NxSlXKIW5hz6cJACH5BAkKAAcALAAAAAAhACMAAAOkeLrc/jDKSau9OOvNlTFd9H3hYxAEWDJfkK5LGwTq+g0zDR/GgM+10A04Cm56OANgqTRmkDTmSOiLMgFOTM9AnFJHuexzYBAIijZf2SweJ8ttbbXLmd5+wBiJosSCoGF/fXEeS1g8gHl9hxODKkh4gkwVIwUekESIhA4FlgV3PyCWG52WI2oGnR2lnUWpqhqVEF4Xi7QjhpsshpOFvLosrnpoEAkAIfkECQoABwAsAAAAACEAIwAAA6l4utz+MMpJq71YGpPr3t1kEAQXQltQnk8aBCa7bMMLy4wx1G8s072PL6SrGQDI4zBThCU/v50zCVhidIYgNPqxWZkDg0AgxB2K4vEXbBSvr1JtZ3uOext0x7FqovF6OXtfe1UzdjAxhINPM013ChtJER8FBQeVRX8GlpggFZWWfjwblTiigGZnfqRmpUKbljKxDrNMeY2eF4R8jUiSur6/Z8GFV2WBtwwJACH5BAkKAAcALAAAAAAhACMAAAO6eLrcZi3KyQwhkGpq8f6ONWQgaAxB8JTfg6YkO50pzD5xhaurhCsGAKCnEw6NucNDCAkyI8ugdAhFKpnJJdMaeiofBejowUseCr9GYa0j1GyMdVgjBxoEuPSZXWKf7gKBeHtzMms0gHgGfDIVLztmjScvNZEyk28qjT40b5aXlHCbDgOhnzedoqOOlKeopaqrCy56sgtotbYKhYW6e7e9tsHBssO6eSTIm1peV0iuFUZDyU7NJnmcuQsJACH5BAkKAAcALAAAAAAhACMAAAOteLrc/jDKSZsxNS9DCNYV54Hh4H0kdAXBgKaOwbYX/Miza1vrVe8KA2AoJL5gwiQgeZz4GMXlcHl8xozQ3kW3KTajL9zsBJ1+sV2fQfALem+XAlRApxu4ioI1UpC76zJ4fRqDBzI+LFyFhH1iiS59fkgziW07jjRAG5QDeECOLk2Tj6KjnZafW6hAej6Smgevr6yysza2tiCuMasUF2Yov2gZUUQbU8YaaqjLpQkAOw==' # NOQA
TEST_PNG_LOGO = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAjCAYAAAAaLGNkAAAAAXNSR0IB2cksfwAAAdVpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8dGlmZjpDb21wcmVzc2lvbj4xPC90aWZmOkNvbXByZXNzaW9uPgogICAgICAgICA8dGlmZjpQaG90b21ldHJpY0ludGVycHJldGF0aW9uPjI8L3RpZmY6UGhvdG9tZXRyaWNJbnRlcnByZXRhdGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+Cjl0tmoAAAHVSURBVFgJ7VZRsoMgDNTOu5E9U+/Ud6Z6JssGNg2oNKD90xkHCNnNkgTbYbieKwNXBn6bgSXQ4+16xi5UDiqDN3Pecr6+1fM5DHh7n1NEIPjjoRLKzOjG3qQ5dRtEy2LCjh/Gz2wDZE2nZYKkrxdn/kY9XQQkGCGqqDY5IgJFkEKgBCzDNGXhTKEye7boFRH6IPJj5EshiNCSjV4R4eSx7zhmR2tcdIuwmWiMeao7e0JHViZEWUI5aP8a9O+rx74D6sGEiJftiX3YeueIiFXg2KrhpqzjVC3dPZFYJZ7NOwwtNwM8R0UkLfH0sT5qck+OlkMq0BucKr0iWG7gpAQksD9esM1z3Lnf6SHjLh67nnKEGxC/iomWhByTeXOQJGHHcKxwHhHKnt1HIdYtmexkIb/HOURWTSJqn2gKMDG0bDUc/D0iAseovxUBoylmQCug6IVhSv+4DIeKI94jAr4AjiSEgQ25JYB+YWT9BZ94AM8erwgFkRifaArA6U0G5KT0m//z26REZuK9okgrT6VwE1jTHjbVzyNAyRwTEPOtuiex9FVBNZCkruaA4PZqFp1u8Rpww9/6rcK5y0EkAxRiZJt79PWOVYWGRE9pbJhavMengMflGyumk0akMsQnAAAAAElFTkSuQmCC' # NOQA
TEST_JPEG_LOGO = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBkRXhpZgAATU0AKgAAAAgAAwEGAAMAAAABAAIAAAESAAMAAAABAAEAAIdpAAQAAAABAAAAMgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAIaADAAQAAAABAAAAIwAAAAD/4QkhaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA1LjQuMCI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiLz4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8P3hwYWNrZXQgZW5kPSJ3Ij8+AP/tADhQaG90b3Nob3AgMy4wADhCSU0EBAAAAAAAADhCSU0EJQAAAAAAENQdjNmPALIE6YAJmOz4Qn7/wAARCAAjACEDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9sAQwAGBgYGBgYKBgYKDgoKCg4SDg4ODhIXEhISEhIXHBcXFxcXFxwcHBwcHBwcIiIiIiIiJycnJycsLCwsLCwsLCws/9sAQwEHBwcLCgsTCgoTLh8aHy4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4u/90ABAAD/9oADAMBAAIRAxEAPwD6poormvFfivSvB2lHVtWLGMtsRE2hnYKzlVLsi52oxALDdjauWKqQCXQfFXh7xP8Aaf7AvYrz7HL5U3lk/K3YjIGVODtcZVsHBODXQV806bcT+E9L03XbCOS2udMsLQanbB4po72xYMfOQpKYyV2zPEwcNwVK7WAr6WriwWMWIUvdcZRdmnuu33rVFSjYKKKK7ST/0PqmuF8Vv4X8S+HNZ0+e/gIsYJvtEsL+bJZsI3UuyxNvBA3gpxvXchyCRXdV8ta3bW667DoloW1y10tLLTJxZWP2hoLSGYzNHclGZpJC0ESk8IAZcRB8is61T2cHK1/1DrY526h8YXHh691vxCz6dafY5Q0U7yGSeQxSxohNzJLcbUeQ4VnVNxBRCWL19b2eraVqE9xa2F3BcS2jbJ0ikV2ibJG1wpJU5UjBx0PpXzrrniy4k17TrrWrGex022ufMijvd9m11PGH8naXKqsUcgR3MhB5U7MA16x4L8F3vhq2sY9Ru4rg6day2tusEAhCrcOkknmEMRI2Y1AcLGT8xYMzZHjZFGu6cquKjaUnt2XS76vv/SN8RVjOdoKyXY9Cooor3TA//9H6pr4gfxRrMvxJ0/whLJE+maVrcVnZRtBCzwQQ3SIipMU80fKignflgPmJr7fr4A/5rf8A9zJ/7eUAdX8SfGviPwl8TtaPh6eK1eTyN0n2eCSUg28OV8ySNn2/KDtztzzjNfZVhY2umWMGm2KeXb2sSQxJknakYCqMkknAHUnNfBXxt/5Kdq//AG7/APpPFX3/AEAFFFFAH//Z' # NOQA
@@ -67,129 +65,6 @@ def test_awx_task_env_validity(get, patch, admin, value, expected):
assert resp.data['AWX_TASK_ENV'] == dict()
-@pytest.mark.django_db
-def test_ldap_settings(get, put, patch, delete, admin):
- url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'ldap'})
- get(url, user=admin, expect=200)
- # The PUT below will fail at the moment because AUTH_LDAP_GROUP_TYPE
- # defaults to None but cannot be set to None.
- # put(url, user=admin, data=response.data, expect=200)
- delete(url, user=admin, expect=204)
- patch(url, user=admin, data={'AUTH_LDAP_SERVER_URI': ''}, expect=200)
- patch(url, user=admin, data={'AUTH_LDAP_SERVER_URI': 'ldap.example.com'}, expect=400)
- patch(url, user=admin, data={'AUTH_LDAP_SERVER_URI': 'ldap://ldap.example.com'}, expect=200)
- patch(url, user=admin, data={'AUTH_LDAP_SERVER_URI': 'ldaps://ldap.example.com'}, expect=200)
- patch(url, user=admin, data={'AUTH_LDAP_SERVER_URI': 'ldap://ldap.example.com:389'}, expect=200)
- patch(url, user=admin, data={'AUTH_LDAP_SERVER_URI': 'ldaps://ldap.example.com:636'}, expect=200)
- patch(url, user=admin, data={'AUTH_LDAP_SERVER_URI': 'ldap://ldap.example.com ldap://ldap2.example.com'}, expect=200)
- patch(url, user=admin, data={'AUTH_LDAP_SERVER_URI': 'ldap://ldap.example.com,ldap://ldap2.example.com'}, expect=200)
- patch(url, user=admin, data={'AUTH_LDAP_SERVER_URI': 'ldap://ldap.example.com, ldap://ldap2.example.com'}, expect=200)
- patch(url, user=admin, data={'AUTH_LDAP_BIND_DN': 'cn=Manager,dc=example,dc=com'}, expect=200)
- patch(url, user=admin, data={'AUTH_LDAP_BIND_DN': u'cn=暴力膜,dc=大新闻,dc=真的粉丝'}, expect=200)
-
-
-@pytest.mark.django_db
-@pytest.mark.parametrize(
- 'value',
- [
- None,
- '',
- 'INVALID',
- 1,
- [1],
- ['INVALID'],
- ],
-)
-def test_ldap_user_flags_by_group_invalid_dn(get, patch, admin, value):
- url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'ldap'})
- patch(url, user=admin, data={'AUTH_LDAP_USER_FLAGS_BY_GROUP': {'is_superuser': value}}, expect=400)
-
-
-@pytest.mark.django_db
-def test_ldap_user_flags_by_group_string(get, patch, admin):
- expected = 'CN=Admins,OU=Groups,DC=example,DC=com'
- url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'ldap'})
- patch(url, user=admin, data={'AUTH_LDAP_USER_FLAGS_BY_GROUP': {'is_superuser': expected}}, expect=200)
- resp = get(url, user=admin)
- assert resp.data['AUTH_LDAP_USER_FLAGS_BY_GROUP']['is_superuser'] == [expected]
-
-
-@pytest.mark.django_db
-def test_ldap_user_flags_by_group_list(get, patch, admin):
- expected = ['CN=Admins,OU=Groups,DC=example,DC=com', 'CN=Superadmins,OU=Groups,DC=example,DC=com']
- url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'ldap'})
- patch(url, user=admin, data={'AUTH_LDAP_USER_FLAGS_BY_GROUP': {'is_superuser': expected}}, expect=200)
- resp = get(url, user=admin)
- assert resp.data['AUTH_LDAP_USER_FLAGS_BY_GROUP']['is_superuser'] == expected
-
-
-@pytest.mark.parametrize(
- 'setting',
- [
- 'AUTH_LDAP_USER_DN_TEMPLATE',
- 'AUTH_LDAP_REQUIRE_GROUP',
- 'AUTH_LDAP_DENY_GROUP',
- ],
-)
-@pytest.mark.django_db
-def test_empty_ldap_dn(get, put, patch, delete, admin, setting):
- url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'ldap'})
- patch(url, user=admin, data={setting: ''}, expect=200)
- resp = get(url, user=admin, expect=200)
- assert resp.data[setting] is None
-
- patch(url, user=admin, data={setting: None}, expect=200)
- resp = get(url, user=admin, expect=200)
- assert resp.data[setting] is None
-
-
-@pytest.mark.django_db
-def test_radius_settings(get, put, patch, delete, admin, settings):
- url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'radius'})
- response = get(url, user=admin, expect=200)
- put(url, user=admin, data=response.data, expect=200)
- # Set secret via the API.
- patch(url, user=admin, data={'RADIUS_SECRET': 'mysecret'}, expect=200)
- response = get(url, user=admin, expect=200)
- assert response.data['RADIUS_SECRET'] == '$encrypted$'
- assert Setting.objects.filter(key='RADIUS_SECRET').first().value.startswith('$encrypted$')
- assert settings.RADIUS_SECRET == 'mysecret'
- # Set secret via settings wrapper.
- settings_wrapper = settings._awx_conf_settings
- settings_wrapper.RADIUS_SECRET = 'mysecret2'
- response = get(url, user=admin, expect=200)
- assert response.data['RADIUS_SECRET'] == '$encrypted$'
- assert Setting.objects.filter(key='RADIUS_SECRET').first().value.startswith('$encrypted$')
- assert settings.RADIUS_SECRET == 'mysecret2'
- # If we send back $encrypted$, the setting is not updated.
- patch(url, user=admin, data={'RADIUS_SECRET': '$encrypted$'}, expect=200)
- response = get(url, user=admin, expect=200)
- assert response.data['RADIUS_SECRET'] == '$encrypted$'
- assert Setting.objects.filter(key='RADIUS_SECRET').first().value.startswith('$encrypted$')
- assert settings.RADIUS_SECRET == 'mysecret2'
- # If we send an empty string, the setting is also set to an empty string.
- patch(url, user=admin, data={'RADIUS_SECRET': ''}, expect=200)
- response = get(url, user=admin, expect=200)
- assert response.data['RADIUS_SECRET'] == ''
- assert Setting.objects.filter(key='RADIUS_SECRET').first().value == ''
- assert settings.RADIUS_SECRET == ''
-
-
-@pytest.mark.django_db
-def test_tacacsplus_settings(get, put, patch, admin):
- url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'tacacsplus'})
- response = get(url, user=admin, expect=200)
- put(url, user=admin, data=response.data, expect=200)
- patch(url, user=admin, data={'TACACSPLUS_SECRET': 'mysecret'}, expect=200)
- patch(url, user=admin, data={'TACACSPLUS_SECRET': ''}, expect=200)
- patch(url, user=admin, data={'TACACSPLUS_HOST': 'localhost'}, expect=400)
- patch(url, user=admin, data={'TACACSPLUS_SECRET': 'mysecret'}, expect=200)
- patch(url, user=admin, data={'TACACSPLUS_HOST': 'localhost'}, expect=200)
- patch(url, user=admin, data={'TACACSPLUS_HOST': '', 'TACACSPLUS_SECRET': ''}, expect=200)
- patch(url, user=admin, data={'TACACSPLUS_HOST': 'localhost', 'TACACSPLUS_SECRET': ''}, expect=400)
- patch(url, user=admin, data={'TACACSPLUS_HOST': 'localhost', 'TACACSPLUS_SECRET': 'mysecret'}, expect=200)
-
-
@pytest.mark.django_db
def test_ui_settings(get, put, patch, delete, admin):
url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'ui'})
@@ -318,76 +193,3 @@ def test_logging_aggregator_connection_test_valid(put, post, admin):
# "Test" the logger
url = reverse('api:setting_logging_test')
post(url, {}, user=admin, expect=202)
-
-
-@pytest.mark.django_db
-@pytest.mark.parametrize('headers', [True, False])
-def test_saml_x509cert_validation(patch, get, admin, headers):
- cert = "MIIEogIBAAKCAQEA1T4za6qBbHxFpN5f9eFvA74MFjrsjcp1uvzOaE23AYKMDEJghJ6dqQ7GwHLNIeIeumqDFmODauIzrgSDJTT5+NG30Rr+rRi0zDkrkBAj/AtA+SaVhbzqB6ZSd7LaMly9XAc+82OKlNpuWS9hPmFaSShzDTXRu5RRyvm4NDCAOGDu5hyVR2pV/ffKDNfNkChnqzvRRW9laQcVmliZhlTGn7nPZ+JbjpwEy0nwW+4zoAiEvwnT52N4xTqIcYOnXtGiaf13dh7FkUfYmS0tzF3+h8QRKwtIm4y+sq84R/kr79/0t5aRUpJynNrECajzmArpL4IjXKTPIyUpTKirJgGnCwIDAQABAoIBAC6bbbm2hpsjfkVOpUKkhxMWUqX5MwK6oYjBAIwjkEAwPFPhnh7eXC87H42oidVCCt1LsmMOVQbjcdAzBEb5kTkk/Twi3k8O+1U3maHfJT5NZ2INYNjeNXh+jb/Dw5UGWAzpOIUR2JQ4Oa4cgPCVbppW0O6uOKz6+fWXJv+hKiUoBCC0TiY52iseHJdUOaKNxYRD2IyIzCAxFSd5tZRaARIYDsugXp3E/TdbsVWA7bmjIBOXq+SquTrlB8x7j3B7+Pi09nAJ2U/uV4PHE+/2Fl009ywfmqancvnhwnz+GQ5jjP+gTfghJfbO+Z6M346rS0Vw+osrPgfyudNHlCswHOECgYEA/Cfq25gDP07wo6+wYWbx6LIzj/SSZy/Ux9P8zghQfoZiPoaq7BQBPAzwLNt7JWST8U11LZA8/wo6ch+HSTMk+m5ieVuru2cHxTDqeNlh94eCrNwPJ5ayA5U6LxAuSCTAzp+rv6KQUx1JcKSEHuh+nRYTKvUDE6iA6YtPLO96lLUCgYEA2H5rOPX2M4w1Q9zjol77lplbPRdczXNd0PIzhy8Z2ID65qvmr1nxBG4f2H96ykW8CKLXNvSXreNZ1BhOXc/3Hv+3mm46iitB33gDX4mlV4Jyo/w5IWhUKRyoW6qXquFFsScxRzTrx/9M+aZeRRLdsBk27HavFEg6jrbQ0SleZL8CgYAaM6Op8d/UgkVrHOR9Go9kmK/W85kK8+NuaE7Ksf57R0eKK8AzC9kc/lMuthfTyOG+n0ff1i8gaVWtai1Ko+/hvfqplacAsDIUgYK70AroB8LCZ5ODj5sr2CPVpB7LDFakod7c6O2KVW6+L7oy5AHUHOkc+5y4PDg5DGrLxo68SQKBgAlGoWF3aG0c/MtDk51JZI43U+lyLs++ua5SMlMAeaMFI7rucpvgxqrh7Qthqukvw7a7A22fXUBeFWM5B2KNnpD9c+hyAKAa6l+gzMQzKZpuRGsyS2BbEAAS8kO7M3Rm4o2MmFfstI2FKs8nibJ79HOvIONQ0n+T+K5Utu2/UAQRAoGAFB4fiIyQ0nYzCf18Z4Wvi/qeIOW+UoBonIN3y1h4wruBywINHxFMHx4aVImJ6R09hoJ9D3Mxli3xF/8JIjfTG5fBSGrGnuofl14d/XtRDXbT2uhVXrIkeLL/ojODwwEx0VhxIRUEjPTvEl6AFSRRcBp3KKzQ/cu7ENDY6GTlOUI=" # noqa
- if headers:
- cert = '-----BEGIN CERTIFICATE-----\n' + cert + '\n-----END CERTIFICATE-----'
- url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'saml'})
- resp = patch(
- url,
- user=admin,
- data={
- 'SOCIAL_AUTH_SAML_ENABLED_IDPS': {
- "okta": {
- "attr_last_name": "LastName",
- "attr_username": "login",
- "entity_id": "http://www.okta.com/abc123",
- "attr_user_permanent_id": "login",
- "url": "https://example.okta.com/app/abc123/xyz123/sso/saml",
- "attr_email": "Email",
- "x509cert": cert,
- "attr_first_name": "FirstName",
- }
- }
- },
- )
- assert resp.status_code == 200
-
-
-@pytest.mark.django_db
-def test_github_settings(get, put, patch, delete, admin):
- url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'github'})
- get(url, user=admin, expect=200)
- delete(url, user=admin, expect=204)
- response = get(url, user=admin, expect=200)
- data = dict(response.data.items())
- put(url, user=admin, data=data, expect=200)
- patch(url, user=admin, data={'SOCIAL_AUTH_GITHUB_KEY': '???'}, expect=200)
- response = get(url, user=admin, expect=200)
- assert response.data['SOCIAL_AUTH_GITHUB_KEY'] == '???'
- data.pop('SOCIAL_AUTH_GITHUB_KEY')
- put(url, user=admin, data=data, expect=200)
- response = get(url, user=admin, expect=200)
- assert response.data['SOCIAL_AUTH_GITHUB_KEY'] == ''
-
-
-@pytest.mark.django_db
-def test_github_enterprise_settings(get, put, patch, delete, admin):
- url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'github-enterprise'})
- get(url, user=admin, expect=200)
- delete(url, user=admin, expect=204)
- response = get(url, user=admin, expect=200)
- data = dict(response.data.items())
- put(url, user=admin, data=data, expect=200)
- patch(
- url,
- user=admin,
- data={
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_URL': 'example.com',
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL': 'example.com',
- },
- expect=200,
- )
- response = get(url, user=admin, expect=200)
- assert response.data['SOCIAL_AUTH_GITHUB_ENTERPRISE_URL'] == 'example.com'
- assert response.data['SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL'] == 'example.com'
- data.pop('SOCIAL_AUTH_GITHUB_ENTERPRISE_URL')
- data.pop('SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL')
- put(url, user=admin, data=data, expect=200)
- response = get(url, user=admin, expect=200)
- assert response.data['SOCIAL_AUTH_GITHUB_ENTERPRISE_URL'] == ''
- assert response.data['SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL'] == ''
diff --git a/awx/main/tests/functional/api/test_survey_spec.py b/awx/main/tests/functional/api/test_survey_spec.py
index cbb22b3bdcce..ec20806f6bfd 100644
--- a/awx/main/tests/functional/api/test_survey_spec.py
+++ b/awx/main/tests/functional/api/test_survey_spec.py
@@ -2,12 +2,12 @@
import pytest
import json
+from ansible_base.lib.utils.models import get_type_for_model
from awx.api.versioning import reverse
from awx.main.models.jobs import JobTemplate, Job
from awx.main.models.activity_stream import ActivityStream
from awx.main.access import JobTemplateAccess
-from awx.main.utils.common import get_type_for_model
@pytest.fixture
diff --git a/awx/main/tests/functional/api/test_unified_job_template.py b/awx/main/tests/functional/api/test_unified_job_template.py
index 1a9adc396583..c293827e437b 100644
--- a/awx/main/tests/functional/api/test_unified_job_template.py
+++ b/awx/main/tests/functional/api/test_unified_job_template.py
@@ -18,7 +18,7 @@ class TestUnifiedOrganization:
def data_for_model(self, model, orm_style=False):
data = {'name': 'foo', 'organization': None}
if model == 'JobTemplate':
- proj = models.Project.objects.create(name="test-proj", playbook_files=['helloworld.yml'])
+ proj = models.Project.objects.create(name="test-proj", playbook_files=['helloworld.yml'], scm_type='git', scm_url='https://foo.invalid')
if orm_style:
data['project_id'] = proj.id
else:
diff --git a/awx/main/tests/functional/api/test_unified_jobs_stdout.py b/awx/main/tests/functional/api/test_unified_jobs_stdout.py
index dad55c5ba061..3565a73d77a7 100644
--- a/awx/main/tests/functional/api/test_unified_jobs_stdout.py
+++ b/awx/main/tests/functional/api/test_unified_jobs_stdout.py
@@ -57,7 +57,7 @@ def _mk_inventory_update(created=None):
[_mk_inventory_update, InventoryUpdateEvent, 'inventory_update', 'api:inventory_update_stdout'],
],
)
-def test_text_stdout(sqlite_copy_expert, Parent, Child, relation, view, get, admin):
+def test_text_stdout(sqlite_copy, Parent, Child, relation, view, get, admin):
job = Parent()
job.save()
for i in range(3):
@@ -79,11 +79,11 @@ def test_text_stdout(sqlite_copy_expert, Parent, Child, relation, view, get, adm
],
)
@pytest.mark.parametrize('download', [True, False])
-def test_ansi_stdout_filtering(sqlite_copy_expert, Parent, Child, relation, view, download, get, admin):
+def test_ansi_stdout_filtering(sqlite_copy, Parent, Child, relation, view, download, get, admin):
job = Parent()
job.save()
for i in range(3):
- Child(**{relation: job, 'stdout': '\x1B[0;36mTesting {}\x1B[0m\n'.format(i), 'start_line': i}).save()
+ Child(**{relation: job, 'stdout': '\x1b[0;36mTesting {}\x1b[0m\n'.format(i), 'start_line': i}).save()
url = reverse(view, kwargs={'pk': job.pk})
# ansi codes in ?format=txt should get filtered
@@ -96,7 +96,7 @@ def test_ansi_stdout_filtering(sqlite_copy_expert, Parent, Child, relation, view
# ask for ansi and you'll get it
fmt = "?format={}".format("ansi_download" if download else "ansi")
response = get(url + fmt, user=admin, expect=200)
- assert smart_str(response.content).splitlines() == ['\x1B[0;36mTesting %d\x1B[0m' % i for i in range(3)]
+ assert smart_str(response.content).splitlines() == ['\x1b[0;36mTesting %d\x1b[0m' % i for i in range(3)]
has_download_header = response.has_header('Content-Disposition')
assert has_download_header if download else not has_download_header
@@ -111,11 +111,11 @@ def test_ansi_stdout_filtering(sqlite_copy_expert, Parent, Child, relation, view
[_mk_inventory_update, InventoryUpdateEvent, 'inventory_update', 'api:inventory_update_stdout'],
],
)
-def test_colorized_html_stdout(sqlite_copy_expert, Parent, Child, relation, view, get, admin):
+def test_colorized_html_stdout(sqlite_copy, Parent, Child, relation, view, get, admin):
job = Parent()
job.save()
for i in range(3):
- Child(**{relation: job, 'stdout': '\x1B[0;36mTesting {}\x1B[0m\n'.format(i), 'start_line': i}).save()
+ Child(**{relation: job, 'stdout': '\x1b[0;36mTesting {}\x1b[0m\n'.format(i), 'start_line': i}).save()
url = reverse(view, kwargs={'pk': job.pk}) + '?format=html'
response = get(url, user=admin, expect=200)
@@ -134,7 +134,7 @@ def test_colorized_html_stdout(sqlite_copy_expert, Parent, Child, relation, view
[_mk_inventory_update, InventoryUpdateEvent, 'inventory_update', 'api:inventory_update_stdout'],
],
)
-def test_stdout_line_range(sqlite_copy_expert, Parent, Child, relation, view, get, admin):
+def test_stdout_line_range(sqlite_copy, Parent, Child, relation, view, get, admin):
job = Parent()
job.save()
for i in range(20):
@@ -146,7 +146,7 @@ def test_stdout_line_range(sqlite_copy_expert, Parent, Child, relation, view, ge
@pytest.mark.django_db
-def test_text_stdout_from_system_job_events(sqlite_copy_expert, get, admin):
+def test_text_stdout_from_system_job_events(sqlite_copy, get, admin):
created = tz_now()
job = SystemJob(created=created)
job.save()
@@ -158,7 +158,7 @@ def test_text_stdout_from_system_job_events(sqlite_copy_expert, get, admin):
@pytest.mark.django_db
-def test_text_stdout_with_max_stdout(sqlite_copy_expert, get, admin):
+def test_text_stdout_with_max_stdout(sqlite_copy, get, admin):
created = tz_now()
job = SystemJob(created=created)
job.save()
@@ -185,7 +185,7 @@ def test_text_stdout_with_max_stdout(sqlite_copy_expert, get, admin):
)
@pytest.mark.parametrize('fmt', ['txt', 'ansi'])
@mock.patch('awx.main.redact.UriCleaner.SENSITIVE_URI_PATTERN', mock.Mock(**{'search.return_value': None})) # really slow for large strings
-def test_max_bytes_display(sqlite_copy_expert, Parent, Child, relation, view, fmt, get, admin):
+def test_max_bytes_display(sqlite_copy, Parent, Child, relation, view, fmt, get, admin):
created = tz_now()
job = Parent(created=created)
job.save()
@@ -255,7 +255,7 @@ def test_legacy_result_stdout_with_max_bytes(Cls, view, fmt, get, admin):
],
)
@pytest.mark.parametrize('fmt', ['txt', 'ansi', 'txt_download', 'ansi_download'])
-def test_text_with_unicode_stdout(sqlite_copy_expert, Parent, Child, relation, view, get, admin, fmt):
+def test_text_with_unicode_stdout(sqlite_copy, Parent, Child, relation, view, get, admin, fmt):
job = Parent()
job.save()
for i in range(3):
@@ -267,7 +267,7 @@ def test_text_with_unicode_stdout(sqlite_copy_expert, Parent, Child, relation, v
@pytest.mark.django_db
-def test_unicode_with_base64_ansi(sqlite_copy_expert, get, admin):
+def test_unicode_with_base64_ansi(sqlite_copy, get, admin):
created = tz_now()
job = Job(created=created)
job.save()
diff --git a/awx/main/tests/functional/api/test_unified_jobs_view.py b/awx/main/tests/functional/api/test_unified_jobs_view.py
index a8c7b53461e8..9a0955ba8012 100644
--- a/awx/main/tests/functional/api/test_unified_jobs_view.py
+++ b/awx/main/tests/functional/api/test_unified_jobs_view.py
@@ -7,7 +7,6 @@
from awx.main.tests.URI import URI
from awx.main.constants import ACTIVE_STATES
-
TEST_STATES = list(ACTIVE_STATES)
TEST_STATES.remove('new')
diff --git a/awx/main/tests/functional/api/test_user.py b/awx/main/tests/functional/api/test_user.py
index c19192c90caa..366f6f943c8d 100644
--- a/awx/main/tests/functional/api/test_user.py
+++ b/awx/main/tests/functional/api/test_user.py
@@ -1,14 +1,13 @@
-from datetime import date
from unittest import mock
import pytest
from django.contrib.sessions.middleware import SessionMiddleware
+from django.test.utils import override_settings
from awx.main.models import User
from awx.api.versioning import reverse
-
#
# user creation
#
@@ -24,6 +23,175 @@ def test_user_create(post, admin):
assert not response.data['is_system_auditor']
+# Disable local password checks to ensure that any ValidationError originates from the Django validators.
+@override_settings(
+ LOCAL_PASSWORD_MIN_LENGTH=1,
+ LOCAL_PASSWORD_MIN_DIGITS=0,
+ LOCAL_PASSWORD_MIN_UPPER=0,
+ LOCAL_PASSWORD_MIN_SPECIAL=0,
+)
+@pytest.mark.django_db
+def test_user_create_with_django_password_validation_basic(post, admin):
+ """Test if the Django password validators are applied correctly."""
+ with override_settings(
+ AUTH_PASSWORD_VALIDATORS=[
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ 'OPTIONS': {
+ 'min_length': 3,
+ },
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ },
+ ],
+ ):
+ # This user should fail the UserAttrSimilarity, MinLength and CommonPassword validators.
+ user_attrs = (
+ {
+ "password": "Password", # NOSONAR
+ "username": "Password",
+ "is_superuser": False,
+ },
+ )
+ print(f"Create user with invalid password {user_attrs=}")
+ response = post(reverse('api:user_list'), user_attrs, admin, middleware=SessionMiddleware(mock.Mock()))
+ assert response.status_code == 400
+ # This user should pass all Django validators.
+ user_attrs = {
+ "password": "r$TyKiOCb#ED", # NOSONAR
+ "username": "TestUser",
+ "is_superuser": False,
+ }
+ print(f"Create user with valid password {user_attrs=}")
+ response = post(reverse('api:user_list'), user_attrs, admin, middleware=SessionMiddleware(mock.Mock()))
+ assert response.status_code == 201
+
+
+@pytest.mark.parametrize(
+ "user_attrs,validators,expected_status_code",
+ [
+ # Test password similarity with username.
+ (
+ {"password": "TestUser1", "username": "TestUser1", "is_superuser": False}, # NOSONAR
+ [
+ {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
+ ],
+ 400,
+ ),
+ (
+ {"password": "abc", "username": "TestUser1", "is_superuser": False}, # NOSONAR
+ [
+ {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
+ ],
+ 201,
+ ),
+ # Test password min length criterion.
+ (
+ {"password": "TooShort", "username": "TestUser1", "is_superuser": False}, # NOSONAR
+ [
+ {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 'OPTIONS': {'min_length': 9}},
+ ],
+ 400,
+ ),
+ (
+ {"password": "LongEnough", "username": "TestUser1", "is_superuser": False}, # NOSONAR
+ [
+ {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 'OPTIONS': {'min_length': 9}},
+ ],
+ 201,
+ ),
+ # Test password is too common criterion.
+ (
+ {"password": "Password", "username": "TestUser1", "is_superuser": False}, # NOSONAR
+ [
+ {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
+ ],
+ 400,
+ ),
+ (
+ {"password": "aEArV$5Vkdw", "username": "TestUser1", "is_superuser": False}, # NOSONAR
+ [
+ {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
+ ],
+ 201,
+ ),
+ # Test if password is only numeric.
+ (
+ {"password": "1234567890", "username": "TestUser1", "is_superuser": False}, # NOSONAR
+ [
+ {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
+ ],
+ 400,
+ ),
+ (
+ {"password": "abc4567890", "username": "TestUser1", "is_superuser": False}, # NOSONAR
+ [
+ {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
+ ],
+ 201,
+ ),
+ ],
+)
+# Disable local password checks to ensure that any ValidationError originates from the Django validators.
+@override_settings(
+ LOCAL_PASSWORD_MIN_LENGTH=1,
+ LOCAL_PASSWORD_MIN_DIGITS=0,
+ LOCAL_PASSWORD_MIN_UPPER=0,
+ LOCAL_PASSWORD_MIN_SPECIAL=0,
+)
+@pytest.mark.django_db
+def test_user_create_with_django_password_validation_ext(post, delete, admin, user_attrs, validators, expected_status_code):
+ """Test the functionality of the single Django password validators."""
+ #
+ default_parameters = {
+ # Default values for input parameters which are None.
+ "user_attrs": {
+ "password": "r$TyKiOCb#ED", # NOSONAR
+ "username": "DefaultUser",
+ "is_superuser": False,
+ },
+ "validators": [
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ 'OPTIONS': {
+ 'min_length': 8,
+ },
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ },
+ ],
+ }
+ user_attrs = user_attrs if user_attrs is not None else default_parameters["user_attrs"]
+ validators = validators if validators is not None else default_parameters["validators"]
+ with override_settings(AUTH_PASSWORD_VALIDATORS=validators):
+ response = post(reverse('api:user_list'), user_attrs, admin, middleware=SessionMiddleware(mock.Mock()))
+ assert response.status_code == expected_status_code
+ # Delete user if it was created succesfully.
+ if response.status_code == 201:
+ response = delete(reverse('api:user_detail', kwargs={'pk': response.data['id']}), admin, middleware=SessionMiddleware(mock.Mock()))
+ assert response.status_code == 204
+ else:
+ # Catch the unexpected behavior that sometimes the user is written
+ # into the database before the validation fails. This actually can
+ # happen if UserSerializer.validate instantiates User(**attrs)!
+ username = user_attrs['username']
+ assert not User.objects.filter(username=username)
+
+
@pytest.mark.django_db
def test_fail_double_create_user(post, admin):
response = post(reverse('api:user_list'), EXAMPLE_USER_DATA, admin, middleware=SessionMiddleware(mock.Mock()))
@@ -33,6 +201,31 @@ def test_fail_double_create_user(post, admin):
assert response.status_code == 400
+@pytest.mark.django_db
+def test_creating_user_retains_session(post, admin):
+ '''
+ Creating a new user should not refresh a new session id for the current user.
+ '''
+ with mock.patch('awx.api.serializers.update_session_auth_hash') as update_session_auth_hash:
+ response = post(reverse('api:user_list'), EXAMPLE_USER_DATA, admin)
+ assert response.status_code == 201
+ assert not update_session_auth_hash.called
+
+
+@pytest.mark.django_db
+def test_updating_own_password_refreshes_session(patch, admin):
+ '''
+ Updating your own password should refresh the session id.
+ '''
+ with mock.patch('awx.api.serializers.update_session_auth_hash') as update_session_auth_hash:
+ # Attention: If the Django password validator `CommonPasswordValidator`
+ # is active, this test case will fail because this validator raises on
+ # password 'newpassword'. Consider changing the hard-coded password to
+ # something uncommon.
+ patch(reverse('api:user_detail', kwargs={'pk': admin.pk}), {'password': 'newpassword'}, admin, middleware=SessionMiddleware(mock.Mock()))
+ assert update_session_auth_hash.called
+
+
@pytest.mark.django_db
def test_create_delete_create_user(post, delete, admin):
response = post(reverse('api:user_list'), EXAMPLE_USER_DATA, admin, middleware=SessionMiddleware(mock.Mock()))
@@ -59,7 +252,23 @@ def test_user_verify_attribute_created(admin, get):
resp = get(reverse('api:user_detail', kwargs={'pk': admin.pk}), admin)
assert resp.data['created'] == admin.date_joined
- past = date(2020, 1, 1).isoformat()
+ past = "2020-01-01T00:00:00Z"
for op, count in (('gt', 1), ('lt', 0)):
resp = get(reverse('api:user_list') + f'?created__{op}={past}', admin)
assert resp.data['count'] == count
+
+
+@pytest.mark.django_db
+def test_org_not_shown_in_admin_user_sublists(admin_user, get, organization):
+ for view_name in ('user_admin_of_organizations_list', 'user_organizations_list'):
+ url = reverse(f'api:{view_name}', kwargs={'pk': admin_user.pk})
+ r = get(url, user=admin_user, expect=200)
+ assert organization.pk not in [org['id'] for org in r.data['results']]
+
+
+@pytest.mark.django_db
+def test_admin_user_not_shown_in_org_users(admin_user, get, organization):
+ for view_name in ('organization_users_list', 'organization_admins_list'):
+ url = reverse(f'api:{view_name}', kwargs={'pk': organization.pk})
+ r = get(url, user=admin_user, expect=200)
+ assert admin_user.pk not in [u['id'] for u in r.data['results']]
diff --git a/awx/main/tests/functional/api/test_workflow_job.py b/awx/main/tests/functional/api/test_workflow_job.py
new file mode 100644
index 000000000000..36553258dbb4
--- /dev/null
+++ b/awx/main/tests/functional/api/test_workflow_job.py
@@ -0,0 +1,54 @@
+import pytest
+
+
+from awx.api.versioning import reverse
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize(
+ "is_admin, status",
+ [
+ [True, 201],
+ [False, 403],
+ ], # if they're a WFJ admin, they get a 201 # if they're not a WFJ *nor* org admin, they get a 403
+)
+def test_workflow_job_relaunch(workflow_job, post, admin_user, alice, is_admin, status):
+ url = reverse("api:workflow_job_relaunch", kwargs={'pk': workflow_job.pk})
+ if is_admin:
+ post(url, user=admin_user, expect=status)
+ else:
+ post(url, user=alice, expect=status)
+
+
+@pytest.mark.django_db
+def test_workflow_job_relaunch_failure(workflow_job, post, admin_user):
+ workflow_job.is_sliced_job = True
+ workflow_job.job_template = None
+ workflow_job.save()
+ url = reverse("api:workflow_job_relaunch", kwargs={'pk': workflow_job.pk})
+ post(url, user=admin_user, expect=400)
+
+
+@pytest.mark.django_db
+def test_workflow_job_relaunch_not_inventory_failure(workflow_job, post, admin_user):
+ workflow_job.is_sliced_job = True
+ workflow_job.inventory = None
+ workflow_job.save()
+ url = reverse("api:workflow_job_relaunch", kwargs={'pk': workflow_job.pk})
+ post(url, user=admin_user, expect=400)
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize(
+ "is_admin, status",
+ [
+ [True, 202],
+ [False, 403],
+ ], # if they're a WFJ admin, they get a 202 # if they're not a WFJ *nor* org admin, they get a 403
+)
+def test_workflow_job_cancel(workflow_job, post, admin_user, alice, is_admin, status):
+ url = reverse("api:workflow_job_cancel", kwargs={'pk': workflow_job.pk})
+ if is_admin:
+ post(url, user=admin_user, expect=status)
+ else:
+ post(url, user=alice, expect=status)
diff --git a/awx/main/tests/functional/commands/test_callback_receiver.py b/awx/main/tests/functional/commands/test_callback_receiver.py
index 7b9346fe73e3..145c48d605bf 100644
--- a/awx/main/tests/functional/commands/test_callback_receiver.py
+++ b/awx/main/tests/functional/commands/test_callback_receiver.py
@@ -34,40 +34,18 @@ def test_wrapup_does_send_notifications(mocker):
mock.assert_called_once_with('succeeded')
-class FakeRedis:
- def keys(self, *args, **kwargs):
- return []
-
- def set(self):
- pass
-
- def get(self):
- return None
-
- @classmethod
- def from_url(cls, *args, **kwargs):
- return cls()
-
- def pipeline(self):
- return self
-
-
class TestCallbackBrokerWorker(TransactionTestCase):
@pytest.fixture(autouse=True)
- def turn_off_websockets(self):
+ def turn_off_websockets_and_redis(self, fake_redis):
with mock.patch('awx.main.dispatch.worker.callback.emit_event_detail', lambda *a, **kw: None):
yield
- def get_worker(self):
- with mock.patch('redis.Redis', new=FakeRedis): # turn off redis stuff
- return CallbackBrokerWorker()
-
def event_create_kwargs(self):
inventory_update = InventoryUpdate.objects.create(source='file', inventory_source=InventorySource.objects.create(source='file'))
return dict(inventory_update=inventory_update, created=inventory_update.created)
def test_flush_with_valid_event(self):
- worker = self.get_worker()
+ worker = CallbackBrokerWorker()
events = [InventoryUpdateEvent(uuid=str(uuid4()), **self.event_create_kwargs())]
worker.buff = {InventoryUpdateEvent: events}
worker.flush()
@@ -75,7 +53,7 @@ def test_flush_with_valid_event(self):
assert InventoryUpdateEvent.objects.filter(uuid=events[0].uuid).count() == 1
def test_flush_with_invalid_event(self):
- worker = self.get_worker()
+ worker = CallbackBrokerWorker()
kwargs = self.event_create_kwargs()
events = [
InventoryUpdateEvent(uuid=str(uuid4()), stdout='good1', **kwargs),
@@ -90,7 +68,7 @@ def test_flush_with_invalid_event(self):
assert worker.buff == {InventoryUpdateEvent: [events[1]]}
def test_duplicate_key_not_saved_twice(self):
- worker = self.get_worker()
+ worker = CallbackBrokerWorker()
events = [InventoryUpdateEvent(uuid=str(uuid4()), **self.event_create_kwargs())]
worker.buff = {InventoryUpdateEvent: events.copy()}
worker.flush()
@@ -104,7 +82,7 @@ def test_duplicate_key_not_saved_twice(self):
assert worker.buff.get(InventoryUpdateEvent, []) == []
def test_give_up_on_bad_event(self):
- worker = self.get_worker()
+ worker = CallbackBrokerWorker()
events = [InventoryUpdateEvent(uuid=str(uuid4()), counter=-2, **self.event_create_kwargs())]
worker.buff = {InventoryUpdateEvent: events.copy()}
@@ -117,7 +95,7 @@ def test_give_up_on_bad_event(self):
assert InventoryUpdateEvent.objects.filter(uuid=events[0].uuid).count() == 0 # sanity
def test_flush_with_empty_buffer(self):
- worker = self.get_worker()
+ worker = CallbackBrokerWorker()
worker.buff = {InventoryUpdateEvent: []}
with mock.patch.object(InventoryUpdateEvent.objects, 'bulk_create') as flush_mock:
worker.flush()
@@ -127,7 +105,7 @@ def test_postgres_invalid_NUL_char(self):
# In postgres, text fields reject NUL character, 0x00
# tests use sqlite3 which will not raise an error
# but we can still test that it is sanitized before saving
- worker = self.get_worker()
+ worker = CallbackBrokerWorker()
kwargs = self.event_create_kwargs()
events = [InventoryUpdateEvent(uuid=str(uuid4()), stdout="\x00", **kwargs)]
assert "\x00" in events[0].stdout # sanity
diff --git a/awx/main/tests/functional/commands/test_cleanup_host_metrics.py b/awx/main/tests/functional/commands/test_cleanup_host_metrics.py
new file mode 100644
index 000000000000..ac6d0bde3243
--- /dev/null
+++ b/awx/main/tests/functional/commands/test_cleanup_host_metrics.py
@@ -0,0 +1,78 @@
+import pytest
+
+from awx.main.tasks.host_metrics import HostMetricTask
+from awx.main.models.inventory import HostMetric
+from awx.main.tests.factories.fixtures import mk_host_metric
+from dateutil.relativedelta import relativedelta
+from django.conf import settings
+from django.utils import timezone
+
+
+@pytest.mark.django_db
+def test_no_host_metrics():
+ """No-crash test"""
+ assert HostMetric.objects.count() == 0
+ HostMetricTask().cleanup(soft_threshold=0, hard_threshold=0)
+ HostMetricTask().cleanup(soft_threshold=24, hard_threshold=42)
+ assert HostMetric.objects.count() == 0
+
+
+@pytest.mark.django_db
+def test_delete_exception():
+ """Crash test"""
+ with pytest.raises(ValueError):
+ HostMetricTask().soft_cleanup("")
+ with pytest.raises(TypeError):
+ HostMetricTask().hard_cleanup(set())
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize('threshold', [settings.CLEANUP_HOST_METRICS_SOFT_THRESHOLD, 20])
+def test_soft_delete(threshold):
+ """Metrics with last_automation < threshold are updated to deleted=True"""
+ mk_host_metric('host_1', first_automation=ago(months=1), last_automation=ago(months=1), deleted=False)
+ mk_host_metric('host_2', first_automation=ago(months=1), last_automation=ago(months=1), deleted=True)
+ mk_host_metric('host_3', first_automation=ago(months=1), last_automation=ago(months=threshold, hours=-1), deleted=False)
+ mk_host_metric('host_4', first_automation=ago(months=1), last_automation=ago(months=threshold, hours=-1), deleted=True)
+ mk_host_metric('host_5', first_automation=ago(months=1), last_automation=ago(months=threshold, hours=1), deleted=False)
+ mk_host_metric('host_6', first_automation=ago(months=1), last_automation=ago(months=threshold, hours=1), deleted=True)
+ mk_host_metric('host_7', first_automation=ago(months=1), last_automation=ago(months=42), deleted=False)
+ mk_host_metric('host_8', first_automation=ago(months=1), last_automation=ago(months=42), deleted=True)
+
+ assert HostMetric.objects.count() == 8
+ assert HostMetric.active_objects.count() == 4
+
+ for i in range(2):
+ HostMetricTask().cleanup(soft_threshold=threshold)
+ assert HostMetric.objects.count() == 8
+
+ hostnames = set(HostMetric.objects.filter(deleted=False).order_by('hostname').values_list('hostname', flat=True))
+ assert hostnames == {'host_1', 'host_3'}
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize('threshold', [settings.CLEANUP_HOST_METRICS_HARD_THRESHOLD, 20])
+def test_hard_delete(threshold):
+ """Metrics with last_deleted < threshold and deleted=True are deleted from the db"""
+ mk_host_metric('host_1', first_automation=ago(months=1), last_deleted=ago(months=1), deleted=False)
+ mk_host_metric('host_2', first_automation=ago(months=1), last_deleted=ago(months=1), deleted=True)
+ mk_host_metric('host_3', first_automation=ago(months=1), last_deleted=ago(months=threshold, hours=-1), deleted=False)
+ mk_host_metric('host_4', first_automation=ago(months=1), last_deleted=ago(months=threshold, hours=-1), deleted=True)
+ mk_host_metric('host_5', first_automation=ago(months=1), last_deleted=ago(months=threshold, hours=1), deleted=False)
+ mk_host_metric('host_6', first_automation=ago(months=1), last_deleted=ago(months=threshold, hours=1), deleted=True)
+ mk_host_metric('host_7', first_automation=ago(months=1), last_deleted=ago(months=42), deleted=False)
+ mk_host_metric('host_8', first_automation=ago(months=1), last_deleted=ago(months=42), deleted=True)
+
+ assert HostMetric.objects.count() == 8
+ assert HostMetric.active_objects.count() == 4
+
+ for i in range(2):
+ HostMetricTask().cleanup(hard_threshold=threshold)
+ assert HostMetric.objects.count() == 6
+
+ hostnames = set(HostMetric.objects.order_by('hostname').values_list('hostname', flat=True))
+ assert hostnames == {'host_1', 'host_2', 'host_3', 'host_4', 'host_5', 'host_7'}
+
+
+def ago(months=0, hours=0):
+ return timezone.now() - relativedelta(months=months, hours=hours)
diff --git a/awx/main/tests/functional/commands/test_commands.py b/awx/main/tests/functional/commands/test_commands.py
index 69f584c2871b..f62dac02064d 100644
--- a/awx/main/tests/functional/commands/test_commands.py
+++ b/awx/main/tests/functional/commands/test_commands.py
@@ -43,9 +43,9 @@ def run_command(name, *args, **options):
],
)
def test_update_password_command(mocker, username, password, expected, changed):
- with mocker.patch.object(UpdatePassword, 'update_password', return_value=changed):
- result, stdout, stderr = run_command('update_password', username=username, password=password)
- if result is None:
- assert stdout == expected
- else:
- assert str(result) == expected
+ mocker.patch.object(UpdatePassword, 'update_password', return_value=changed)
+ result, stdout, stderr = run_command('update_password', username=username, password=password)
+ if result is None:
+ assert stdout == expected
+ else:
+ assert str(result) == expected
diff --git a/awx/main/tests/functional/commands/test_host_metric_summary_monthly.py b/awx/main/tests/functional/commands/test_host_metric_summary_monthly.py
new file mode 100644
index 000000000000..525fdd789ff0
--- /dev/null
+++ b/awx/main/tests/functional/commands/test_host_metric_summary_monthly.py
@@ -0,0 +1,382 @@
+import pytest
+import datetime
+from dateutil.relativedelta import relativedelta
+from django.conf import settings
+from django.utils import timezone
+
+
+from awx.main.management.commands.host_metric_summary_monthly import Command
+from awx.main.models.inventory import HostMetric, HostMetricSummaryMonthly
+from awx.main.tests.factories.fixtures import mk_host_metric, mk_host_metric_summary
+
+
+@pytest.fixture
+def threshold():
+ return int(getattr(settings, 'CLEANUP_HOST_METRICS_HARD_THRESHOLD', 36))
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize("metrics_cnt", [0, 1, 2, 3])
+@pytest.mark.parametrize("mode", ["old_data", "actual_data", "all_data"])
+def test_summaries_counts(threshold, metrics_cnt, mode):
+ assert HostMetricSummaryMonthly.objects.count() == 0
+
+ for idx in range(metrics_cnt):
+ if mode == "old_data" or mode == "all_data":
+ mk_host_metric(None, months_ago(threshold + idx, "dt"))
+ elif mode == "actual_data" or mode == "all_data":
+ mk_host_metric(None, (months_ago(threshold - idx, "dt")))
+
+ Command().handle()
+
+ # Number of records is equal to host metrics' hard cleanup months
+ assert HostMetricSummaryMonthly.objects.count() == threshold
+
+ # Records start with date in the month following to the threshold month
+ date = months_ago(threshold - 1)
+ for metric in list(HostMetricSummaryMonthly.objects.order_by('date').all()):
+ assert metric.date == date
+ date += relativedelta(months=1)
+
+ # Older record are untouched
+ mk_host_metric_summary(date=months_ago(threshold + 10))
+ Command().handle()
+
+ assert HostMetricSummaryMonthly.objects.count() == threshold + 1
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize("mode", ["old_data", "actual_data", "all_data"])
+def test_summary_values(threshold, mode):
+ tester = {"old_data": MetricsTesterOldData(threshold), "actual_data": MetricsTesterActualData(threshold), "all_data": MetricsTesterCombinedData(threshold)}[
+ mode
+ ]
+
+ for iteration in ["create_metrics", "add_old_summaries", "change_metrics", "delete_metrics", "add_metrics"]:
+ getattr(tester, iteration)() # call method by string
+
+ # Operation is idempotent, repeat twice
+ for _ in range(2):
+ Command().handle()
+ # call assert method by string
+ getattr(tester, f"assert_{iteration}")()
+
+
+class MetricsTester:
+ def __init__(self, threshold, ignore_asserts=False):
+ self.threshold = threshold
+ self.expected_summaries = {}
+ self.ignore_asserts = ignore_asserts
+
+ def add_old_summaries(self):
+ """These records don't correspond with Host metrics"""
+ mk_host_metric_summary(self.below(4), license_consumed=100, hosts_added=10, hosts_deleted=5)
+ mk_host_metric_summary(self.below(3), license_consumed=105, hosts_added=20, hosts_deleted=10)
+ mk_host_metric_summary(self.below(2), license_consumed=115, hosts_added=60, hosts_deleted=75)
+
+ def assert_add_old_summaries(self):
+ """Old summary records should be untouched"""
+ self.expected_summaries[self.below(4)] = {"date": self.below(4), "license_consumed": 100, "hosts_added": 10, "hosts_deleted": 5}
+ self.expected_summaries[self.below(3)] = {"date": self.below(3), "license_consumed": 105, "hosts_added": 20, "hosts_deleted": 10}
+ self.expected_summaries[self.below(2)] = {"date": self.below(2), "license_consumed": 115, "hosts_added": 60, "hosts_deleted": 75}
+
+ self.assert_host_metric_summaries()
+
+ def assert_host_metric_summaries(self):
+ """Ignore asserts when old/actual test object is used only as a helper for Combined test"""
+ if self.ignore_asserts:
+ return True
+
+ for summary in list(HostMetricSummaryMonthly.objects.order_by('date').all()):
+ assert self.expected_summaries.get(summary.date, None) is not None
+
+ assert self.expected_summaries[summary.date] == {
+ "date": summary.date,
+ "license_consumed": summary.license_consumed,
+ "hosts_added": summary.hosts_added,
+ "hosts_deleted": summary.hosts_deleted,
+ }
+
+ def below(self, months, fmt="date"):
+ """months below threshold, returns first date of that month"""
+ date = months_ago(self.threshold + months)
+ if fmt == "dt":
+ return timezone.make_aware(datetime.datetime.combine(date, datetime.datetime.min.time()))
+ else:
+ return date
+
+ def above(self, months, fmt="date"):
+ """months above threshold, returns first date of that month"""
+ date = months_ago(self.threshold - months)
+ if fmt == "dt":
+ return timezone.make_aware(datetime.datetime.combine(date, datetime.datetime.min.time()))
+ else:
+ return date
+
+
+class MetricsTesterOldData(MetricsTester):
+ def create_metrics(self):
+ """Creates 7 host metrics older than delete threshold"""
+ mk_host_metric("host_1", first_automation=self.below(3, "dt"))
+ mk_host_metric("host_2", first_automation=self.below(2, "dt"))
+ mk_host_metric("host_3", first_automation=self.below(2, "dt"), last_deleted=self.above(2, "dt"), deleted=False)
+ mk_host_metric("host_4", first_automation=self.below(2, "dt"), last_deleted=self.above(2, "dt"), deleted=True)
+ mk_host_metric("host_5", first_automation=self.below(2, "dt"), last_deleted=self.below(2, "dt"), deleted=True)
+ mk_host_metric("host_6", first_automation=self.below(1, "dt"), last_deleted=self.below(1, "dt"), deleted=False)
+ mk_host_metric("host_7", first_automation=self.below(1, "dt"))
+
+ def assert_create_metrics(self):
+ """
+ Month 1 is computed from older host metrics,
+ Month 2 has deletion (host_4)
+ Other months are unchanged (same as month 2)
+ """
+ self.expected_summaries = {
+ self.above(1): {"date": self.above(1), "license_consumed": 6, "hosts_added": 0, "hosts_deleted": 0},
+ self.above(2): {"date": self.above(2), "license_consumed": 5, "hosts_added": 0, "hosts_deleted": 1},
+ }
+ # no change in months 3+
+ idx = 3
+ month = self.above(idx)
+ while month <= beginning_of_the_month():
+ self.expected_summaries[self.above(idx)] = {"date": self.above(idx), "license_consumed": 5, "hosts_added": 0, "hosts_deleted": 0}
+ month += relativedelta(months=1)
+ idx += 1
+
+ self.assert_host_metric_summaries()
+
+ def add_old_summaries(self):
+ super().add_old_summaries()
+
+ def assert_add_old_summaries(self):
+ super().assert_add_old_summaries()
+
+ @staticmethod
+ def change_metrics():
+ """Hosts 1,2 soft deleted, host_4 automated again (undeleted)"""
+ HostMetric.objects.filter(hostname='host_1').update(last_deleted=beginning_of_the_month("dt"), deleted=True)
+ HostMetric.objects.filter(hostname='host_2').update(last_deleted=timezone.now(), deleted=True)
+ HostMetric.objects.filter(hostname='host_4').update(deleted=False)
+
+ def assert_change_metrics(self):
+ """
+ Summaries since month 2 were changed (host_4 restored == automated again)
+ Current month has 2 deletions (host_1, host_2)
+ """
+ self.expected_summaries[self.above(2)] |= {'hosts_deleted': 0}
+ for idx in range(2, self.threshold):
+ self.expected_summaries[self.above(idx)] |= {'license_consumed': 6}
+ self.expected_summaries[beginning_of_the_month()] |= {'license_consumed': 4, 'hosts_deleted': 2}
+
+ self.assert_host_metric_summaries()
+
+ @staticmethod
+ def delete_metrics():
+ """Deletes metric deleted before the threshold"""
+ HostMetric.objects.filter(hostname='host_5').delete()
+
+ def assert_delete_metrics(self):
+ """No change"""
+ self.assert_host_metric_summaries()
+
+ @staticmethod
+ def add_metrics():
+ """Adds new metrics"""
+ mk_host_metric("host_24", first_automation=beginning_of_the_month("dt"))
+ mk_host_metric("host_25", first_automation=beginning_of_the_month("dt")) # timezone.now())
+
+ def assert_add_metrics(self):
+ """Summary in current month is updated"""
+ self.expected_summaries[beginning_of_the_month()]['license_consumed'] = 6
+ self.expected_summaries[beginning_of_the_month()]['hosts_added'] = 2
+
+ self.assert_host_metric_summaries()
+
+
+class MetricsTesterActualData(MetricsTester):
+ def create_metrics(self):
+ """Creates 16 host metrics newer than delete threshold"""
+ mk_host_metric("host_8", first_automation=self.above(1, "dt"))
+ mk_host_metric("host_9", first_automation=self.above(1, "dt"), last_deleted=self.above(1, "dt"))
+ mk_host_metric("host_10", first_automation=self.above(1, "dt"), last_deleted=self.above(1, "dt"), deleted=True)
+ mk_host_metric("host_11", first_automation=self.above(1, "dt"), last_deleted=self.above(2, "dt"))
+ mk_host_metric("host_12", first_automation=self.above(1, "dt"), last_deleted=self.above(2, "dt"), deleted=True)
+ mk_host_metric("host_13", first_automation=self.above(2, "dt"))
+ mk_host_metric("host_14", first_automation=self.above(2, "dt"), last_deleted=self.above(2, "dt"))
+ mk_host_metric("host_15", first_automation=self.above(2, "dt"), last_deleted=self.above(2, "dt"), deleted=True)
+ mk_host_metric("host_16", first_automation=self.above(2, "dt"), last_deleted=self.above(3, "dt"))
+ mk_host_metric("host_17", first_automation=self.above(2, "dt"), last_deleted=self.above(3, "dt"), deleted=True)
+ mk_host_metric("host_18", first_automation=self.above(4, "dt"))
+ # next one shouldn't happen in real (deleted=True, last_deleted = NULL)
+ mk_host_metric("host_19", first_automation=self.above(4, "dt"), deleted=True)
+ mk_host_metric("host_20", first_automation=self.above(4, "dt"), last_deleted=self.above(4, "dt"))
+ mk_host_metric("host_21", first_automation=self.above(4, "dt"), last_deleted=self.above(4, "dt"), deleted=True)
+ mk_host_metric("host_22", first_automation=self.above(4, "dt"), last_deleted=self.above(5, "dt"))
+ mk_host_metric("host_23", first_automation=self.above(4, "dt"), last_deleted=self.above(5, "dt"), deleted=True)
+
+ def assert_create_metrics(self):
+ self.expected_summaries = {
+ self.above(1): {"date": self.above(1), "license_consumed": 4, "hosts_added": 5, "hosts_deleted": 1},
+ self.above(2): {"date": self.above(2), "license_consumed": 7, "hosts_added": 5, "hosts_deleted": 2},
+ self.above(3): {"date": self.above(3), "license_consumed": 6, "hosts_added": 0, "hosts_deleted": 1},
+ self.above(4): {"date": self.above(4), "license_consumed": 11, "hosts_added": 6, "hosts_deleted": 1},
+ self.above(5): {"date": self.above(5), "license_consumed": 10, "hosts_added": 0, "hosts_deleted": 1},
+ }
+ # no change in months 6+
+ idx = 6
+ month = self.above(idx)
+ while month <= beginning_of_the_month():
+ self.expected_summaries[self.above(idx)] = {"date": self.above(idx), "license_consumed": 10, "hosts_added": 0, "hosts_deleted": 0}
+ month += relativedelta(months=1)
+ idx += 1
+
+ self.assert_host_metric_summaries()
+
+ def add_old_summaries(self):
+ super().add_old_summaries()
+
+ def assert_add_old_summaries(self):
+ super().assert_add_old_summaries()
+
+ @staticmethod
+ def change_metrics():
+ """
+ - Hosts 12, 19, 21 were automated again (undeleted)
+ - Host 16 was soft deleted
+ - Host 17 was undeleted and soft deleted again
+ """
+ HostMetric.objects.filter(hostname='host_12').update(deleted=False)
+ HostMetric.objects.filter(hostname='host_16').update(last_deleted=timezone.now(), deleted=True)
+ HostMetric.objects.filter(hostname='host_17').update(last_deleted=beginning_of_the_month("dt"), deleted=True)
+ HostMetric.objects.filter(hostname='host_19').update(deleted=False)
+ HostMetric.objects.filter(hostname='host_21').update(deleted=False)
+
+ def assert_change_metrics(self):
+ """
+ Summaries since month 2 were changed
+ Current month has 2 deletions (host_16, host_17)
+ """
+ self.expected_summaries[self.above(2)] |= {'license_consumed': 8, 'hosts_deleted': 1}
+ self.expected_summaries[self.above(3)] |= {'license_consumed': 8, 'hosts_deleted': 0}
+ self.expected_summaries[self.above(4)] |= {'license_consumed': 14, 'hosts_deleted': 0}
+
+ # month 5 had hosts_deleted 1 => license_consumed == 14 - 1
+ for idx in range(5, self.threshold):
+ self.expected_summaries[self.above(idx)] |= {'license_consumed': 13}
+ self.expected_summaries[beginning_of_the_month()] |= {'license_consumed': 11, 'hosts_deleted': 2}
+
+ self.assert_host_metric_summaries()
+
+ def delete_metrics(self):
+ """Hard cleanup can't delete metrics newer than threshold. No change"""
+ pass
+
+ def assert_delete_metrics(self):
+ """No change"""
+ self.assert_host_metric_summaries()
+
+ @staticmethod
+ def add_metrics():
+ """Adds new metrics"""
+ mk_host_metric("host_26", first_automation=beginning_of_the_month("dt"))
+ mk_host_metric("host_27", first_automation=timezone.now())
+
+ def assert_add_metrics(self):
+ """
+ Two metrics were deleted in current month by change_metrics()
+ Two metrics are added now
+ => license_consumed is equal to the previous month (13 - 2 + 2)
+ """
+ self.expected_summaries[beginning_of_the_month()] |= {'license_consumed': 13, 'hosts_added': 2}
+
+ self.assert_host_metric_summaries()
+
+
+class MetricsTesterCombinedData(MetricsTester):
+ def __init__(self, threshold):
+ super().__init__(threshold)
+ self.old_data = MetricsTesterOldData(threshold, ignore_asserts=True)
+ self.actual_data = MetricsTesterActualData(threshold, ignore_asserts=True)
+
+ def assert_host_metric_summaries(self):
+ self._combine_expected_summaries()
+ super().assert_host_metric_summaries()
+
+ def create_metrics(self):
+ self.old_data.create_metrics()
+ self.actual_data.create_metrics()
+
+ def assert_create_metrics(self):
+ self.old_data.assert_create_metrics()
+ self.actual_data.assert_create_metrics()
+
+ self.assert_host_metric_summaries()
+
+ def add_old_summaries(self):
+ super().add_old_summaries()
+
+ def assert_add_old_summaries(self):
+ self.old_data.assert_add_old_summaries()
+ self.actual_data.assert_add_old_summaries()
+
+ self.assert_host_metric_summaries()
+
+ def change_metrics(self):
+ self.old_data.change_metrics()
+ self.actual_data.change_metrics()
+
+ def assert_change_metrics(self):
+ self.old_data.assert_change_metrics()
+ self.actual_data.assert_change_metrics()
+
+ self.assert_host_metric_summaries()
+
+ def delete_metrics(self):
+ self.old_data.delete_metrics()
+ self.actual_data.delete_metrics()
+
+ def assert_delete_metrics(self):
+ self.old_data.assert_delete_metrics()
+ self.actual_data.assert_delete_metrics()
+
+ self.assert_host_metric_summaries()
+
+ def add_metrics(self):
+ self.old_data.add_metrics()
+ self.actual_data.add_metrics()
+
+ def assert_add_metrics(self):
+ self.old_data.assert_add_metrics()
+ self.actual_data.assert_add_metrics()
+
+ self.assert_host_metric_summaries()
+
+ def _combine_expected_summaries(self):
+ """
+ Expected summaries are sum of expected values for tests with old and actual data
+ Except data older than hard delete threshold (these summaries are untouched by task => the same in all tests)
+ """
+ for date, summary in self.old_data.expected_summaries.items():
+ if date <= months_ago(self.threshold):
+ license_consumed = summary['license_consumed']
+ hosts_added = summary['hosts_added']
+ hosts_deleted = summary['hosts_deleted']
+ else:
+ license_consumed = summary['license_consumed'] + self.actual_data.expected_summaries[date]['license_consumed']
+ hosts_added = summary['hosts_added'] + self.actual_data.expected_summaries[date]['hosts_added']
+ hosts_deleted = summary['hosts_deleted'] + self.actual_data.expected_summaries[date]['hosts_deleted']
+ self.expected_summaries[date] = {'date': date, 'license_consumed': license_consumed, 'hosts_added': hosts_added, 'hosts_deleted': hosts_deleted}
+
+
+def months_ago(num, fmt="date"):
+ if num is None:
+ return None
+ return beginning_of_the_month(fmt) - relativedelta(months=num)
+
+
+def beginning_of_the_month(fmt="date"):
+ date = datetime.date.today().replace(day=1)
+ if fmt == "dt":
+ return timezone.make_aware(datetime.datetime.combine(date, datetime.datetime.min.time()))
+ else:
+ return date
diff --git a/awx/main/tests/functional/commands/test_inventory_import.py b/awx/main/tests/functional/commands/test_inventory_import.py
index 75a09fc47627..6860889bee2b 100644
--- a/awx/main/tests/functional/commands/test_inventory_import.py
+++ b/awx/main/tests/functional/commands/test_inventory_import.py
@@ -18,7 +18,6 @@
from awx.main.models import Inventory, Host, Group, InventorySource
from awx.main.utils.mem_inventory import MemGroup
-
TEST_INVENTORY_CONTENT = {
"_meta": {"hostvars": {}},
"all": {"children": ["others", "servers", "ungrouped"], "vars": {"vara": "A"}},
diff --git a/awx/main/tests/functional/commands/test_oauth2_token_create.py b/awx/main/tests/functional/commands/test_oauth2_token_create.py
deleted file mode 100644
index 5c7a13813717..000000000000
--- a/awx/main/tests/functional/commands/test_oauth2_token_create.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# Python
-import pytest
-import string
-import random
-from io import StringIO
-
-# Django
-from django.contrib.auth.models import User
-from django.core.management import call_command
-from django.core.management.base import CommandError
-
-# AWX
-from awx.main.models.oauth import OAuth2AccessToken
-
-
-@pytest.mark.django_db
-@pytest.mark.inventory_import
-class TestOAuth2CreateCommand:
- def test_no_user_option(self):
- out = StringIO()
- with pytest.raises(CommandError) as excinfo:
- call_command('create_oauth2_token', stdout=out)
- assert 'Username not supplied.' in str(excinfo.value)
- out.close()
-
- def test_non_existing_user(self):
- out = StringIO()
- fake_username = ''
- while fake_username == '' or User.objects.filter(username=fake_username).exists():
- fake_username = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6))
- arg = '--user=' + fake_username
- with pytest.raises(CommandError) as excinfo:
- call_command('create_oauth2_token', arg, stdout=out)
- assert 'The user does not exist.' in str(excinfo.value)
- out.close()
-
- def test_correct_user(self, alice):
- out = StringIO()
- arg = '--user=' + 'alice'
- call_command('create_oauth2_token', arg, stdout=out)
- generated_token = out.getvalue().strip()
- assert OAuth2AccessToken.objects.filter(user=alice, token=generated_token).count() == 1
- assert OAuth2AccessToken.objects.get(user=alice, token=generated_token).scope == 'write'
- out.close()
diff --git a/awx/main/tests/functional/commands/test_oauth2_token_revoke.py b/awx/main/tests/functional/commands/test_oauth2_token_revoke.py
deleted file mode 100644
index 69b25fd0a849..000000000000
--- a/awx/main/tests/functional/commands/test_oauth2_token_revoke.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# Python
-import datetime
-import pytest
-import string
-import random
-from io import StringIO
-
-# Django
-from django.core.management import call_command
-from django.core.management.base import CommandError
-
-# AWX
-from awx.main.models import RefreshToken
-from awx.main.models.oauth import OAuth2AccessToken
-from awx.api.versioning import reverse
-
-
-@pytest.mark.django_db
-class TestOAuth2RevokeCommand:
- def test_non_existing_user(self):
- out = StringIO()
- fake_username = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6))
- arg = '--user=' + fake_username
- with pytest.raises(CommandError) as excinfo:
- call_command('revoke_oauth2_tokens', arg, stdout=out)
- assert 'A user with that username does not exist' in str(excinfo.value)
- out.close()
-
- def test_revoke_all_access_tokens(self, post, admin, alice):
- url = reverse('api:o_auth2_token_list')
- for user in (admin, alice):
- post(url, {'description': 'test token', 'scope': 'read'}, user)
- assert OAuth2AccessToken.objects.count() == 2
- call_command('revoke_oauth2_tokens')
- assert OAuth2AccessToken.objects.count() == 0
-
- def test_revoke_access_token_for_user(self, post, admin, alice):
- url = reverse('api:o_auth2_token_list')
- post(url, {'description': 'test token', 'scope': 'read'}, alice)
- assert OAuth2AccessToken.objects.count() == 1
- call_command('revoke_oauth2_tokens', '--user=admin')
- assert OAuth2AccessToken.objects.count() == 1
- call_command('revoke_oauth2_tokens', '--user=alice')
- assert OAuth2AccessToken.objects.count() == 0
-
- def test_revoke_all_refresh_tokens(self, post, admin, oauth_application):
- url = reverse('api:o_auth2_token_list')
- post(url, {'description': 'test token for', 'scope': 'read', 'application': oauth_application.pk}, admin)
- assert OAuth2AccessToken.objects.count() == 1
- assert RefreshToken.objects.count() == 1
-
- call_command('revoke_oauth2_tokens')
- assert OAuth2AccessToken.objects.count() == 0
- assert RefreshToken.objects.count() == 1
- for r in RefreshToken.objects.all():
- assert r.revoked is None
-
- call_command('revoke_oauth2_tokens', '--all')
- assert RefreshToken.objects.count() == 1
- for r in RefreshToken.objects.all():
- assert r.revoked is not None
- assert isinstance(r.revoked, datetime.datetime)
diff --git a/awx/main/tests/functional/commands/test_secret_key_regeneration.py b/awx/main/tests/functional/commands/test_secret_key_regeneration.py
index 808fefbdc997..05584e5101e7 100644
--- a/awx/main/tests/functional/commands/test_secret_key_regeneration.py
+++ b/awx/main/tests/functional/commands/test_secret_key_regeneration.py
@@ -12,7 +12,6 @@
from awx.main.management.commands import regenerate_secret_key
from awx.main.utils.encryption import encrypt_field, decrypt_field, encrypt_value
-
PREFIX = '$encrypted$UTF8$AESCBC$'
@@ -147,22 +146,6 @@ def test_survey_spec(self, inventory, project, survey_spec_factory, cls):
with override_settings(SECRET_KEY=new_key):
assert json.loads(new_job.decrypted_extra_vars())['secret_key'] == 'donttell'
- def test_oauth2_application_client_secret(self, oauth_application):
- # test basic decryption
- secret = oauth_application.client_secret
- assert len(secret) == 128
-
- # re-key the client_secret
- new_key = regenerate_secret_key.Command().handle()
-
- # verify that the old SECRET_KEY doesn't work
- with pytest.raises(InvalidToken):
- models.OAuth2Application.objects.get(pk=oauth_application.pk).client_secret
-
- # verify that the new SECRET_KEY *does* work
- with override_settings(SECRET_KEY=new_key):
- assert models.OAuth2Application.objects.get(pk=oauth_application.pk).client_secret == secret
-
def test_use_custom_key_with_tower_secret_key_env_var(self):
custom_key = 'MXSq9uqcwezBOChl/UfmbW1k4op+bC+FQtwPqgJ1u9XV'
os.environ['TOWER_SECRET_KEY'] = custom_key
diff --git a/awx/main/tests/functional/conftest.py b/awx/main/tests/functional/conftest.py
index 4f8b6bc83c5a..8f2186112090 100644
--- a/awx/main/tests/functional/conftest.py
+++ b/awx/main/tests/functional/conftest.py
@@ -1,22 +1,28 @@
+import logging
+
# Python
import pytest
from unittest import mock
-import tempfile
-import shutil
import urllib.parse
from unittest.mock import PropertyMock
+import importlib
# Django
from django.urls import resolve
from django.http import Http404
+from django.apps import apps as global_apps
from django.core.handlers.exception import response_for_exception
from django.contrib.auth.models import User
from django.core.serializers.json import DjangoJSONEncoder
from django.db.backends.sqlite3.base import SQLiteCursorWrapper
+from django.db.models.signals import post_migrate
+
+from awx.main.migrations._dab_rbac import setup_managed_role_definitions
+
# AWX
from awx.main.models.projects import Project
-from awx.main.models.ha import Instance
+from awx.main.models.ha import Instance, InstanceGroup
from rest_framework.test import (
APIRequestFactory,
@@ -30,7 +36,6 @@
Organization,
Team,
)
-from awx.main.models.rbac import Role
from awx.main.models.notifications import NotificationTemplate, Notification
from awx.main.models.events import (
JobEvent,
@@ -41,17 +46,82 @@
)
from awx.main.models.workflow import WorkflowJobTemplate
from awx.main.models.ad_hoc_commands import AdHocCommand
-from awx.main.models.oauth import OAuth2Application as Application
from awx.main.models.execution_environments import ExecutionEnvironment
+from awx.main.utils import is_testing
+
+logger = logging.getLogger(__name__)
__SWAGGER_REQUESTS__ = {}
+# HACK: the dab_resource_registry app required ServiceID in migrations which checks do not run
+dab_rr_initial = importlib.import_module('ansible_base.resource_registry.migrations.0001_initial')
+
+
+def create_service_id(app_config, apps=global_apps, **kwargs):
+ try:
+ apps.get_model("dab_resource_registry", "ServiceID")
+ except LookupError:
+ logger.info('Looks like reverse migration, not creating resource registry ServiceID')
+ return
+ dab_rr_initial.create_service_id(apps, None)
+
+
+if is_testing():
+ post_migrate.connect(create_service_id)
+
+
@pytest.fixture(scope="session")
def swagger_autogen(requests=__SWAGGER_REQUESTS__):
return requests
+class FakeRedis:
+ def __init__(self, *args, **kwargs):
+ # Accept and ignore all arguments to match redis.Redis signature
+ pass
+
+ def keys(self, *args, **kwargs):
+ return []
+
+ def set(self, *args, **kwargs):
+ pass
+
+ def get(self, *args, **kwargs):
+ return None
+
+ def rpush(self, *args, **kwargs):
+ return 1
+
+ def blpop(self, *args, **kwargs):
+ return None
+
+ def delete(self, *args, **kwargs):
+ pass
+
+ def llen(self, *args, **kwargs):
+ return 0
+
+ def scan_iter(self, *args, **kwargs):
+ return iter([])
+
+ @classmethod
+ def from_url(cls, *args, **kwargs):
+ return cls()
+
+ def pipeline(self):
+ return self
+
+ def ping(self):
+ return
+
+
+@pytest.fixture
+def fake_redis():
+ with mock.patch('redis.Redis', new=FakeRedis): # turn off redis stuff
+ yield
+
+
@pytest.fixture
def user():
def u(name, is_superuser=False):
@@ -80,6 +150,17 @@ def deploy_jobtemplate(project, inventory, credential):
return jt
+@pytest.fixture()
+def execution_environment():
+ return ExecutionEnvironment.objects.create(name="test-ee", description="test-ee", managed=True)
+
+
+@pytest.fixture
+def setup_managed_roles():
+ "Run the migration script to pre-create managed role definitions"
+ setup_managed_role_definitions(global_apps, None)
+
+
@pytest.fixture
def team(organization):
return organization.teams.create(name='test-team')
@@ -92,20 +173,6 @@ def team_member(user, team):
return ret
-@pytest.fixture(scope="session", autouse=True)
-def project_playbooks():
- """
- Return playbook_files as playbooks for manual projects when testing.
- """
-
- class PlaybooksMock(mock.PropertyMock):
- def __get__(self, obj, obj_type):
- return obj.playbook_files
-
- mocked = mock.patch.object(Project, 'playbooks', new_callable=PlaybooksMock)
- mocked.start()
-
-
@pytest.fixture
def run_computed_fields_right_away(request):
def run_me(inventory_id):
@@ -178,12 +245,6 @@ def factory(name):
return factory
-@pytest.fixture
-def user_project(user):
- owner = user('owner')
- return Project.objects.create(name="test-user-project", created_by=owner, description="test-user-project-desc")
-
-
@pytest.fixture
def insights_project():
return Project.objects.create(name="test-insights-project", scm_type="insights")
@@ -333,13 +394,6 @@ def inventory(organization):
return organization.inventories.create(name="test-inv")
-@pytest.fixture
-def insights_inventory(inventory):
- inventory.scm_type = 'insights'
- inventory.save()
- return inventory
-
-
@pytest.fixture
def scm_inventory_source(inventory, project):
inv_src = InventorySource(
@@ -423,7 +477,7 @@ def admin(user):
@pytest.fixture
def system_auditor(user):
u = user('an-auditor', False)
- Role.singleton('system_auditor').members.add(u)
+ u.is_system_auditor = True
return u
@@ -490,25 +544,16 @@ def g(name):
@pytest.fixture
-def hosts(group_factory):
- group1 = group_factory('group-1')
-
- def rf(host_count=1):
- hosts = []
- for i in range(0, host_count):
- name = '%s-host-%s' % (group1.name, i)
- (host, created) = group1.inventory.hosts.get_or_create(name=name)
- if created:
- group1.hosts.add(host)
- hosts.append(host)
- return hosts
-
- return rf
+def group(inventory):
+ return inventory.groups.create(name='single-group')
@pytest.fixture
-def group(inventory):
- return inventory.groups.create(name='single-group')
+def constructed_inventory(organization):
+ """
+ creates a new constructed inventory source
+ """
+ return Inventory.objects.create(name='dummy1', kind='constructed', organization=organization)
@pytest.fixture
@@ -704,6 +749,11 @@ def jt_linked(organization, project, inventory, machine_credential, credential,
return jt
+@pytest.fixture
+def instance_group():
+ return InstanceGroup.objects.create(name="east")
+
+
@pytest.fixture
def workflow_job_template(organization):
wjt = WorkflowJobTemplate.objects.create(name='test-workflow_job_template', organization=organization)
@@ -735,6 +785,30 @@ def factory(system_job_template=system_job_template, initial_state='new', create
return factory
+@pytest.fixture
+def wfjt(workflow_job_template_factory, organization):
+ objects = workflow_job_template_factory('test_workflow', organization=organization, persisted=True)
+ return objects.workflow_job_template
+
+
+@pytest.fixture
+def wfjt_with_nodes(workflow_job_template_factory, organization, job_template):
+ objects = workflow_job_template_factory(
+ 'test_workflow', organization=organization, workflow_job_template_nodes=[{'unified_job_template': job_template}], persisted=True
+ )
+ return objects.workflow_job_template
+
+
+@pytest.fixture
+def wfjt_node(wfjt_with_nodes):
+ return wfjt_with_nodes.workflow_job_template_nodes.all()[0]
+
+
+@pytest.fixture
+def workflow_job(wfjt):
+ return wfjt.workflow_jobs.create(name='test_workflow')
+
+
def dumps(value):
return DjangoJSONEncoder().encode(value)
@@ -752,30 +826,43 @@ def get_db_prep_save(self, value, connection, **kwargs):
return value
-@pytest.fixture
-def oauth_application(admin):
- return Application.objects.create(name='test app', user=admin, client_type='confidential', authorization_grant_type='password')
-
-
-@pytest.fixture
-def sqlite_copy_expert(request):
- # copy_expert is postgres-specific, and SQLite doesn't support it; mock its
- # behavior to test that it writes a file that contains stdout from events
- path = tempfile.mkdtemp(prefix='job-event-stdout')
+class MockCopy:
+ events = []
+ index = -1
- def write_stdout(self, sql, fd):
- # simulate postgres copy_expert support with ORM code
+ def __init__(self, sql):
+ self.events = []
parts = sql.split(' ')
tablename = parts[parts.index('from') + 1]
for cls in (JobEvent, AdHocCommandEvent, ProjectUpdateEvent, InventoryUpdateEvent, SystemJobEvent):
if cls._meta.db_table == tablename:
for event in cls.objects.order_by('start_line').all():
- fd.write(event.stdout)
+ self.events.append(event.stdout)
+
+ def read(self):
+ self.index = self.index + 1
+ if self.index < len(self.events):
+ return memoryview(self.events[self.index].encode())
+
+ return None
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ pass
+
+
+@pytest.fixture
+def sqlite_copy(request, mocker):
+ # copy is postgres-specific, and SQLite doesn't support it; mock its
+ # behavior to test that it writes a file that contains stdout from events
+
+ def write_stdout(self, sql):
+ mock_copy = MockCopy(sql)
+ return mock_copy
- setattr(SQLiteCursorWrapper, 'copy_expert', write_stdout)
- request.addfinalizer(lambda: shutil.rmtree(path))
- request.addfinalizer(lambda: delattr(SQLiteCursorWrapper, 'copy_expert'))
- return path
+ mocker.patch.object(SQLiteCursorWrapper, 'copy', write_stdout, create=True)
@pytest.fixture
diff --git a/awx/main/tests/functional/dab_feature_flags/test_feature_flags_api.py b/awx/main/tests/functional/dab_feature_flags/test_feature_flags_api.py
new file mode 100644
index 000000000000..fb483000fb90
--- /dev/null
+++ b/awx/main/tests/functional/dab_feature_flags/test_feature_flags_api.py
@@ -0,0 +1,35 @@
+import pytest
+from flags.state import get_flags, flag_state
+from ansible_base.feature_flags.models import AAPFlag
+from ansible_base.feature_flags.utils import create_initial_data as seed_feature_flags
+from django.conf import settings
+from awx.main.models import User
+
+
+@pytest.mark.django_db
+def test_feature_flags_list_endpoint(get):
+ bob = User.objects.create(username='bob', password='test_user', is_superuser=True)
+ url = "/api/v2/feature_flags/states/"
+ response = get(url, user=bob, expect=200)
+ assert len(get_flags()) > 0
+ assert len(response.data["results"]) == len(get_flags())
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize('flag_val', (True, False))
+def test_feature_flags_list_endpoint_override(get, flag_val):
+ bob = User.objects.create(username='bob', password='test_user', is_superuser=True)
+
+ AAPFlag.objects.all().delete()
+ flag_name = "FEATURE_INDIRECT_NODE_COUNTING_ENABLED"
+ setattr(settings, flag_name, flag_val)
+ seed_feature_flags()
+ url = "/api/v2/feature_flags/states/"
+ response = get(url, user=bob, expect=200)
+
+ results = response.data["results"]
+ flag_names = [flag["name"] for flag in results]
+
+ assert flag_name in flag_names, f"{flag_name} should be present in feature flags"
+ assert all(name.startswith("FEATURE_") for name in flag_names), "All feature flags should start with FEATURE_ prefix"
+ assert flag_state(flag_name) == flag_val
diff --git a/awx/main/tests/functional/dab_rbac/test_access_list.py b/awx/main/tests/functional/dab_rbac/test_access_list.py
new file mode 100644
index 000000000000..0a409efa9c8a
--- /dev/null
+++ b/awx/main/tests/functional/dab_rbac/test_access_list.py
@@ -0,0 +1,125 @@
+import pytest
+
+from awx.main.models import User
+from awx.api.versioning import reverse
+
+
+@pytest.mark.django_db
+def test_access_list_superuser(get, admin_user, inventory):
+ url = reverse('api:inventory_access_list', kwargs={'pk': inventory.id})
+
+ response = get(url, user=admin_user, expect=200)
+ by_username = {}
+ for entry in response.data['results']:
+ by_username[entry['username']] = entry
+ assert 'admin' in by_username
+
+ assert len(by_username['admin']['summary_fields']['indirect_access']) == 1
+ assert len(by_username['admin']['summary_fields']['direct_access']) == 0
+ access_entry = by_username['admin']['summary_fields']['indirect_access'][0]
+ assert sorted(access_entry['descendant_roles']) == sorted(['adhoc_role', 'use_role', 'update_role', 'read_role', 'admin_role'])
+
+
+@pytest.mark.django_db
+def test_access_list_system_auditor(get, admin_user, inventory):
+ sys_auditor = User.objects.create(username='sys-aud')
+ sys_auditor.is_system_auditor = True
+ assert sys_auditor.is_system_auditor
+ url = reverse('api:inventory_access_list', kwargs={'pk': inventory.id})
+
+ response = get(url, user=admin_user, expect=200)
+ by_username = {}
+ for entry in response.data['results']:
+ by_username[entry['username']] = entry
+ assert 'sys-aud' in by_username
+
+ assert len(by_username['sys-aud']['summary_fields']['indirect_access']) == 1
+ assert len(by_username['sys-aud']['summary_fields']['direct_access']) == 0
+ access_entry = by_username['sys-aud']['summary_fields']['indirect_access'][0]
+ assert access_entry['descendant_roles'] == ['read_role']
+
+
+@pytest.mark.django_db
+def test_access_list_direct_access(get, admin_user, inventory):
+ u1 = User.objects.create(username='u1')
+
+ inventory.admin_role.members.add(u1)
+
+ url = reverse('api:inventory_access_list', kwargs={'pk': inventory.id})
+ response = get(url, user=admin_user, expect=200)
+ by_username = {}
+ for entry in response.data['results']:
+ by_username[entry['username']] = entry
+ assert 'u1' in by_username
+
+ assert len(by_username['u1']['summary_fields']['direct_access']) == 1
+ assert len(by_username['u1']['summary_fields']['indirect_access']) == 0
+ access_entry = by_username['u1']['summary_fields']['direct_access'][0]
+ assert sorted(access_entry['descendant_roles']) == sorted(['adhoc_role', 'use_role', 'update_role', 'read_role', 'admin_role'])
+
+
+@pytest.mark.django_db
+def test_access_list_organization_access(get, admin_user, inventory):
+ u2 = User.objects.create(username='u2')
+
+ inventory.organization.inventory_admin_role.members.add(u2)
+
+ # User has indirect access to the inventory
+ url = reverse('api:inventory_access_list', kwargs={'pk': inventory.id})
+ response = get(url, user=admin_user, expect=200)
+ by_username = {}
+ for entry in response.data['results']:
+ by_username[entry['username']] = entry
+ assert 'u2' in by_username
+
+ assert len(by_username['u2']['summary_fields']['indirect_access']) == 1
+ assert len(by_username['u2']['summary_fields']['direct_access']) == 0
+ access_entry = by_username['u2']['summary_fields']['indirect_access'][0]
+ assert sorted(access_entry['descendant_roles']) == sorted(['adhoc_role', 'use_role', 'update_role', 'read_role', 'admin_role'])
+
+ # Test that user shows up in the organization access list with direct access of expected roles
+ url = reverse('api:organization_access_list', kwargs={'pk': inventory.organization_id})
+ response = get(url, user=admin_user, expect=200)
+ by_username = {}
+ for entry in response.data['results']:
+ by_username[entry['username']] = entry
+ assert 'u2' in by_username
+
+ assert len(by_username['u2']['summary_fields']['direct_access']) == 1
+ assert len(by_username['u2']['summary_fields']['indirect_access']) == 0
+ access_entry = by_username['u2']['summary_fields']['direct_access'][0]
+ assert sorted(access_entry['descendant_roles']) == sorted(['inventory_admin_role', 'read_role'])
+
+
+@pytest.mark.django_db
+def test_team_indirect_access(get, team, admin_user, inventory):
+ u1 = User.objects.create(username='u1')
+ team.member_role.members.add(u1)
+
+ inventory.organization.inventory_admin_role.parents.add(team.member_role)
+
+ url = reverse('api:inventory_access_list', kwargs={'pk': inventory.id})
+ response = get(url, user=admin_user, expect=200)
+ by_username = {}
+ for entry in response.data['results']:
+ by_username[entry['username']] = entry
+ assert 'u1' in by_username
+
+ assert len(by_username['u1']['summary_fields']['direct_access']) == 1
+ assert len(by_username['u1']['summary_fields']['indirect_access']) == 0
+ access_entry = by_username['u1']['summary_fields']['direct_access'][0]
+ assert sorted(access_entry['descendant_roles']) == sorted(['adhoc_role', 'use_role', 'update_role', 'read_role', 'admin_role'])
+
+
+@pytest.mark.django_db
+def test_workflow_access_list(workflow_job_template, alice, bob, setup_managed_roles, get, admin_user):
+ """Basic verification that WFJT access_list is functional"""
+ workflow_job_template.admin_role.members.add(alice)
+ workflow_job_template.organization.workflow_admin_role.members.add(bob)
+
+ url = reverse('api:workflow_job_template_access_list', kwargs={'pk': workflow_job_template.pk})
+ for u in (alice, bob, admin_user):
+ response = get(url, user=u, expect=200)
+ user_ids = [item['id'] for item in response.data['results']]
+ assert alice.pk in user_ids
+ assert bob.pk in user_ids
diff --git a/awx/main/tests/functional/dab_rbac/test_access_regressions.py b/awx/main/tests/functional/dab_rbac/test_access_regressions.py
new file mode 100644
index 000000000000..abd334269709
--- /dev/null
+++ b/awx/main/tests/functional/dab_rbac/test_access_regressions.py
@@ -0,0 +1,41 @@
+import pytest
+
+from awx.main.access import InstanceGroupAccess, NotificationTemplateAccess
+
+from ansible_base.rbac.models import RoleDefinition
+
+
+@pytest.mark.django_db
+def test_instance_group_object_role_delete(rando, instance_group, setup_managed_roles):
+ """Basic functionality of IG object-level admin role function AAP-25506"""
+ rd = RoleDefinition.objects.get(name='InstanceGroup Admin')
+ rd.give_permission(rando, instance_group)
+ access = InstanceGroupAccess(rando)
+ assert access.can_delete(instance_group)
+
+
+@pytest.mark.django_db
+def test_notification_template_object_role_change(rando, notification_template, setup_managed_roles):
+ """Basic functionality of NT object-level admin role function AAP-25493"""
+ rd = RoleDefinition.objects.get(name='NotificationTemplate Admin')
+ rd.give_permission(rando, notification_template)
+ access = NotificationTemplateAccess(rando)
+ assert access.can_change(notification_template, {'name': 'new name'})
+
+
+@pytest.mark.django_db
+def test_organization_auditor_role(rando, setup_managed_roles, organization, inventory, project, jt_linked):
+ obj_list = (inventory, project, jt_linked)
+ for obj in obj_list:
+ assert obj.organization == organization, obj # sanity
+
+ assert [rando.has_obj_perm(obj, 'view') for obj in obj_list] == [False for i in range(3)], obj_list
+
+ rd = RoleDefinition.objects.get(name='Organization Audit')
+ rd.give_permission(rando, organization)
+
+ codename_set = set(rd.permissions.values_list('codename', flat=True))
+ assert not ({'view_inventory', 'view_jobtemplate', 'audit_organization'} - codename_set) # sanity
+
+ assert [obj in type(obj).access_qs(rando) for obj in obj_list] == [True for i in range(3)], obj_list
+ assert [rando.has_obj_perm(obj, 'view') for obj in obj_list] == [True for i in range(3)], obj_list
diff --git a/awx/main/tests/functional/dab_rbac/test_consolidate_teams.py b/awx/main/tests/functional/dab_rbac/test_consolidate_teams.py
new file mode 100644
index 000000000000..1e42059e5664
--- /dev/null
+++ b/awx/main/tests/functional/dab_rbac/test_consolidate_teams.py
@@ -0,0 +1,147 @@
+import pytest
+
+from django.contrib.contenttypes.models import ContentType
+from django.test import override_settings
+from django.apps import apps
+
+from ansible_base.rbac.models import RoleDefinition, RoleUserAssignment, RoleTeamAssignment
+from ansible_base.rbac.migrations._utils import give_permissions
+
+from awx.main.models import User, Team
+from awx.main.migrations._dab_rbac import consolidate_indirect_user_roles
+
+
+@pytest.mark.django_db
+@override_settings(ANSIBLE_BASE_ALLOW_TEAM_PARENTS=True)
+def test_consolidate_indirect_user_roles_with_nested_teams(setup_managed_roles, organization):
+ """
+ Test the consolidate_indirect_user_roles function with a nested team hierarchy.
+ Setup:
+ - Users: A, B, C, D
+ - Teams: E, F, G
+ - Direct assignments: A→(E,F,G), B→E, C→F, D→G
+ - Team hierarchy: F→E (F is member of E), G→F (G is member of F)
+ Expected result after consolidation:
+ - Team E should have users: A, B, C, D (A directly, B directly, C through F, D through G→F)
+ - Team F should have users: A, C, D (A directly, C directly, D through G)
+ - Team G should have users: A, D (A directly, D directly)
+ """
+ user_a = User.objects.create_user(username='user_a')
+ user_b = User.objects.create_user(username='user_b')
+ user_c = User.objects.create_user(username='user_c')
+ user_d = User.objects.create_user(username='user_d')
+
+ team_e = Team.objects.create(name='Team E', organization=organization)
+ team_f = Team.objects.create(name='Team F', organization=organization)
+ team_g = Team.objects.create(name='Team G', organization=organization)
+
+ # Get role definition and content type for give_permissions
+ team_member_role = RoleDefinition.objects.get(name='Team Member')
+ team_content_type = ContentType.objects.get_for_model(Team)
+
+ # Assign users to teams
+ give_permissions(apps=apps, rd=team_member_role, users=[user_a], object_id=team_e.id, content_type_id=team_content_type.id)
+ give_permissions(apps=apps, rd=team_member_role, users=[user_a], object_id=team_f.id, content_type_id=team_content_type.id)
+ give_permissions(apps=apps, rd=team_member_role, users=[user_a], object_id=team_g.id, content_type_id=team_content_type.id)
+ give_permissions(apps=apps, rd=team_member_role, users=[user_b], object_id=team_e.id, content_type_id=team_content_type.id)
+ give_permissions(apps=apps, rd=team_member_role, users=[user_c], object_id=team_f.id, content_type_id=team_content_type.id)
+ give_permissions(apps=apps, rd=team_member_role, users=[user_d], object_id=team_g.id, content_type_id=team_content_type.id)
+
+ # Mirror user assignments in the old RBAC system because signals don't run in tests
+ team_e.member_role.members.add(user_a.id, user_b.id)
+ team_f.member_role.members.add(user_a.id, user_c.id)
+ team_g.member_role.members.add(user_a.id, user_d.id)
+
+ # Setup team-to-team relationships
+ give_permissions(apps=apps, rd=team_member_role, teams=[team_f], object_id=team_e.id, content_type_id=team_content_type.id)
+ give_permissions(apps=apps, rd=team_member_role, teams=[team_g], object_id=team_f.id, content_type_id=team_content_type.id)
+
+ # Verify initial direct assignments
+ team_e_users_before = set(RoleUserAssignment.objects.filter(role_definition=team_member_role, object_id=team_e.id).values_list('user_id', flat=True))
+ assert team_e_users_before == {user_a.id, user_b.id}
+ team_f_users_before = set(RoleUserAssignment.objects.filter(role_definition=team_member_role, object_id=team_f.id).values_list('user_id', flat=True))
+ assert team_f_users_before == {user_a.id, user_c.id}
+ team_g_users_before = set(RoleUserAssignment.objects.filter(role_definition=team_member_role, object_id=team_g.id).values_list('user_id', flat=True))
+ assert team_g_users_before == {user_a.id, user_d.id}
+
+ # Verify team-to-team relationships exist
+ assert RoleTeamAssignment.objects.filter(role_definition=team_member_role, team=team_f, object_id=team_e.id).exists()
+ assert RoleTeamAssignment.objects.filter(role_definition=team_member_role, team=team_g, object_id=team_f.id).exists()
+
+ # Run the consolidation function
+ consolidate_indirect_user_roles(apps, None)
+
+ # Verify consolidation
+ team_e_users_after = set(RoleUserAssignment.objects.filter(role_definition=team_member_role, object_id=team_e.id).values_list('user_id', flat=True))
+ assert team_e_users_after == {user_a.id, user_b.id, user_c.id, user_d.id}, f"Team E should have users A, B, C, D but has {team_e_users_after}"
+ team_f_users_after = set(RoleUserAssignment.objects.filter(role_definition=team_member_role, object_id=team_f.id).values_list('user_id', flat=True))
+ assert team_f_users_after == {user_a.id, user_c.id, user_d.id}, f"Team F should have users A, C, D but has {team_f_users_after}"
+ team_g_users_after = set(RoleUserAssignment.objects.filter(role_definition=team_member_role, object_id=team_g.id).values_list('user_id', flat=True))
+ assert team_g_users_after == {user_a.id, user_d.id}, f"Team G should have users A, D but has {team_g_users_after}"
+
+ # Verify team member changes are mirrored to the old RBAC system
+ assert team_e_users_after == set(team_e.member_role.members.all().values_list('id', flat=True))
+ assert team_f_users_after == set(team_f.member_role.members.all().values_list('id', flat=True))
+ assert team_g_users_after == set(team_g.member_role.members.all().values_list('id', flat=True))
+
+ # Verify team-to-team relationships are removed after consolidation
+ assert not RoleTeamAssignment.objects.filter(
+ role_definition=team_member_role, team=team_f, object_id=team_e.id
+ ).exists(), "Team-to-team relationship F→E should be removed"
+ assert not RoleTeamAssignment.objects.filter(
+ role_definition=team_member_role, team=team_g, object_id=team_f.id
+ ).exists(), "Team-to-team relationship G→F should be removed"
+
+
+@pytest.mark.django_db
+@override_settings(ANSIBLE_BASE_ALLOW_TEAM_PARENTS=True)
+def test_consolidate_indirect_user_roles_no_team_relationships(setup_managed_roles, organization):
+ """
+ Test that the function handles the case where there are no team-to-team relationships.
+ It should return early without making any changes.
+ """
+ # Create a user and team with direct assignment
+ user = User.objects.create_user(username='test_user')
+ team = Team.objects.create(name='Test Team', organization=organization)
+
+ team_member_role = RoleDefinition.objects.get(name='Team Member')
+ team_content_type = ContentType.objects.get_for_model(Team)
+ give_permissions(apps=apps, rd=team_member_role, users=[user], object_id=team.id, content_type_id=team_content_type.id)
+
+ # Compare count of assignments before and after consolidation
+ assignments_before = RoleUserAssignment.objects.filter(role_definition=team_member_role).count()
+ consolidate_indirect_user_roles(apps, None)
+ assignments_after = RoleUserAssignment.objects.filter(role_definition=team_member_role).count()
+
+ assert assignments_before == assignments_after, "Number of assignments should not change when there are no team-to-team relationships"
+
+
+@pytest.mark.django_db
+@override_settings(ANSIBLE_BASE_ALLOW_TEAM_PARENTS=True)
+def test_consolidate_indirect_user_roles_circular_reference(setup_managed_roles, organization):
+ """
+ Test that the function handles circular team references without infinite recursion.
+ """
+ team_a = Team.objects.create(name='Team A', organization=organization)
+ team_b = Team.objects.create(name='Team B', organization=organization)
+
+ # Create a user assigned to team A
+ user = User.objects.create_user(username='test_user')
+
+ team_member_role = RoleDefinition.objects.get(name='Team Member')
+ team_content_type = ContentType.objects.get_for_model(Team)
+ give_permissions(apps=apps, rd=team_member_role, users=[user], object_id=team_a.id, content_type_id=team_content_type.id)
+
+ # Create circular team relationships: A → B → A
+ give_permissions(apps=apps, rd=team_member_role, teams=[team_b], object_id=team_a.id, content_type_id=team_content_type.id)
+ give_permissions(apps=apps, rd=team_member_role, teams=[team_a], object_id=team_b.id, content_type_id=team_content_type.id)
+
+ # Run the consolidation function - should not raise an exception
+ consolidate_indirect_user_roles(apps, None)
+
+ # Both teams should have the user assigned
+ team_a_users = set(RoleUserAssignment.objects.filter(role_definition=team_member_role, object_id=team_a.id).values_list('user_id', flat=True))
+ team_b_users = set(RoleUserAssignment.objects.filter(role_definition=team_member_role, object_id=team_b.id).values_list('user_id', flat=True))
+
+ assert user.id in team_a_users, "User should be assigned to team A"
+ assert user.id in team_b_users, "User should be assigned to team B"
diff --git a/awx/main/tests/functional/dab_rbac/test_dab_rbac_api.py b/awx/main/tests/functional/dab_rbac/test_dab_rbac_api.py
new file mode 100644
index 000000000000..3b09272d8c84
--- /dev/null
+++ b/awx/main/tests/functional/dab_rbac/test_dab_rbac_api.py
@@ -0,0 +1,196 @@
+import pytest
+
+from django.urls import reverse as django_reverse
+
+from awx.api.versioning import reverse
+from awx.main.models import JobTemplate, Inventory, Organization
+from awx.main.access import JobTemplateAccess, WorkflowJobTemplateAccess
+
+from ansible_base.rbac.models import RoleDefinition
+from ansible_base.rbac import permission_registry
+
+
+@pytest.mark.django_db
+def test_managed_roles_created(setup_managed_roles):
+ "Managed RoleDefinitions are created in post_migration signal, we expect to see them here"
+ for cls in (JobTemplate, Inventory):
+ ct = permission_registry.content_type_model.objects.get_for_model(cls)
+ rds = list(RoleDefinition.objects.filter(content_type=ct))
+ assert len(rds) > 1
+ assert f'{cls.__name__} Admin' in [rd.name for rd in rds]
+ for rd in rds:
+ assert rd.managed is True
+
+
+@pytest.mark.django_db
+def test_custom_read_role(admin_user, post, setup_managed_roles):
+ rd_url = django_reverse('roledefinition-list')
+ resp = post(
+ url=rd_url,
+ data={"name": "read role made for test", "content_type": "awx.inventory", "permissions": ['awx.view_inventory']},
+ user=admin_user,
+ expect=201,
+ )
+ rd_id = resp.data['id']
+ rd = RoleDefinition.objects.get(id=rd_id)
+ assert rd.content_type == permission_registry.content_type_model.objects.get_for_model(Inventory)
+
+
+@pytest.mark.django_db
+def test_custom_system_roles_prohibited(admin_user, post):
+ rd_url = django_reverse('roledefinition-list')
+ resp = post(url=rd_url, data={"name": "read role made for test", "content_type": None, "permissions": ['awx.view_inventory']}, user=admin_user, expect=400)
+ assert 'System-wide roles are not enabled' in str(resp.data)
+
+
+@pytest.mark.django_db
+def test_assignment_to_invisible_user(admin_user, alice, rando, inventory, post, setup_managed_roles):
+ "Alice can not see rando, and so can not give them a role assignment"
+ rd = RoleDefinition.objects.get(name='Inventory Admin')
+ rd.give_permission(alice, inventory)
+ url = django_reverse('roleuserassignment-list')
+ r = post(url=url, data={"user": rando.id, "role_definition": rd.id, "object_id": inventory.id}, user=alice, expect=400)
+ assert 'does not exist' in str(r.data)
+ assert not rando.has_obj_perm(inventory, 'change')
+
+
+@pytest.mark.django_db
+def test_assign_managed_role(admin_user, alice, rando, inventory, post, setup_managed_roles, organization):
+ rd = RoleDefinition.objects.get(name='Inventory Admin')
+ rd.give_permission(alice, inventory)
+ # When alice and rando are members of the same org, they can see each other
+ member_rd = RoleDefinition.objects.get(name='Organization Member')
+ for u in (alice, rando):
+ member_rd.give_permission(u, organization)
+ # Now that alice has full permissions to the inventory, and can see rando, she will give rando permission
+ url = django_reverse('roleuserassignment-list')
+ post(url=url, data={"user": rando.id, "role_definition": rd.id, "object_id": inventory.id}, user=alice, expect=201)
+ assert rando.has_obj_perm(inventory, 'change') is True
+
+
+@pytest.mark.django_db
+def test_assign_custom_delete_role(admin_user, rando, inventory, delete, patch):
+ # TODO: just a delete_inventory, without change_inventory
+ rd, _ = RoleDefinition.objects.get_or_create(
+ name='inventory-delete',
+ permissions=['delete_inventory', 'view_inventory', 'change_inventory'],
+ content_type=permission_registry.content_type_model.objects.get_for_model(Inventory),
+ )
+ rd.give_permission(rando, inventory)
+ inv_id = inventory.pk
+ inv_url = reverse('api:inventory_detail', kwargs={'pk': inv_id})
+ # TODO: eventually this will be valid test, for now ignore
+ # patch(url=inv_url, data={"description": "new"}, user=rando, expect=403)
+ delete(url=inv_url, user=rando, expect=202)
+ assert Inventory.objects.get(id=inv_id).pending_deletion
+
+
+@pytest.mark.django_db
+def test_assign_custom_add_role(admin_user, rando, organization, post, setup_managed_roles):
+ rd, _ = RoleDefinition.objects.get_or_create(
+ name='inventory-add',
+ permissions=['add_inventory', 'view_organization'],
+ content_type=permission_registry.content_type_model.objects.get_for_model(Organization),
+ )
+ rd.give_permission(rando, organization)
+ url = reverse('api:inventory_list')
+ r = post(url=url, data={'name': 'abc', 'organization': organization.id}, user=rando, expect=201)
+ inv_id = r.data['id']
+ inventory = Inventory.objects.get(id=inv_id)
+ assert rando.has_obj_perm(inventory, 'change')
+
+
+@pytest.mark.django_db
+def test_jt_creation_permissions(setup_managed_roles, inventory, project, rando):
+ """This tests that if you assign someone required permissions in the new API
+ using the managed roles, then that works to give permissions to create a job template"""
+ inv_rd = RoleDefinition.objects.get(name='Inventory Admin')
+ proj_rd = RoleDefinition.objects.get(name='Project Admin')
+ # establish prior state
+ access = JobTemplateAccess(rando)
+ assert not access.can_add({'inventory': inventory.pk, 'project': project.pk, 'name': 'foo-jt'})
+
+ inv_rd.give_permission(rando, inventory)
+ proj_rd.give_permission(rando, project)
+
+ assert access.can_add({'inventory': inventory.pk, 'project': project.pk, 'name': 'foo-jt'})
+
+
+@pytest.mark.django_db
+def test_workflow_creation_permissions(setup_managed_roles, organization, workflow_job_template, rando):
+ """Similar to JT, assigning new roles gives creator permissions"""
+ org_wf_rd = RoleDefinition.objects.get(name='Organization WorkflowJobTemplate Admin')
+ assert workflow_job_template.organization == organization # sanity
+ # establish prior state
+ access = WorkflowJobTemplateAccess(rando)
+ assert not access.can_add({'name': 'foo-flow', 'organization': organization.pk})
+ org_wf_rd.give_permission(rando, organization)
+
+ assert access.can_add({'name': 'foo-flow', 'organization': organization.pk})
+
+
+@pytest.mark.django_db
+def test_assign_credential_to_user_of_another_org(setup_managed_roles, credential, admin_user, rando, org_admin, organization, post):
+ '''Test that a credential can only be assigned to a user in the same organization'''
+ # cannot assign credential to rando, as rando is not in the same org as the credential
+ rd = RoleDefinition.objects.get(name="Credential Admin")
+ credential.organization = organization
+ credential.save(update_fields=['organization'])
+ assert credential.organization not in Organization.access_qs(rando, 'member')
+ url = django_reverse('roleuserassignment-list')
+ resp = post(url=url, data={"user": rando.id, "role_definition": rd.id, "object_id": credential.id}, user=admin_user, expect=400)
+ assert "You cannot grant credential access to a User not in the credentials' organization" in str(resp.data)
+
+ # can assign credential to superuser
+ rando.is_superuser = True
+ rando.save()
+ post(url=url, data={"user": rando.id, "role_definition": rd.id, "object_id": credential.id}, user=admin_user, expect=201)
+
+ # can assign credential to org_admin
+ assert credential.organization in Organization.access_qs(org_admin, 'member')
+ post(url=url, data={"user": org_admin.id, "role_definition": rd.id, "object_id": credential.id}, user=admin_user, expect=201)
+
+
+@pytest.mark.django_db
+def test_adding_user_to_org_member_role(setup_managed_roles, organization, admin, bob, post, get):
+ '''
+ Adding user to organization member role via the legacy RBAC endpoints
+ should give them access to the organization detail
+ '''
+ url_detail = reverse('api:organization_detail', kwargs={'pk': organization.id})
+ get(url_detail, user=bob, expect=403)
+
+ role = organization.member_role
+ url = reverse('api:role_users_list', kwargs={'pk': role.id})
+ post(url, data={'id': bob.id}, user=admin, expect=204)
+
+ get(url_detail, user=bob, expect=200)
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize('actor', ['user', 'team'])
+@pytest.mark.parametrize('role_name', ['Organization Admin', 'Organization Member', 'Team Admin', 'Team Member'])
+def test_adding_actor_to_platform_roles(setup_managed_roles, role_name, actor, organization, team, admin, bob, post):
+ '''
+ Allow user to be added to platform-level roles
+ Exceptions:
+ - Team cannot be added to Organization Member or Admin role
+ - Team cannot be added to Team Admin or Team Member role
+ '''
+ if actor == 'team':
+ expect = 400
+ else:
+ expect = 201
+ rd = RoleDefinition.objects.get(name=role_name)
+ endpoint = 'roleuserassignment-list' if actor == 'user' else 'roleteamassignment-list'
+ url = django_reverse(endpoint)
+ object_id = team.id if 'Team' in role_name else organization.id
+ data = {'object_id': object_id, 'role_definition': rd.id}
+ actor_id = bob.id if actor == 'user' else team.id
+ data[actor] = actor_id
+ r = post(url, data=data, user=admin, expect=expect)
+ if expect == 400:
+ if 'Organization' in role_name:
+ assert 'Assigning organization member permission to teams is not allowed' in str(r.data)
+ if 'Team' in role_name:
+ assert 'Assigning team permissions to other teams is not allowed' in str(r.data)
diff --git a/awx/main/tests/functional/dab_rbac/test_external_auditor.py b/awx/main/tests/functional/dab_rbac/test_external_auditor.py
new file mode 100644
index 000000000000..c3602d736627
--- /dev/null
+++ b/awx/main/tests/functional/dab_rbac/test_external_auditor.py
@@ -0,0 +1,120 @@
+import pytest
+
+from django.apps import apps
+
+from ansible_base.rbac.managed import SystemAuditor
+from ansible_base.rbac import permission_registry
+
+from awx.main.access import check_user_access, get_user_queryset
+from awx.main.models import User, AdHocCommandEvent
+from awx.api.versioning import reverse
+
+
+@pytest.fixture
+def ext_auditor_rd():
+ info = SystemAuditor(overrides={'name': 'Alien Auditor', 'shortname': 'ext_auditor'})
+ rd, _ = info.get_or_create(apps)
+ return rd
+
+
+@pytest.fixture
+def ext_auditor(ext_auditor_rd):
+ u = User.objects.create(username='external-auditor-user')
+ ext_auditor_rd.give_global_permission(u)
+ return u
+
+
+@pytest.fixture
+def obj_factory(request):
+ def _rf(fixture_name):
+ obj = request.getfixturevalue(fixture_name)
+
+ # special case to make obj organization-scoped
+ if obj._meta.model_name == 'executionenvironment':
+ obj.organization = request.getfixturevalue('organization')
+ obj.save(update_fields=['organization'])
+
+ return obj
+
+ return _rf
+
+
+@pytest.mark.django_db
+def test_access_qs_external_auditor(ext_auditor_rd, rando, job_template):
+ ext_auditor_rd.give_global_permission(rando)
+ jt_cls = apps.get_model('main', 'JobTemplate')
+ ujt_cls = apps.get_model('main', 'UnifiedJobTemplate')
+ assert job_template in jt_cls.access_qs(rando)
+ assert job_template.id in jt_cls.access_ids_qs(rando)
+ assert job_template.id in ujt_cls.accessible_pk_qs(rando, 'read_role')
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize('model', sorted(permission_registry.all_registered_models, key=lambda cls: cls._meta.model_name))
+class TestExternalAuditorRoleAllModels:
+ def test_access_can_read_method(self, obj_factory, model, ext_auditor, rando):
+ fixture_name = model._meta.verbose_name.replace(' ', '_')
+ obj = obj_factory(fixture_name)
+
+ assert check_user_access(rando, model, 'read', obj) is False
+ assert check_user_access(ext_auditor, model, 'read', obj) is True
+
+ def test_access_get_queryset(self, obj_factory, model, ext_auditor, rando):
+ fixture_name = model._meta.verbose_name.replace(' ', '_')
+ obj = obj_factory(fixture_name)
+
+ assert obj not in get_user_queryset(rando, model)
+ assert obj in get_user_queryset(ext_auditor, model)
+
+ def test_global_list(self, obj_factory, model, ext_auditor, rando, get):
+ fixture_name = model._meta.verbose_name.replace(' ', '_')
+ obj_factory(fixture_name)
+
+ url = reverse(f'api:{fixture_name}_list')
+ r = get(url, user=rando, expect=200)
+ initial_ct = r.data['count']
+
+ r = get(url, user=ext_auditor, expect=200)
+ assert r.data['count'] == initial_ct + 1
+
+ if fixture_name in ('job_template', 'workflow_job_template'):
+ url = reverse('api:unified_job_template_list')
+ r = get(url, user=rando, expect=200)
+ initial_ct = r.data['count']
+
+ r = get(url, user=ext_auditor, expect=200)
+ assert r.data['count'] == initial_ct + 1
+
+ def test_detail_view(self, obj_factory, model, ext_auditor, rando, get):
+ fixture_name = model._meta.verbose_name.replace(' ', '_')
+ obj = obj_factory(fixture_name)
+
+ url = reverse(f'api:{fixture_name}_detail', kwargs={'pk': obj.pk})
+ get(url, user=rando, expect=403) # NOTE: should be 401
+ get(url, user=ext_auditor, expect=200)
+
+
+@pytest.mark.django_db
+class TestExternalAuditorNonRoleModels:
+ def test_ad_hoc_command_view(self, ad_hoc_command_factory, rando, ext_auditor, get):
+ """The AdHocCommandAccess class references is_system_auditor
+
+ this is to prove it works with other system-level view roles"""
+ ad_hoc_command = ad_hoc_command_factory()
+ url = reverse('api:ad_hoc_command_list')
+ r = get(url, user=rando, expect=200)
+ assert r.data['count'] == 0
+ r = get(url, user=ext_auditor, expect=200)
+ assert r.data['count'] == 1
+ assert r.data['results'][0]['id'] == ad_hoc_command.id
+
+ event = AdHocCommandEvent.objects.create(ad_hoc_command=ad_hoc_command)
+ url = reverse('api:ad_hoc_command_ad_hoc_command_events_list', kwargs={'pk': ad_hoc_command.id})
+ r = get(url, user=rando, expect=403)
+ r = get(url, user=ext_auditor, expect=200)
+ assert r.data['count'] == 1
+
+ url = reverse('api:ad_hoc_command_event_detail', kwargs={'pk': event.id})
+ r = get(url, user=rando, expect=403)
+ r = get(url, user=ext_auditor, expect=200)
+ assert r.data['id'] == event.id
diff --git a/awx/main/tests/functional/dab_rbac/test_managed_roles.py b/awx/main/tests/functional/dab_rbac/test_managed_roles.py
new file mode 100644
index 000000000000..82fd661fa593
--- /dev/null
+++ b/awx/main/tests/functional/dab_rbac/test_managed_roles.py
@@ -0,0 +1,56 @@
+import pytest
+
+from ansible_base.rbac.models import RoleDefinition, DABPermission, RoleUserAssignment
+
+
+@pytest.mark.django_db
+def test_roles_to_not_create(setup_managed_roles):
+ assert RoleDefinition.objects.filter(name='Organization Admin').count() == 1
+
+ SHOULD_NOT_EXIST = ('Organization Organization Admin', 'Organization Team Admin', 'Organization InstanceGroup Admin')
+
+ bad_rds = RoleDefinition.objects.filter(name__in=SHOULD_NOT_EXIST)
+ if bad_rds.exists():
+ bad_names = list(bad_rds.values_list('name', flat=True))
+ raise Exception(f'Found RoleDefinitions that should not exist: {bad_names}')
+
+
+@pytest.mark.django_db
+def test_org_admin_role(setup_managed_roles):
+ rd = RoleDefinition.objects.get(name='Organization Admin')
+ codenames = list(rd.permissions.values_list('codename', flat=True))
+ assert 'view_inventory' in codenames
+ assert 'change_inventory' in codenames
+
+
+@pytest.mark.django_db
+def test_project_update_role(setup_managed_roles):
+ """Role to allow updating a project on the object-level should exist"""
+ assert RoleDefinition.objects.filter(name='Project Update').count() == 1
+
+
+@pytest.mark.django_db
+def test_org_child_add_permission(setup_managed_roles):
+ for model_name in ('Project', 'NotificationTemplate', 'WorkflowJobTemplate', 'Inventory'):
+ rd = RoleDefinition.objects.get(name=f'Organization {model_name} Admin')
+ assert 'add_' in str(rd.permissions.values_list('codename', flat=True)), f'The {rd.name} role definition expected to contain add_ permissions'
+
+ # special case for JobTemplate, anyone can create one with use permission to project/inventory
+ assert not DABPermission.objects.filter(codename='add_jobtemplate').exists()
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize('resource_name', ['Team', 'Organization'])
+@pytest.mark.parametrize('action', ['Member', 'Admin'])
+def test_legacy_RBAC_uses_platform_roles(setup_managed_roles, resource_name, action, team, bob, organization):
+ '''
+ Assignment to legacy RBAC roles should use platform role definitions
+ e.g. Team Admin, Team Member, Organization Member, Organization Admin
+ '''
+ resource = team if resource_name == 'Team' else organization
+ if action == 'Member':
+ resource.member_role.members.add(bob)
+ else:
+ resource.admin_role.members.add(bob)
+ rd = RoleDefinition.objects.get(name=f'{resource_name} {action}')
+ assert RoleUserAssignment.objects.filter(role_definition=rd, user=bob, object_id=resource.id).exists()
diff --git a/awx/main/tests/functional/dab_rbac/test_translation_layer.py b/awx/main/tests/functional/dab_rbac/test_translation_layer.py
new file mode 100644
index 000000000000..98ee58f5b8c3
--- /dev/null
+++ b/awx/main/tests/functional/dab_rbac/test_translation_layer.py
@@ -0,0 +1,210 @@
+from unittest import mock
+import json
+
+import pytest
+
+from crum import impersonate
+
+from awx.main.fields import ImplicitRoleField
+from awx.main.models.rbac import get_role_from_object_role, give_creator_permissions, get_role_codenames, get_role_definition
+from awx.main.models import User, Organization, WorkflowJobTemplate, WorkflowJobTemplateNode, Team
+from awx.api.versioning import reverse
+
+from ansible_base.rbac.models import RoleUserAssignment, RoleDefinition
+from ansible_base.rbac import permission_registry
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize(
+ 'role_name',
+ [
+ 'execution_environment_admin_role',
+ 'workflow_admin_role',
+ 'project_admin_role',
+ 'admin_role',
+ 'auditor_role',
+ 'read_role',
+ 'execute_role',
+ 'approval_role',
+ 'notification_admin_role',
+ ],
+)
+def test_round_trip_roles(organization, rando, role_name, setup_managed_roles):
+ """
+ Make an assignment with the old-style role,
+ get the equivelent new role
+ get the old role again
+ """
+ getattr(organization, role_name).members.add(rando)
+ assignment = RoleUserAssignment.objects.get(user=rando)
+ old_role = get_role_from_object_role(assignment.object_role)
+ assert old_role.id == getattr(organization, role_name).id
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize('model', sorted(permission_registry.all_registered_models, key=lambda cls: cls._meta.model_name))
+def test_role_migration_matches(request, model, setup_managed_roles):
+ fixture_name = model._meta.verbose_name.replace(' ', '_')
+ obj = request.getfixturevalue(fixture_name)
+ role_ct = 0
+ for field in obj._meta.get_fields():
+ if isinstance(field, ImplicitRoleField):
+ if field.name == 'read_role':
+ continue # intentionally left as "Compat" roles
+ role_ct += 1
+ old_role = getattr(obj, field.name)
+ old_codenames = set(get_role_codenames(old_role))
+ rd = get_role_definition(old_role)
+ new_codenames = set(rd.permissions.values_list('codename', flat=True))
+ # all the old roles should map to a non-Compat role definition
+ if 'Compat' not in rd.name:
+ model_rds = RoleDefinition.objects.filter(content_type=permission_registry.content_type_model.objects.get_for_model(obj))
+ rd_data = {}
+ for rd in model_rds:
+ rd_data[rd.name] = list(rd.permissions.values_list('codename', flat=True))
+ assert (
+ 'Compat' not in rd.name
+ ), f'Permissions for old vs new roles did not match.\nold {field.name}: {old_codenames}\nnew:\n{json.dumps(rd_data, indent=2)}'
+ assert new_codenames == set(old_codenames)
+
+ # In the old system these models did not have object-level roles, all others expect some model roles
+ if model._meta.model_name not in ('notificationtemplate', 'executionenvironment'):
+ assert role_ct > 0
+
+
+@pytest.mark.django_db
+def test_role_naming(setup_managed_roles):
+ qs = RoleDefinition.objects.filter(content_type=permission_registry.content_type_model.objects.get(model='jobtemplate'), name__endswith='dmin')
+ assert qs.count() == 1 # sanity
+ rd = qs.first()
+ assert rd.name == 'JobTemplate Admin'
+ assert rd.description
+ assert rd.created_by is None
+
+
+@pytest.mark.django_db
+def test_action_role_naming(setup_managed_roles):
+ qs = RoleDefinition.objects.filter(content_type=permission_registry.content_type_model.objects.get(model='jobtemplate'), name__endswith='ecute')
+ assert qs.count() == 1 # sanity
+ rd = qs.first()
+ assert rd.name == 'JobTemplate Execute'
+ assert rd.description
+ assert rd.created_by is None
+
+
+@pytest.mark.django_db
+def test_compat_role_naming(setup_managed_roles, job_template, rando, alice):
+ with impersonate(alice):
+ job_template.read_role.members.add(rando)
+ qs = RoleDefinition.objects.filter(content_type=permission_registry.content_type_model.objects.get(model='jobtemplate'), name__endswith='ompat')
+ assert qs.count() == 1 # sanity
+ rd = qs.first()
+ assert rd.name == 'JobTemplate Read Compat'
+ assert rd.description
+ assert rd.created_by is None
+
+
+@pytest.mark.django_db
+def test_organization_admin_has_audit(setup_managed_roles):
+ """This formalizes a behavior change from old to new RBAC system
+
+ Previously, the auditor_role did not list admin_role as a parent
+ this made various queries hard to deal with, requiring adding 2 conditions
+ The new system should explicitly list the auditor permission in org admin role"""
+ rd = RoleDefinition.objects.get(name='Organization Admin')
+ assert 'audit_organization' in rd.permissions.values_list('codename', flat=True)
+
+
+@pytest.mark.django_db
+def test_organization_level_permissions(organization, inventory, setup_managed_roles):
+ u1 = User.objects.create(username='alice')
+ u2 = User.objects.create(username='bob')
+
+ organization.inventory_admin_role.members.add(u1)
+ organization.workflow_admin_role.members.add(u2)
+
+ assert u1 in inventory.admin_role
+ assert u1 in organization.inventory_admin_role
+ assert u2 in organization.workflow_admin_role
+
+ assert u2 not in organization.inventory_admin_role
+ assert u1 not in organization.workflow_admin_role
+ assert not (set(u1.has_roles.all()) & set(u2.has_roles.all())) # user have no roles in common
+
+ # Old style
+ assert set(Organization.accessible_objects(u1, 'inventory_admin_role')) == set([organization])
+ assert set(Organization.accessible_objects(u2, 'inventory_admin_role')) == set()
+ assert set(Organization.accessible_objects(u1, 'workflow_admin_role')) == set()
+ assert set(Organization.accessible_objects(u2, 'workflow_admin_role')) == set([organization])
+
+ # New style
+ assert set(Organization.access_qs(u1, 'add_inventory')) == set([organization])
+ assert set(Organization.access_qs(u1, 'change_inventory')) == set([organization])
+ assert set(Organization.access_qs(u2, 'add_inventory')) == set()
+ assert set(Organization.access_qs(u1, 'add_workflowjobtemplate')) == set()
+ assert set(Organization.access_qs(u2, 'add_workflowjobtemplate')) == set([organization])
+
+
+@pytest.mark.django_db
+def test_organization_execute_role(organization, rando, setup_managed_roles):
+ organization.execute_role.members.add(rando)
+ assert rando in organization.execute_role
+ assert set(Organization.accessible_objects(rando, 'execute_role')) == set([organization])
+
+
+@pytest.mark.django_db
+def test_workflow_approval_list(get, post, admin_user, setup_managed_roles):
+ workflow_job_template = WorkflowJobTemplate.objects.create()
+ approval_node = WorkflowJobTemplateNode.objects.create(workflow_job_template=workflow_job_template)
+ url = reverse('api:workflow_job_template_node_create_approval', kwargs={'pk': approval_node.pk, 'version': 'v2'})
+ post(url, {'name': 'URL Test', 'description': 'An approval', 'timeout': 0}, user=admin_user)
+ approval_node.refresh_from_db()
+ approval_jt = approval_node.unified_job_template
+ approval_jt.create_unified_job()
+
+ r = get(url=reverse('api:workflow_approval_list'), user=admin_user, expect=200)
+ assert r.data['count'] >= 1
+
+
+@pytest.mark.django_db
+def test_creator_permission(rando, admin_user, inventory, setup_managed_roles):
+ give_creator_permissions(rando, inventory)
+ assert rando in inventory.admin_role
+ assert rando in inventory.admin_role.members.all()
+
+
+@pytest.mark.django_db
+def test_implicit_parents_no_assignments(organization):
+ """Through the normal course of creating models, we should not be changing DAB RBAC permissions"""
+ with mock.patch('awx.main.models.rbac.give_or_remove_permission') as mck:
+ Team.objects.create(name='random team', organization=organization)
+ mck.assert_not_called()
+
+
+@pytest.mark.django_db
+def test_user_auditor_rel(organization, rando, setup_managed_roles):
+ assert rando not in organization.auditor_role
+ audit_rd = RoleDefinition.objects.get(name='Organization Audit')
+ audit_rd.give_permission(rando, organization)
+ assert list(Organization.access_qs(rando, 'audit')) == [organization]
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize('resource_name', ['Organization', 'Team'])
+@pytest.mark.parametrize('role_name', ['Member', 'Admin'])
+def test_mapping_from_role_definitions_to_roles(organization, team, rando, role_name, resource_name, setup_managed_roles):
+ """
+ ensure mappings for platform roles are correct
+ e.g.
+ Organization Member > organization.member_role
+ Organization Admin > organization.admin_role
+ Team Member > team.member_role
+ Team Admin > team.admin_role
+ """
+ resource = organization if resource_name == 'Organization' else team
+ old_role_name = f"{role_name.lower()}_role"
+ getattr(resource, old_role_name).members.add(rando)
+ assignment = RoleUserAssignment.objects.get(user=rando)
+ assert assignment.role_definition.name == f'{resource_name} {role_name}'
+ old_role = get_role_from_object_role(assignment.object_role)
+ assert old_role.id == getattr(resource, old_role_name).id
diff --git a/awx/main/tests/functional/dab_rbac/test_translation_layer_new_to_old.py b/awx/main/tests/functional/dab_rbac/test_translation_layer_new_to_old.py
new file mode 100644
index 000000000000..92efef838769
--- /dev/null
+++ b/awx/main/tests/functional/dab_rbac/test_translation_layer_new_to_old.py
@@ -0,0 +1,80 @@
+from ansible_base.rbac.models import RoleDefinition, RoleUserAssignment, RoleTeamAssignment
+from ansible_base.lib.utils.response import get_relative_url
+import pytest
+
+
+@pytest.mark.django_db
+class TestNewToOld:
+ '''
+ Tests that the DAB RBAC system is correctly translated to the old RBAC system
+ Namely, tests functionality of the _sync_assignments_to_old_rbac signal handler
+ '''
+
+ def test_new_to_old_rbac_addition(self, admin, post, inventory, bob, setup_managed_roles):
+ '''
+ Assign user to Inventory Admin role definition, should be added to inventory.admin_role.members
+ '''
+ rd = RoleDefinition.objects.get(name='Inventory Admin')
+
+ url = get_relative_url('roleuserassignment-list')
+ post(url, user=admin, data={'role_definition': rd.id, 'user': bob.id, 'object_id': inventory.id}, expect=201)
+ assert bob in inventory.admin_role.members.all()
+
+ def test_new_to_old_rbac_removal(self, admin, delete, inventory, bob, setup_managed_roles):
+ '''
+ Remove user from Inventory Admin role definition, should be deleted from inventory.admin_role.members
+ '''
+ inventory.admin_role.members.add(bob)
+
+ rd = RoleDefinition.objects.get(name='Inventory Admin')
+ user_assignment = RoleUserAssignment.objects.get(user=bob, role_definition=rd, object_id=inventory.id)
+
+ url = get_relative_url('roleuserassignment-detail', kwargs={'pk': user_assignment.id})
+ delete(url, user=admin, expect=204)
+ assert bob not in inventory.admin_role.members.all()
+
+ def test_new_to_old_rbac_team_member_addition(self, admin, post, team, bob, setup_managed_roles):
+ '''
+ Assign user to Team Member role definition, should be added to team.member_role.members
+ '''
+ rd = RoleDefinition.objects.get(name='Team Member')
+
+ url = get_relative_url('roleuserassignment-list')
+ post(url, user=admin, data={'role_definition': rd.id, 'user': bob.id, 'object_id': team.id}, expect=201)
+ assert bob in team.member_role.members.all()
+
+ def test_new_to_old_rbac_team_member_removal(self, admin, delete, team, bob, setup_managed_roles):
+ '''
+ Remove user from Team Member role definition, should be deleted from team.member_role.members
+ '''
+ team.member_role.members.add(bob)
+
+ rd = RoleDefinition.objects.get(name='Team Member')
+ user_assignment = RoleUserAssignment.objects.get(user=bob, role_definition=rd, object_id=team.id)
+
+ url = get_relative_url('roleuserassignment-detail', kwargs={'pk': user_assignment.id})
+ delete(url, user=admin, expect=204)
+ assert bob not in team.member_role.members.all()
+
+ def test_new_to_old_rbac_team_addition(self, admin, post, team, inventory, setup_managed_roles):
+ '''
+ Assign team to Inventory Admin role definition, should be added to inventory.admin_role.parents
+ '''
+ rd = RoleDefinition.objects.get(name='Inventory Admin')
+
+ url = get_relative_url('roleteamassignment-list')
+ post(url, user=admin, data={'role_definition': rd.id, 'team': team.id, 'object_id': inventory.id}, expect=201)
+ assert team.member_role in inventory.admin_role.parents.all()
+
+ def test_new_to_old_rbac_team_removal(self, admin, delete, team, inventory, setup_managed_roles):
+ '''
+ Remove team from Inventory Admin role definition, should be deleted from inventory.admin_role.parents
+ '''
+ inventory.admin_role.parents.add(team.member_role)
+
+ rd = RoleDefinition.objects.get(name='Inventory Admin')
+ team_assignment = RoleTeamAssignment.objects.get(team=team, role_definition=rd, object_id=inventory.id)
+
+ url = get_relative_url('roleteamassignment-detail', kwargs={'pk': team_assignment.id})
+ delete(url, user=admin, expect=204)
+ assert team.member_role not in inventory.admin_role.parents.all()
diff --git a/awx/main/tests/functional/dab_resource_registry/test_ansible_id_display.py b/awx/main/tests/functional/dab_resource_registry/test_ansible_id_display.py
new file mode 100644
index 000000000000..bf0d5502628b
--- /dev/null
+++ b/awx/main/tests/functional/dab_resource_registry/test_ansible_id_display.py
@@ -0,0 +1,39 @@
+import pytest
+
+from ansible_base.resource_registry.models import Resource
+
+from awx.api.versioning import reverse
+
+
+def assert_has_resource(list_response, obj=None):
+ data = list_response.data
+ assert 'resource' in data['results'][0]['summary_fields']
+ resource_data = data['results'][0]['summary_fields']['resource']
+ assert resource_data['ansible_id']
+ resource = Resource.objects.filter(ansible_id=resource_data['ansible_id']).first()
+ assert resource
+ assert resource.content_object
+ if obj:
+ objects = [Resource.objects.get(ansible_id=entry['summary_fields']['resource']['ansible_id']).content_object for entry in data['results']]
+ assert obj in objects
+
+
+@pytest.mark.django_db
+def test_organization_ansible_id(organization, admin_user, get):
+ url = reverse('api:organization_list')
+ response = get(url=url, user=admin_user, expect=200)
+ assert_has_resource(response, obj=organization)
+
+
+@pytest.mark.django_db
+def test_team_ansible_id(team, admin_user, get):
+ url = reverse('api:team_list')
+ response = get(url=url, user=admin_user, expect=200)
+ assert_has_resource(response, obj=team)
+
+
+@pytest.mark.django_db
+def test_user_ansible_id(rando, admin_user, get):
+ url = reverse('api:user_list')
+ response = get(url=url, user=admin_user, expect=200)
+ assert_has_resource(response, obj=rando)
diff --git a/awx/main/tests/functional/dab_resource_registry/test_resource_list.py b/awx/main/tests/functional/dab_resource_registry/test_resource_list.py
new file mode 100644
index 000000000000..b57030fb46bc
--- /dev/null
+++ b/awx/main/tests/functional/dab_resource_registry/test_resource_list.py
@@ -0,0 +1,15 @@
+import pytest
+
+from ansible_base.lib.utils.response import get_relative_url
+
+
+@pytest.mark.django_db
+def test_users_in_resource_list(admin_user, rando, get):
+ url = get_relative_url("resource-list")
+ get(url=url, expect=200, user=admin_user)
+
+
+@pytest.mark.django_db
+def test_user_resource_detail(admin_user, rando, get):
+ url = get_relative_url("resource-detail", kwargs={'ansible_id': str(rando.resource.ansible_id)})
+ get(url=url, expect=200, user=admin_user)
diff --git a/awx/main/tests/functional/management/test_dispatcherd.py b/awx/main/tests/functional/management/test_dispatcherd.py
new file mode 100644
index 000000000000..92f1d208d9a8
--- /dev/null
+++ b/awx/main/tests/functional/management/test_dispatcherd.py
@@ -0,0 +1,17 @@
+import pytest
+
+from awx.main.dispatch.config import get_dispatcherd_config
+from awx.main.management.commands.dispatcherd import _hash_config
+
+
+@pytest.mark.django_db
+def test_dispatcherd_config_hash_is_stable(settings, monkeypatch):
+ monkeypatch.setenv('AWX_COMPONENT', 'dispatcher')
+ settings.CLUSTER_HOST_ID = 'test-node'
+ settings.JOB_EVENT_WORKERS = 1
+ settings.DISPATCHER_SCHEDULE = {}
+
+ config_one = get_dispatcherd_config(for_service=True)
+ config_two = get_dispatcherd_config(for_service=True)
+
+ assert _hash_config(config_one) == _hash_config(config_two)
diff --git a/awx/main/tests/functional/test_inventory_source_migration.py b/awx/main/tests/functional/migrations/test_inventory_source_migration.py
similarity index 84%
rename from awx/main/tests/functional/test_inventory_source_migration.py
rename to awx/main/tests/functional/migrations/test_inventory_source_migration.py
index 812b4b45b940..6d17e22936cf 100644
--- a/awx/main/tests/functional/test_inventory_source_migration.py
+++ b/awx/main/tests/functional/migrations/test_inventory_source_migration.py
@@ -31,15 +31,30 @@ def test_apply_new_instance_id(inventory_source):
assert host2.instance_id == 'bad_user'
-@pytest.mark.django_db
-def test_cloudforms_inventory_removal(inventory):
- ManagedCredentialType(
+def cleanup_cloudforms():
+ if 'cloudforms' in ManagedCredentialType.registry:
+ del ManagedCredentialType.registry['cloudforms']
+ assert 'cloudforms' not in CredentialType.defaults
+
+
+@pytest.fixture
+def cloudforms_mct():
+ ManagedCredentialType.registry['cloudforms'] = ManagedCredentialType(
name='Red Hat CloudForms',
namespace='cloudforms',
kind='cloud',
managed=True,
inputs={},
+ injectors={},
)
+ yield
+ ManagedCredentialType.registry.pop('cloudforms', None)
+
+
+@pytest.mark.django_db
+def test_cloudforms_inventory_removal(request, inventory, cloudforms_mct):
+ request.addfinalizer(cleanup_cloudforms)
+
CredentialType.defaults['cloudforms']().save()
cloudforms = CredentialType.objects.get(namespace='cloudforms')
Credential.objects.create(
diff --git a/awx/main/tests/functional/migrations/test_jt_rename_migration.py b/awx/main/tests/functional/migrations/test_jt_rename_migration.py
new file mode 100644
index 000000000000..4d624c41be2c
--- /dev/null
+++ b/awx/main/tests/functional/migrations/test_jt_rename_migration.py
@@ -0,0 +1,56 @@
+import pytest
+
+from awx.main.migrations._db_constraints import _rename_duplicates
+from awx.main.models import JobTemplate
+
+
+@pytest.mark.django_db
+def test_rename_job_template_duplicates(organization, project):
+ ids = []
+ for i in range(5):
+ jt = JobTemplate.objects.create(name=f'jt-{i}', organization=organization, project=project)
+ ids.append(jt.id) # saved in order of creation
+
+ # Hack to first allow duplicate names of JT to test migration
+ JobTemplate.objects.filter(id__in=ids).update(org_unique=False)
+
+ # Set all JTs to the same name
+ JobTemplate.objects.filter(id__in=ids).update(name='same_name_for_test')
+
+ _rename_duplicates(JobTemplate)
+
+ first_jt = JobTemplate.objects.get(id=ids[0])
+ assert first_jt.name == 'same_name_for_test'
+
+ for i, pk in enumerate(ids):
+ if i == 0:
+ continue
+ jt = JobTemplate.objects.get(id=pk)
+ # Name should be set based on creation order
+ assert jt.name == f'same_name_for_test_dup{i}'
+
+
+@pytest.mark.django_db
+def test_rename_job_template_name_too_long(organization, project):
+ ids = []
+ for i in range(3):
+ jt = JobTemplate.objects.create(name=f'jt-{i}', organization=organization, project=project)
+ ids.append(jt.id) # saved in order of creation
+
+ JobTemplate.objects.filter(id__in=ids).update(org_unique=False)
+
+ chars = 512
+ # Set all JTs to the same reaaaaaaly long name
+ JobTemplate.objects.filter(id__in=ids).update(name='A' * chars)
+
+ _rename_duplicates(JobTemplate)
+
+ first_jt = JobTemplate.objects.get(id=ids[0])
+ assert first_jt.name == 'A' * chars
+
+ for i, pk in enumerate(ids):
+ if i == 0:
+ continue
+ jt = JobTemplate.objects.get(id=pk)
+ assert jt.name.endswith(f'dup{i}')
+ assert len(jt.name) <= 512
diff --git a/awx/main/tests/functional/migrations/test_org_admin_migration.py b/awx/main/tests/functional/migrations/test_org_admin_migration.py
new file mode 100644
index 000000000000..84bed9ac8839
--- /dev/null
+++ b/awx/main/tests/functional/migrations/test_org_admin_migration.py
@@ -0,0 +1,16 @@
+import pytest
+
+from django.apps import apps
+
+from awx.main.models import InstanceGroup
+from awx.main.migrations import _OrgAdmin_to_use_ig as orgadmin
+
+
+@pytest.mark.django_db
+def test_migrate_admin_role(org_admin, organization):
+ instance_group = InstanceGroup.objects.create(name='test')
+ organization.admin_role.members.add(org_admin)
+ organization.instance_groups.add(instance_group)
+ orgadmin.migrate_org_admin_to_use(apps, None)
+ assert org_admin in instance_group.use_role.members.all()
+ assert instance_group.use_role.members.count() == 1
diff --git a/awx/main/tests/functional/migrations/test_rbac_migration.py b/awx/main/tests/functional/migrations/test_rbac_migration.py
new file mode 100644
index 000000000000..8ee411ba1a5a
--- /dev/null
+++ b/awx/main/tests/functional/migrations/test_rbac_migration.py
@@ -0,0 +1,49 @@
+import pytest
+
+from awx.main.migrations import _rbac as rbac
+from awx.main.models import UnifiedJobTemplate, InventorySource, Inventory, JobTemplate, Project, Organization
+
+
+@pytest.mark.django_db
+def test_implied_organization_subquery_inventory():
+ orgs = []
+ for i in range(3):
+ orgs.append(Organization.objects.create(name='foo{}'.format(i)))
+ orgs.append(orgs[0])
+ for i in range(4):
+ org = orgs[i]
+ if i == 2:
+ inventory = Inventory.objects.create(name='foo{}'.format(i))
+ else:
+ inventory = Inventory.objects.create(name='foo{}'.format(i), organization=org)
+ inv_src = InventorySource.objects.create(name='foo{}'.format(i), inventory=inventory, source='ec2')
+ sources = UnifiedJobTemplate.objects.annotate(test_field=rbac.implicit_org_subquery(UnifiedJobTemplate, InventorySource))
+ for inv_src in sources:
+ assert inv_src.test_field == inv_src.inventory.organization_id
+
+
+@pytest.mark.django_db
+def test_implied_organization_subquery_job_template():
+ jts = []
+ for i in range(5):
+ if i <= 3:
+ org = Organization.objects.create(name='foo{}'.format(i))
+ else:
+ org = None
+ if i <= 4:
+ proj = Project.objects.create(name='foo{}'.format(i), organization=org)
+ else:
+ proj = None
+ jts.append(JobTemplate.objects.create(name='foo{}'.format(i), project=proj))
+ # test case of sharing same org
+ jts[2].project.organization = jts[3].project.organization
+ jts[2].save()
+ ujts = UnifiedJobTemplate.objects.annotate(test_field=rbac.implicit_org_subquery(UnifiedJobTemplate, JobTemplate))
+ for jt in ujts:
+ if not isinstance(jt, JobTemplate): # some are projects
+ assert jt.test_field is None
+ else:
+ if jt.project is None:
+ assert jt.test_field is None
+ else:
+ assert jt.test_field == jt.project.organization_id
diff --git a/awx/main/tests/functional/migrations/test_token_sjt_removal.py b/awx/main/tests/functional/migrations/test_token_sjt_removal.py
new file mode 100644
index 000000000000..35770fd4ca4e
--- /dev/null
+++ b/awx/main/tests/functional/migrations/test_token_sjt_removal.py
@@ -0,0 +1,58 @@
+import pytest
+from django.apps import apps
+from django.utils.timezone import now
+
+from awx.main.migrations._create_system_jobs import delete_clear_tokens_sjt
+
+SJT_NAME = 'Cleanup Expired OAuth 2 Tokens'
+
+
+def create_cleartokens_jt(apps, schema_editor):
+ # Deleted data migration
+ SystemJobTemplate = apps.get_model('main', 'SystemJobTemplate')
+ Schedule = apps.get_model('main', 'Schedule')
+ ContentType = apps.get_model('contenttypes', 'ContentType')
+ sjt_ct = ContentType.objects.get_for_model(SystemJobTemplate)
+ now_dt = now()
+ schedule_time = now_dt.strftime('%Y%m%dT%H%M%SZ')
+
+ sjt, created = SystemJobTemplate.objects.get_or_create(
+ job_type='cleanup_tokens',
+ defaults=dict(
+ name=SJT_NAME,
+ description='Cleanup expired OAuth 2 access and refresh tokens',
+ polymorphic_ctype=sjt_ct,
+ created=now_dt,
+ modified=now_dt,
+ ),
+ )
+ if created:
+ sched = Schedule(
+ name=SJT_NAME,
+ rrule='DTSTART:%s RRULE:FREQ=WEEKLY;INTERVAL=1' % schedule_time,
+ description='Removes expired OAuth 2 access and refresh tokens',
+ enabled=True,
+ created=now_dt,
+ modified=now_dt,
+ extra_data={},
+ )
+ sched.unified_job_template = sjt
+ sched.save()
+
+
+@pytest.mark.django_db
+def test_clear_token_sjt():
+ SystemJobTemplate = apps.get_model('main', 'SystemJobTemplate')
+ Schedule = apps.get_model('main', 'Schedule')
+ create_cleartokens_jt(apps, None)
+ qs = SystemJobTemplate.objects.filter(name=SJT_NAME)
+ assert qs.count() == 1
+ sjt = qs.first()
+ assert Schedule.objects.filter(unified_job_template=sjt).count() == 1
+ assert Schedule.objects.filter(unified_job_template__systemjobtemplate__name=SJT_NAME).count() == 1
+
+ # Now run the migration logic to remove
+ delete_clear_tokens_sjt(apps, None)
+ assert SystemJobTemplate.objects.filter(name=SJT_NAME).count() == 0
+ # Making sure that the schedule is cleaned up is the main point of this test
+ assert Schedule.objects.filter(unified_job_template__systemjobtemplate__name=SJT_NAME).count() == 0
diff --git a/awx/main/tests/functional/models/test_activity_stream.py b/awx/main/tests/functional/models/test_activity_stream.py
index f8ae40b54054..8be052628d97 100644
--- a/awx/main/tests/functional/models/test_activity_stream.py
+++ b/awx/main/tests/functional/models/test_activity_stream.py
@@ -104,11 +104,13 @@ def test_auditor_is_recorded(self, post, value):
else:
assert len(entry_qs) == 1
# unfortunate, the original creation does _not_ set a real is_auditor field
- assert 'is_system_auditor' not in json.loads(entry_qs[0].changes)
+ assert 'is_system_auditor' not in json.loads(entry_qs[0].changes) # NOTE: if this fails, see special note
+ # special note - if system auditor flag is moved to user model then we expect this assertion to be changed
+ # make sure that an extra entry is not created, expectation for count would change to 1
if value:
- auditor_changes = json.loads(entry_qs[1].changes)
- assert auditor_changes['object2'] == 'user'
- assert auditor_changes['object2_pk'] == u.pk
+ entry = entry_qs[1]
+ assert json.loads(entry.changes) == {'is_system_auditor': [False, True]}
+ assert entry.object1 == 'user'
def test_user_no_op_api(self, system_auditor):
as_ct = ActivityStream.objects.count()
diff --git a/awx/main/tests/functional/models/test_context_managers.py b/awx/main/tests/functional/models/test_context_managers.py
index 9807d8a6e9cb..7dfc0da11729 100644
--- a/awx/main/tests/functional/models/test_context_managers.py
+++ b/awx/main/tests/functional/models/test_context_managers.py
@@ -1,7 +1,6 @@
import pytest
# AWX context managers for testing
-from awx.main.models.rbac import batch_role_ancestor_rebuilding
from awx.main.signals import disable_activity_stream, disable_computed_fields, update_inventory_computed_fields
# AWX models
@@ -10,15 +9,6 @@
from awx.main.tests.functional import immediate_on_commit
-@pytest.mark.django_db
-def test_rbac_batch_rebuilding(rando, organization):
- with batch_role_ancestor_rebuilding():
- organization.admin_role.members.add(rando)
- inventory = organization.inventories.create(name='test-inventory')
- assert rando not in inventory.admin_role
- assert rando in inventory.admin_role
-
-
@pytest.mark.django_db
def test_disable_activity_stream():
with disable_activity_stream():
@@ -31,13 +21,13 @@ class TestComputedFields:
def test_computed_fields_normal_use(self, mocker, inventory):
job = Job.objects.create(name='fake-job', inventory=inventory)
with immediate_on_commit():
- with mocker.patch.object(update_inventory_computed_fields, 'delay'):
- job.delete()
- update_inventory_computed_fields.delay.assert_called_once_with(inventory.id)
+ mocker.patch.object(update_inventory_computed_fields, 'delay')
+ job.delete()
+ update_inventory_computed_fields.delay.assert_called_once_with(inventory.id)
def test_disable_computed_fields(self, mocker, inventory):
job = Job.objects.create(name='fake-job', inventory=inventory)
with disable_computed_fields():
- with mocker.patch.object(update_inventory_computed_fields, 'delay'):
- job.delete()
- update_inventory_computed_fields.delay.assert_not_called()
+ mocker.patch.object(update_inventory_computed_fields, 'delay')
+ job.delete()
+ update_inventory_computed_fields.delay.assert_not_called()
diff --git a/awx/main/tests/functional/test_db_credential.py b/awx/main/tests/functional/models/test_db_credential.py
similarity index 100%
rename from awx/main/tests/functional/test_db_credential.py
rename to awx/main/tests/functional/models/test_db_credential.py
diff --git a/awx/main/tests/functional/models/test_events.py b/awx/main/tests/functional/models/test_events.py
index 758e69b64146..51c1adf529ee 100644
--- a/awx/main/tests/functional/models/test_events.py
+++ b/awx/main/tests/functional/models/test_events.py
@@ -3,178 +3,212 @@
from django.utils.timezone import now
-from awx.main.models import Job, JobEvent, Inventory, Host, JobHostSummary
+from django.db.models import Q
-
-@pytest.mark.django_db
-@mock.patch('awx.main.models.events.emit_event_detail')
-def test_parent_changed(emit):
- j = Job()
- j.save()
- JobEvent.create_from_data(job_id=j.pk, uuid='abc123', event='playbook_on_task_start').save()
- assert JobEvent.objects.count() == 1
- for e in JobEvent.objects.all():
- assert e.changed is False
-
- JobEvent.create_from_data(job_id=j.pk, parent_uuid='abc123', event='runner_on_ok', event_data={'res': {'changed': ['localhost']}}).save()
- # the `playbook_on_stats` event is where we update the parent changed linkage
- JobEvent.create_from_data(job_id=j.pk, parent_uuid='abc123', event='playbook_on_stats').save()
- events = JobEvent.objects.filter(event__in=['playbook_on_task_start', 'runner_on_ok'])
- assert events.count() == 2
- for e in events.all():
- assert e.changed is True
-
-
-@pytest.mark.django_db
-@pytest.mark.parametrize('event', JobEvent.FAILED_EVENTS)
-@mock.patch('awx.main.models.events.emit_event_detail')
-def test_parent_failed(emit, event):
- j = Job()
- j.save()
- JobEvent.create_from_data(job_id=j.pk, uuid='abc123', event='playbook_on_task_start').save()
- assert JobEvent.objects.count() == 1
- for e in JobEvent.objects.all():
- assert e.failed is False
-
- JobEvent.create_from_data(job_id=j.pk, parent_uuid='abc123', event=event).save()
-
- # the `playbook_on_stats` event is where we update the parent failed linkage
- JobEvent.create_from_data(job_id=j.pk, parent_uuid='abc123', event='playbook_on_stats').save()
- events = JobEvent.objects.filter(event__in=['playbook_on_task_start', event])
- assert events.count() == 2
- for e in events.all():
- assert e.failed is True
-
-
-@pytest.mark.django_db
-def test_host_summary_generation():
- hostnames = [f'Host {i}' for i in range(100)]
- inv = Inventory()
- inv.save()
- Host.objects.bulk_create([Host(created=now(), modified=now(), name=h, inventory_id=inv.id) for h in hostnames])
- j = Job(inventory=inv)
- j.save()
- host_map = dict((host.name, host.id) for host in inv.hosts.all())
- JobEvent.create_from_data(
- job_id=j.pk,
- parent_uuid='abc123',
- event='playbook_on_stats',
- event_data={
- 'ok': dict((hostname, len(hostname)) for hostname in hostnames),
- 'changed': {},
- 'dark': {},
- 'failures': {},
- 'ignored': {},
- 'processed': {},
- 'rescued': {},
- 'skipped': {},
- },
- host_map=host_map,
- ).save()
-
- assert j.job_host_summaries.count() == len(hostnames)
- assert sorted([s.host_name for s in j.job_host_summaries.all()]) == sorted(hostnames)
-
- for s in j.job_host_summaries.all():
- assert host_map[s.host_name] == s.host_id
- assert s.ok == len(s.host_name)
- assert s.changed == 0
- assert s.dark == 0
- assert s.failures == 0
- assert s.ignored == 0
- assert s.processed == 0
- assert s.rescued == 0
- assert s.skipped == 0
-
- for host in Host.objects.all():
- assert host.last_job_id == j.id
- assert host.last_job_host_summary.host == host
-
-
-@pytest.mark.django_db
-def test_host_summary_generation_with_deleted_hosts():
- hostnames = [f'Host {i}' for i in range(10)]
- inv = Inventory()
- inv.save()
- Host.objects.bulk_create([Host(created=now(), modified=now(), name=h, inventory_id=inv.id) for h in hostnames])
- j = Job(inventory=inv)
- j.save()
- host_map = dict((host.name, host.id) for host in inv.hosts.all())
-
- # delete half of the hosts during the playbook run
- for h in inv.hosts.all()[:5]:
- h.delete()
-
- JobEvent.create_from_data(
- job_id=j.pk,
- parent_uuid='abc123',
- event='playbook_on_stats',
- event_data={
- 'ok': dict((hostname, len(hostname)) for hostname in hostnames),
- 'changed': {},
- 'dark': {},
- 'failures': {},
- 'ignored': {},
- 'processed': {},
- 'rescued': {},
- 'skipped': {},
- },
- host_map=host_map,
- ).save()
-
- ids = sorted([s.host_id or -1 for s in j.job_host_summaries.order_by('id').all()])
- names = sorted([s.host_name for s in j.job_host_summaries.all()])
- assert ids == [-1, -1, -1, -1, -1, 6, 7, 8, 9, 10]
- assert names == ['Host 0', 'Host 1', 'Host 2', 'Host 3', 'Host 4', 'Host 5', 'Host 6', 'Host 7', 'Host 8', 'Host 9']
+from awx.main.models import Job, JobEvent, Inventory, Host, JobHostSummary, HostMetric
@pytest.mark.django_db
-def test_host_summary_generation_with_limit():
- # Make an inventory with 10 hosts, run a playbook with a --limit
- # pointed at *one* host,
- # Verify that *only* that host has an associated JobHostSummary and that
- # *only* that host has an updated value for .last_job.
- hostnames = [f'Host {i}' for i in range(10)]
- inv = Inventory()
- inv.save()
- Host.objects.bulk_create([Host(created=now(), modified=now(), name=h, inventory_id=inv.id) for h in hostnames])
- j = Job(inventory=inv)
- j.save()
-
- # host map is a data structure that tracks a mapping of host name --> ID
- # for the inventory, _regardless_ of whether or not there's a limit
- # applied to the actual playbook run
- host_map = dict((host.name, host.id) for host in inv.hosts.all())
-
- # by making the playbook_on_stats *only* include Host 1, we're emulating
- # the behavior of a `--limit=Host 1`
- matching_host = Host.objects.get(name='Host 1')
- JobEvent.create_from_data(
- job_id=j.pk,
+class TestEvents:
+ def setup_method(self):
+ self.hostnames = []
+ self.host_map = dict()
+ self.inventory = None
+ self.job = None
+
+ @mock.patch('awx.main.models.events.emit_event_detail')
+ def test_parent_changed(self, emit):
+ j = Job()
+ j.save()
+ JobEvent.create_from_data(job_id=j.pk, uuid='abc123', event='playbook_on_task_start').save()
+ assert JobEvent.objects.count() == 1
+ for e in JobEvent.objects.all():
+ assert e.changed is False
+
+ JobEvent.create_from_data(job_id=j.pk, parent_uuid='abc123', event='runner_on_ok', event_data={'res': {'changed': ['localhost']}}).save()
+ # the `playbook_on_stats` event is where we update the parent changed linkage
+ JobEvent.create_from_data(job_id=j.pk, parent_uuid='abc123', event='playbook_on_stats').save()
+ events = JobEvent.objects.filter(event__in=['playbook_on_task_start', 'runner_on_ok'])
+ assert events.count() == 2
+ for e in events.all():
+ assert e.changed is True
+
+ @pytest.mark.parametrize('event', JobEvent.FAILED_EVENTS)
+ @mock.patch('awx.main.models.events.emit_event_detail')
+ def test_parent_failed(self, emit, event):
+ j = Job()
+ j.save()
+ JobEvent.create_from_data(job_id=j.pk, uuid='abc123', event='playbook_on_task_start').save()
+ assert JobEvent.objects.count() == 1
+ for e in JobEvent.objects.all():
+ assert e.failed is False
+
+ JobEvent.create_from_data(job_id=j.pk, parent_uuid='abc123', event=event).save()
+
+ # the `playbook_on_stats` event is where we update the parent failed linkage
+ JobEvent.create_from_data(job_id=j.pk, parent_uuid='abc123', event='playbook_on_stats').save()
+ events = JobEvent.objects.filter(event__in=['playbook_on_task_start', event])
+ assert events.count() == 2
+ for e in events.all():
+ assert e.failed is True
+
+ def test_host_summary_generation(self):
+ self._generate_hosts(100)
+ self._create_job_event(ok=dict((hostname, len(hostname)) for hostname in self.hostnames))
+
+ assert self.job.job_host_summaries.count() == len(self.hostnames)
+ assert sorted([s.host_name for s in self.job.job_host_summaries.all()]) == sorted(self.hostnames)
+
+ for s in self.job.job_host_summaries.all():
+ assert self.host_map[s.host_name] == s.host_id
+ assert s.ok == len(s.host_name)
+ assert s.changed == 0
+ assert s.dark == 0
+ assert s.failures == 0
+ assert s.ignored == 0
+ assert s.processed == 0
+ assert s.rescued == 0
+ assert s.skipped == 0
+
+ for host in Host.objects.all():
+ assert host.last_job_id == self.job.id
+ assert host.last_job_host_summary.host == host
+
+ def test_host_summary_generation_with_deleted_hosts(self):
+ self._generate_hosts(10)
+
+ # delete half of the hosts during the playbook run
+ for h in self.inventory.hosts.all()[:5]:
+ h.delete()
+
+ self._create_job_event(ok=dict((hostname, len(hostname)) for hostname in self.hostnames))
+
+ ids = sorted([s.host_id or -1 for s in self.job.job_host_summaries.order_by('id').all()])
+ names = sorted([s.host_name for s in self.job.job_host_summaries.all()])
+ assert ids == [-1, -1, -1, -1, -1, 6, 7, 8, 9, 10]
+ assert names == ['Host 0', 'Host 1', 'Host 2', 'Host 3', 'Host 4', 'Host 5', 'Host 6', 'Host 7', 'Host 8', 'Host 9']
+
+ def test_host_summary_generation_with_limit(self):
+ # Make an inventory with 10 hosts, run a playbook with a --limit
+ # pointed at *one* host,
+ # Verify that *only* that host has an associated JobHostSummary and that
+ # *only* that host has an updated value for .last_job.
+ self._generate_hosts(10)
+
+ # by making the playbook_on_stats *only* include Host 1, we're emulating
+ # the behavior of a `--limit=Host 1`
+ matching_host = Host.objects.get(name='Host 1')
+ self._create_job_event(ok={matching_host.name: len(matching_host.name)}) # effectively, limit=Host 1
+
+ # since the playbook_on_stats only references one host,
+ # there should *only* be on JobHostSummary record (and it should
+ # be related to the appropriate Host)
+ assert JobHostSummary.objects.count() == 1
+ for h in Host.objects.all():
+ if h.name == 'Host 1':
+ assert h.last_job_id == self.job.id
+ assert h.last_job_host_summary_id == JobHostSummary.objects.first().id
+ else:
+ # all other hosts in the inventory should remain untouched
+ assert h.last_job_id is None
+ assert h.last_job_host_summary_id is None
+
+ def test_host_metrics_insert(self):
+ self._generate_hosts(10)
+
+ self._create_job_event(
+ ok=dict((hostname, len(hostname)) for hostname in self.hostnames[0:3]),
+ failures=dict((hostname, len(hostname)) for hostname in self.hostnames[3:6]),
+ processed=dict((hostname, len(hostname)) for hostname in self.hostnames[6:9]),
+ skipped=dict((hostname, len(hostname)) for hostname in [self.hostnames[9]]),
+ )
+
+ metrics = HostMetric.objects.all()
+ assert len(metrics) == 10
+ for hm in metrics:
+ assert hm.automated_counter == 1
+ assert hm.last_automation is not None
+ assert hm.deleted is False
+
+ def test_host_metrics_update(self):
+ self._generate_hosts(12)
+
+ self._create_job_event(ok=dict((hostname, len(hostname)) for hostname in self.hostnames))
+
+ # Soft delete 6 of the 12 host metrics, every even host like "Host 2" or "Host 4"
+ for host_name in self.hostnames[::2]:
+ hm = HostMetric.objects.get(hostname=host_name.lower())
+ hm.soft_delete()
+
+ assert len(HostMetric.objects.filter(Q(deleted=False) & Q(deleted_counter=0) & Q(last_deleted__isnull=True))) == 6
+ assert len(HostMetric.objects.filter(Q(deleted=True) & Q(deleted_counter=1) & Q(last_deleted__isnull=False))) == 6
+
+ # hostnames in 'ignored' and 'rescued' stats are ignored
+ self.job = Job(inventory=self.inventory)
+ self.job.save()
+ self._create_job_event(
+ ignored=dict((hostname, len(hostname)) for hostname in self.hostnames[0:6]),
+ rescued=dict((hostname, len(hostname)) for hostname in self.hostnames[6:11]),
+ )
+
+ assert len(HostMetric.objects.filter(Q(deleted=False) & Q(deleted_counter=0) & Q(last_deleted__isnull=True))) == 6
+ assert len(HostMetric.objects.filter(Q(deleted=True) & Q(deleted_counter=1) & Q(last_deleted__isnull=False))) == 6
+
+ # hostnames in 'changed', 'dark', 'failures', 'ok', 'processed', 'skipped' are processed
+ self.job = Job(inventory=self.inventory)
+ self.job.save()
+ self._create_job_event(
+ changed=dict((hostname, len(hostname)) for hostname in self.hostnames[0:2]),
+ dark=dict((hostname, len(hostname)) for hostname in self.hostnames[2:4]),
+ failures=dict((hostname, len(hostname)) for hostname in self.hostnames[4:6]),
+ ok=dict((hostname, len(hostname)) for hostname in self.hostnames[6:8]),
+ processed=dict((hostname, len(hostname)) for hostname in self.hostnames[8:10]),
+ skipped=dict((hostname, len(hostname)) for hostname in self.hostnames[10:12]),
+ )
+ assert len(HostMetric.objects.filter(Q(deleted=False) & Q(deleted_counter=0) & Q(last_deleted__isnull=True))) == 6
+
+ # one of those 6 hosts is dark, so will not be counted
+ assert len(HostMetric.objects.filter(Q(deleted=False) & Q(deleted_counter=1) & Q(last_deleted__isnull=False))) == 5
+
+ def _generate_hosts(self, cnt, id_from=0):
+ self.hostnames = [f'Host {i}' for i in range(id_from, id_from + cnt)]
+ self.inventory = Inventory()
+ self.inventory.save()
+ Host.objects.bulk_create([Host(created=now(), modified=now(), name=h, inventory_id=self.inventory.id) for h in self.hostnames])
+ self.job = Job(inventory=self.inventory)
+ self.job.save()
+
+ # host map is a data structure that tracks a mapping of host name --> ID
+ # for the inventory, _regardless_ of whether or not there's a limit
+ # applied to the actual playbook run
+ self.host_map = dict((host.name, host.id) for host in self.inventory.hosts.all())
+
+ def _create_job_event(
+ self,
parent_uuid='abc123',
event='playbook_on_stats',
- event_data={
- 'ok': {matching_host.name: len(matching_host.name)}, # effectively, limit=Host 1
- 'changed': {},
- 'dark': {},
- 'failures': {},
- 'ignored': {},
- 'processed': {},
- 'rescued': {},
- 'skipped': {},
- },
- host_map=host_map,
- ).save()
-
- # since the playbook_on_stats only references one host,
- # there should *only* be on JobHostSummary record (and it should
- # be related to the appropriate Host)
- assert JobHostSummary.objects.count() == 1
- for h in Host.objects.all():
- if h.name == 'Host 1':
- assert h.last_job_id == j.id
- assert h.last_job_host_summary_id == JobHostSummary.objects.first().id
- else:
- # all other hosts in the inventory should remain untouched
- assert h.last_job_id is None
- assert h.last_job_host_summary_id is None
+ ok=None,
+ changed=None,
+ dark=None,
+ failures=None,
+ ignored=None,
+ processed=None,
+ rescued=None,
+ skipped=None,
+ ):
+ JobEvent.create_from_data(
+ job_id=self.job.pk,
+ parent_uuid=parent_uuid,
+ event=event,
+ event_data={
+ 'ok': ok or {},
+ 'changed': changed or {},
+ 'dark': dark or {},
+ 'failures': failures or {},
+ 'ignored': ignored or {},
+ 'processed': processed or {},
+ 'rescued': rescued or {},
+ 'skipped': skipped or {},
+ },
+ host_map=self.host_map,
+ ).save()
diff --git a/awx/main/tests/functional/models/test_ha.py b/awx/main/tests/functional/models/test_ha.py
new file mode 100644
index 000000000000..c8ee9dc0a7b3
--- /dev/null
+++ b/awx/main/tests/functional/models/test_ha.py
@@ -0,0 +1,45 @@
+import pytest
+
+# AWX
+from awx.main.ha import is_ha_environment
+from awx.main.models.ha import Instance
+from awx.main.utils.common import get_auto_max_workers
+
+# Django
+from django.test.utils import override_settings
+
+
+@pytest.mark.django_db
+def test_multiple_instances():
+ for i in range(2):
+ Instance.objects.create(hostname=f'foo{i}', node_type='hybrid')
+ assert is_ha_environment()
+
+
+@pytest.mark.django_db
+def test_db_localhost():
+ Instance.objects.create(hostname='foo', node_type='hybrid')
+ Instance.objects.create(hostname='bar', node_type='execution')
+ assert is_ha_environment() is False
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize(
+ 'settings',
+ [
+ dict(SYSTEM_TASK_ABS_MEM='16Gi', SYSTEM_TASK_ABS_CPU='24', SYSTEM_TASK_FORKS_MEM=400, SYSTEM_TASK_FORKS_CPU=4),
+ dict(SYSTEM_TASK_ABS_MEM='124Gi', SYSTEM_TASK_ABS_CPU='2', SYSTEM_TASK_FORKS_MEM=None, SYSTEM_TASK_FORKS_CPU=None),
+ ],
+ ids=['cpu_dominated', 'memory_dominated'],
+)
+def test_dispatcher_max_workers_reserve(settings, fake_redis):
+ """This tests that the dispatcher max_workers matches instance capacity
+
+ Assumes capacity_adjustment is 1,
+ plus reserve worker count
+ """
+ with override_settings(**settings):
+ i = Instance.objects.create(hostname='test-1', node_type='hybrid')
+ i.local_health_check()
+
+ assert get_auto_max_workers() == i.capacity + 7, (i.cpu, i.memory, i.cpu_capacity, i.mem_capacity)
diff --git a/awx/main/tests/functional/models/test_host_metric.py b/awx/main/tests/functional/models/test_host_metric.py
index 1f560e474fec..dad829543590 100644
--- a/awx/main/tests/functional/models/test_host_metric.py
+++ b/awx/main/tests/functional/models/test_host_metric.py
@@ -20,3 +20,53 @@ def test_host_metrics_generation():
date_today = now().strftime('%Y-%m-%d')
result = HostMetric.objects.filter(first_automation__startswith=date_today).count()
assert result == len(hostnames)
+
+
+@pytest.mark.django_db
+def test_soft_delete():
+ hostnames = [f'Host to delete {i}' for i in range(2)]
+ current_time = now()
+ HostMetric.objects.bulk_create([HostMetric(hostname=h, last_automation=current_time, automated_counter=42) for h in hostnames])
+
+ hm = HostMetric.objects.get(hostname="Host to delete 0")
+ assert hm.last_deleted is None
+
+ last_deleted = None
+ for _ in range(3):
+ # soft delete 1st
+ # 2nd/3rd delete don't have an effect
+ hm.soft_delete()
+ if last_deleted is None:
+ last_deleted = hm.last_deleted
+
+ assert hm.deleted is True
+ assert hm.deleted_counter == 1
+ assert hm.last_deleted == last_deleted
+ assert hm.automated_counter == 42
+
+ # 2nd record is not touched
+ hm = HostMetric.objects.get(hostname="Host to delete 1")
+ assert hm.deleted is False
+ assert hm.deleted_counter == 0
+ assert hm.last_deleted is None
+ assert hm.automated_counter == 42
+
+
+@pytest.mark.django_db
+def test_soft_restore():
+ current_time = now()
+ HostMetric.objects.create(hostname="Host 1", last_automation=current_time, deleted=True)
+ HostMetric.objects.create(hostname="Host 2", last_automation=current_time, deleted=True, last_deleted=current_time)
+ HostMetric.objects.create(hostname="Host 3", last_automation=current_time, deleted=False, last_deleted=current_time)
+ HostMetric.objects.all().update(automated_counter=42, deleted_counter=10)
+
+ # 1. deleted, last_deleted not null
+ for hm in HostMetric.objects.all():
+ for _ in range(3):
+ hm.soft_restore()
+ assert hm.deleted is False
+ assert hm.automated_counter == 42 and hm.deleted_counter == 10
+ if hm.hostname == "Host 1":
+ assert hm.last_deleted is None
+ else:
+ assert hm.last_deleted == current_time
diff --git a/awx/main/tests/functional/models/test_inventory.py b/awx/main/tests/functional/models/test_inventory.py
index a1db473d3ed0..3a739a3b815e 100644
--- a/awx/main/tests/functional/models/test_inventory.py
+++ b/awx/main/tests/functional/models/test_inventory.py
@@ -5,8 +5,8 @@
# AWX
from awx.main.models import Host, Inventory, InventorySource, InventoryUpdate, CredentialType, Credential, Job
-from awx.main.constants import CLOUD_PROVIDERS
from awx.main.utils.filters import SmartFilter
+from awx.main.utils.plugins import discover_available_cloud_provider_plugin_names
@pytest.mark.django_db
@@ -166,10 +166,11 @@ def test_extra_credentials(self, project, credential):
def test_all_cloud_sources_covered(self):
"""Code in several places relies on the fact that the older
- CLOUD_PROVIDERS constant contains the same names as what are
+ discover_cloud_provider_plugin_names returns the same names as what are
defined within the injectors
"""
- assert set(CLOUD_PROVIDERS) == set(InventorySource.injectors.keys())
+ # slight exception case for constructed, because it has a FQCN but is not a cloud source
+ assert set(discover_available_cloud_provider_plugin_names()) | set(['constructed']) == set(InventorySource.injectors.keys())
@pytest.mark.parametrize('source,filename', [('ec2', 'aws_ec2.yml'), ('openstack', 'openstack.yml'), ('gce', 'gcp_compute.yml')])
def test_plugin_filenames(self, source, filename):
@@ -192,6 +193,7 @@ def test_plugin_filenames(self, source, filename):
('satellite6', 'theforeman.foreman.foreman'),
('insights', 'redhatinsights.insights.insights'),
('controller', 'awx.awx.tower'),
+ ('terraform', 'cloud.terraform.terraform_state'),
],
)
def test_plugin_proper_names(self, source, proper_name):
@@ -270,6 +272,7 @@ def test_inventory_update_excessively_long_name(inventory, inventory_source):
class TestHostManager:
def test_host_filter_not_smart(self, setup_ec2_gce, organization):
smart_inventory = Inventory(name='smart', organization=organization, host_filter='inventory_sources__source=ec2')
+ smart_inventory.save()
assert len(smart_inventory.hosts.all()) == 0
def test_host_distinctness(self, setup_inventory_groups, organization):
diff --git a/awx/main/tests/functional/models/test_notifications.py b/awx/main/tests/functional/models/test_notifications.py
index 2d1d5e0f1719..2c1d6022de45 100644
--- a/awx/main/tests/functional/models/test_notifications.py
+++ b/awx/main/tests/functional/models/test_notifications.py
@@ -98,7 +98,7 @@ def check_structure(self, expected_structure, obj):
@pytest.mark.django_db
@pytest.mark.parametrize('JobClass', [AdHocCommand, InventoryUpdate, Job, ProjectUpdate, SystemJob, WorkflowJob])
- def test_context(self, JobClass, sqlite_copy_expert, project, inventory_source):
+ def test_context(self, JobClass, sqlite_copy, project, inventory_source):
"""The Jinja context defines all of the fields that can be used by a template. Ensure that the context generated
for each job type has the expected structure."""
kwargs = {}
diff --git a/awx/main/tests/functional/models/test_schedule.py b/awx/main/tests/functional/models/test_schedule.py
index 6ad711537386..97051ae45f0e 100644
--- a/awx/main/tests/functional/models/test_schedule.py
+++ b/awx/main/tests/functional/models/test_schedule.py
@@ -1,11 +1,11 @@
-from datetime import datetime, timedelta
+from datetime import datetime, timedelta, timezone
from contextlib import contextmanager
+from zoneinfo import ZoneInfo
from django.utils.timezone import now
from django.db.utils import IntegrityError
from unittest import mock
import pytest
-import pytz
from awx.main.models import JobTemplate, Schedule, ActivityStream
@@ -76,7 +76,7 @@ def test_computed_fields_time_change(self, job_template):
with self.assert_no_unwanted_stuff(s):
# force update of next_run, as if schedule re-calculation had not happened
# since this time
- old_next_run = datetime(2009, 3, 13, tzinfo=pytz.utc)
+ old_next_run = datetime(2009, 3, 13, tzinfo=timezone.utc)
Schedule.objects.filter(pk=s.pk).update(next_run=old_next_run)
s.next_run = old_next_run
prior_modified = s.modified
@@ -121,30 +121,16 @@ def test_computed_fields_turning_off_by_deleting(self, job_template):
assert job_template.next_schedule == expected_schedule
-@pytest.mark.django_db
-@pytest.mark.parametrize('freq, delta', (('MINUTELY', 1), ('HOURLY', 1)))
-def test_past_week_rrule(job_template, freq, delta):
- # see: https://github.com/ansible/awx/issues/8071
- recent = datetime.utcnow() - timedelta(days=3)
- recent = recent.replace(hour=0, minute=0, second=0, microsecond=0)
- recent_dt = recent.strftime('%Y%m%d')
- rrule = f'DTSTART;TZID=America/New_York:{recent_dt}T000000 RRULE:FREQ={freq};INTERVAL={delta};COUNT=5' # noqa
- sched = Schedule.objects.create(name='example schedule', rrule=rrule, unified_job_template=job_template)
- first_event = sched.rrulestr(sched.rrule)[0]
- assert first_event.replace(tzinfo=None) == recent
-
-
@pytest.mark.django_db
@pytest.mark.parametrize('freq, delta', (('MINUTELY', 1), ('HOURLY', 1)))
def test_really_old_dtstart(job_template, freq, delta):
# see: https://github.com/ansible/awx/issues/8071
# If an event is per-minute/per-hour and was created a *really long*
- # time ago, we should just bump forward to start counting "in the last week"
+ # time ago, we should just bump forward the dtstart
rrule = f'DTSTART;TZID=America/New_York:20150101T000000 RRULE:FREQ={freq};INTERVAL={delta}' # noqa
sched = Schedule.objects.create(name='example schedule', rrule=rrule, unified_job_template=job_template)
- last_week = (datetime.utcnow() - timedelta(days=7)).date()
first_event = sched.rrulestr(sched.rrule)[0]
- assert last_week == first_event.date()
+ assert now() - first_event < timedelta(days=1)
# the next few scheduled events should be the next minute/hour incremented
next_five_events = list(sched.rrulestr(sched.rrule).xafter(now(), count=5))
@@ -273,7 +259,7 @@ def test_utc_until_in_the_past(job_template):
@pytest.mark.django_db
-@mock.patch('awx.main.models.schedules.now', lambda: datetime(2030, 3, 5, tzinfo=pytz.utc))
+@mock.patch('awx.main.models.schedules.now', lambda: datetime(2030, 3, 5, tzinfo=timezone.utc))
def test_dst_phantom_hour(job_template):
# The DST period in the United States begins at 02:00 (2 am) local time, so
# the hour from 2:00:00 to 2:59:59 does not exist in the night of the
@@ -470,15 +456,15 @@ def test_skip_sundays():
RRULE:INTERVAL=1;FREQ=DAILY
EXRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SU
'''
- timezone = pytz.timezone("America/New_York")
- friday_apr_29th = datetime(2022, 4, 29, 0, 0, 0, 0, timezone)
- monday_may_2nd = datetime(2022, 5, 2, 23, 59, 59, 999, timezone)
+ tz = ZoneInfo("America/New_York")
+ friday_apr_29th = datetime(2022, 4, 29, 0, 0, 0, 0, tz)
+ monday_may_2nd = datetime(2022, 5, 2, 23, 59, 59, 999, tz)
ruleset = Schedule.rrulestr(rrule)
gen = ruleset.between(friday_apr_29th, monday_may_2nd, True)
# We should only get Fri, Sat and Mon (skipping Sunday)
assert len(list(gen)) == 3
- saturday_night = datetime(2022, 4, 30, 23, 59, 59, 9999, timezone)
- monday_morning = datetime(2022, 5, 2, 0, 0, 0, 0, timezone)
+ saturday_night = datetime(2022, 4, 30, 23, 59, 59, 9999, tz)
+ monday_morning = datetime(2022, 5, 2, 0, 0, 0, 0, tz)
gen = ruleset.between(saturday_night, monday_morning, True)
assert len(list(gen)) == 0
@@ -490,17 +476,17 @@ def test_skip_sundays():
[
pytest.param(
'DTSTART;TZID=America/New_York:20210310T150000 RRULE:INTERVAL=1;FREQ=DAILY;UNTIL=20210430T150000Z EXRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SU;COUNT=5',
- datetime(2021, 4, 29, 19, 0, 0, tzinfo=pytz.utc),
+ datetime(2021, 4, 29, 19, 0, 0, tzinfo=timezone.utc),
id="Single rule in rule set with UTC TZ aware until",
),
pytest.param(
'DTSTART;TZID=America/New_York:20220310T150000 RRULE:INTERVAL=1;FREQ=DAILY;UNTIL=20220430T150000 EXRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SU;COUNT=5',
- datetime(2022, 4, 30, 19, 0, tzinfo=pytz.utc),
+ datetime(2022, 4, 30, 19, 0, tzinfo=timezone.utc),
id="Single rule in ruleset with naive until",
),
pytest.param(
'DTSTART;TZID=America/New_York:20220310T150000 RRULE:INTERVAL=1;FREQ=DAILY;COUNT=4 EXRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SU;COUNT=5',
- datetime(2022, 3, 12, 20, 0, tzinfo=pytz.utc),
+ datetime(2022, 3, 12, 20, 0, tzinfo=timezone.utc),
id="Single rule in ruleset with count",
),
pytest.param(
@@ -515,12 +501,12 @@ def test_skip_sundays():
),
pytest.param(
'DTSTART;TZID=America/New_York:20220310T150000 RRULE:INTERVAL=1;FREQ=DAILY;UNTIL=20220430T150000Z',
- datetime(2022, 4, 29, 19, 0, tzinfo=pytz.utc),
+ datetime(2022, 4, 29, 19, 0, tzinfo=timezone.utc),
id="Single rule in rule with UTZ TZ aware until",
),
pytest.param(
'DTSTART;TZID=America/New_York:20220310T150000 RRULE:INTERVAL=1;FREQ=DAILY;UNTIL=20220430T150000',
- datetime(2022, 4, 30, 19, 0, tzinfo=pytz.utc),
+ datetime(2022, 4, 30, 19, 0, tzinfo=timezone.utc),
id="Single rule in rule with naive until",
),
pytest.param(
@@ -535,12 +521,12 @@ def test_skip_sundays():
),
pytest.param(
'DTSTART;TZID=America/New_York:20220310T150000 RRULE:INTERVAL=1;FREQ=DAILY;BYDAY=SU;UNTIL=20220430T1500Z RRULE:INTERVAL=1;FREQ=DAILY;BYDAY=MO;COUNT=4',
- datetime(2022, 4, 24, 19, 0, tzinfo=pytz.utc),
+ datetime(2022, 4, 24, 19, 0, tzinfo=timezone.utc),
id="Multi rule one with until and one with an count",
),
pytest.param(
'DTSTART;TZID=America/New_York:20010430T1500 RRULE:INTERVAL=1;FREQ=DAILY;BYDAY=SU;COUNT=1',
- datetime(2001, 5, 6, 19, 0, tzinfo=pytz.utc),
+ datetime(2001, 5, 6, 19, 0, tzinfo=timezone.utc),
id="Rule with count but ends in the past",
),
pytest.param(
diff --git a/awx/main/tests/functional/models/test_unified_job.py b/awx/main/tests/functional/models/test_unified_job.py
index 389ea731b91c..0618085cef5f 100644
--- a/awx/main/tests/functional/models/test_unified_job.py
+++ b/awx/main/tests/functional/models/test_unified_job.py
@@ -1,28 +1,23 @@
import itertools
import pytest
+from uuid import uuid4
# CRUM
from crum import impersonate
-# Django
-from django.contrib.contenttypes.models import ContentType
-
# AWX
-from awx.main.models import UnifiedJobTemplate, Job, JobTemplate, WorkflowJobTemplate, WorkflowApprovalTemplate, Project, WorkflowJob, Schedule, Credential
+from awx.main.models import UnifiedJobTemplate, Job, JobTemplate, WorkflowJobTemplate, Project, WorkflowJob, Schedule, Credential
from awx.api.versioning import reverse
from awx.main.constants import JOB_VARIABLE_PREFIXES
@pytest.mark.django_db
-def test_subclass_types(rando):
- assert set(UnifiedJobTemplate._submodels_with_roles()) == set(
- [
- ContentType.objects.get_for_model(JobTemplate).id,
- ContentType.objects.get_for_model(Project).id,
- ContentType.objects.get_for_model(WorkflowJobTemplate).id,
- ContentType.objects.get_for_model(WorkflowApprovalTemplate).id,
- ]
- )
+def test_subclass_types():
+ assert set(UnifiedJobTemplate._submodels_with_roles()) == {
+ JobTemplate,
+ Project,
+ WorkflowJobTemplate,
+ }
@pytest.mark.django_db
@@ -39,6 +34,64 @@ def test_soft_unique_together(post, project, admin_user):
assert 'combination already exists' in str(r.data)
+@pytest.mark.django_db
+class TestJobCancel:
+ """
+ Coverage for UnifiedJob.cancel, focused on interaction with dispatcherd objects.
+ Using mocks for the dispatcherd objects, because tests by default use a no-op broker.
+ """
+
+ def test_cancel_sets_flag_and_clears_start_args(self, mocker):
+ job = Job.objects.create(status='running', name='foo-job', celery_task_id=str(uuid4()), controller_node='foo', start_args='{"secret": "value"}')
+ job.websocket_emit_status = mocker.MagicMock()
+
+ assert job.can_cancel is True
+ assert job.cancel_flag is False
+
+ job.cancel()
+ job.refresh_from_db()
+
+ assert job.cancel_flag is True
+ assert job.start_args == ''
+
+ def test_cancel_sets_job_explanation(self, mocker):
+ job = Job.objects.create(status='running', name='foo-job', celery_task_id=str(uuid4()), controller_node='foo')
+ job.websocket_emit_status = mocker.MagicMock()
+ job_explanation = 'giggity giggity'
+
+ job.cancel(job_explanation=job_explanation)
+ job.refresh_from_db()
+
+ assert job.job_explanation == job_explanation
+
+ def test_cancel_sends_control_message(self, mocker):
+ celery_task_id = str(uuid4())
+ job = Job.objects.create(status='running', name='foo-job', celery_task_id=celery_task_id, controller_node='foo')
+ job.websocket_emit_status = mocker.MagicMock()
+ control = mocker.MagicMock()
+ get_control = mocker.patch('awx.main.models.unified_jobs.get_control_from_settings', return_value=control)
+
+ job.cancel()
+
+ get_control.assert_called_once_with(default_publish_channel='foo')
+ control.control.assert_called_once_with('cancel', data={'uuid': celery_task_id})
+
+ def test_cancel_refreshes_task_id_before_sending_control(self, mocker):
+ job = Job.objects.create(status='pending', name='foo-job', celery_task_id='', controller_node='bar')
+ job.websocket_emit_status = mocker.MagicMock()
+ celery_task_id = str(uuid4())
+ Job.objects.filter(pk=job.pk).update(status='running', celery_task_id=celery_task_id)
+ control = mocker.MagicMock()
+ get_control = mocker.patch('awx.main.models.unified_jobs.get_control_from_settings', return_value=control)
+ refresh_spy = mocker.spy(job, 'refresh_from_db')
+
+ job.cancel()
+
+ refresh_spy.assert_called_once_with(fields=['celery_task_id', 'controller_node'])
+ get_control.assert_called_once_with(default_publish_channel='bar')
+ control.control.assert_called_once_with('cancel', data={'uuid': celery_task_id})
+
+
@pytest.mark.django_db
class TestCreateUnifiedJob:
"""
diff --git a/awx/main/tests/functional/models/test_workflow.py b/awx/main/tests/functional/models/test_workflow.py
index a21fbaa73bc1..924b5346c550 100644
--- a/awx/main/tests/functional/models/test_workflow.py
+++ b/awx/main/tests/functional/models/test_workflow.py
@@ -377,7 +377,7 @@ def test_apply_workflow_job_prompts(self, workflow_job_template, wfjt_prompts, p
assert workflow_job.scm_branch is None
assert workflow_job.job_tags is None
assert workflow_job.skip_tags is None
- assert len(workflow_job.labels.all()) is 0
+ assert len(workflow_job.labels.all()) == 0
# fields from prompts used
workflow_job = workflow_job_template.create_unified_job(**prompts_data)
diff --git a/awx/main/tests/functional/test_rbac_api.py b/awx/main/tests/functional/rbac/test_rbac_api.py
similarity index 90%
rename from awx/main/tests/functional/test_rbac_api.py
rename to awx/main/tests/functional/rbac/test_rbac_api.py
index b697ef3144c2..61e7425c4da1 100644
--- a/awx/main/tests/functional/test_rbac_api.py
+++ b/awx/main/tests/functional/rbac/test_rbac_api.py
@@ -3,7 +3,9 @@
from django.db import transaction
from awx.api.versioning import reverse
-from awx.main.models.rbac import Role, ROLE_SINGLETON_SYSTEM_ADMINISTRATOR
+from awx.main.models.rbac import Role
+
+from django.test.utils import override_settings
@pytest.fixture
@@ -31,8 +33,6 @@ def test_get_roles_list_user(organization, inventory, team, get, user):
'Users can see all roles they have access to, but not all roles'
this_user = user('user-test_get_roles_list_user')
organization.member_role.members.add(this_user)
- custom_role = Role.objects.create(role_field='custom_role-test_get_roles_list_user')
- organization.member_role.children.add(custom_role)
url = reverse('api:role_list')
response = get(url, this_user)
@@ -46,10 +46,8 @@ def test_get_roles_list_user(organization, inventory, team, get, user):
for r in roles['results']:
role_hash[r['id']] = r
- assert Role.singleton(ROLE_SINGLETON_SYSTEM_ADMINISTRATOR).id in role_hash
assert organization.admin_role.id in role_hash
assert organization.member_role.id in role_hash
- assert custom_role.id in role_hash
assert inventory.admin_role.id not in role_hash
assert team.member_role.id not in role_hash
@@ -57,7 +55,8 @@ def test_get_roles_list_user(organization, inventory, team, get, user):
@pytest.mark.django_db
def test_roles_visibility(get, organization, project, admin, alice, bob):
- Role.singleton('system_auditor').members.add(alice)
+ alice.is_system_auditor = True
+ alice.save()
assert get(reverse('api:role_list') + '?id=%d' % project.update_role.id, user=admin).data['count'] == 1
assert get(reverse('api:role_list') + '?id=%d' % project.update_role.id, user=alice).data['count'] == 1
assert get(reverse('api:role_list') + '?id=%d' % project.update_role.id, user=bob).data['count'] == 0
@@ -67,7 +66,8 @@ def test_roles_visibility(get, organization, project, admin, alice, bob):
@pytest.mark.django_db
def test_roles_filter_visibility(get, organization, project, admin, alice, bob):
- Role.singleton('system_auditor').members.add(alice)
+ alice.is_system_auditor = True
+ alice.save()
project.update_role.members.add(admin)
assert get(reverse('api:user_roles_list', kwargs={'pk': admin.id}) + '?id=%d' % project.update_role.id, user=admin).data['count'] == 1
@@ -105,15 +105,6 @@ def test_cant_delete_role(delete, admin, inventory):
#
-@pytest.mark.django_db
-def test_get_user_roles_list(get, admin):
- url = reverse('api:user_roles_list', kwargs={'pk': admin.id})
- response = get(url, admin)
- assert response.status_code == 200
- roles = response.data
- assert roles['count'] > 0 # 'system_administrator' role if nothing else
-
-
@pytest.mark.django_db
def test_user_view_other_user_roles(organization, inventory, team, get, alice, bob):
'Users can see roles for other users, but only the roles that that user has access to see as well'
@@ -141,7 +132,6 @@ def test_user_view_other_user_roles(organization, inventory, team, get, alice, b
assert organization.admin_role.id in role_hash
assert custom_role.id not in role_hash # doesn't show up in the user roles list, not an explicit grant
- assert Role.singleton(ROLE_SINGLETON_SYSTEM_ADMINISTRATOR).id not in role_hash
assert inventory.admin_role.id not in role_hash
assert team.member_role.id not in role_hash # alice can't see this
@@ -197,6 +187,7 @@ def test_remove_role_from_user(role, post, admin):
@pytest.mark.django_db
+@override_settings(ANSIBLE_BASE_ALLOW_TEAM_ORG_ADMIN=True, ANSIBLE_BASE_ALLOW_TEAM_ORG_MEMBER=True)
def test_get_teams_roles_list(get, team, organization, admin):
team.member_role.children.add(organization.admin_role)
url = reverse('api:team_roles_list', kwargs={'pk': team.id})
@@ -396,36 +387,6 @@ def test_remove_team_from_role(post, team, admin, role):
assert role.parents.filter(id=team.member_role.id).count() == 0
-#
-# /roles//parents/
-#
-
-
-@pytest.mark.django_db
-def test_role_parents(get, team, admin, role):
- role.parents.add(team.member_role)
- url = reverse('api:role_parents_list', kwargs={'pk': role.id})
- response = get(url, admin)
- assert response.status_code == 200
- assert response.data['count'] == 1
- assert response.data['results'][0]['id'] == team.member_role.id
-
-
-#
-# /roles//children/
-#
-
-
-@pytest.mark.django_db
-def test_role_children(get, team, admin, role):
- role.parents.add(team.member_role)
- url = reverse('api:role_children_list', kwargs={'pk': team.member_role.id})
- response = get(url, admin)
- assert response.status_code == 200
- assert response.data['count'] == 2
- assert response.data['results'][0]['id'] == role.id or response.data['results'][1]['id'] == role.id
-
-
#
# Generics
#
diff --git a/awx/main/tests/functional/test_rbac_credential.py b/awx/main/tests/functional/rbac/test_rbac_credential.py
similarity index 100%
rename from awx/main/tests/functional/test_rbac_credential.py
rename to awx/main/tests/functional/rbac/test_rbac_credential.py
diff --git a/awx/main/tests/functional/rbac/test_rbac_execution_environment.py b/awx/main/tests/functional/rbac/test_rbac_execution_environment.py
new file mode 100644
index 000000000000..b6d98f073b9e
--- /dev/null
+++ b/awx/main/tests/functional/rbac/test_rbac_execution_environment.py
@@ -0,0 +1,147 @@
+import pytest
+
+from awx.main.access import ExecutionEnvironmentAccess
+from awx.main.models import ExecutionEnvironment, Organization, Team
+from awx.main.models.rbac import get_role_codenames
+
+from awx.api.versioning import reverse
+from django.urls import reverse as django_reverse
+
+from ansible_base.rbac.models import RoleDefinition
+from ansible_base.rbac import permission_registry
+
+
+@pytest.fixture
+def ee_rd():
+ return RoleDefinition.objects.create_from_permissions(
+ name='EE object admin',
+ permissions=['change_executionenvironment', 'delete_executionenvironment'],
+ content_type=permission_registry.content_type_model.objects.get_for_model(ExecutionEnvironment),
+ )
+
+
+@pytest.fixture
+def org_ee_rd():
+ return RoleDefinition.objects.create_from_permissions(
+ name='EE org admin',
+ permissions=['add_executionenvironment', 'change_executionenvironment', 'delete_executionenvironment', 'view_organization'],
+ content_type=permission_registry.content_type_model.objects.get_for_model(Organization),
+ )
+
+
+@pytest.mark.django_db
+def test_old_ee_role_maps_to_correct_permissions(organization):
+ assert set(get_role_codenames(organization.execution_environment_admin_role)) == {
+ 'view_organization',
+ 'add_executionenvironment',
+ 'change_executionenvironment',
+ 'delete_executionenvironment',
+ }
+
+
+@pytest.fixture
+def org_ee(organization):
+ return ExecutionEnvironment.objects.create(name='some user ee', organization=organization)
+
+
+@pytest.fixture
+def check_user_capabilities(get, setup_managed_roles):
+ def _rf(user, obj, expected):
+ url = reverse('api:execution_environment_list')
+ r = get(url, user=user, expect=200)
+ for item in r.data['results']:
+ if item['id'] == obj.pk:
+ assert expected == item['summary_fields']['user_capabilities']
+ break
+ else:
+ raise RuntimeError(f'Could not find expected object ({obj}) in EE list result: {r.data}')
+
+ return _rf
+
+
+# ___ begin tests ___
+
+
+@pytest.mark.django_db
+def test_any_user_can_view_global_ee(control_plane_execution_environment, rando):
+ assert ExecutionEnvironmentAccess(rando).can_read(control_plane_execution_environment)
+
+
+@pytest.mark.django_db
+def test_managed_ee_not_assignable(control_plane_execution_environment, ee_rd, rando, admin_user, post):
+ url = django_reverse('roleuserassignment-list')
+ r = post(url, {'role_definition': ee_rd.pk, 'user': rando.id, 'object_id': control_plane_execution_environment.pk}, user=admin_user, expect=400)
+ assert 'Can not assign object roles to managed Execution Environment' in str(r.data)
+
+
+@pytest.mark.django_db
+def test_org_member_required_for_assignment(org_ee, ee_rd, rando, admin_user, post):
+ url = django_reverse('roleuserassignment-list')
+ r = post(url, {'role_definition': ee_rd.pk, 'user': rando.id, 'object_id': org_ee.pk}, user=admin_user, expect=400)
+ assert 'User must have view permission to Execution Environment organization' in str(r.data)
+
+
+@pytest.mark.django_db
+def test_team_can_have_permission(org_ee, ee_rd, rando, admin_user, post):
+ org2 = Organization.objects.create(name='a different team')
+ team = Team.objects.create(name='a team', organization=org2)
+ team.member_role.members.add(rando)
+ assert org_ee not in ExecutionEnvironmentAccess(rando).get_queryset() # user can not view the EE
+
+ url = django_reverse('roleteamassignment-list')
+
+ # can give object roles to the team now
+ post(url, {'role_definition': ee_rd.pk, 'team': team.id, 'object_id': org_ee.pk}, user=admin_user, expect=201)
+ assert rando.has_obj_perm(org_ee, 'change')
+ assert org_ee in ExecutionEnvironmentAccess(rando).get_queryset() # user can view the EE now
+
+
+@pytest.mark.django_db
+def test_give_object_permission_to_ee(setup_managed_roles, org_ee, ee_rd, org_member, check_user_capabilities):
+ access = ExecutionEnvironmentAccess(org_member)
+ assert access.can_read(org_ee) # by virtue of being an org member
+ assert not access.can_change(org_ee, {'name': 'new'})
+ check_user_capabilities(org_member, org_ee, {'edit': False, 'delete': False, 'copy': False})
+
+ ee_rd.give_permission(org_member, org_ee)
+ assert access.can_change(org_ee, {'name': 'new', 'organization': org_ee.organization.id})
+
+ check_user_capabilities(org_member, org_ee, {'edit': True, 'delete': True, 'copy': False})
+
+
+@pytest.mark.django_db
+def test_need_related_organization_access(org_ee, ee_rd, org_member):
+ org2 = Organization.objects.create(name='another organization')
+ ee_rd.give_permission(org_member, org_ee)
+ org2.member_role.members.add(org_member)
+ access = ExecutionEnvironmentAccess(org_member)
+ assert access.can_change(org_ee, {'name': 'new', 'organization': org_ee.organization})
+ assert access.can_change(org_ee, {'name': 'new', 'organization': org_ee.organization.id})
+ assert not access.can_change(org_ee, {'name': 'new', 'organization': org2.id})
+ assert not access.can_change(org_ee, {'name': 'new', 'organization': org2})
+
+ # User can make the change if they have relevant permission to the new organization
+ org_ee.organization.execution_environment_admin_role.members.add(org_member)
+ org2.execution_environment_admin_role.members.add(org_member)
+ assert access.can_change(org_ee, {'name': 'new', 'organization': org2.id})
+ assert access.can_change(org_ee, {'name': 'new', 'organization': org2})
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize('style', ['new', 'old'])
+def test_give_org_permission_to_ee(setup_managed_roles, org_ee, organization, org_member, check_user_capabilities, style, org_ee_rd):
+ access = ExecutionEnvironmentAccess(org_member)
+ assert not access.can_change(org_ee, {'name': 'new'})
+ check_user_capabilities(org_member, org_ee, {'edit': False, 'delete': False, 'copy': False})
+
+ if style == 'new':
+ org_ee_rd.give_permission(org_member, organization)
+ assert org_member.has_obj_perm(org_ee.organization, 'add_executionenvironment') # sanity
+ else:
+ organization.execution_environment_admin_role.members.add(org_member)
+
+ assert access.can_change(org_ee, {'name': 'new', 'organization': organization.id})
+ check_user_capabilities(org_member, org_ee, {'edit': True, 'delete': True, 'copy': True})
+
+ # Extra check, user can not remove the EE from the organization
+ assert not access.can_change(org_ee, {'name': 'new', 'organization': None})
diff --git a/awx/main/tests/functional/rbac/test_rbac_instance_groups.py b/awx/main/tests/functional/rbac/test_rbac_instance_groups.py
new file mode 100644
index 000000000000..418e5a351a34
--- /dev/null
+++ b/awx/main/tests/functional/rbac/test_rbac_instance_groups.py
@@ -0,0 +1,117 @@
+import pytest
+
+from awx.main.access import (
+ InstanceGroupAccess,
+ OrganizationAccess,
+ InventoryAccess,
+ JobTemplateAccess,
+)
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize(
+ "obj_perm,allowed,readonly,partial", [("admin_role", True, True, True), ("use_role", False, True, True), ("read_role", False, True, False)]
+)
+def test_ig_role_base_visibility(default_instance_group, rando, obj_perm, allowed, partial, readonly):
+ if obj_perm:
+ getattr(default_instance_group, obj_perm).members.add(rando)
+
+ assert readonly == InstanceGroupAccess(rando).can_read(default_instance_group)
+ assert partial == InstanceGroupAccess(rando).can_use(default_instance_group)
+ assert not InstanceGroupAccess(rando).can_add(default_instance_group)
+ assert allowed == InstanceGroupAccess(rando).can_admin(default_instance_group)
+ assert allowed == InstanceGroupAccess(rando).can_change(default_instance_group, {'name': 'New Name'})
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize(
+ "obj_perm,subobj_perm,allowed", [('admin_role', 'use_role', True), ('admin_role', 'read_role', False), ('admin_role', 'admin_role', True)]
+)
+def test_ig_role_based_associability(default_instance_group, rando, organization, job_template_factory, obj_perm, subobj_perm, allowed):
+ objects = job_template_factory('jt', organization=organization, project='p', inventory='i', credential='c')
+ if obj_perm:
+ getattr(objects.job_template, obj_perm).members.add(rando)
+ getattr(objects.inventory, obj_perm).members.add(rando)
+ getattr(objects.organization, obj_perm).members.add(rando)
+ if subobj_perm:
+ getattr(default_instance_group, subobj_perm).members.add(rando)
+
+ assert allowed == JobTemplateAccess(rando).can_attach(objects.job_template, default_instance_group, 'instance_groups', None)
+ assert allowed == InventoryAccess(rando).can_attach(objects.inventory, default_instance_group, 'instance_groups', None)
+ assert allowed == OrganizationAccess(rando).can_attach(objects.organization, default_instance_group, 'instance_groups', None)
+
+
+@pytest.mark.django_db
+def test_ig_use_with_org_admin(default_instance_group, rando, org_admin):
+ default_instance_group.use_role.members.add(rando)
+
+ assert list(InstanceGroupAccess(org_admin).get_queryset()) != [default_instance_group]
+ assert list(InstanceGroupAccess(rando).get_queryset()) == [default_instance_group]
+
+
+@pytest.mark.django_db
+def test_ig_normal_user_visibility(organization, default_instance_group, user):
+ u = user('user', False)
+ assert len(InstanceGroupAccess(u).get_queryset()) == 0
+ organization.instance_groups.add(default_instance_group)
+ organization.member_role.members.add(u)
+ assert len(InstanceGroupAccess(u).get_queryset()) == 0
+
+
+@pytest.mark.django_db
+def test_ig_admin_user_visibility(organization, default_instance_group, admin, system_auditor, org_admin):
+ assert len(InstanceGroupAccess(admin).get_queryset()) == 1
+ assert len(InstanceGroupAccess(system_auditor).get_queryset()) == 1
+ assert len(InstanceGroupAccess(org_admin).get_queryset()) == 0
+ organization.instance_groups.add(default_instance_group)
+ assert len(InstanceGroupAccess(org_admin).get_queryset()) == 0
+
+
+@pytest.mark.django_db
+def test_ig_normal_user_associability(organization, default_instance_group, user):
+ u = user('user', False)
+ access = OrganizationAccess(u)
+ assert not access.can_attach(organization, default_instance_group, 'instance_groups', None)
+ organization.instance_groups.add(default_instance_group)
+ organization.member_role.members.add(u)
+ assert not access.can_attach(organization, default_instance_group, 'instance_groups', None)
+
+
+@pytest.mark.django_db
+def test_ig_associability(organization, default_instance_group, admin, system_auditor, org_admin, org_member, job_template_factory):
+ admin_access = OrganizationAccess(admin)
+ auditor_access = OrganizationAccess(system_auditor)
+ oadmin_access = OrganizationAccess(org_admin)
+ omember_access = OrganizationAccess(org_member)
+ assert admin_access.can_attach(organization, default_instance_group, 'instance_groups', None)
+ assert not oadmin_access.can_attach(organization, default_instance_group, 'instance_groups', None)
+ assert not auditor_access.can_attach(organization, default_instance_group, 'instance_groups', None)
+ assert not omember_access.can_attach(organization, default_instance_group, 'instance_groups', None)
+
+ organization.instance_groups.add(default_instance_group)
+
+ assert admin_access.can_unattach(organization, default_instance_group, 'instance_groups', None)
+ assert not oadmin_access.can_unattach(organization, default_instance_group, 'instance_groups', None)
+ assert not auditor_access.can_unattach(organization, default_instance_group, 'instance_groups', None)
+ assert not omember_access.can_unattach(organization, default_instance_group, 'instance_groups', None)
+
+ objects = job_template_factory('jt', organization=organization, project='p', inventory='i', credential='c')
+ admin_access = InventoryAccess(admin)
+ auditor_access = InventoryAccess(system_auditor)
+ oadmin_access = InventoryAccess(org_admin)
+ omember_access = InventoryAccess(org_member)
+
+ assert admin_access.can_attach(objects.inventory, default_instance_group, 'instance_groups', None)
+ assert not oadmin_access.can_attach(objects.inventory, default_instance_group, 'instance_groups', None)
+ assert not auditor_access.can_attach(objects.inventory, default_instance_group, 'instance_groups', None)
+ assert not omember_access.can_attach(objects.inventory, default_instance_group, 'instance_groups', None)
+
+ admin_access = JobTemplateAccess(admin)
+ auditor_access = JobTemplateAccess(system_auditor)
+ oadmin_access = JobTemplateAccess(org_admin)
+ omember_access = JobTemplateAccess(org_member)
+
+ assert admin_access.can_attach(objects.job_template, default_instance_group, 'instance_groups', None)
+ assert not oadmin_access.can_attach(objects.job_template, default_instance_group, 'instance_groups', None)
+ assert not auditor_access.can_attach(objects.job_template, default_instance_group, 'instance_groups', None)
+ assert not omember_access.can_attach(objects.job_template, default_instance_group, 'instance_groups', None)
diff --git a/awx/main/tests/functional/test_rbac_inventory.py b/awx/main/tests/functional/rbac/test_rbac_inventory.py
similarity index 100%
rename from awx/main/tests/functional/test_rbac_inventory.py
rename to awx/main/tests/functional/rbac/test_rbac_inventory.py
diff --git a/awx/main/tests/functional/test_rbac_job.py b/awx/main/tests/functional/rbac/test_rbac_job.py
similarity index 92%
rename from awx/main/tests/functional/test_rbac_job.py
rename to awx/main/tests/functional/rbac/test_rbac_job.py
index ff5c6c25a255..c4bcee00d684 100644
--- a/awx/main/tests/functional/test_rbac_job.py
+++ b/awx/main/tests/functional/rbac/test_rbac_job.py
@@ -2,7 +2,15 @@
from rest_framework.exceptions import PermissionDenied
-from awx.main.access import JobAccess, JobLaunchConfigAccess, AdHocCommandAccess, InventoryUpdateAccess, ProjectUpdateAccess
+from awx.main.access import (
+ JobAccess,
+ JobLaunchConfigAccess,
+ AdHocCommandAccess,
+ InventoryUpdateAccess,
+ ProjectUpdateAccess,
+ SystemJobTemplateAccess,
+ SystemJobAccess,
+)
from awx.main.models import (
Job,
JobLaunchConfig,
@@ -350,3 +358,26 @@ def test_can_use_minor(self, rando):
assert access.can_use(config)
assert rando.can_access(JobLaunchConfig, 'use', config)
+
+
+@pytest.mark.django_db
+class TestSystemJobTemplateAccess:
+ def test_system_job_template_auditor(self, system_auditor, system_job_template):
+ access = SystemJobTemplateAccess(system_auditor)
+ assert access.can_read(system_job_template)
+ assert not access.can_start(system_job_template)
+
+ def test_system_job_template_rando(self, rando, system_job_template):
+ access = SystemJobTemplateAccess(rando)
+ assert not access.can_read(system_job_template)
+ assert not access.can_start(system_job_template)
+
+ def test_system_job_template_superuser(self, admin_user, system_job_template):
+ access = SystemJobTemplateAccess(admin_user)
+ assert access.can_read(system_job_template)
+ assert access.can_start(system_job_template)
+
+ def test_org_auditor_view_system_job(self, system_job_template, org_auditor):
+ system_job = system_job_template.create_unified_job()
+ access = SystemJobAccess(org_auditor)
+ assert not access.can_read(system_job)
diff --git a/awx/main/tests/functional/test_rbac_job_start.py b/awx/main/tests/functional/rbac/test_rbac_job_start.py
similarity index 100%
rename from awx/main/tests/functional/test_rbac_job_start.py
rename to awx/main/tests/functional/rbac/test_rbac_job_start.py
diff --git a/awx/main/tests/functional/test_rbac_job_templates.py b/awx/main/tests/functional/rbac/test_rbac_job_templates.py
similarity index 86%
rename from awx/main/tests/functional/test_rbac_job_templates.py
rename to awx/main/tests/functional/rbac/test_rbac_job_templates.py
index bccec0a1c2e1..34f82d9a74b9 100644
--- a/awx/main/tests/functional/test_rbac_job_templates.py
+++ b/awx/main/tests/functional/rbac/test_rbac_job_templates.py
@@ -4,7 +4,7 @@
from awx.api.versioning import reverse
from awx.main.access import BaseAccess, JobTemplateAccess, ScheduleAccess
from awx.main.models.jobs import JobTemplate
-from awx.main.models import Project, Organization, Inventory, Schedule, User
+from awx.main.models import Project, Organization, Schedule
@mock.patch.object(BaseAccess, 'check_license', return_value=None)
@@ -165,7 +165,7 @@ def test_system_admin_orphan_capabilities(self, job_template, admin_user):
@pytest.mark.django_db
@pytest.mark.job_permissions
-def test_job_template_creator_access(project, organization, rando, post):
+def test_job_template_creator_access(project, organization, rando, post, setup_managed_roles):
project.use_role.members.add(rando)
response = post(
url=reverse('api:job_template_list'),
@@ -177,13 +177,19 @@ def test_job_template_creator_access(project, organization, rando, post):
jt_pk = response.data['id']
jt_obj = JobTemplate.objects.get(pk=jt_pk)
# Creating a JT should place the creator in the admin role
- assert rando in jt_obj.admin_role.members.all()
+ assert rando in jt_obj.admin_role
@pytest.mark.django_db
@pytest.mark.job_permissions
-@pytest.mark.parametrize('lacking', ['project', 'inventory'])
-def test_job_template_insufficient_creator_permissions(lacking, project, inventory, organization, rando, post):
+@pytest.mark.parametrize(
+ 'lacking,reason',
+ [
+ ('project', 'You do not have use permission on Project'),
+ ('inventory', 'You do not have use permission on Inventory'),
+ ],
+)
+def test_job_template_insufficient_creator_permissions(lacking, reason, project, inventory, organization, rando, post):
if lacking != 'project':
project.use_role.members.add(rando)
else:
@@ -192,12 +198,13 @@ def test_job_template_insufficient_creator_permissions(lacking, project, invento
inventory.use_role.members.add(rando)
else:
inventory.read_role.members.add(rando)
- post(
+ response = post(
url=reverse('api:job_template_list'),
data=dict(name='newly-created-jt', inventory=inventory.id, project=project.pk, playbook='helloworld.yml'),
user=rando,
expect=403,
)
+ assert reason in response.data[lacking]
@pytest.mark.django_db
@@ -283,48 +290,3 @@ def test_orphan_JT_adoption(self, project, patch, admin_user, org_admin):
assert org_admin not in jt.admin_role
patch(url=jt.get_absolute_url(), data={'project': project.id}, user=admin_user, expect=200)
assert org_admin in jt.admin_role
-
- def test_inventory_read_transfer_direct(self, patch):
- orgs = []
- invs = []
- admins = []
- for i in range(2):
- org = Organization.objects.create(name='org{}'.format(i))
- org_admin = User.objects.create(username='user{}'.format(i))
- inv = Inventory.objects.create(organization=org, name='inv{}'.format(i))
- org.auditor_role.members.add(org_admin)
-
- orgs.append(org)
- admins.append(org_admin)
- invs.append(inv)
-
- jt = JobTemplate.objects.create(name='foo', inventory=invs[0])
- assert admins[0] in jt.read_role
- assert admins[1] not in jt.read_role
-
- jt.inventory = invs[1]
- jt.save(update_fields=['inventory'])
- assert admins[0] not in jt.read_role
- assert admins[1] in jt.read_role
-
- def test_inventory_read_transfer_indirect(self, patch):
- orgs = []
- admins = []
- for i in range(2):
- org = Organization.objects.create(name='org{}'.format(i))
- org_admin = User.objects.create(username='user{}'.format(i))
- org.auditor_role.members.add(org_admin)
-
- orgs.append(org)
- admins.append(org_admin)
-
- inv = Inventory.objects.create(organization=orgs[0], name='inv{}'.format(i))
-
- jt = JobTemplate.objects.create(name='foo', inventory=inv)
- assert admins[0] in jt.read_role
- assert admins[1] not in jt.read_role
-
- inv.organization = orgs[1]
- inv.save(update_fields=['organization'])
- assert admins[0] not in jt.read_role
- assert admins[1] in jt.read_role
diff --git a/awx/main/tests/functional/test_rbac_label.py b/awx/main/tests/functional/rbac/test_rbac_label.py
similarity index 100%
rename from awx/main/tests/functional/test_rbac_label.py
rename to awx/main/tests/functional/rbac/test_rbac_label.py
diff --git a/awx/main/tests/functional/test_labels.py b/awx/main/tests/functional/rbac/test_rbac_labels.py
similarity index 100%
rename from awx/main/tests/functional/test_labels.py
rename to awx/main/tests/functional/rbac/test_rbac_labels.py
diff --git a/awx/main/tests/functional/test_rbac_notifications.py b/awx/main/tests/functional/rbac/test_rbac_notifications.py
similarity index 98%
rename from awx/main/tests/functional/test_rbac_notifications.py
rename to awx/main/tests/functional/rbac/test_rbac_notifications.py
index d05efa244c9b..72d5d016a954 100644
--- a/awx/main/tests/functional/test_rbac_notifications.py
+++ b/awx/main/tests/functional/rbac/test_rbac_notifications.py
@@ -99,7 +99,9 @@ def test_notification_template_access_org_user(notification_template, user):
@pytest.mark.django_db
def test_notificaiton_template_orphan_access_org_admin(notification_template, organization, org_admin):
notification_template.organization = None
+ notification_template.save(update_fields=['organization'])
access = NotificationTemplateAccess(org_admin)
+ assert not org_admin.has_obj_perm(notification_template, 'change')
assert not access.can_change(notification_template, {'organization': organization.id})
diff --git a/awx/main/tests/functional/test_rbac_organization.py b/awx/main/tests/functional/rbac/test_rbac_organization.py
similarity index 75%
rename from awx/main/tests/functional/test_rbac_organization.py
rename to awx/main/tests/functional/rbac/test_rbac_organization.py
index ddb0692ea3ed..7a07225d3057 100644
--- a/awx/main/tests/functional/test_rbac_organization.py
+++ b/awx/main/tests/functional/rbac/test_rbac_organization.py
@@ -48,3 +48,17 @@ def test_org_resource_role(ext_auth, organization, rando, org_admin):
assert access.can_attach(organization, rando, 'member_role.members') == ext_auth
organization.member_role.members.add(rando)
assert access.can_unattach(organization, rando, 'member_role.members') == ext_auth
+
+
+@pytest.mark.django_db
+def test_delete_org_while_workflow_active(workflow_job_template):
+ '''
+ Delete org while workflow job is active (i.e. changing status)
+ '''
+ assert workflow_job_template.organization # sanity check
+ wj = workflow_job_template.create_unified_job() # status should be new
+ workflow_job_template.organization.delete()
+ wj.refresh_from_db()
+ assert wj.status != 'pending' # sanity check
+ wj.status = 'pending' # status needs to change in order to trigger workflow_job_template.save()
+ wj.save(update_fields=['status'])
diff --git a/awx/main/tests/functional/test_rbac_project.py b/awx/main/tests/functional/rbac/test_rbac_project.py
similarity index 100%
rename from awx/main/tests/functional/test_rbac_project.py
rename to awx/main/tests/functional/rbac/test_rbac_project.py
diff --git a/awx/main/tests/functional/test_rbac_role.py b/awx/main/tests/functional/rbac/test_rbac_role.py
similarity index 100%
rename from awx/main/tests/functional/test_rbac_role.py
rename to awx/main/tests/functional/rbac/test_rbac_role.py
diff --git a/awx/main/tests/functional/test_rbac_team.py b/awx/main/tests/functional/rbac/test_rbac_team.py
similarity index 98%
rename from awx/main/tests/functional/test_rbac_team.py
rename to awx/main/tests/functional/rbac/test_rbac_team.py
index a18a69a94bb3..6c3e68c6c1d7 100644
--- a/awx/main/tests/functional/test_rbac_team.py
+++ b/awx/main/tests/functional/rbac/test_rbac_team.py
@@ -92,7 +92,7 @@ def test_team_accessible_by(team, user, project):
u = user('team_member', False)
team.member_role.children.add(project.use_role)
- assert team in project.read_role
+ assert list(Project.accessible_objects(team, 'read_role')) == [project]
assert u not in project.read_role
team.member_role.members.add(u)
diff --git a/awx/main/tests/functional/test_rbac_user.py b/awx/main/tests/functional/rbac/test_rbac_user.py
similarity index 78%
rename from awx/main/tests/functional/test_rbac_user.py
rename to awx/main/tests/functional/rbac/test_rbac_user.py
index d5386343bd8f..10ca851bbe57 100644
--- a/awx/main/tests/functional/test_rbac_user.py
+++ b/awx/main/tests/functional/rbac/test_rbac_user.py
@@ -4,7 +4,7 @@
from django.test import TransactionTestCase
from awx.main.access import UserAccess, RoleAccess, TeamAccess
-from awx.main.models import User, Organization, Inventory, Role
+from awx.main.models import User, Organization, Inventory, get_system_auditor_role
class TestSysAuditorTransactional(TransactionTestCase):
@@ -18,7 +18,8 @@ def inventory(self):
def test_auditor_caching(self):
rando = self.rando()
- with self.assertNumQueries(1):
+ get_system_auditor_role() # pre-create role, normally done by migrations
+ with self.assertNumQueries(2):
v = rando.is_system_auditor
assert not v
with self.assertNumQueries(0):
@@ -122,25 +123,6 @@ def test_team_org_resource_role(ext_auth, organization, rando, org_admin, team):
] == [True for i in range(2)]
-@pytest.mark.django_db
-def test_user_accessible_objects(user, organization):
- """
- We cannot directly use accessible_objects for User model because
- both editing and read permissions are obligated to complex business logic
- """
- admin = user('admin', False)
- u = user('john', False)
- access = UserAccess(admin)
- assert access.get_queryset().count() == 1 # can only see himself
-
- organization.member_role.members.add(u)
- organization.member_role.members.add(admin)
- assert access.get_queryset().count() == 2
-
- organization.member_role.members.remove(u)
- assert access.get_queryset().count() == 1
-
-
@pytest.mark.django_db
def test_org_admin_create_sys_auditor(org_admin):
access = UserAccess(org_admin)
@@ -172,34 +154,3 @@ def test_org_admin_cannot_delete_member_attached_to_other_group(org_admin, org_m
access = UserAccess(org_admin)
other_org.member_role.members.add(org_member)
assert not access.can_delete(org_member)
-
-
-@pytest.mark.parametrize('reverse', (True, False))
-@pytest.mark.django_db
-def test_consistency_of_is_superuser_flag(reverse):
- users = [User.objects.create(username='rando_{}'.format(i)) for i in range(2)]
- for u in users:
- assert u.is_superuser is False
-
- system_admin = Role.singleton('system_administrator')
- if reverse:
- for u in users:
- u.roles.add(system_admin)
- else:
- system_admin.members.add(*[u.id for u in users]) # like .add(42, 54)
-
- for u in users:
- u.refresh_from_db()
- assert u.is_superuser is True
-
- users[0].roles.clear()
- for u in users:
- u.refresh_from_db()
- assert users[0].is_superuser is False
- assert users[1].is_superuser is True
-
- system_admin.members.clear()
-
- for u in users:
- u.refresh_from_db()
- assert u.is_superuser is False
diff --git a/awx/main/tests/functional/test_rbac_workflow.py b/awx/main/tests/functional/rbac/test_rbac_workflow.py
similarity index 88%
rename from awx/main/tests/functional/test_rbac_workflow.py
rename to awx/main/tests/functional/rbac/test_rbac_workflow.py
index 4c29907519c1..c94f4f4df881 100644
--- a/awx/main/tests/functional/test_rbac_workflow.py
+++ b/awx/main/tests/functional/rbac/test_rbac_workflow.py
@@ -1,6 +1,7 @@
import pytest
from awx.main.access import (
+ UnifiedJobAccess,
WorkflowJobTemplateAccess,
WorkflowJobTemplateNodeAccess,
WorkflowJobAccess,
@@ -13,30 +14,6 @@
from awx.main.models import InventorySource, JobLaunchConfig
-@pytest.fixture
-def wfjt(workflow_job_template_factory, organization):
- objects = workflow_job_template_factory('test_workflow', organization=organization, persisted=True)
- return objects.workflow_job_template
-
-
-@pytest.fixture
-def wfjt_with_nodes(workflow_job_template_factory, organization, job_template):
- objects = workflow_job_template_factory(
- 'test_workflow', organization=organization, workflow_job_template_nodes=[{'unified_job_template': job_template}], persisted=True
- )
- return objects.workflow_job_template
-
-
-@pytest.fixture
-def wfjt_node(wfjt_with_nodes):
- return wfjt_with_nodes.workflow_job_template_nodes.all()[0]
-
-
-@pytest.fixture
-def workflow_job(wfjt):
- return wfjt.workflow_jobs.create(name='test_workflow')
-
-
@pytest.mark.django_db
class TestWorkflowJobTemplateAccess:
def test_random_user_no_edit(self, wfjt, rando):
@@ -59,6 +36,13 @@ def test_org_workflow_admin_role_inheritance(self, wfjt, org_member):
assert org_member in wfjt.execute_role
assert org_member in wfjt.read_role
+ def test_non_super_admin_no_add_without_org(self, wfjt, organization, rando):
+ organization.member_role.members.add(rando)
+ wfjt.admin_role.members.add(rando)
+ access = WorkflowJobTemplateAccess(rando, save_messages=True)
+ assert not access.can_add({'name': 'without org'})
+ assert 'An organization is required to create a workflow job template for normal user' in access.messages['organization']
+
@pytest.mark.django_db
class TestWorkflowJobTemplateNodeAccess:
@@ -148,7 +132,7 @@ def test_attacher_permissions(self, wfjt_node, job_template, rando, add_wfjt_adm
elif permission_type == 'instance_groups':
sub_obj = InstanceGroup.objects.create()
org = Organization.objects.create()
- org.admin_role.members.add(rando) # only admins can see IGs
+ sub_obj.use_role.members.add(rando) # only admins can see IGs
org.instance_groups.add(sub_obj)
access = WorkflowJobTemplateNodeAccess(rando)
@@ -262,6 +246,30 @@ def test_relaunch_inventory_access(self, workflow_job, inventory, rando):
inventory.use_role.members.add(rando)
assert WorkflowJobAccess(rando).can_start(workflow_job)
+ @pytest.mark.parametrize('org_role', ['admin_role', 'auditor_role'])
+ def test_workflow_job_org_audit_access(self, workflow_job_template, rando, org_role):
+ assert workflow_job_template.organization # sanity
+ workflow_job = workflow_job_template.create_unified_job()
+ assert workflow_job.organization # sanity
+
+ assert not UnifiedJobAccess(rando).can_read(workflow_job)
+ assert not WorkflowJobAccess(rando).can_read(workflow_job)
+ assert workflow_job not in WorkflowJobAccess(rando).filtered_queryset()
+
+ org = workflow_job.organization
+ role = getattr(org, org_role)
+ role.members.add(rando)
+
+ assert UnifiedJobAccess(rando).can_read(workflow_job)
+ assert WorkflowJobAccess(rando).can_read(workflow_job)
+ assert workflow_job in WorkflowJobAccess(rando).filtered_queryset()
+
+ # Organization-level permissions should persist after deleting the WFJT
+ workflow_job_template.delete()
+ assert UnifiedJobAccess(rando).can_read(workflow_job)
+ assert WorkflowJobAccess(rando).can_read(workflow_job)
+ assert workflow_job in WorkflowJobAccess(rando).filtered_queryset()
+
@pytest.mark.django_db
class TestWFJTCopyAccess:
diff --git a/awx/main/tests/functional/task_management/test_rampart_groups.py b/awx/main/tests/functional/task_management/test_rampart_groups.py
index 48ea9edb0805..f4bb81f405a6 100644
--- a/awx/main/tests/functional/task_management/test_rampart_groups.py
+++ b/awx/main/tests/functional/task_management/test_rampart_groups.py
@@ -21,13 +21,13 @@ def test_multi_group_basic_job_launch(instance_factory, controlplane_instance_gr
j2 = create_job(objects2.job_template)
with mock.patch('awx.main.models.Job.task_impact', new_callable=mock.PropertyMock) as mock_task_impact:
mock_task_impact.return_value = 500
- with mocker.patch("awx.main.scheduler.TaskManager.start_task"):
- TaskManager().schedule()
- TaskManager.start_task.assert_has_calls([mock.call(j1, ig1, i1), mock.call(j2, ig2, i2)])
+ mocker.patch("awx.main.scheduler.TaskManager.start_task")
+ TaskManager().schedule()
+ TaskManager.start_task.assert_has_calls([mock.call(j1, ig1, i1), mock.call(j2, ig2, i2)])
@pytest.mark.django_db
-def test_multi_group_with_shared_dependency(instance_factory, controlplane_instance_group, mocker, instance_group_factory, job_template_factory):
+def test_multi_group_with_shared_dependency(instance_factory, controlplane_instance_group, instance_group_factory, job_template_factory):
i1 = instance_factory("i1")
i2 = instance_factory("i2")
ig1 = instance_group_factory("ig1", instances=[i1])
@@ -50,7 +50,7 @@ def test_multi_group_with_shared_dependency(instance_factory, controlplane_insta
objects2 = job_template_factory('jt2', organization=objects1.organization, project=p, inventory='inv2', credential='cred2')
objects2.job_template.instance_groups.add(ig2)
j2 = create_job(objects2.job_template, dependencies_processed=False)
- with mocker.patch("awx.main.scheduler.TaskManager.start_task"):
+ with mock.patch("awx.main.scheduler.TaskManager.start_task"):
DependencyManager().schedule()
TaskManager().schedule()
pu = p.project_updates.first()
@@ -73,10 +73,10 @@ def test_workflow_job_no_instancegroup(workflow_job_template_factory, controlpla
wfj = wfjt.create_unified_job()
wfj.status = "pending"
wfj.save()
- with mocker.patch("awx.main.scheduler.TaskManager.start_task"):
- TaskManager().schedule()
- TaskManager.start_task.assert_called_once_with(wfj, None, None)
- assert wfj.instance_group is None
+ mocker.patch("awx.main.scheduler.TaskManager.start_task")
+ TaskManager().schedule()
+ TaskManager.start_task.assert_called_once_with(wfj, None, None)
+ assert wfj.instance_group is None
@pytest.mark.django_db
diff --git a/awx/main/tests/functional/task_management/test_scheduler.py b/awx/main/tests/functional/task_management/test_scheduler.py
index 42d144d5ccd3..7293873d7ccd 100644
--- a/awx/main/tests/functional/task_management/test_scheduler.py
+++ b/awx/main/tests/functional/task_management/test_scheduler.py
@@ -5,7 +5,7 @@
from awx.main.scheduler import TaskManager, DependencyManager, WorkflowManager
from awx.main.utils import encrypt_field
-from awx.main.models import WorkflowJobTemplate, JobTemplate, Job
+from awx.main.models import WorkflowJobTemplate, JobTemplate, Job, Project, InventorySource, Inventory
from awx.main.models.ha import Instance
from . import create_job
from django.conf import settings
@@ -16,9 +16,9 @@ def test_single_job_scheduler_launch(hybrid_instance, controlplane_instance_grou
instance = controlplane_instance_group.instances.all()[0]
objects = job_template_factory('jt', organization='org1', project='proj', inventory='inv', credential='cred')
j = create_job(objects.job_template)
- with mocker.patch("awx.main.scheduler.TaskManager.start_task"):
- TaskManager().schedule()
- TaskManager.start_task.assert_called_once_with(j, controlplane_instance_group, instance)
+ mocker.patch("awx.main.scheduler.TaskManager.start_task")
+ TaskManager().schedule()
+ TaskManager.start_task.assert_called_once_with(j, controlplane_instance_group, instance)
@pytest.mark.django_db
@@ -331,15 +331,13 @@ def test_single_job_dependencies_project_launch(controlplane_instance_group, job
p.save(skip_update=True)
with mock.patch("awx.main.scheduler.TaskManager.start_task"):
dm = DependencyManager()
- with mock.patch.object(DependencyManager, "create_project_update", wraps=dm.create_project_update) as mock_pu:
- dm.schedule()
- mock_pu.assert_called_once_with(j)
- pu = [x for x in p.project_updates.all()]
- assert len(pu) == 1
- TaskManager().schedule()
- TaskManager.start_task.assert_called_once_with(pu[0], controlplane_instance_group, instance)
- pu[0].status = "successful"
- pu[0].save()
+ dm.schedule()
+ pu = [x for x in p.project_updates.all()]
+ assert len(pu) == 1
+ TaskManager().schedule()
+ TaskManager.start_task.assert_called_once_with(pu[0], controlplane_instance_group, instance)
+ pu[0].status = "successful"
+ pu[0].save()
with mock.patch("awx.main.scheduler.TaskManager.start_task"):
TaskManager().schedule()
TaskManager.start_task.assert_called_once_with(j, controlplane_instance_group, instance)
@@ -359,22 +357,21 @@ def test_single_job_dependencies_inventory_update_launch(controlplane_instance_g
i.inventory_sources.add(ii)
with mock.patch("awx.main.scheduler.TaskManager.start_task"):
dm = DependencyManager()
- with mock.patch.object(DependencyManager, "create_inventory_update", wraps=dm.create_inventory_update) as mock_iu:
- dm.schedule()
- mock_iu.assert_called_once_with(j, ii)
- iu = [x for x in ii.inventory_updates.all()]
- assert len(iu) == 1
- TaskManager().schedule()
- TaskManager.start_task.assert_called_once_with(iu[0], controlplane_instance_group, instance)
- iu[0].status = "successful"
- iu[0].save()
+ dm.schedule()
+ assert ii.inventory_updates.count() == 1
+ iu = [x for x in ii.inventory_updates.all()]
+ assert len(iu) == 1
+ TaskManager().schedule()
+ TaskManager.start_task.assert_called_once_with(iu[0], controlplane_instance_group, instance)
+ iu[0].status = "successful"
+ iu[0].save()
with mock.patch("awx.main.scheduler.TaskManager.start_task"):
TaskManager().schedule()
TaskManager.start_task.assert_called_once_with(j, controlplane_instance_group, instance)
@pytest.mark.django_db
-def test_inventory_update_launches_project_update(controlplane_instance_group, scm_inventory_source):
+def test_inventory_update_launches_project_update(scm_inventory_source):
ii = scm_inventory_source
project = scm_inventory_source.source_project
project.scm_update_on_launch = True
@@ -382,11 +379,51 @@ def test_inventory_update_launches_project_update(controlplane_instance_group, s
iu = ii.create_inventory_update()
iu.status = "pending"
iu.save()
+ assert project.project_updates.count() == 0
with mock.patch("awx.main.scheduler.TaskManager.start_task"):
dm = DependencyManager()
- with mock.patch.object(DependencyManager, "create_project_update", wraps=dm.create_project_update) as mock_pu:
- dm.schedule()
- mock_pu.assert_called_with(iu, project_id=project.id)
+ dm.schedule()
+ assert project.project_updates.count() == 1
+
+
+@pytest.mark.django_db
+def test_dependency_isolation(organization):
+ """Spawning both a job project update dependency, and an inventory update project dependency
+
+ this should keep dependencies isolated"""
+ with mock.patch('awx.main.models.unified_jobs.UnifiedJobTemplate.update'):
+ updating_projects = [
+ Project.objects.create(name=f'iso-proj{i}', organization=organization, scm_url='https://foo.invalid', scm_type='git', scm_update_on_launch=True)
+ for i in range(2)
+ ]
+
+ inv_src = InventorySource.objects.create(
+ name='iso-inv',
+ organization=organization,
+ source_project=updating_projects[0],
+ source='scm',
+ inventory=Inventory.objects.create(name='for-inv-src', organization=organization),
+ )
+
+ inv_update = inv_src.create_unified_job()
+ inv_update.signal_start()
+ assert not inv_update.dependent_jobs.exists()
+
+ jt = JobTemplate.objects.create(
+ project=updating_projects[1],
+ inventory=Inventory.objects.create(name='one-off', organization=organization), # non-updating inventory source
+ )
+ job = jt.create_unified_job()
+ job.signal_start()
+ assert not job.dependent_jobs.exists()
+
+ dm = DependencyManager()
+ dm.schedule()
+
+ # in a single run, the completely unrelated inventory and jobs are linked to their own dependencies
+ assert (inv_update.dependent_jobs.count(), job.dependent_jobs.count()) == (1, 1)
+ assert inv_update.dependent_jobs.first().project == updating_projects[0]
+ assert job.dependent_jobs.first().project == updating_projects[1]
@pytest.mark.django_db
@@ -407,9 +444,8 @@ def test_job_dependency_with_already_updated(controlplane_instance_group, job_te
j.save()
with mock.patch("awx.main.scheduler.TaskManager.start_task"):
dm = DependencyManager()
- with mock.patch.object(DependencyManager, "create_inventory_update", wraps=dm.create_inventory_update) as mock_iu:
- dm.schedule()
- mock_iu.assert_not_called()
+ dm.schedule()
+ assert ii.inventory_updates.count() == 0
with mock.patch("awx.main.scheduler.TaskManager.start_task"):
TaskManager().schedule()
TaskManager.start_task.assert_called_once_with(j, controlplane_instance_group, instance)
@@ -442,7 +478,9 @@ def test_shared_dependencies_launch(controlplane_instance_group, job_template_fa
TaskManager().schedule()
pu = p.project_updates.first()
iu = ii.inventory_updates.first()
- TaskManager.start_task.assert_has_calls([mock.call(iu, controlplane_instance_group, instance), mock.call(pu, controlplane_instance_group, instance)])
+ TaskManager.start_task.assert_has_calls(
+ [mock.call(iu, controlplane_instance_group, instance), mock.call(pu, controlplane_instance_group, instance)], any_order=True
+ )
pu.status = "successful"
pu.finished = pu.created + timedelta(seconds=1)
pu.save()
@@ -451,7 +489,9 @@ def test_shared_dependencies_launch(controlplane_instance_group, job_template_fa
iu.save()
with mock.patch("awx.main.scheduler.TaskManager.start_task"):
TaskManager().schedule()
- TaskManager.start_task.assert_has_calls([mock.call(j1, controlplane_instance_group, instance), mock.call(j2, controlplane_instance_group, instance)])
+ TaskManager.start_task.assert_has_calls(
+ [mock.call(j1, controlplane_instance_group, instance), mock.call(j2, controlplane_instance_group, instance)], any_order=True
+ )
pu = [x for x in p.project_updates.all()]
iu = [x for x in ii.inventory_updates.all()]
assert len(pu) == 1
diff --git a/awx/main/tests/functional/tasks/test_host_indirect.py b/awx/main/tests/functional/tasks/test_host_indirect.py
new file mode 100644
index 000000000000..cfa98d2391cc
--- /dev/null
+++ b/awx/main/tests/functional/tasks/test_host_indirect.py
@@ -0,0 +1,366 @@
+import yaml
+from functools import reduce
+from unittest import mock
+
+import pytest
+
+from django.utils.timezone import now, timedelta
+
+from awx.main.tasks.host_indirect import (
+ build_indirect_host_data,
+ fetch_job_event_query,
+ save_indirect_host_entries,
+ cleanup_and_save_indirect_host_entries_fallback,
+)
+from awx.main.models.event_query import EventQuery
+from awx.main.models.indirect_managed_node_audit import IndirectManagedNodeAudit
+
+"""These are unit tests, similar to test_indirect_host_counting in the live tests"""
+
+
+TEST_JQ = "{name: .name, canonical_facts: {host_name: .direct_host_name}, facts: {another_host_name: .direct_host_name}}"
+
+
+class Query(dict):
+ def __init__(self, resolved_action: str, query_jq: dict):
+ self._resolved_action = resolved_action.split('.')
+ self._collection_ns, self._collection_name, self._module_name = self._resolved_action
+
+ super().__init__({self.resolve_key: {'query': query_jq}})
+
+ def get_fqcn(self):
+ return f'{self._collection_ns}.{self._collection_name}'
+
+ @property
+ def resolve_value(self):
+ return self[self.resolve_key]
+
+ @property
+ def resolve_key(self):
+ return f'{self.get_fqcn()}.{self._module_name}'
+
+ def resolve(self, module_name=None):
+ return {f'{self.get_fqcn()}.{module_name or self._module_name}': self.resolve_value}
+
+ def create_event_query(self, module_name=None):
+ if (module_name := module_name or self._module_name) == '*':
+ raise ValueError('Invalid module name *')
+ return self.create_event_queries([module_name])
+
+ def create_event_queries(self, module_names):
+ queries = {}
+ for name in module_names:
+ queries |= self.resolve(name)
+ return EventQuery.objects.create(
+ fqcn=self.get_fqcn(),
+ collection_version='1.0.1',
+ event_query=yaml.dump(queries, default_flow_style=False),
+ )
+
+ def create_registered_event(self, job, module_name):
+ job.job_events.create(event_data={'resolved_action': f'{self.get_fqcn()}.{module_name}', 'res': {'direct_host_name': 'foo_host', 'name': 'vm-foo'}})
+
+
+@pytest.fixture
+def bare_job(job_factory):
+ job = job_factory()
+ job.installed_collections = {'demo.query': {'version': '1.0.1'}, 'demo2.query': {'version': '1.0.1'}}
+ job.event_queries_processed = False
+ job.save(update_fields=['installed_collections', 'event_queries_processed'])
+ return job
+
+
+def create_registered_event(job, task_name='demo.query.example'):
+ return job.job_events.create(event_data={'resolved_action': task_name, 'res': {'direct_host_name': 'foo_host', 'name': 'vm-foo'}})
+
+
+@pytest.fixture
+def job_with_counted_event(bare_job):
+ create_registered_event(bare_job)
+ return bare_job
+
+
+def create_audit_record(name, job, organization, created=now()):
+ record = IndirectManagedNodeAudit.objects.create(name=name, job=job, organization=organization)
+ record.created = created
+ record.save()
+ return record
+
+
+@pytest.fixture
+def event_query():
+ "This is ordinarily created by the artifacts callback"
+ return Query('demo.query.example', TEST_JQ).create_event_query()
+
+
+@pytest.fixture
+def old_audit_record(bare_job, organization):
+ created_at = now() - timedelta(days=10)
+ return create_audit_record(name="old_job", job=bare_job, organization=organization, created=created_at)
+
+
+@pytest.fixture
+def new_audit_record(bare_job, organization):
+ return IndirectManagedNodeAudit.objects.create(name="new_job", job=bare_job, organization=organization)
+
+
+# ---- end fixtures ----
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize(
+ 'queries,expected_matches',
+ (
+ pytest.param(
+ [],
+ 0,
+ id='no_results',
+ ),
+ pytest.param(
+ [Query('demo.query.example', TEST_JQ)],
+ 1,
+ id='fully_qualified',
+ ),
+ pytest.param(
+ [Query('demo.query.*', TEST_JQ)],
+ 1,
+ id='wildcard',
+ ),
+ pytest.param(
+ [
+ Query('demo.query.*', TEST_JQ),
+ Query('demo.query.example', TEST_JQ),
+ ],
+ 1,
+ id='wildcard_and_fully_qualified',
+ ),
+ pytest.param(
+ [
+ Query('demo.query.*', TEST_JQ),
+ Query('demo.query.example', {}),
+ ],
+ 0,
+ id='wildcard_and_fully_qualified',
+ ),
+ pytest.param(
+ [
+ Query('demo.query.example', {}),
+ Query('demo.query.*', TEST_JQ),
+ ],
+ 0,
+ id='ordering_should_not_matter',
+ ),
+ ),
+)
+def test_build_indirect_host_data(job_with_counted_event, queries: Query, expected_matches: int):
+ data = build_indirect_host_data(job_with_counted_event, {k: v for d in queries for k, v in d.items()})
+ assert len(data) == expected_matches
+
+
+@mock.patch('awx.main.tasks.host_indirect.logger.debug')
+@pytest.mark.django_db
+@pytest.mark.parametrize(
+ 'task_name',
+ (
+ pytest.param(
+ 'demo.query',
+ id='no_results',
+ ),
+ pytest.param(
+ 'demo',
+ id='no_results',
+ ),
+ pytest.param(
+ 'a.b.c.d',
+ id='no_results',
+ ),
+ ),
+)
+def test_build_indirect_host_data_malformed_module_name(mock_logger_debug, bare_job, task_name: str):
+ create_registered_event(bare_job, task_name)
+ assert build_indirect_host_data(bare_job, Query('demo.query.example', TEST_JQ)) == []
+ mock_logger_debug.assert_called_once_with(f"Malformed invocation module name '{task_name}'. Expected to be of the form 'a.b.c'")
+
+
+@mock.patch('awx.main.tasks.host_indirect.logger.info')
+@pytest.mark.django_db
+@pytest.mark.parametrize(
+ 'query',
+ (
+ pytest.param(
+ 'demo.query',
+ id='no_results',
+ ),
+ pytest.param(
+ 'demo',
+ id='no_results',
+ ),
+ pytest.param(
+ 'a.b.c.d',
+ id='no_results',
+ ),
+ ),
+)
+def test_build_indirect_host_data_malformed_query(mock_logger_info, job_with_counted_event, query: str):
+ assert build_indirect_host_data(job_with_counted_event, {query: {'query': TEST_JQ}}) == []
+ mock_logger_info.assert_called_once_with(f"Skiping malformed query '{query}'. Expected to be of the form 'a.b.c'")
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize(
+ 'query',
+ (
+ pytest.param(
+ Query('demo.query.example', TEST_JQ),
+ id='fully_qualified',
+ ),
+ pytest.param(
+ Query('demo.query.*', TEST_JQ),
+ id='wildcard',
+ ),
+ ),
+)
+def test_fetch_job_event_query(bare_job, query: Query):
+ query.create_event_query(module_name='example')
+ assert fetch_job_event_query(bare_job) == query.resolve('example')
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize(
+ 'queries',
+ (
+ [
+ Query('demo.query.example', TEST_JQ),
+ Query('demo2.query.example', TEST_JQ),
+ ],
+ [
+ Query('demo.query.*', TEST_JQ),
+ Query('demo2.query.example', TEST_JQ),
+ ],
+ ),
+)
+def test_fetch_multiple_job_event_query(bare_job, queries: list[Query]):
+ for q in queries:
+ q.create_event_query(module_name='example')
+ assert fetch_job_event_query(bare_job) == reduce(lambda acc, q: acc | q.resolve('example'), queries, {})
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize(
+ ('state',),
+ (
+ pytest.param(
+ [
+ (
+ Query('demo.query.example', TEST_JQ),
+ ['example'],
+ ),
+ ],
+ id='fully_qualified',
+ ),
+ pytest.param(
+ [
+ (
+ Query('demo.query.example', TEST_JQ),
+ ['example'] * 3,
+ ),
+ ],
+ id='multiple_events_same_module_same_host',
+ ),
+ pytest.param(
+ [
+ (
+ Query('demo.query.example', TEST_JQ),
+ ['example'],
+ ),
+ (
+ Query('demo2.query.example', TEST_JQ),
+ ['example'],
+ ),
+ ],
+ id='multiple_modules',
+ ),
+ pytest.param(
+ [
+ (
+ Query('demo.query.*', TEST_JQ),
+ ['example', 'example2'],
+ ),
+ ],
+ id='multiple_modules_same_collection',
+ ),
+ ),
+)
+def test_save_indirect_host_entries(bare_job, state):
+ all_task_names = []
+ for entry in state:
+ query, module_names = entry
+ all_task_names.extend([f'{query.get_fqcn()}.{module_name}' for module_name in module_names])
+ query.create_event_queries(module_names)
+ [query.create_registered_event(bare_job, n) for n in module_names]
+
+ save_indirect_host_entries(bare_job.id)
+ bare_job.refresh_from_db()
+
+ assert bare_job.event_queries_processed is True
+
+ assert IndirectManagedNodeAudit.objects.filter(job=bare_job).count() == 1
+ host_audit = IndirectManagedNodeAudit.objects.filter(job=bare_job).first()
+
+ assert host_audit.count == len(all_task_names)
+ assert host_audit.canonical_facts == {'host_name': 'foo_host'}
+ assert host_audit.facts == {'another_host_name': 'foo_host'}
+ assert host_audit.organization == bare_job.organization
+ assert host_audit.name == 'vm-foo'
+ assert set(host_audit.events) == set(all_task_names)
+
+
+@pytest.mark.django_db
+def test_events_not_fully_processed_no_op(bare_job):
+ # I have a job that produced 12 events, but those are not saved
+ bare_job.emitted_events = 12
+ bare_job.finished = now()
+ bare_job.save(update_fields=['emitted_events', 'finished'])
+
+ # Running the normal post-run task will do nothing at this point
+ assert bare_job.event_queries_processed is False
+ with mock.patch('time.sleep'): # for test speedup
+ save_indirect_host_entries(bare_job.id)
+ bare_job.refresh_from_db()
+ assert bare_job.event_queries_processed is False
+
+ # Right away, the fallback processing will not run either
+ cleanup_and_save_indirect_host_entries_fallback()
+ bare_job.refresh_from_db()
+ assert bare_job.event_queries_processed is False
+
+ # After 3 hours have passed...
+ bare_job.finished = now() - timedelta(hours=3)
+
+ # Create the expected job events
+ for _ in range(12):
+ create_registered_event(bare_job)
+
+ bare_job.save(update_fields=['finished'])
+
+ # The fallback task will now process indirect host query data for this job
+ cleanup_and_save_indirect_host_entries_fallback()
+
+ # Test code to process anyway, events collected or not
+ save_indirect_host_entries(bare_job.id, wait_for_events=False)
+ bare_job.refresh_from_db()
+ assert bare_job.event_queries_processed is True
+
+
+@pytest.mark.django_db
+def test_job_id_does_not_exist():
+ save_indirect_host_entries(10000001)
+
+
+@pytest.mark.django_db
+def test_cleanup_old_audit_records(old_audit_record, new_audit_record):
+ count_before_cleanup = IndirectManagedNodeAudit.objects.count()
+ assert count_before_cleanup == 2
+ cleanup_and_save_indirect_host_entries_fallback()
+ count_after_cleanup = IndirectManagedNodeAudit.objects.count()
+ assert count_after_cleanup == 1
diff --git a/awx/main/tests/functional/tasks/test_tasks_jobs.py b/awx/main/tests/functional/tasks/test_tasks_jobs.py
new file mode 100644
index 000000000000..012ee20fdb5e
--- /dev/null
+++ b/awx/main/tests/functional/tasks/test_tasks_jobs.py
@@ -0,0 +1,31 @@
+import pytest
+
+from awx.main.tasks.jobs import RunJob
+from awx.main.models import Job
+
+
+@pytest.mark.django_db
+def test_does_not_run_reaped_job(mocker, mock_me):
+ job = Job.objects.create(status='failed', job_explanation='This job has been reaped.')
+ mock_run = mocker.patch('awx.main.tasks.jobs.ansible_runner.interface.run')
+ try:
+ RunJob().run(job.id)
+ except Exception:
+ pass
+ job.refresh_from_db()
+ assert job.status == 'failed'
+ mock_run.assert_not_called()
+
+
+@pytest.mark.django_db
+def test_cancel_flag_on_start(jt_linked, caplog):
+ job = jt_linked.create_unified_job()
+ job.status = 'waiting'
+ job.cancel_flag = True
+ job.save()
+
+ task = RunJob()
+ task.run(job.id)
+
+ job = Job.objects.get(id=job.id)
+ assert job.status == 'canceled'
diff --git a/awx/main/tests/functional/tasks/test_tasks_system.py b/awx/main/tests/functional/tasks/test_tasks_system.py
new file mode 100644
index 000000000000..96ce7aa41347
--- /dev/null
+++ b/awx/main/tests/functional/tasks/test_tasks_system.py
@@ -0,0 +1,166 @@
+import copy
+import json
+import logging
+import os
+import tempfile
+import shutil
+from unittest import mock
+
+import pytest
+
+from awx.main.tasks.system import CleanupImagesAndFiles, execution_node_health_check, inspect_established_receptor_connections, clear_setting_cache
+from awx.main.management.commands.dispatcherd import Command
+from awx.main.models import Instance, Job, ReceptorAddress, InstanceLink
+
+
+@pytest.mark.django_db
+class TestLinkState:
+ @pytest.fixture(autouse=True)
+ def configure_settings(self, settings):
+ settings.IS_K8S = True
+
+ def test_inspect_established_receptor_connections(self):
+ '''
+ Change link state from ADDING to ESTABLISHED
+ if the receptor status KnownConnectionCosts field
+ has an entry for the source and target node.
+ '''
+ hop1 = Instance.objects.create(hostname='hop1')
+ hop2 = Instance.objects.create(hostname='hop2')
+ hop2addr = ReceptorAddress.objects.create(instance=hop2, address='hop2', port=5678)
+ InstanceLink.objects.create(source=hop1, target=hop2addr, link_state=InstanceLink.States.ADDING)
+
+ # calling with empty KnownConnectionCosts should not change the link state
+ inspect_established_receptor_connections({"KnownConnectionCosts": {}})
+ assert InstanceLink.objects.get(source=hop1, target=hop2addr).link_state == InstanceLink.States.ADDING
+
+ mesh_state = {"KnownConnectionCosts": {"hop1": {"hop2": 1}}}
+ inspect_established_receptor_connections(mesh_state)
+ assert InstanceLink.objects.get(source=hop1, target=hop2addr).link_state == InstanceLink.States.ESTABLISHED
+
+
+@pytest.fixture
+def job_folder_factory(request):
+ def _rf(job_id='1234'):
+ pdd_path = tempfile.mkdtemp(prefix=f'awx_{job_id}_')
+
+ def test_folder_cleanup():
+ if os.path.exists(pdd_path):
+ shutil.rmtree(pdd_path)
+
+ request.addfinalizer(test_folder_cleanup)
+
+ return pdd_path
+
+ return _rf
+
+
+@pytest.fixture
+def mock_job_folder(job_folder_factory):
+ return job_folder_factory()
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize('node_type', ('control. hybrid'))
+def test_no_worker_info_on_AWX_nodes(node_type):
+ hostname = 'us-south-3-compute.invalid'
+ Instance.objects.create(hostname=hostname, node_type=node_type)
+ assert execution_node_health_check(hostname) is None
+
+
+@pytest.mark.django_db
+def test_folder_cleanup_stale_file(mock_job_folder, mock_me):
+ CleanupImagesAndFiles.run()
+ assert os.path.exists(mock_job_folder) # grace period should protect folder from deletion
+
+ CleanupImagesAndFiles.run(grace_period=0)
+ assert not os.path.exists(mock_job_folder) # should be deleted
+
+
+@pytest.mark.django_db
+def test_folder_cleanup_running_job(mock_job_folder, me_inst):
+ job = Job.objects.create(id=1234, controller_node=me_inst.hostname, status='running')
+ CleanupImagesAndFiles.run(grace_period=0)
+ assert os.path.exists(mock_job_folder) # running job should prevent folder from getting deleted
+
+ job.status = 'failed'
+ job.save(update_fields=['status'])
+ CleanupImagesAndFiles.run(grace_period=0)
+ assert not os.path.exists(mock_job_folder) # job is finished and no grace period, should delete
+
+
+@pytest.mark.django_db
+def test_folder_cleanup_multiple_running_jobs(job_folder_factory, me_inst):
+ jobs = []
+ dirs = []
+ num_jobs = 3
+
+ for i in range(num_jobs):
+ job = Job.objects.create(controller_node=me_inst.hostname, status='running')
+ dirs.append(job_folder_factory(job.id))
+ jobs.append(job)
+
+ CleanupImagesAndFiles.run(grace_period=0)
+
+ assert [os.path.exists(d) for d in dirs] == [True for i in range(num_jobs)]
+
+
+@pytest.mark.django_db
+def test_clear_setting_cache_log_level_branch(settings):
+ settings.LOG_AGGREGATOR_LEVEL = 'DEBUG'
+ settings.CLUSTER_HOST_ID = 'control-node'
+ published_messages = []
+
+ class DummyBroker:
+ def publish_message(self, channel, message):
+ published_messages.append((channel, message))
+
+ def close(self):
+ pass
+
+ dummy_broker = DummyBroker()
+
+ with mock.patch('dispatcherd.control.get_broker', return_value=dummy_broker) as mock_get_broker:
+ clear_setting_cache(['LOG_AGGREGATOR_LEVEL'])
+
+ mock_get_broker.assert_called_once()
+ assert published_messages, 'control command was not sent through the broker'
+ queue, payload = published_messages[-1]
+ assert queue == 'control-node'
+ body = json.loads(payload)
+ assert body['control'] == 'set_log_level'
+ assert body['control_data'] == {'level': 'DEBUG'}
+
+
+@pytest.mark.django_db
+def test_configure_dispatcher_logging_updates_level(settings):
+ original_logging_settings = copy.deepcopy(settings.LOGGING)
+ settings.LOGGING = {
+ 'version': 1,
+ 'disable_existing_loggers': False,
+ 'filters': {
+ 'dynamic_level_filter': {
+ '()': 'logging.Filter',
+ }
+ },
+ 'handlers': {
+ 'console': {
+ 'class': 'logging.StreamHandler',
+ 'filters': ['dynamic_level_filter'],
+ 'stream': 'ext://sys.stdout',
+ }
+ },
+ 'loggers': {
+ 'dispatcherd': {
+ 'handlers': ['console'],
+ 'level': 'INFO',
+ 'propagate': False,
+ }
+ },
+ }
+ settings.LOG_AGGREGATOR_LEVEL = 'WARNING'
+
+ Command().configure_dispatcher_logging()
+
+ assert logging.getLogger('dispatcherd').level == logging.WARNING
+ settings.LOGGING = original_logging_settings
diff --git a/awx/main/tests/functional/test_apps.py b/awx/main/tests/functional/test_apps.py
new file mode 100644
index 000000000000..a52d4aa723ed
--- /dev/null
+++ b/awx/main/tests/functional/test_apps.py
@@ -0,0 +1,26 @@
+import pytest
+
+from django.apps import apps
+
+
+@pytest.fixture
+def mock_setup_tower_managed_defaults(mocker):
+ return mocker.patch('awx.main.models.credential.CredentialType.setup_tower_managed_defaults')
+
+
+@pytest.mark.django_db
+def test_load_credential_types_feature_migrations_ran(mocker, mock_setup_tower_managed_defaults):
+ mocker.patch('awx.main.apps.is_database_synchronized', return_value=True)
+
+ apps.get_app_config('main')._load_credential_types_feature()
+
+ mock_setup_tower_managed_defaults.assert_called_once()
+
+
+@pytest.mark.django_db
+def test_load_credential_types_feature_migrations_not_ran(mocker, mock_setup_tower_managed_defaults):
+ mocker.patch('awx.main.apps.is_database_synchronized', return_value=False)
+
+ apps.get_app_config('main')._load_credential_types_feature()
+
+ mock_setup_tower_managed_defaults.assert_not_called()
diff --git a/awx/main/tests/functional/test_bulk.py b/awx/main/tests/functional/test_bulk.py
new file mode 100644
index 000000000000..6b166cdf2bff
--- /dev/null
+++ b/awx/main/tests/functional/test_bulk.py
@@ -0,0 +1,447 @@
+import pytest
+
+from uuid import uuid4
+
+from awx.api.versioning import reverse
+
+from awx.main.models.jobs import JobTemplate
+from awx.main.models import Organization, Inventory, WorkflowJob, ExecutionEnvironment, Host
+from awx.main.scheduler import TaskManager
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize('num_hosts, num_queries', [(1, 15), (10, 15)])
+def test_bulk_host_create_num_queries(organization, inventory, post, get, user, num_hosts, num_queries, django_assert_max_num_queries):
+ '''
+ If I am a...
+ org admin
+ inventory admin at org level
+ admin of a particular inventory
+ superuser
+
+ Bulk Host create should take under a certain number of queries
+ '''
+ inventory.organization = organization
+ inventory_admin = user('inventory_admin', False)
+ org_admin = user('org_admin', False)
+ org_inv_admin = user('org_admin', False)
+ superuser = user('admin', True)
+ for u in [org_admin, org_inv_admin, inventory_admin]:
+ organization.member_role.members.add(u)
+ organization.admin_role.members.add(org_admin)
+ organization.inventory_admin_role.members.add(org_inv_admin)
+ inventory.admin_role.members.add(inventory_admin)
+
+ for u in [org_admin, inventory_admin, org_inv_admin, superuser]:
+ hosts = [{'name': uuid4()} for i in range(num_hosts)]
+ with django_assert_max_num_queries(num_queries):
+ bulk_host_create_response = post(reverse('api:bulk_host_create'), {'inventory': inventory.id, 'hosts': hosts}, u, expect=201).data
+ assert len(bulk_host_create_response['hosts']) == len(hosts), f"unexpected number of hosts created for user {u}"
+
+
+@pytest.mark.django_db
+def test_bulk_host_create_rbac(organization, inventory, post, get, user):
+ '''
+ If I am a...
+ org admin
+ inventory admin at org level
+ admin of a particular invenotry
+ ... I can bulk add hosts
+
+ Everyone else cannot
+ '''
+ inventory.organization = organization
+ inventory_admin = user('inventory_admin', False)
+ org_admin = user('org_admin', False)
+ org_inv_admin = user('org_admin', False)
+ auditor = user('auditor', False)
+ member = user('member', False)
+ use_inv_member = user('member', False)
+ for u in [org_admin, org_inv_admin, auditor, member, inventory_admin, use_inv_member]:
+ organization.member_role.members.add(u)
+ organization.admin_role.members.add(org_admin)
+ organization.inventory_admin_role.members.add(org_inv_admin)
+ inventory.admin_role.members.add(inventory_admin)
+ inventory.use_role.members.add(use_inv_member)
+ organization.auditor_role.members.add(auditor)
+
+ for indx, u in enumerate([org_admin, inventory_admin, org_inv_admin]):
+ bulk_host_create_response = post(
+ reverse('api:bulk_host_create'), {'inventory': inventory.id, 'hosts': [{'name': f'foobar-{indx}'}]}, u, expect=201
+ ).data
+ assert len(bulk_host_create_response['hosts']) == 1, f"unexpected number of hosts created for user {u}"
+ assert Host.objects.filter(inventory__id=inventory.id)[0].name == 'foobar-0'
+
+ for indx, u in enumerate([member, auditor, use_inv_member]):
+ bulk_host_create_response = post(
+ reverse('api:bulk_host_create'), {'inventory': inventory.id, 'hosts': [{'name': f'foobar2-{indx}'}]}, u, expect=400
+ ).data
+ assert bulk_host_create_response['__all__'][0] == f'Inventory with id {inventory.id} not found or lack permissions to add hosts.'
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize('num_jobs, num_queries', [(1, 25), (10, 25)])
+def test_bulk_job_launch_queries(job_template, organization, inventory, project, post, get, user, num_jobs, num_queries, django_assert_max_num_queries):
+ '''
+ if I have access to the unified job template
+ ... I can launch the bulk job
+ ... and the number of queries should NOT scale with the number of jobs
+ '''
+ normal_user = user('normal_user', False)
+ org_admin = user('org_admin', False)
+ jt = JobTemplate.objects.create(name='my-jt', ask_inventory_on_launch=True, project=project, playbook='helloworld.yml')
+ organization.member_role.members.add(normal_user)
+ organization.admin_role.members.add(org_admin)
+ jt.execute_role.members.add(normal_user)
+ inventory.use_role.members.add(normal_user)
+ jt.save()
+ inventory.save()
+ jobs = [{'unified_job_template': jt.id, 'inventory': inventory.id} for _ in range(num_jobs)]
+
+ # This is not working, we need to figure that out if we want to include tests for more jobs
+ # with mock.patch('awx.api.serializers.settings.BULK_JOB_MAX_LAUNCH', num_jobs + 1):
+ with django_assert_max_num_queries(num_queries):
+ bulk_job_launch_response = post(reverse('api:bulk_job_launch'), {'name': 'Bulk Job Launch', 'jobs': jobs}, normal_user, expect=201).data
+
+ # Run task manager so the workflow job nodes actually spawn
+ TaskManager().schedule()
+
+ for u in (org_admin, normal_user):
+ bulk_job = get(bulk_job_launch_response['url'], u, expect=200).data
+ assert organization.id == bulk_job['summary_fields']['organization']['id']
+ resp = get(bulk_job_launch_response['related']['workflow_nodes'], u)
+ assert resp.data['count'] == num_jobs
+ for item in resp.data['results']:
+ assert item["unified_job_template"] == jt.id
+ assert item["inventory"] == inventory.id
+
+
+@pytest.mark.django_db
+def test_bulk_job_launch_no_access_to_job_template(job_template, organization, inventory, project, credential, post, get, user):
+ '''
+ if I don't have access to the unified job templare
+ ... I can't launch the bulk job
+ '''
+ normal_user = user('normal_user', False)
+ jt = JobTemplate.objects.create(name='my-jt', inventory=inventory, project=project, playbook='helloworld.yml')
+ jt.save()
+ organization.member_role.members.add(normal_user)
+ bulk_job_launch_response = post(
+ reverse('api:bulk_job_launch'), {'name': 'Bulk Job Launch', 'jobs': [{'unified_job_template': jt.id}]}, normal_user, expect=400
+ ).data
+ assert bulk_job_launch_response['__all__'][0] == f'Job Templates {{{jt.id}}} not found or you don\'t have permissions to access it'
+
+
+@pytest.mark.django_db
+def test_bulk_job_launch_no_org_assigned(job_template, organization, inventory, project, credential, post, get, user):
+ '''
+ if I am not part of any organization...
+ ... I can't launch the bulk job
+ '''
+ normal_user = user('normal_user', False)
+ jt = JobTemplate.objects.create(name='my-jt', inventory=inventory, project=project, playbook='helloworld.yml')
+ jt.save()
+ jt.execute_role.members.add(normal_user)
+ bulk_job_launch_response = post(
+ reverse('api:bulk_job_launch'), {'name': 'Bulk Job Launch', 'jobs': [{'unified_job_template': jt.id}]}, normal_user, expect=400
+ ).data
+ assert bulk_job_launch_response['__all__'][0] == 'User not part of any organization, please assign an organization to assign to the bulk job'
+
+
+@pytest.mark.django_db
+def test_bulk_job_launch_multiple_org_assigned(job_template, organization, inventory, project, credential, post, get, user):
+ '''
+ if I am part of multiple organization...
+ and if I do not provide org at the launch time
+ ... I can't launch the bulk job
+ '''
+ normal_user = user('normal_user', False)
+ org1 = Organization.objects.create(name='foo1')
+ org2 = Organization.objects.create(name='foo2')
+ org1.member_role.members.add(normal_user)
+ org2.member_role.members.add(normal_user)
+ jt = JobTemplate.objects.create(name='my-jt', inventory=inventory, project=project, playbook='helloworld.yml')
+ jt.save()
+ jt.execute_role.members.add(normal_user)
+ bulk_job_launch_response = post(
+ reverse('api:bulk_job_launch'), {'name': 'Bulk Job Launch', 'jobs': [{'unified_job_template': jt.id}]}, normal_user, expect=400
+ ).data
+ assert bulk_job_launch_response['__all__'][0] == 'User has permission to multiple Organizations, please set one of them in the request'
+
+
+@pytest.mark.django_db
+def test_bulk_job_launch_specific_org(job_template, organization, inventory, project, credential, post, get, user):
+ '''
+ if I am part of multiple organization...
+ and if I provide org at the launch time
+ ... I can launch the bulk job
+ '''
+ normal_user = user('normal_user', False)
+ org1 = Organization.objects.create(name='foo1')
+ org2 = Organization.objects.create(name='foo2')
+ org1.member_role.members.add(normal_user)
+ org2.member_role.members.add(normal_user)
+ jt = JobTemplate.objects.create(name='my-jt', inventory=inventory, project=project, playbook='helloworld.yml')
+ jt.save()
+ jt.execute_role.members.add(normal_user)
+ bulk_job_launch_response = post(
+ reverse('api:bulk_job_launch'), {'name': 'Bulk Job Launch', 'jobs': [{'unified_job_template': jt.id}], 'organization': org1.id}, normal_user, expect=201
+ ).data
+ bulk_job_id = bulk_job_launch_response['id']
+ bulk_job_obj = WorkflowJob.objects.filter(id=bulk_job_id, is_bulk_job=True).first()
+ assert org1.id == bulk_job_obj.organization.id
+
+
+@pytest.mark.django_db
+def test_bulk_job_launch_inventory_no_access(job_template, organization, inventory, project, credential, post, get, user):
+ '''
+ if I don't have access to the inventory...
+ and if I try to use it at the launch time
+ ... I can't launch the bulk job
+ '''
+ normal_user = user('normal_user', False)
+ org1 = Organization.objects.create(name='foo1')
+ org2 = Organization.objects.create(name='foo2')
+ jt = JobTemplate.objects.create(name='my-jt', inventory=inventory, project=project, playbook='helloworld.yml')
+ jt.save()
+ org1.member_role.members.add(normal_user)
+ inv = Inventory.objects.create(name='inv1', organization=org2)
+ jt.execute_role.members.add(normal_user)
+ bulk_job_launch_response = post(
+ reverse('api:bulk_job_launch'), {'name': 'Bulk Job Launch', 'jobs': [{'unified_job_template': jt.id, 'inventory': inv.id}]}, normal_user, expect=400
+ ).data
+ assert bulk_job_launch_response['__all__'][0] == f'Inventories {{{inv.id}}} not found or you don\'t have permissions to access it'
+
+
+@pytest.mark.django_db
+def test_bulk_job_inventory_prompt(job_template, organization, inventory, project, credential, post, get, user):
+ '''
+ Job template has an inventory set as prompt_on_launch
+ and if I provide the inventory as a parameter in bulk job
+ ... job uses that inventory
+ '''
+ normal_user = user('normal_user', False)
+ org1 = Organization.objects.create(name='foo1')
+ jt = JobTemplate.objects.create(name='my-jt', ask_inventory_on_launch=True, project=project, playbook='helloworld.yml')
+ jt.save()
+ org1.member_role.members.add(normal_user)
+ inv = Inventory.objects.create(name='inv1', organization=org1)
+ jt.execute_role.members.add(normal_user)
+ inv.use_role.members.add(normal_user)
+ bulk_job_launch_response = post(
+ reverse('api:bulk_job_launch'), {'name': 'Bulk Job Launch', 'jobs': [{'unified_job_template': jt.id, 'inventory': inv.id}]}, normal_user, expect=201
+ ).data
+ bulk_job_id = bulk_job_launch_response['id']
+ node = WorkflowJob.objects.get(id=bulk_job_id).workflow_job_nodes.all().order_by('created')
+ assert inv.id == node[0].inventory.id
+
+
+@pytest.mark.django_db
+def test_bulk_job_set_all_prompt(job_template, organization, inventory, project, credentialtype_ssh, post, get, user):
+ '''
+ Job template has many fields set as prompt_on_launch
+ and if I provide all those fields as a parameter in bulk job
+ ... job uses them
+ '''
+ normal_user = user('normal_user', False)
+ jt = JobTemplate.objects.create(
+ name='my-jt',
+ ask_inventory_on_launch=True,
+ ask_diff_mode_on_launch=True,
+ ask_job_type_on_launch=True,
+ ask_verbosity_on_launch=True,
+ ask_execution_environment_on_launch=True,
+ ask_forks_on_launch=True,
+ ask_job_slice_count_on_launch=True,
+ ask_timeout_on_launch=True,
+ ask_variables_on_launch=True,
+ ask_scm_branch_on_launch=True,
+ ask_limit_on_launch=True,
+ ask_skip_tags_on_launch=True,
+ ask_tags_on_launch=True,
+ project=project,
+ playbook='helloworld.yml',
+ )
+ jt.save()
+ organization.member_role.members.add(normal_user)
+ inv = Inventory.objects.create(name='inv1', organization=organization)
+ ee = ExecutionEnvironment.objects.create(name='test-ee', image='quay.io/foo/bar')
+ jt.execute_role.members.add(normal_user)
+ inv.use_role.members.add(normal_user)
+ bulk_job_launch_response = post(
+ reverse('api:bulk_job_launch'),
+ {
+ 'name': 'Bulk Job Launch',
+ 'jobs': [
+ {
+ 'unified_job_template': jt.id,
+ 'inventory': inv.id,
+ 'diff_mode': True,
+ 'job_type': 'check',
+ 'verbosity': 3,
+ 'execution_environment': ee.id,
+ 'forks': 1,
+ 'job_slice_count': 1,
+ 'timeout': 200,
+ 'extra_data': {'prompted_key': 'prompted_val'},
+ 'scm_branch': 'non_dev',
+ 'limit': 'kansas',
+ 'skip_tags': 'foobar',
+ 'job_tags': 'untagged',
+ }
+ ],
+ },
+ normal_user,
+ expect=201,
+ ).data
+ bulk_job_id = bulk_job_launch_response['id']
+ node = WorkflowJob.objects.get(id=bulk_job_id).workflow_job_nodes.all().order_by('created')
+ assert node[0].inventory.id == inv.id
+ assert node[0].diff_mode == True
+ assert node[0].job_type == 'check'
+ assert node[0].verbosity == 3
+ assert node[0].execution_environment.id == ee.id
+ assert node[0].forks == 1
+ assert node[0].job_slice_count == 1
+ assert node[0].timeout == 200
+ assert node[0].extra_data == {'prompted_key': 'prompted_val'}
+ assert node[0].scm_branch == 'non_dev'
+ assert node[0].limit == 'kansas'
+ assert node[0].skip_tags == 'foobar'
+ assert node[0].job_tags == 'untagged'
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize('num_hosts, num_queries', [(1, 70), (10, 150), (25, 250)])
+def test_bulk_host_delete_num_queries(organization, inventory, post, get, user, num_hosts, num_queries, django_assert_max_num_queries):
+ '''
+ If I am a...
+ org admin
+ inventory admin at org level
+ admin of a particular inventory
+ superuser
+
+ Bulk Host delete should take under a certain number of queries
+ '''
+ users_list = setup_admin_users_list(organization, inventory, user)
+ for u in users_list:
+ hosts = [{'name': str(uuid4())} for i in range(num_hosts)]
+ with django_assert_max_num_queries(num_queries):
+ bulk_host_create_response = post(reverse('api:bulk_host_create'), {'inventory': inventory.id, 'hosts': hosts}, u, expect=201).data
+ assert len(bulk_host_create_response['hosts']) == len(hosts), f"unexpected number of hosts created for user {u}"
+ hosts_ids_created = get_inventory_hosts(get, inventory.id, u)
+ bulk_host_delete_response = post(reverse('api:bulk_host_delete'), {'hosts': hosts_ids_created}, u, expect=201).data
+ assert len(bulk_host_delete_response['hosts'].keys()) == len(hosts), f"unexpected number of hosts deleted for user {u}"
+
+
+@pytest.mark.django_db
+def test_bulk_host_delete_rbac(organization, inventory, post, get, user):
+ '''
+ If I am a...
+ org admin
+ inventory admin at org level
+ admin of a particular invenotry
+ ... I can bulk delete hosts
+
+ Everyone else cannot
+ '''
+ admin_users_list = setup_admin_users_list(organization, inventory, user)
+ users_list = setup_none_admin_uses_list(organization, inventory, user)
+
+ for indx, u in enumerate(admin_users_list):
+ bulk_host_create_response = post(
+ reverse('api:bulk_host_create'), {'inventory': inventory.id, 'hosts': [{'name': f'foobar-{indx}'}]}, u, expect=201
+ ).data
+ assert len(bulk_host_create_response['hosts']) == 1, f"unexpected number of hosts created for user {u}"
+ assert Host.objects.filter(inventory__id=inventory.id)[0].name == f'foobar-{indx}'
+ hosts_ids_created = get_inventory_hosts(get, inventory.id, u)
+ bulk_host_delete_response = post(reverse('api:bulk_host_delete'), {'hosts': hosts_ids_created}, u, expect=201).data
+ assert len(bulk_host_delete_response['hosts'].keys()) == 1, f"unexpected number of hosts deleted by user {u}"
+
+ for indx, create_u in enumerate(admin_users_list):
+ bulk_host_create_response = post(
+ reverse('api:bulk_host_create'), {'inventory': inventory.id, 'hosts': [{'name': f'foobar2-{indx}'}]}, create_u, expect=201
+ ).data
+ print(bulk_host_create_response)
+ assert bulk_host_create_response['hosts'][0]['name'] == f'foobar2-{indx}'
+ hosts_ids_created = get_inventory_hosts(get, inventory.id, create_u)
+ print(f"Try to delete {hosts_ids_created}")
+ for delete_u in users_list:
+ bulk_host_delete_response = post(reverse('api:bulk_host_delete'), {'hosts': hosts_ids_created}, delete_u, expect=403).data
+ assert "Lack permissions to delete hosts from this inventory." in bulk_host_delete_response['inventories'].values()
+
+
+@pytest.mark.django_db
+def test_bulk_host_delete_from_multiple_inv(organization, inventory, post, get, user):
+ '''
+ If I am inventory admin at org level
+
+ Bulk Host delete should be enabled only on my inventory
+ '''
+ num_hosts = 10
+ inventory.organization = organization
+
+ # Create second inventory
+ inv2 = organization.inventories.create(name="second-test-inv")
+ inv2.organization = organization
+ admin2_user = user('inventory2_admin', False)
+ inv2.admin_role.members.add(admin2_user)
+
+ admin_user = user('inventory_admin', False)
+ inventory.admin_role.members.add(admin_user)
+
+ organization.member_role.members.add(admin_user)
+ organization.member_role.members.add(admin2_user)
+
+ hosts = [{'name': str(uuid4())} for i in range(num_hosts)]
+ hosts2 = [{'name': str(uuid4())} for i in range(num_hosts)]
+
+ # create hosts in each of the inventories
+ bulk_host_create_response = post(reverse('api:bulk_host_create'), {'inventory': inventory.id, 'hosts': hosts}, admin_user, expect=201).data
+ assert len(bulk_host_create_response['hosts']) == len(hosts), f"unexpected number of hosts created for user {admin_user}"
+
+ bulk_host_create_response2 = post(reverse('api:bulk_host_create'), {'inventory': inv2.id, 'hosts': hosts2}, admin2_user, expect=201).data
+ assert len(bulk_host_create_response2['hosts']) == len(hosts), f"unexpected number of hosts created for user {admin2_user}"
+
+ # get all hosts ids - from both inventories
+ hosts_ids_created = get_inventory_hosts(get, inventory.id, admin_user)
+ hosts_ids_created += get_inventory_hosts(get, inv2.id, admin2_user)
+
+ expected_error = "Lack permissions to delete hosts from this inventory."
+ # try to delete ALL hosts with admin user of inventory 1.
+ for inv_name, invadmin in zip([inv2.name, inventory.name], [admin_user, admin2_user]):
+ bulk_host_delete_response = post(reverse('api:bulk_host_delete'), {'hosts': hosts_ids_created}, invadmin, expect=403).data
+ result_message = bulk_host_delete_response['inventories'][inv_name]
+ assert result_message == expected_error, f"deleted hosts without permission by user {invadmin}"
+
+
+def setup_admin_users_list(organization, inventory, user):
+ inventory.organization = organization
+ inventory_admin = user('inventory_admin', False)
+ org_admin = user('org_admin', False)
+ org_inv_admin = user('org_admin', False)
+ superuser = user('admin', True)
+ for u in [org_admin, org_inv_admin, inventory_admin]:
+ organization.member_role.members.add(u)
+ organization.admin_role.members.add(org_admin)
+ organization.inventory_admin_role.members.add(org_inv_admin)
+ inventory.admin_role.members.add(inventory_admin)
+ return [inventory_admin, org_inv_admin, superuser, org_admin]
+
+
+def setup_none_admin_uses_list(organization, inventory, user):
+ inventory.organization = organization
+ auditor = user('auditor', False)
+ member = user('member', False)
+ use_inv_member = user('member', False)
+ for u in [auditor, member, use_inv_member]:
+ organization.member_role.members.add(u)
+ inventory.use_role.members.add(use_inv_member)
+ organization.auditor_role.members.add(auditor)
+ return [auditor, member, use_inv_member]
+
+
+def get_inventory_hosts(get, inv_id, use_user):
+ data = get(reverse('api:inventory_hosts_list', kwargs={'pk': inv_id}), use_user, expect=200).data
+ results = [host['id'] for host in data['results']]
+ return results
diff --git a/awx/main/tests/functional/test_copy.py b/awx/main/tests/functional/test_copy.py
index 0574f9ccbdef..7c156979a8b7 100644
--- a/awx/main/tests/functional/test_copy.py
+++ b/awx/main/tests/functional/test_copy.py
@@ -43,7 +43,7 @@ def test_job_template_copy(
c.save()
assert get(reverse('api:job_template_copy', kwargs={'pk': job_template_with_survey_passwords.pk}), alice, expect=200).data['can_copy'] is True
jt_copy_pk_alice = post(
- reverse('api:job_template_copy', kwargs={'pk': job_template_with_survey_passwords.pk}), {'name': 'new jt name'}, alice, expect=201
+ reverse('api:job_template_copy', kwargs={'pk': job_template_with_survey_passwords.pk}), {'name': 'new jt name alice'}, alice, expect=201
).data['id']
jt_copy_admin = type(job_template_with_survey_passwords).objects.get(pk=jt_copy_pk)
@@ -53,7 +53,7 @@ def test_job_template_copy(
assert jt_copy_alice.created_by == alice
for jt_copy in (jt_copy_admin, jt_copy_alice):
- assert jt_copy.name == 'new jt name'
+ assert jt_copy.name.startswith('new jt name')
assert jt_copy.project == project
assert jt_copy.inventory == inventory
assert jt_copy.playbook == job_template_with_survey_passwords.playbook
@@ -123,6 +123,24 @@ def test_inventory_copy(inventory, group_factory, post, get, alice, organization
assert set(group_2_2_copy.hosts.all()) == set()
+@pytest.mark.django_db
+@pytest.mark.parametrize(
+ "is_admin, can_copy, status",
+ [
+ [True, True, 200],
+ [False, False, 200],
+ ],
+)
+def test_workflow_job_template_copy_access(get, admin_user, alice, workflow_job_template, is_admin, can_copy, status):
+ url = reverse('api:workflow_job_template_copy', kwargs={'pk': workflow_job_template.pk})
+ if is_admin:
+ response = get(url, user=admin_user, expect=status)
+ else:
+ workflow_job_template.organization.auditor_role.members.add(alice)
+ response = get(url, user=alice, expect=status)
+ assert response.data['can_copy'] == can_copy
+
+
@pytest.mark.django_db
def test_workflow_job_template_copy(workflow_job_template, post, get, admin, organization):
'''
diff --git a/awx/main/tests/functional/test_credential.py b/awx/main/tests/functional/test_credential.py
index d2937412daa5..2ecc508088a0 100644
--- a/awx/main/tests/functional/test_credential.py
+++ b/awx/main/tests/functional/test_credential.py
@@ -78,18 +78,22 @@ def test_default_cred_types():
[
'aim',
'aws',
+ 'aws_secretsmanager_credential',
'azure_kv',
'azure_rm',
+ 'bitbucket_dc_token',
'centrify_vault_kv',
'conjur',
'controller',
'galaxy_api_token',
'gce',
'github_token',
+ 'github_app_lookup',
'gitlab_token',
'gpg_public_key',
'hashivault_kv',
'hashivault_ssh',
+ 'hcp_terraform',
'insights',
'kubernetes_bearer_token',
'net',
@@ -99,6 +103,7 @@ def test_default_cred_types():
'satellite6',
'scm',
'ssh',
+ 'terraform',
'thycotic_dsv',
'thycotic_tss',
'vault',
@@ -336,3 +341,25 @@ def test_credential_get_input(organization_factory):
# verify return values for encrypted secret fields are decrypted
assert cred.inputs['vault_password'].startswith('$encrypted$')
assert cred.get_input('vault_password') == 'testing321'
+
+
+@pytest.mark.django_db
+def test_idempotent_credential_type_setup():
+ """
+ awx main app ready() calls `setup_tower_managed_defaults()` to register CredentialType(s).
+ This is problematic in our testing system. pytest_django deviates from the production ready() call path. pytest_django calls our apps ready() function
+ before migrations run. This is a problem since we interact with tables in the database that do not yet exist.
+
+ Now forget about what you just read because we do not _actually_ want to register CredentialType(s) in our test at all. So then
+ you would expect this bit of code to spy on `setup_tower_managed_defaults` and assert it was not called BUT registering a spy early
+ enough is hard. The call to ready() from pytest_django happens via pytest hooks very early https://github.com/pytest-dev/pytest-django/blob/1157a7c5c74f4b4e0f4aca8312f3fe67eb00568e/pytest_django/plugin.py#L266C5-L266C34
+
+ Instead of ensuring that `setup_tower_managed_defaults()` is explicitly not called, we check it _implicitly_ by observing that no credential type records are created.
+ """
+ assert CredentialType.objects.count() == 0
+ CredentialType.setup_tower_managed_defaults()
+ total = CredentialType.objects.count()
+ assert total > 0
+
+ CredentialType.setup_tower_managed_defaults()
+ assert CredentialType.objects.count() == total
diff --git a/awx/main/tests/functional/test_credential_plugins.py b/awx/main/tests/functional/test_credential_plugins.py
deleted file mode 100644
index 3ea31fce425d..000000000000
--- a/awx/main/tests/functional/test_credential_plugins.py
+++ /dev/null
@@ -1,78 +0,0 @@
-import pytest
-from unittest import mock
-from awx.main.credential_plugins import hashivault
-
-
-def test_imported_azure_cloud_sdk_vars():
- from awx.main.credential_plugins import azure_kv
-
- assert len(azure_kv.clouds) > 0
- assert all([hasattr(c, 'name') for c in azure_kv.clouds])
- assert all([hasattr(c, 'suffixes') for c in azure_kv.clouds])
- assert all([hasattr(c.suffixes, 'keyvault_dns') for c in azure_kv.clouds])
-
-
-def test_hashivault_approle_auth():
- kwargs = {
- 'role_id': 'the_role_id',
- 'secret_id': 'the_secret_id',
- }
- expected_res = {
- 'role_id': 'the_role_id',
- 'secret_id': 'the_secret_id',
- }
- res = hashivault.approle_auth(**kwargs)
- assert res == expected_res
-
-
-def test_hashivault_kubernetes_auth():
- kwargs = {
- 'kubernetes_role': 'the_kubernetes_role',
- }
- expected_res = {
- 'role': 'the_kubernetes_role',
- 'jwt': 'the_jwt',
- }
- with mock.patch('pathlib.Path') as path_mock:
- mock.mock_open(path_mock.return_value.open, read_data='the_jwt')
- res = hashivault.kubernetes_auth(**kwargs)
- path_mock.assert_called_with('/var/run/secrets/kubernetes.io/serviceaccount/token')
- assert res == expected_res
-
-
-def test_hashivault_handle_auth_token():
- kwargs = {
- 'token': 'the_token',
- }
- token = hashivault.handle_auth(**kwargs)
- assert token == kwargs['token']
-
-
-def test_hashivault_handle_auth_approle():
- kwargs = {
- 'role_id': 'the_role_id',
- 'secret_id': 'the_secret_id',
- }
- with mock.patch.object(hashivault, 'method_auth') as method_mock:
- method_mock.return_value = 'the_token'
- token = hashivault.handle_auth(**kwargs)
- method_mock.assert_called_with(**kwargs, auth_param=kwargs)
- assert token == 'the_token'
-
-
-def test_hashivault_handle_auth_kubernetes():
- kwargs = {
- 'kubernetes_role': 'the_kubernetes_role',
- }
- with mock.patch.object(hashivault, 'method_auth') as method_mock:
- with mock.patch('pathlib.Path') as path_mock:
- mock.mock_open(path_mock.return_value.open, read_data='the_jwt')
- method_mock.return_value = 'the_token'
- token = hashivault.handle_auth(**kwargs)
- method_mock.assert_called_with(**kwargs, auth_param={'role': 'the_kubernetes_role', 'jwt': 'the_jwt'})
- assert token == 'the_token'
-
-
-def test_hashivault_handle_auth_not_enough_args():
- with pytest.raises(Exception):
- hashivault.handle_auth()
diff --git a/awx/main/tests/functional/test_dispatch.py b/awx/main/tests/functional/test_dispatch.py
index 86e90e50a0f4..7226318a19bf 100644
--- a/awx/main/tests/functional/test_dispatch.py
+++ b/awx/main/tests/functional/test_dispatch.py
@@ -1,19 +1,12 @@
import datetime
-import multiprocessing
-import random
-import signal
-import time
from unittest import mock
-
from django.utils.timezone import now as tz_now
import pytest
from awx.main.models import Job, WorkflowJob, Instance
from awx.main.dispatch import reaper
-from awx.main.dispatch.pool import StatefulPoolWorker, WorkerPool, AutoscalePool
-from awx.main.dispatch.publish import task
-from awx.main.dispatch.worker import BaseWorker, TaskWorker
-
+from awx.main.tasks import system
+from dispatcherd.publish import task
'''
Prevent logger. calls from triggering database operations
@@ -56,286 +49,9 @@ def multiply(a, b):
return a * b
-class SimpleWorker(BaseWorker):
- def perform_work(self, body, *args):
- pass
-
-
-class ResultWriter(BaseWorker):
- def perform_work(self, body, result_queue):
- result_queue.put(body + '!!!')
-
-
-class SlowResultWriter(BaseWorker):
- def perform_work(self, body, result_queue):
- time.sleep(3)
- super(SlowResultWriter, self).perform_work(body, result_queue)
-
-
-@pytest.mark.usefixtures("disable_database_settings")
-class TestPoolWorker:
- def setup_method(self, test_method):
- self.worker = StatefulPoolWorker(1000, self.tick, tuple())
-
- def tick(self):
- self.worker.finished.put(self.worker.queue.get()['uuid'])
- time.sleep(0.5)
-
- def test_qsize(self):
- assert self.worker.qsize == 0
- for i in range(3):
- self.worker.put({'task': 'abc123'})
- assert self.worker.qsize == 3
-
- def test_put(self):
- assert len(self.worker.managed_tasks) == 0
- assert self.worker.messages_finished == 0
- self.worker.put({'task': 'abc123'})
-
- assert len(self.worker.managed_tasks) == 1
- assert self.worker.messages_sent == 1
-
- def test_managed_tasks(self):
- self.worker.put({'task': 'abc123'})
- self.worker.calculate_managed_tasks()
- assert len(self.worker.managed_tasks) == 1
-
- self.tick()
- self.worker.calculate_managed_tasks()
- assert len(self.worker.managed_tasks) == 0
-
- def test_current_task(self):
- self.worker.put({'task': 'abc123'})
- assert self.worker.current_task['task'] == 'abc123'
-
- def test_quit(self):
- self.worker.quit()
- assert self.worker.queue.get() == 'QUIT'
-
- def test_idle_busy(self):
- assert self.worker.idle is True
- assert self.worker.busy is False
- self.worker.put({'task': 'abc123'})
- assert self.worker.busy is True
- assert self.worker.idle is False
-
-
-@pytest.mark.django_db
-class TestWorkerPool:
- def setup_method(self, test_method):
- self.pool = WorkerPool(min_workers=3)
-
- def teardown_method(self, test_method):
- self.pool.stop(signal.SIGTERM)
-
- def test_worker(self):
- self.pool.init_workers(SimpleWorker().work_loop)
- assert len(self.pool) == 3
- for worker in self.pool.workers:
- assert worker.messages_sent == 0
- assert worker.alive is True
-
- def test_single_task(self):
- self.pool.init_workers(SimpleWorker().work_loop)
- self.pool.write(0, 'xyz')
- assert self.pool.workers[0].messages_sent == 1 # worker at index 0 handled one task
- assert self.pool.workers[1].messages_sent == 0
- assert self.pool.workers[2].messages_sent == 0
-
- def test_queue_preference(self):
- self.pool.init_workers(SimpleWorker().work_loop)
- self.pool.write(2, 'xyz')
- assert self.pool.workers[0].messages_sent == 0
- assert self.pool.workers[1].messages_sent == 0
- assert self.pool.workers[2].messages_sent == 1 # worker at index 2 handled one task
-
- def test_worker_processing(self):
- result_queue = multiprocessing.Queue()
- self.pool.init_workers(ResultWriter().work_loop, result_queue)
- for i in range(10):
- self.pool.write(random.choice(range(len(self.pool))), 'Hello, Worker {}'.format(i))
- all_messages = [result_queue.get(timeout=1) for i in range(10)]
- all_messages.sort()
- assert all_messages == ['Hello, Worker {}!!!'.format(i) for i in range(10)]
-
- total_handled = sum([worker.messages_sent for worker in self.pool.workers])
- assert total_handled == 10
-
-
-@pytest.mark.django_db
-class TestAutoScaling:
- def setup_method(self, test_method):
- self.pool = AutoscalePool(min_workers=2, max_workers=10)
-
- def teardown_method(self, test_method):
- self.pool.stop(signal.SIGTERM)
-
- def test_scale_up(self):
- result_queue = multiprocessing.Queue()
- self.pool.init_workers(SlowResultWriter().work_loop, result_queue)
-
- # start with two workers, write an event to each worker and make it busy
- assert len(self.pool) == 2
- for i, w in enumerate(self.pool.workers):
- w.put('Hello, Worker {}'.format(0))
- assert len(self.pool) == 2
-
- # wait for the subprocesses to start working on their tasks and be marked busy
- time.sleep(1)
- assert self.pool.should_grow
-
- # write a third message, expect a new worker to spawn because all
- # workers are busy
- self.pool.write(0, 'Hello, Worker {}'.format(2))
- assert len(self.pool) == 3
-
- def test_scale_down(self):
- self.pool.init_workers(ResultWriter().work_loop, multiprocessing.Queue())
-
- # start with two workers, and scale up to 10 workers
- assert len(self.pool) == 2
- for i in range(8):
- self.pool.up()
- assert len(self.pool) == 10
-
- # cleanup should scale down to 8 workers
- self.pool.cleanup()
- assert len(self.pool) == 2
-
- def test_max_scale_up(self):
- self.pool.init_workers(ResultWriter().work_loop, multiprocessing.Queue())
-
- assert len(self.pool) == 2
- for i in range(25):
- self.pool.up()
- assert self.pool.max_workers == 10
- assert self.pool.full is True
- assert len(self.pool) == 10
-
- def test_equal_worker_distribution(self):
- # if all workers are busy, spawn new workers *before* adding messages
- # to an existing queue
- self.pool.init_workers(SlowResultWriter().work_loop, multiprocessing.Queue)
-
- # start with two workers, write an event to each worker and make it busy
- assert len(self.pool) == 2
- for i in range(10):
- self.pool.write(0, 'Hello, World!')
- assert len(self.pool) == 10
- for w in self.pool.workers:
- assert w.busy
- assert len(w.managed_tasks) == 1
-
- # the queue is full at 10, the _next_ write should put the message into
- # a worker's backlog
- assert len(self.pool) == 10
- for w in self.pool.workers:
- assert w.messages_sent == 1
- self.pool.write(0, 'Hello, World!')
- assert len(self.pool) == 10
- assert self.pool.workers[0].messages_sent == 2
-
- def test_lost_worker_autoscale(self):
- # if a worker exits, it should be replaced automatically up to min_workers
- self.pool.init_workers(ResultWriter().work_loop, multiprocessing.Queue())
-
- # start with two workers, kill one of them
- assert len(self.pool) == 2
- assert not self.pool.should_grow
- alive_pid = self.pool.workers[1].pid
- self.pool.workers[0].process.terminate()
- time.sleep(2) # wait a moment for sigterm
-
- # clean up and the dead worker
- self.pool.cleanup()
- assert len(self.pool) == 1
- assert self.pool.workers[0].pid == alive_pid
-
- # the next queue write should replace the lost worker
- self.pool.write(0, 'Hello, Worker')
- assert len(self.pool) == 2
-
-
-@pytest.mark.usefixtures("disable_database_settings")
-class TestTaskDispatcher:
- @property
- def tm(self):
- return TaskWorker()
-
- def test_function_dispatch(self):
- result = self.tm.perform_work({'task': 'awx.main.tests.functional.test_dispatch.add', 'args': [2, 2]})
- assert result == 4
-
- def test_function_dispatch_must_be_decorated(self):
- result = self.tm.perform_work({'task': 'awx.main.tests.functional.test_dispatch.restricted', 'args': [2, 2]})
- assert isinstance(result, ValueError)
- assert str(result) == 'awx.main.tests.functional.test_dispatch.restricted is not decorated with @task()' # noqa
-
- def test_method_dispatch(self):
- result = self.tm.perform_work({'task': 'awx.main.tests.functional.test_dispatch.Adder', 'args': [2, 2]})
- assert result == 4
-
- def test_method_dispatch_must_be_decorated(self):
- result = self.tm.perform_work({'task': 'awx.main.tests.functional.test_dispatch.Restricted', 'args': [2, 2]})
- assert isinstance(result, ValueError)
- assert str(result) == 'awx.main.tests.functional.test_dispatch.Restricted is not decorated with @task()' # noqa
-
- def test_python_function_cannot_be_imported(self):
- result = self.tm.perform_work(
- {
- 'task': 'os.system',
- 'args': ['ls'],
- }
- )
- assert isinstance(result, ValueError)
- assert str(result) == 'os.system is not a valid awx task' # noqa
-
- def test_undefined_function_cannot_be_imported(self):
- result = self.tm.perform_work({'task': 'awx.foo.bar'})
- assert isinstance(result, ModuleNotFoundError)
- assert str(result) == "No module named 'awx.foo'" # noqa
-
-
-class TestTaskPublisher:
- def test_function_callable(self):
- assert add(2, 2) == 4
-
- def test_method_callable(self):
- assert Adder().run(2, 2) == 4
-
- def test_function_apply_async(self):
- message, queue = add.apply_async([2, 2], queue='foobar')
- assert message['args'] == [2, 2]
- assert message['kwargs'] == {}
- assert message['task'] == 'awx.main.tests.functional.test_dispatch.add'
- assert queue == 'foobar'
-
- def test_method_apply_async(self):
- message, queue = Adder.apply_async([2, 2], queue='foobar')
- assert message['args'] == [2, 2]
- assert message['kwargs'] == {}
- assert message['task'] == 'awx.main.tests.functional.test_dispatch.Adder'
- assert queue == 'foobar'
-
- def test_apply_async_queue_required(self):
- with pytest.raises(ValueError) as e:
- message, queue = add.apply_async([2, 2])
- assert "awx.main.tests.functional.test_dispatch.add: Queue value required and may not be None" == e.value.args[0]
-
- def test_queue_defined_in_task_decorator(self):
- message, queue = multiply.apply_async([2, 2])
- assert queue == 'hard-math'
-
- def test_queue_overridden_from_task_decorator(self):
- message, queue = multiply.apply_async([2, 2], queue='not-so-hard')
- assert queue == 'not-so-hard'
-
- def test_apply_with_callable_queuename(self):
- message, queue = add.apply_async([2, 2], queue=lambda: 'called')
- assert queue == 'called'
-
-
yesterday = tz_now() - datetime.timedelta(days=1)
+minute = tz_now() - datetime.timedelta(seconds=120)
+now = tz_now()
@pytest.mark.django_db
@@ -346,11 +62,6 @@ class TestJobReaper(object):
('running', '', '', None, False), # running, not assigned to the instance
('running', 'awx', '', None, True), # running, has the instance as its execution_node
('running', '', 'awx', None, True), # running, has the instance as its controller_node
- ('waiting', '', '', None, False), # waiting, not assigned to the instance
- ('waiting', 'awx', '', None, False), # waiting, was edited less than a minute ago
- ('waiting', '', 'awx', None, False), # waiting, was edited less than a minute ago
- ('waiting', 'awx', '', yesterday, False), # waiting, managed by another node, ignore
- ('waiting', '', 'awx', yesterday, True), # waiting, assigned to the controller_node, stale
],
)
def test_should_reap(self, status, fail, execution_node, controller_node, modified):
@@ -368,7 +79,6 @@ def test_should_reap(self, status, fail, execution_node, controller_node, modifi
# (because .save() overwrites it to _now_)
Job.objects.filter(id=j.id).update(modified=modified)
reaper.reap(i)
- reaper.reap_waiting(i)
job = Job.objects.first()
if fail:
assert job.status == 'failed'
@@ -377,14 +87,30 @@ def test_should_reap(self, status, fail, execution_node, controller_node, modifi
else:
assert job.status == status
+ def test_waiting_job_sent_back_to_pending(self):
+ this_inst = Instance(hostname='awx')
+ this_inst.save()
+ lost_inst = Instance(hostname='lost', node_type=Instance.Types.EXECUTION, node_state=Instance.States.UNAVAILABLE)
+ lost_inst.save()
+ job = Job.objects.create(status='waiting', controller_node=lost_inst.hostname, execution_node='lost')
+
+ system._heartbeat_handle_lost_instances([lost_inst], this_inst)
+ job.refresh_from_db()
+
+ assert job.status == 'pending'
+ assert job.controller_node == ''
+ assert job.execution_node == ''
+
@pytest.mark.parametrize(
- 'excluded_uuids, fail',
+ 'excluded_uuids, fail, started',
[
- (['abc123'], False),
- ([], True),
+ (['abc123'], False, None),
+ ([], False, None),
+ ([], True, minute),
],
)
- def test_do_not_reap_excluded_uuids(self, excluded_uuids, fail):
+ def test_do_not_reap_excluded_uuids(self, excluded_uuids, fail, started):
+ """Modified Test to account for ref_time in reap()"""
i = Instance(hostname='awx')
i.save()
j = Job(
@@ -395,10 +121,13 @@ def test_do_not_reap_excluded_uuids(self, excluded_uuids, fail):
celery_task_id='abc123',
)
j.save()
+ if started:
+ Job.objects.filter(id=j.id).update(started=started)
# if the UUID is excluded, don't reap it
- reaper.reap(i, excluded_uuids=excluded_uuids)
+ reaper.reap(i, excluded_uuids=excluded_uuids, ref_time=now)
job = Job.objects.first()
+
if fail:
assert job.status == 'failed'
assert 'marked as failed' in job.job_explanation
@@ -414,3 +143,20 @@ def test_workflow_does_not_reap(self):
reaper.reap(i)
assert WorkflowJob.objects.first().status == 'running'
+
+ def test_should_not_reap_new(self):
+ """
+ This test is designed specifically to ensure that jobs that are launched after the dispatcher has provided a list of UUIDs aren't reaped.
+ It is very racy and this test is designed with that in mind
+ """
+ i = Instance(hostname='awx')
+ # ref_time is set to 10 seconds in the past to mimic someone launching a job in the heartbeat window.
+ ref_time = tz_now() - datetime.timedelta(seconds=10)
+ # creating job at current time
+ job = Job.objects.create(status='running', controller_node=i.hostname)
+ reaper.reap(i, ref_time=ref_time)
+ # explictly refreshing from db to ensure up to date cache
+ job.refresh_from_db()
+ assert job.started > ref_time
+ assert job.status == 'running'
+ assert job.job_explanation == ''
diff --git a/awx/main/tests/functional/test_fixture_factories.py b/awx/main/tests/functional/test_fixture_factories.py
index 1af7b6624650..30fa1247ae9a 100644
--- a/awx/main/tests/functional/test_fixture_factories.py
+++ b/awx/main/tests/functional/test_fixture_factories.py
@@ -50,13 +50,11 @@ def test_org_factory_roles(organization_factory):
teams=['team1', 'team2'],
users=['team1:foo', 'bar'],
projects=['baz', 'bang'],
- roles=['team2.member_role:foo', 'team1.admin_role:bar', 'team1.admin_role:team2.admin_role', 'baz.admin_role:foo'],
+ roles=['team2.member_role:foo', 'team1.admin_role:bar', 'baz.admin_role:foo'],
)
-
- assert objects.users.bar in objects.teams.team2.admin_role
+ assert objects.users.bar in objects.teams.team1.admin_role
assert objects.users.foo in objects.projects.baz.admin_role
assert objects.users.foo in objects.teams.team1.member_role
- assert objects.teams.team2.admin_role in objects.teams.team1.admin_role.children.all()
@pytest.mark.django_db
diff --git a/awx/main/tests/functional/test_ha.py b/awx/main/tests/functional/test_ha.py
deleted file mode 100644
index 0b4ced53c218..000000000000
--- a/awx/main/tests/functional/test_ha.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import pytest
-
-# AWX
-from awx.main.ha import is_ha_environment
-from awx.main.models.ha import Instance
-
-
-@pytest.mark.django_db
-def test_multiple_instances():
- for i in range(2):
- Instance.objects.create(hostname=f'foo{i}', node_type='hybrid')
- assert is_ha_environment()
-
-
-@pytest.mark.django_db
-def test_db_localhost():
- Instance.objects.create(hostname='foo', node_type='hybrid')
- Instance.objects.create(hostname='bar', node_type='execution')
- assert is_ha_environment() is False
diff --git a/awx/main/tests/functional/test_instance_group_ordering.py b/awx/main/tests/functional/test_instance_group_ordering.py
index 42c69ffc7fe0..fb8c0db16858 100644
--- a/awx/main/tests/functional/test_instance_group_ordering.py
+++ b/awx/main/tests/functional/test_instance_group_ordering.py
@@ -1,6 +1,6 @@
import pytest
-from awx.main.models import InstanceGroup
+from awx.main.models import InstanceGroup, Inventory
@pytest.fixture(scope='function')
@@ -38,6 +38,16 @@ def test_instance_group_ordering(source_model):
assert source_model.instance_groups.through.objects.count() == 0
+@pytest.mark.django_db
+@pytest.mark.parametrize('source_model', ['job_template', 'inventory', 'organization'], indirect=True)
+def test_instance_group_bulk_add(source_model):
+ groups = [InstanceGroup.objects.create(name='host-%d' % i) for i in range(5)]
+ groups.reverse()
+ with pytest.raises(RuntimeError) as err:
+ source_model.instance_groups.add(*groups)
+ assert 'Ordered many-to-many fields do not support multiple objects' in str(err)
+
+
@pytest.mark.django_db
@pytest.mark.parametrize('source_model', ['job_template', 'inventory', 'organization'], indirect=True)
def test_instance_group_middle_deletion(source_model):
@@ -66,3 +76,33 @@ def test_explicit_ordering(source_model):
assert [g.name for g in source_model.instance_groups.all()] == ['host-4', 'host-3', 'host-2', 'host-1', 'host-0']
assert [g.name for g in source_model.instance_groups.order_by('name').all()] == ['host-0', 'host-1', 'host-2', 'host-3', 'host-4']
+
+
+@pytest.mark.django_db
+def test_input_inventories_ordering():
+ constructed_inventory = Inventory.objects.create(name='my_constructed', kind='constructed')
+ input_inventories = [Inventory.objects.create(name='inv-%d' % i) for i in range(5)]
+ input_inventories.reverse()
+ for inv in input_inventories:
+ constructed_inventory.input_inventories.add(inv)
+
+ assert [g.name for g in constructed_inventory.input_inventories.all()] == ['inv-4', 'inv-3', 'inv-2', 'inv-1', 'inv-0']
+ assert [(row.position, row.input_inventory.name) for row in constructed_inventory.input_inventories.through.objects.all()] == [
+ (0, 'inv-4'),
+ (1, 'inv-3'),
+ (2, 'inv-2'),
+ (3, 'inv-1'),
+ (4, 'inv-0'),
+ ]
+
+ constructed_inventory.input_inventories.remove(input_inventories[0])
+ assert [g.name for g in constructed_inventory.input_inventories.all()] == ['inv-3', 'inv-2', 'inv-1', 'inv-0']
+ assert [(row.position, row.input_inventory.name) for row in constructed_inventory.input_inventories.through.objects.all()] == [
+ (0, 'inv-3'),
+ (1, 'inv-2'),
+ (2, 'inv-1'),
+ (3, 'inv-0'),
+ ]
+
+ constructed_inventory.input_inventories.clear()
+ assert constructed_inventory.input_inventories.through.objects.count() == 0
diff --git a/awx/main/tests/functional/test_instances.py b/awx/main/tests/functional/test_instances.py
index df6d17786819..4ed652179d91 100644
--- a/awx/main/tests/functional/test_instances.py
+++ b/awx/main/tests/functional/test_instances.py
@@ -94,17 +94,18 @@ def test_instance_dup(org_admin, organization, project, instance_factory, instan
ig_all = instance_group_factory("all", instances=[i1, i2, i3])
ig_dup = instance_group_factory("duplicates", instances=[i1])
- project.organization.instance_groups.add(ig_all, ig_dup)
+ project.organization.instance_groups.add(ig_all)
+ project.organization.instance_groups.add(ig_dup)
actual_num_instances = Instance.objects.count()
list_response = get(reverse('api:instance_list'), user=system_auditor)
api_num_instances_auditor = list(list_response.data.items())[0][1]
+ ig_all.read_role.members.add(org_admin)
list_response2 = get(reverse('api:instance_list'), user=org_admin)
api_num_instances_oa = list(list_response2.data.items())[0][1]
assert api_num_instances_auditor == actual_num_instances
- # Note: The org_admin will not see the default 'tower' node
- # (instance fixture) because it is not in its group, as expected
+ # Note: The org_admin will not see instances unless at least read_role to the IG has been assigned
assert api_num_instances_oa == (actual_num_instances - 1)
diff --git a/awx/main/tests/functional/test_inventory_input_constructed.py b/awx/main/tests/functional/test_inventory_input_constructed.py
new file mode 100644
index 000000000000..2602cf294777
--- /dev/null
+++ b/awx/main/tests/functional/test_inventory_input_constructed.py
@@ -0,0 +1,61 @@
+import pytest
+from awx.main.models import Inventory
+from awx.api.versioning import reverse
+
+
+@pytest.mark.django_db
+def test_constructed_inventory_post(post, admin_user, organization):
+ inv1 = Inventory.objects.create(name='dummy1', kind='constructed', organization=organization)
+ inv2 = Inventory.objects.create(name='dummy2', kind='constructed', organization=organization)
+ resp = post(
+ url=reverse('api:inventory_input_inventories', kwargs={'pk': inv1.pk}),
+ data={'id': inv2.pk},
+ user=admin_user,
+ expect=400,
+ )
+ assert resp.status_code == 400
+
+
+@pytest.mark.django_db
+def test_add_constructed_inventory_source(post, admin_user, constructed_inventory):
+ resp = post(
+ url=reverse('api:inventory_inventory_sources_list', kwargs={'pk': constructed_inventory.pk}),
+ data={'name': 'dummy1', 'source': 'constructed'},
+ user=admin_user,
+ expect=400,
+ )
+ assert resp.status_code == 400
+
+
+@pytest.mark.django_db
+def test_add_constructed_inventory_host(post, admin_user, constructed_inventory):
+ resp = post(
+ url=reverse('api:inventory_hosts_list', kwargs={'pk': constructed_inventory.pk}),
+ data={'name': 'dummy1'},
+ user=admin_user,
+ expect=400,
+ )
+ assert resp.status_code == 400
+
+
+@pytest.mark.django_db
+def test_add_constructed_inventory_group(post, admin_user, constructed_inventory):
+ resp = post(
+ reverse('api:inventory_groups_list', kwargs={'pk': constructed_inventory.pk}),
+ data={'name': 'group-test'},
+ user=admin_user,
+ expect=400,
+ )
+ assert resp.status_code == 400
+
+
+@pytest.mark.django_db
+def test_edit_constructed_inventory_source(patch, admin_user, inventory_source_factory):
+ inv_src = inventory_source_factory(name='dummy1', source='constructed')
+ resp = patch(
+ reverse('api:inventory_source_detail', kwargs={'pk': inv_src.pk}),
+ data={'description': inv_src.name},
+ user=admin_user,
+ expect=400,
+ )
+ assert resp.status_code == 400
diff --git a/awx/main/tests/functional/test_inventory_source_injectors.py b/awx/main/tests/functional/test_inventory_source_injectors.py
index 97fc8a7c17cc..8f740db72f72 100644
--- a/awx/main/tests/functional/test_inventory_source_injectors.py
+++ b/awx/main/tests/functional/test_inventory_source_injectors.py
@@ -5,12 +5,13 @@
import re
from collections import namedtuple
+from awx_plugins.interfaces._temporary_private_container_api import get_incontainer_path
+
from awx.main.tasks.jobs import RunInventoryUpdate
from awx.main.models import InventorySource, Credential, CredentialType, UnifiedJob, ExecutionEnvironment
-from awx.main.constants import CLOUD_PROVIDERS, STANDARD_INVENTORY_UPDATE_ENV
+from awx.main.constants import STANDARD_INVENTORY_UPDATE_ENV
from awx.main.tests import data
-from awx.main.utils.execution_environments import to_container_path
-
+from awx.main.utils.plugins import discover_available_cloud_provider_plugin_names
from django.conf import settings
DATA = os.path.join(os.path.dirname(data.__file__), 'inventory')
@@ -46,6 +47,8 @@ def generate_fake_var(element):
def credential_kind(source):
"""Given the inventory source kind, return expected credential kind"""
+ if source == 'openshift_virtualization':
+ return 'kubernetes_bearer_token'
return source.replace('ec2', 'aws')
@@ -107,12 +110,13 @@ def read_content(private_data_dir, raw_env, inventory_update):
for filename in os.listdir(os.path.join(private_data_dir, subdir)):
filename_list.append(os.path.join(subdir, filename))
filename_list = sorted(filename_list, key=lambda fn: inverse_env.get(os.path.join(private_data_dir, fn), [fn])[0])
+ inventory_content = ""
for filename in filename_list:
if filename in ('args', 'project'):
continue # Ansible runner
abs_file_path = os.path.join(private_data_dir, filename)
file_aliases[abs_file_path] = filename
- runner_path = to_container_path(abs_file_path, private_data_dir)
+ runner_path = get_incontainer_path(abs_file_path, private_data_dir)
if runner_path in inverse_env:
referenced_paths.add(abs_file_path)
alias = 'file_reference'
@@ -121,7 +125,7 @@ def read_content(private_data_dir, raw_env, inventory_update):
break
alias = 'file_reference_{}'.format(i)
else:
- raise RuntimeError('Test not able to cope with >10 references by env vars. ' 'Something probably went very wrong.')
+ raise RuntimeError('Test not able to cope with >10 references by env vars. Something probably went very wrong.')
file_aliases[abs_file_path] = alias
for env_key in inverse_env[runner_path]:
env[env_key] = '{{{{ {} }}}}'.format(alias)
@@ -130,6 +134,7 @@ def read_content(private_data_dir, raw_env, inventory_update):
dir_contents[abs_file_path] = f.read()
# Declare a reference to inventory plugin file if it exists
if abs_file_path.endswith('.yml') and 'plugin: ' in dir_contents[abs_file_path]:
+ inventory_content = dir_contents[abs_file_path]
referenced_paths.add(abs_file_path) # used as inventory file
elif cache_file_regex.match(abs_file_path):
file_aliases[abs_file_path] = 'cache_file'
@@ -157,7 +162,11 @@ def read_content(private_data_dir, raw_env, inventory_update):
content = {}
for abs_file_path, file_content in dir_contents.items():
# assert that all files laid down are used
- if abs_file_path not in referenced_paths and abs_file_path not in ignore_files:
+ if (
+ abs_file_path not in referenced_paths
+ and get_incontainer_path(abs_file_path, private_data_dir) not in inventory_content
+ and abs_file_path not in ignore_files
+ ):
raise AssertionError(
"File {} is not referenced. References and files:\n{}\n{}".format(abs_file_path, json.dumps(env, indent=4), json.dumps(dir_contents, indent=4))
)
@@ -182,15 +191,14 @@ def create_reference_data(source_dir, env, content):
json.dump(env, f, indent=4, sort_keys=True)
+@mock.patch('awx_plugins.interfaces._temporary_private_licensing_api.detect_server_product_name', return_value='NOT-AWX')
@pytest.mark.django_db
-@pytest.mark.parametrize('this_kind', CLOUD_PROVIDERS)
-def test_inventory_update_injected_content(this_kind, inventory, fake_credential_factory, mock_me):
+@pytest.mark.parametrize('this_kind', discover_available_cloud_provider_plugin_names())
+def test_inventory_update_injected_content(product_name, this_kind, inventory, fake_credential_factory, mock_me):
ExecutionEnvironment.objects.create(name='Control Plane EE', managed=True)
ExecutionEnvironment.objects.create(name='Default Job EE', managed=False)
injector = InventorySource.injectors[this_kind]
- if injector.plugin_name is None:
- pytest.skip('Use of inventory plugin is not enabled for this source')
src_vars = dict(base_source_var='value_of_var')
src_vars['plugin'] = injector.get_proper_name()
@@ -200,7 +208,7 @@ def test_inventory_update_injected_content(this_kind, inventory, fake_credential
source_vars=src_vars,
)
inventory_source.credentials.add(fake_credential_factory(this_kind))
- inventory_update = inventory_source.create_unified_job()
+ inventory_update = inventory_source.create_unified_job(_eager_fields={'status': 'waiting'})
task = RunInventoryUpdate()
def substitute_run(awx_receptor_job):
@@ -214,6 +222,10 @@ def substitute_run(awx_receptor_job):
private_data_dir = envvars.pop('AWX_PRIVATE_DATA_DIR')
assert envvars.pop('ANSIBLE_INVENTORY_ENABLED') == 'auto'
set_files = bool(os.getenv("MAKE_INVENTORY_REFERENCE_FILES", 'false').lower()[0] not in ['f', '0'])
+
+ # Ensure the directory exists before trying to list/read it
+ os.makedirs(private_data_dir, exist_ok=True)
+
env, content = read_content(private_data_dir, envvars, inventory_update)
# Assert inventory plugin inventory file is in private_data_dir
@@ -222,7 +234,7 @@ def substitute_run(awx_receptor_job):
len([True for k in content.keys() if k.endswith(inventory_filename)]) > 0
), f"'{inventory_filename}' file not found in inventory update runtime files {content.keys()}"
- env.pop('ANSIBLE_COLLECTIONS_PATHS', None) # collection paths not relevant to this test
+ env.pop('ANSIBLE_COLLECTIONS_PATH', None)
base_dir = os.path.join(DATA, 'plugins')
if not os.path.exists(base_dir):
os.mkdir(base_dir)
@@ -234,7 +246,7 @@ def substitute_run(awx_receptor_job):
source_dir = os.path.join(base_dir, this_kind) # this_kind is a global
if not os.path.exists(source_dir):
- raise FileNotFoundError('Maybe you never made reference files? ' 'MAKE_INVENTORY_REFERENCE_FILES=true py.test ...\noriginal: {}')
+ raise FileNotFoundError('Maybe you never made reference files? MAKE_INVENTORY_REFERENCE_FILES=true py.test ...\noriginal: {}')
files_dir = os.path.join(source_dir, 'files')
try:
expected_file_list = os.listdir(files_dir)
diff --git a/awx/main/tests/functional/test_jobs.py b/awx/main/tests/functional/test_jobs.py
index da3b9fd57cd3..7d4a0ed5b7d2 100644
--- a/awx/main/tests/functional/test_jobs.py
+++ b/awx/main/tests/functional/test_jobs.py
@@ -6,6 +6,7 @@
from awx.main.models import (
Job,
Instance,
+ Host,
JobHostSummary,
InventoryUpdate,
InventorySource,
@@ -18,6 +19,9 @@
ExecutionEnvironment,
)
from awx.main.tasks.system import cluster_node_heartbeat
+from awx.main.utils.db import bulk_update_sorted_by_id
+
+from django.db import OperationalError
from django.test.utils import override_settings
@@ -33,9 +37,9 @@ def test_orphan_unified_job_creation(instance, inventory):
@pytest.mark.django_db
-@mock.patch('awx.main.tasks.system.inspect_execution_nodes', lambda *args, **kwargs: None)
-@mock.patch('awx.main.models.ha.get_cpu_effective_capacity', lambda cpu: 8)
-@mock.patch('awx.main.models.ha.get_mem_effective_capacity', lambda mem: 62)
+@mock.patch('awx.main.tasks.system.inspect_execution_and_hop_nodes', lambda *args, **kwargs: None)
+@mock.patch('awx.main.models.ha.get_cpu_effective_capacity', lambda cpu, is_control_node: 8)
+@mock.patch('awx.main.models.ha.get_mem_effective_capacity', lambda mem, is_control_node: 62)
def test_job_capacity_and_with_inactive_node():
i = Instance.objects.create(hostname='test-1')
i.save_health_data('18.0.1', 2, 8000)
@@ -46,7 +50,7 @@ def test_job_capacity_and_with_inactive_node():
i.save()
with override_settings(CLUSTER_HOST_ID=i.hostname):
with mock.patch.object(redis.client.Redis, 'ping', lambda self: True):
- cluster_node_heartbeat()
+ cluster_node_heartbeat(None)
i = Instance.objects.get(id=i.id)
assert i.capacity == 0
@@ -112,6 +116,63 @@ def test_job_notification_host_data(inventory, machine_credential, project, job_
}
+@pytest.mark.django_db
+class TestAnsibleFactsSave:
+ current_call = 0
+
+ def test_update_hosts_deleted_host(self, inventory):
+ hosts = [Host.objects.create(inventory=inventory, name=f'foo{i}') for i in range(3)]
+ for host in hosts:
+ host.ansible_facts = {'foo': 'bar'}
+ last_pk = hosts[-1].pk
+ assert inventory.hosts.count() == 3
+ Host.objects.get(pk=last_pk).delete()
+ assert inventory.hosts.count() == 2
+ bulk_update_sorted_by_id(Host, hosts, fields=['ansible_facts'])
+ assert inventory.hosts.count() == 2
+ for host in inventory.hosts.all():
+ host.refresh_from_db()
+ assert host.ansible_facts == {'foo': 'bar'}
+
+ def test_update_hosts_forever_deadlock(self, inventory, mocker):
+ hosts = [Host.objects.create(inventory=inventory, name=f'foo{i}') for i in range(3)]
+ for host in hosts:
+ host.ansible_facts = {'foo': 'bar'}
+ db_mock = mocker.patch('awx.main.tasks.facts.Host.objects.bulk_update')
+ db_mock.side_effect = OperationalError('deadlock detected')
+ with pytest.raises(OperationalError):
+ bulk_update_sorted_by_id(Host, hosts, fields=['ansible_facts'])
+
+ def fake_bulk_update(self, host_list):
+ if self.current_call > 2:
+ return Host.objects.bulk_update(host_list, ['ansible_facts', 'ansible_facts_modified'])
+ self.current_call += 1
+ raise OperationalError('deadlock detected')
+
+
+@pytest.mark.django_db
+def test_update_hosts_resolved_deadlock(inventory, mocker):
+
+ hosts = [Host.objects.create(inventory=inventory, name=f'foo{i}') for i in range(3)]
+
+ # Set ansible_facts for each host
+ for host in hosts:
+ host.ansible_facts = {'foo': 'bar'}
+
+ bulk_update_sorted_by_id(Host, hosts, fields=['ansible_facts'])
+
+ # Save changes and refresh from DB to ensure the updated facts are saved
+ for host in hosts:
+ host.save() # Ensure changes are persisted in the DB
+ host.refresh_from_db() # Refresh from DB to get latest data
+
+ # Assert that the ansible_facts were updated correctly
+ for host in inventory.hosts.all():
+ assert host.ansible_facts == {'foo': 'bar'}
+
+ bulk_update_sorted_by_id(Host, hosts, fields=['ansible_facts'])
+
+
@pytest.mark.django_db
class TestLaunchConfig:
def test_null_creation_from_prompts(self):
diff --git a/awx/main/tests/functional/test_ldap.py b/awx/main/tests/functional/test_ldap.py
deleted file mode 100644
index 2467ff52e38f..000000000000
--- a/awx/main/tests/functional/test_ldap.py
+++ /dev/null
@@ -1,103 +0,0 @@
-import ldap
-import ldif
-import pytest
-import os
-from mockldap import MockLdap
-
-from awx.api.versioning import reverse
-
-
-@pytest.fixture
-def ldap_generator():
- def fn(fname, host='localhost'):
- fh = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), fname), 'rb')
- ctrl = ldif.LDIFRecordList(fh)
- ctrl.parse()
-
- directory = dict(ctrl.all_records)
-
- mockldap = MockLdap(directory)
-
- mockldap.start()
- mockldap['ldap://{}/'.format(host)]
-
- conn = ldap.initialize('ldap://{}/'.format(host))
-
- return conn
- # mockldap.stop()
-
- return fn
-
-
-@pytest.fixture
-def ldap_settings_generator():
- def fn(prefix='', dc='ansible', host='ldap.ansible.com'):
- prefix = '_{}'.format(prefix) if prefix else ''
-
- data = {
- 'AUTH_LDAP_SERVER_URI': 'ldap://{}'.format(host),
- 'AUTH_LDAP_BIND_DN': 'cn=eng_user1,ou=people,dc={},dc=com'.format(dc),
- 'AUTH_LDAP_BIND_PASSWORD': 'password',
- "AUTH_LDAP_USER_SEARCH": ["ou=people,dc={},dc=com".format(dc), "SCOPE_SUBTREE", "(cn=%(user)s)"],
- "AUTH_LDAP_TEAM_MAP": {
- "LDAP Sales": {"organization": "LDAP Organization", "users": "cn=sales,ou=groups,dc={},dc=com".format(dc), "remove": True},
- "LDAP IT": {"organization": "LDAP Organization", "users": "cn=it,ou=groups,dc={},dc=com".format(dc), "remove": True},
- "LDAP Engineering": {"organization": "LDAP Organization", "users": "cn=engineering,ou=groups,dc={},dc=com".format(dc), "remove": True},
- },
- "AUTH_LDAP_REQUIRE_GROUP": None,
- "AUTH_LDAP_USER_ATTR_MAP": {"first_name": "givenName", "last_name": "sn", "email": "mail"},
- "AUTH_LDAP_GROUP_SEARCH": ["dc={},dc=com".format(dc), "SCOPE_SUBTREE", "(objectClass=groupOfNames)"],
- "AUTH_LDAP_USER_FLAGS_BY_GROUP": {"is_superuser": "cn=superusers,ou=groups,dc={},dc=com".format(dc)},
- "AUTH_LDAP_ORGANIZATION_MAP": {
- "LDAP Organization": {
- "admins": "cn=engineering_admins,ou=groups,dc={},dc=com".format(dc),
- "remove_admins": False,
- "users": [
- "cn=engineering,ou=groups,dc={},dc=com".format(dc),
- "cn=sales,ou=groups,dc={},dc=com".format(dc),
- "cn=it,ou=groups,dc={},dc=com".format(dc),
- ],
- "remove_users": False,
- }
- },
- }
-
- if prefix:
- data_new = dict()
- for k, v in data.items():
- k_new = k.replace('AUTH_LDAP', 'AUTH_LDAP{}'.format(prefix))
- data_new[k_new] = v
- else:
- data_new = data
-
- return data_new
-
- return fn
-
-
-# Note: mockldap isn't fully featured. Fancy queries aren't fully baked.
-# However, objects returned are solid so they should flow through django ldap middleware nicely.
-@pytest.mark.skip(reason="Needs Update - CA")
-@pytest.mark.django_db
-def test_login(ldap_generator, patch, post, admin, ldap_settings_generator):
- auth_url = reverse('api:auth_token_view')
- ldap_settings_url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'ldap'})
-
- # Generate mock ldap servers and init with ldap data
- ldap_generator("../data/ldap_example.ldif", "ldap.example.com")
- ldap_generator("../data/ldap_redhat.ldif", "ldap.redhat.com")
- ldap_generator("../data/ldap_ansible.ldif", "ldap.ansible.com")
-
- ldap_settings_example = ldap_settings_generator(dc='example')
- ldap_settings_ansible = ldap_settings_generator(prefix='1', dc='ansible')
- ldap_settings_redhat = ldap_settings_generator(prefix='2', dc='redhat')
-
- # eng_user1 exists in ansible and redhat but not example
- patch(ldap_settings_url, user=admin, data=ldap_settings_example, expect=200)
-
- post(auth_url, data={'username': 'eng_user1', 'password': 'password'}, expect=400)
-
- patch(ldap_settings_url, user=admin, data=ldap_settings_ansible, expect=200)
- patch(ldap_settings_url, user=admin, data=ldap_settings_redhat, expect=200)
-
- post(auth_url, data={'username': 'eng_user1', 'password': 'password'}, expect=200)
diff --git a/awx/main/tests/functional/test_licenses.py b/awx/main/tests/functional/test_licenses.py
index 4b8ee7a9189a..aefec6153b3c 100644
--- a/awx/main/tests/functional/test_licenses.py
+++ b/awx/main/tests/functional/test_licenses.py
@@ -1,5 +1,4 @@
import glob
-import json
import os
from django.conf import settings
@@ -12,122 +11,99 @@
from pip._internal.req.constructors import parse_req_from_line
-def test_python_and_js_licenses():
- def index_licenses(path):
- # Check for GPL (forbidden) and LGPL (need to ship source)
- # This is not meant to be an exhaustive check.
- def check_license(license_file):
- with open(license_file) as f:
- data = f.read()
- is_lgpl = 'GNU LESSER GENERAL PUBLIC LICENSE' in data.upper()
- # The LGPL refers to the GPL in-text
- # Case-sensitive for GPL to match license text and not PSF license reference
- is_gpl = 'GNU GENERAL PUBLIC LICENSE' in data and not is_lgpl
- return (is_gpl, is_lgpl)
-
- def find_embedded_source_version(path, name):
- for entry in os.listdir(path):
- # Check variations of '-' and '_' in filenames due to python
- for fname in [name, name.replace('-', '_')]:
- if entry.startswith(fname) and entry.endswith('.tar.gz'):
- v = entry.split(name + '-')[1].split('.tar.gz')[0]
- return v
- return None
-
- list = {}
- for txt_file in glob.glob('%s/*.txt' % path):
- filename = txt_file.split('/')[-1]
- name = filename[:-4].lower()
- (is_gpl, is_lgpl) = check_license(txt_file)
- list[name] = {
- 'name': name,
- 'filename': filename,
- 'gpl': is_gpl,
- 'source_required': (is_gpl or is_lgpl),
- 'source_version': find_embedded_source_version(path, name),
- }
- return list
-
- def read_api_requirements(path):
- ret = {}
- skip_pbr_license_check = False
- for req_file in ['requirements.txt', 'requirements_git.txt']:
- fname = '%s/%s' % (path, req_file)
-
- for reqt in parse_requirements(fname, session=''):
- parsed_requirement = parse_req_from_line(reqt.requirement, None)
- name = parsed_requirement.requirement.name
- version = str(parsed_requirement.requirement.specifier)
- if version.startswith('=='):
- version = version[2:]
- if parsed_requirement.link:
- if str(parsed_requirement.link).startswith(('http://', 'https://')):
- (name, version) = str(parsed_requirement.requirement).split('==', 1)
- else:
- (name, version) = parsed_requirement.link.filename.split('@', 1)
- if name.endswith('.git'):
- name = name[:-4]
- if name == 'receptor':
- name = 'receptorctl'
- if name == 'ansible-runner':
- skip_pbr_license_check = True
- ret[name] = {'name': name, 'version': version}
- if 'pbr' in ret and skip_pbr_license_check:
- del ret['pbr']
- return ret
-
- def read_ui_requirements(path):
- def json_deps(jsondata):
- ret = {}
- deps = jsondata.get('dependencies', {})
- for key in deps.keys():
- key = key.lower()
- devonly = deps[key].get('dev', False)
- if not devonly:
- if key not in ret.keys():
- depname = key.replace('/', '-')
- if depname[0] == '@':
- depname = depname[1:]
- ret[depname] = {'name': depname, 'version': deps[key]['version']}
- ret.update(json_deps(deps[key]))
- return ret
-
- with open('%s/package-lock.json' % path) as f:
- jsondata = json.load(f)
- return json_deps(jsondata)
-
- def remediate_licenses_and_requirements(licenses, requirements):
- errors = []
- items = list(licenses.keys())
- items.sort()
- for item in items:
- if item not in [r.lower() for r in requirements.keys()] and item != 'awx':
- errors.append(" license file %s does not correspond to an existing requirement; it should be removed." % (licenses[item]['filename'],))
- continue
- # uWSGI has a linking exception
- if licenses[item]['gpl'] and item != 'uwsgi':
- errors.append(" license for %s is GPL. This software cannot be used." % (item,))
- if licenses[item]['source_required']:
- version = requirements[item]['version']
- if version != licenses[item]['source_version']:
- errors.append(" embedded source for %s is %s instead of the required version %s" % (item, licenses[item]['source_version'], version))
- elif licenses[item]['source_version']:
- errors.append(" embedded source version %s for %s is included despite not being needed" % (licenses[item]['source_version'], item))
- items = list(requirements.keys())
- items.sort()
- for item in items:
- if item.lower() not in licenses.keys():
- errors.append(" license for requirement %s is missing" % (item,))
- return errors
+def check_license(license_file):
+ with open(license_file) as f:
+ data = f.read()
+ is_lgpl = 'GNU LESSER GENERAL PUBLIC LICENSE' in data.upper()
+ is_gpl = 'GNU GENERAL PUBLIC LICENSE' in data and not is_lgpl
+ return is_gpl, is_lgpl
+
+
+def find_embedded_source_version(path, name):
+ files = os.listdir(path)
+ tgz_files = [f for f in files if f.endswith('.tar.gz')]
+ for tgz in tgz_files:
+ pkg_name = tgz.split('-')[0].split('_')[0]
+ if pkg_name == name:
+ return tgz.split('-')[1].split('.tar.gz')[0]
+ return None
+
+
+def index_licenses(path):
+ licenses = {}
+ for txt_file in glob.glob(f'{path}/*.txt'):
+ filename = os.path.basename(txt_file)
+ name = filename[:-4].lower()
+ is_gpl, is_lgpl = check_license(txt_file)
+ licenses[name] = {
+ 'name': name,
+ 'filename': filename,
+ 'gpl': is_gpl,
+ 'source_required': is_gpl or is_lgpl,
+ 'source_version': find_embedded_source_version(path, name),
+ }
+ return licenses
+
+
+def parse_requirement(reqt):
+ parsed_requirement = parse_req_from_line(reqt.requirement, None)
+ assert parsed_requirement.requirement, reqt.__dict__
+ name = parsed_requirement.requirement.name
+ version = str(parsed_requirement.requirement.specifier)
+ if version.startswith('=='):
+ version = version[2:]
+ if parsed_requirement.link:
+ if str(parsed_requirement.link).startswith(('http://', 'https://')):
+ name, version = str(parsed_requirement.requirement).split('==', 1)
+ else:
+ name, version = parsed_requirement.link.filename.split('@', 1)
+ if name.endswith('.git'):
+ name = name[:-4]
+ if name == 'receptor':
+ name = 'receptorctl'
+ return name, version
- base_dir = settings.BASE_DIR
- api_licenses = index_licenses('%s/../licenses' % base_dir)
- ui_licenses = index_licenses('%s/../licenses/ui' % base_dir)
- api_requirements = read_api_requirements('%s/../requirements' % base_dir)
- ui_requirements = read_ui_requirements('%s/ui' % base_dir)
+def read_api_requirements(path):
+ requirements = {}
+ skip_pbr_license_check = False
+ for req_file in ['requirements.txt', 'requirements_git.txt']:
+ fname = f'{path}/{req_file}'
+ for reqt in parse_requirements(fname, session=''):
+ name, version = parse_requirement(reqt)
+ if name == 'ansible-runner':
+ skip_pbr_license_check = True
+ requirements[name] = {'name': name, 'version': version}
+ if 'pbr' in requirements and skip_pbr_license_check:
+ del requirements['pbr']
+ return requirements
+
+
+def remediate_licenses_and_requirements(licenses, requirements):
errors = []
- errors += remediate_licenses_and_requirements(ui_licenses, ui_requirements)
- errors += remediate_licenses_and_requirements(api_licenses, api_requirements)
+ for item in sorted(licenses.keys()):
+ if item not in [r.lower() for r in requirements.keys()] and item != 'awx':
+ errors.append(f" license file {licenses[item]['filename']} does not correspond to an existing requirement; it should be removed.")
+ continue
+ if licenses[item]['gpl'] and item != 'uwsgi':
+ errors.append(f" license for {item} is GPL. This software cannot be used.")
+ if licenses[item]['source_required']:
+ version = requirements[item]['version']
+ if version != licenses[item]['source_version']:
+ errors.append(f" embedded source for {item} is {licenses[item]['source_version']} instead of the required version {version}")
+ elif licenses[item]['source_version']:
+ errors.append(f" embedded source version {licenses[item]['source_version']} for {item} is included despite not being needed")
+ for item in sorted(requirements.keys()):
+ if item.lower() not in licenses.keys():
+ errors.append(f" license for requirement {item} is missing")
+ return errors
+
+
+def test_python_licenses():
+ base_dir = settings.BASE_DIR
+ api_licenses = index_licenses(f'{base_dir}/../licenses')
+ api_requirements = read_api_requirements(f'{base_dir}/../requirements')
+
+ errors = remediate_licenses_and_requirements(api_licenses, api_requirements)
if errors:
raise Exception('Included licenses not consistent with requirements:\n%s' % '\n'.join(errors))
diff --git a/awx/main/tests/functional/test_migrations.py b/awx/main/tests/functional/test_migrations.py
new file mode 100644
index 000000000000..caa9579f6abb
--- /dev/null
+++ b/awx/main/tests/functional/test_migrations.py
@@ -0,0 +1,226 @@
+import pytest
+
+from django_test_migrations.plan import all_migrations, nodes_to_tuples
+from django.utils.timezone import now
+
+"""
+Most tests that live in here can probably be deleted at some point. They are mainly
+for a developer. When AWX versions that users upgrade from falls out of support that
+is when migration tests can be deleted. This is also a good time to squash. Squashing
+will likely mess with the tests that live here.
+The smoke test should be kept in here. The smoke test ensures that our migrations
+continue to work when sqlite is the backing database (vs. the default DB of postgres).
+"""
+
+
+@pytest.mark.django_db
+class TestMigrationSmoke:
+ def test_happy_path(self, migrator):
+ """
+ This smoke test runs all the migrations.
+ Example of how to use django-test-migration to invoke particular migration(s)
+ while weaving in object creation and assertions.
+ Note that this is more than just an example. It is a smoke test because it runs ALL
+ the migrations. Our "normal" unit tests subvert the migrations running because it is slow.
+ """
+ migration_nodes = all_migrations('default')
+ migration_tuples = nodes_to_tuples(migration_nodes)
+ final_migration = migration_tuples[-1]
+ migrator.apply_initial_migration(('main', None))
+ # I just picked a newish migration at the time of writing this.
+ # If someone from the future finds themselves here because the are squashing migrations
+ # it is fine to change the 0180_... below to some other newish migration
+ intermediate_state = migrator.apply_tested_migration(('main', '0180_add_hostmetric_fields'))
+ Instance = intermediate_state.apps.get_model('main', 'Instance')
+ # Create any old object in the database
+ Instance.objects.create(hostname='foobar', node_type='control')
+ final_state = migrator.apply_tested_migration(final_migration)
+ Instance = final_state.apps.get_model('main', 'Instance')
+ assert Instance.objects.filter(hostname='foobar').count() == 1
+
+ def test_receptor_address(self, migrator):
+ old_state = migrator.apply_initial_migration(('main', '0188_add_bitbucket_dc_webhook'))
+ Instance = old_state.apps.get_model('main', 'Instance')
+ for i in range(3):
+ Instance.objects.create(hostname=f'foobar{i}', node_type='hop')
+ foo = Instance.objects.create(hostname='foo', node_type='execution', listener_port=1234)
+ bar = Instance.objects.create(hostname='bar', node_type='execution', listener_port=None)
+ bar.peers.add(foo)
+ new_state = migrator.apply_tested_migration(
+ ('main', '0189_inbound_hop_nodes'),
+ )
+ Instance = new_state.apps.get_model('main', 'Instance')
+ ReceptorAddress = new_state.apps.get_model('main', 'ReceptorAddress')
+ # We can now test how our migration worked, new field is there:
+ assert ReceptorAddress.objects.filter(address='foo', port=1234).count() == 1
+ assert not ReceptorAddress.objects.filter(address='bar').exists()
+ bar = Instance.objects.get(hostname='bar')
+ fooaddr = ReceptorAddress.objects.get(address='foo')
+ bar_peers = bar.peers.all()
+ assert len(bar_peers) == 1
+ assert fooaddr in bar_peers
+
+ def test_migrate_DAB_RBAC(self, migrator):
+ old_state = migrator.apply_initial_migration(('main', '0190_alter_inventorysource_source_and_more'))
+ Organization = old_state.apps.get_model('main', 'Organization')
+ Team = old_state.apps.get_model('main', 'Team')
+ User = old_state.apps.get_model('auth', 'User')
+ org = Organization.objects.create(name='arbitrary-org', created=now(), modified=now())
+ user = User.objects.create(username='random-user')
+ org.read_role.members.add(user)
+ org.member_role.members.add(user)
+
+ team = Team.objects.create(name='arbitrary-team', organization=org, created=now(), modified=now())
+ team.member_role.members.add(user)
+
+ new_state = migrator.apply_tested_migration(
+ ('main', '0192_custom_roles'),
+ )
+ RoleUserAssignment = new_state.apps.get_model('dab_rbac', 'RoleUserAssignment')
+ assert RoleUserAssignment.objects.filter(user=user.id, object_id=org.id).exists()
+ assert RoleUserAssignment.objects.filter(user=user.id, role_definition__name='Organization Member', object_id=org.id).exists()
+ assert RoleUserAssignment.objects.filter(user=user.id, role_definition__name='Team Member', object_id=team.id).exists()
+
+ # Regression testing for bug that comes from current vs past models mismatch
+ RoleDefinition = new_state.apps.get_model('dab_rbac', 'RoleDefinition')
+ assert not RoleDefinition.objects.filter(name='Organization Organization Admin').exists()
+ # Test special cases in managed role creation
+ assert not RoleDefinition.objects.filter(name='Organization Team Admin').exists()
+ assert not RoleDefinition.objects.filter(name='Organization InstanceGroup Admin').exists()
+ # Test that a removed EE model permission has been deleted
+ new_state = migrator.apply_tested_migration(
+ ('main', '0195_EE_permissions'),
+ )
+ DABPermission = new_state.apps.get_model('dab_rbac', 'DABPermission')
+ assert not DABPermission.objects.filter(codename='view_executionenvironment').exists()
+
+ # Test create a Project with a duplicate name
+ Organization = new_state.apps.get_model('main', 'Organization')
+ Project = new_state.apps.get_model('main', 'Project')
+ WorkflowJobTemplate = new_state.apps.get_model('main', 'WorkflowJobTemplate')
+ org = Organization.objects.create(name='duplicate-obj-organization', created=now(), modified=now())
+ proj_ids = []
+ for i in range(3):
+ proj = Project.objects.create(name='duplicate-project-name', organization=org, created=now(), modified=now())
+ proj_ids.append(proj.id)
+
+ # Test create WorkflowJobTemplate with duplicate names
+ wfjt_ids = []
+ for i in range(3):
+ wfjt = WorkflowJobTemplate.objects.create(name='duplicate-workflow-name', organization=org, created=now(), modified=now())
+ wfjt_ids.append(wfjt.id)
+
+ # The uniqueness rules will not apply to InventorySource
+ Inventory = new_state.apps.get_model('main', 'Inventory')
+ InventorySource = new_state.apps.get_model('main', 'InventorySource')
+ inv = Inventory.objects.create(name='migration-test-inv', organization=org, created=now(), modified=now())
+ InventorySource.objects.create(name='migration-test-src', source='file', inventory=inv, organization=org, created=now(), modified=now())
+
+ # Apply migration 0200 which should rename duplicates
+ new_state = migrator.apply_tested_migration(
+ ('main', '0200_template_name_constraint'),
+ )
+
+ # Get the models from the new state for verification
+ Project = new_state.apps.get_model('main', 'Project')
+ WorkflowJobTemplate = new_state.apps.get_model('main', 'WorkflowJobTemplate')
+ InventorySource = new_state.apps.get_model('main', 'InventorySource')
+
+ for i, proj_id in enumerate(proj_ids):
+ proj = Project.objects.get(id=proj_id)
+ if i == 0:
+ assert proj.name == 'duplicate-project-name'
+ else:
+ assert proj.name != 'duplicate-project-name'
+ assert proj.name.startswith('duplicate-project-name')
+
+ # Verify WorkflowJobTemplate duplicates are renamed
+ for i, wfjt_id in enumerate(wfjt_ids):
+ wfjt = WorkflowJobTemplate.objects.get(id=wfjt_id)
+ if i == 0:
+ assert wfjt.name == 'duplicate-workflow-name'
+ else:
+ assert wfjt.name != 'duplicate-workflow-name'
+ assert wfjt.name.startswith('duplicate-workflow-name')
+
+ # The inventory source had this field set to avoid the constrains
+ inv_src = InventorySource.objects.get(name='migration-test-src')
+ assert inv_src.org_unique is False
+ for proj in Project.objects.all():
+ assert proj.org_unique is True
+
+ # Piggyback test for the new credential types
+ validate_exists = ['GitHub App Installation Access Token Lookup', 'Terraform backend configuration']
+ CredentialType = new_state.apps.get_model('main', 'CredentialType')
+ # simulate an upgrade by deleting existing types with these names
+ for expected_name in validate_exists:
+ ct = CredentialType.objects.filter(name=expected_name).first()
+ if ct:
+ ct.delete()
+
+ new_state = migrator.apply_tested_migration(
+ ('main', '0201_create_managed_creds'),
+ )
+
+ CredentialType = new_state.apps.get_model('main', 'CredentialType')
+ for expected_name in validate_exists:
+ assert CredentialType.objects.filter(
+ name=expected_name
+ ).exists(), f'Could not find {expected_name} credential type name, all names: {list(CredentialType.objects.values_list("name", flat=True))}'
+
+ # Verify the system_administrator role exists
+ Role = new_state.apps.get_model('main', 'Role')
+ assert Role.objects.filter(
+ singleton_name='system_administrator', role_field='system_administrator'
+ ).exists(), "expected to find a system_administrator singleton role"
+
+
+@pytest.mark.django_db
+class TestGithubAppBug:
+ """
+ Tests that `awx-manage createsuperuser` runs successfully after
+ the `github_app` CredentialType kind is updated to `github_app_lookup`
+ via the migration.
+ """
+
+ def test_after_github_app_kind_migration(self, migrator):
+ """
+ Verifies that `createsuperuser` does not raise a KeyError
+ after the 0204_squashed_deletions migration (which includes
+ the `update_github_app_kind` logic) is applied.
+ """
+ # 1. Apply migrations up to the point *before* the 0204_squashed_deletions migration.
+ # This simulates the state where the problematic CredentialType might exist.
+ # We use 0203_remove_team_of_teams as the direct predecessor.
+ old_state = migrator.apply_tested_migration(('main', '0203_remove_team_of_teams'))
+
+ # Get the CredentialType model from the historical state.
+ CredentialType = old_state.apps.get_model('main', 'CredentialType')
+
+ # Create a CredentialType with the old, problematic 'namespace' value
+ CredentialType.objects.create(
+ name='Legacy GitHub App Credential',
+ kind='external',
+ namespace='github_app', # The namespace that causes the KeyError in the registry lookup
+ managed=True,
+ created=now(),
+ modified=now(),
+ )
+
+ # Apply the migration that includes the fix (0204_squashed_deletions).
+ new_state = migrator.apply_tested_migration(('main', '0204_squashed_deletions'))
+
+ # Verify that the CredentialType with the old 'kind' no longer exists
+ # and the 'kind' has been updated to the new value.
+ CredentialType = new_state.apps.get_model('main', 'CredentialType') # Get CredentialType model from the new state
+
+ # Assertion 1: The CredentialType with the old 'github_app' kind should no longer exist.
+ assert not CredentialType.objects.filter(
+ namespace='github_app'
+ ).exists(), "CredentialType with old 'github_app' kind should no longer exist after migration."
+
+ # Assertion 2: The CredentialType should now exist with the new 'github_app_lookup' kind
+ # and retain its original name.
+ assert CredentialType.objects.filter(
+ namespace='github_app_lookup', name='Legacy GitHub App Credential'
+ ).exists(), "CredentialType should be updated to 'github_app_lookup' and retain its name."
diff --git a/awx/main/tests/functional/test_named_url.py b/awx/main/tests/functional/test_named_url.py
index 884ecd7dc0c7..5bf12653ac04 100644
--- a/awx/main/tests/functional/test_named_url.py
+++ b/awx/main/tests/functional/test_named_url.py
@@ -1,11 +1,6 @@
# -*- coding: utf-8 -*-
-from unittest import mock
-
import pytest
-from django.core.exceptions import ImproperlyConfigured
-from django.conf import settings
-
from awx.api.versioning import reverse
from awx.main.middleware import URLModificationMiddleware
from awx.main.models import ( # noqa
@@ -23,25 +18,6 @@
User,
WorkflowJobTemplate,
)
-from awx.conf import settings_registry
-
-
-def setup_module(module):
- # In real-world scenario, named url graph structure is populated by __init__
- # of URLModificationMiddleware. The way Django bootstraps ensures the initialization
- # will happen *once and only once*, while the number of initialization is uncontrollable
- # in unit test environment. So it is wrapped by try-except block to mute any
- # unwanted exceptions.
- try:
- URLModificationMiddleware(mock.Mock())
- except ImproperlyConfigured:
- pass
-
-
-def teardown_module(module):
- # settings_registry will be persistent states unless we explicitly clean them up.
- settings_registry.unregister('NAMED_URL_FORMATS')
- settings_registry.unregister('NAMED_URL_GRAPH_NODES')
@pytest.mark.django_db
@@ -143,7 +119,7 @@ def test_notification_template(get, admin_user):
@pytest.mark.django_db
-def test_instance(get, admin_user):
+def test_instance(get, admin_user, settings):
test_instance = Instance.objects.create(uuid=settings.SYSTEM_UUID, hostname="localhost", capacity=100)
url = reverse('api:instance_detail', kwargs={'pk': test_instance.pk})
response = get(url, user=admin_user, expect=200)
@@ -227,3 +203,76 @@ def test_403_vs_404(get):
get(f'/api/v2/users/{cindy.pk}/', expect=401)
get('/api/v2/users/cindy/', expect=404)
+
+
+@pytest.mark.django_db
+class TestConvertNamedUrl:
+ @pytest.mark.parametrize(
+ "url",
+ (
+ "/api/",
+ "/api/v2/",
+ "/api/v2/hosts/",
+ "/api/v2/hosts/1/",
+ "/api/v2/organizations/1/inventories/",
+ "/api/foo/",
+ "/api/foo/v2/",
+ "/api/foo/v2/organizations/",
+ "/api/foo/v2/organizations/1/",
+ "/api/foo/v2/organizations/1/inventories/",
+ "/api/foobar/",
+ "/api/foobar/v2/",
+ "/api/foobar/v2/organizations/",
+ "/api/foobar/v2/organizations/1/",
+ "/api/foobar/v2/organizations/1/inventories/",
+ "/api/foobar/v2/organizations/1/inventories/",
+ ),
+ )
+ def test_noop(self, url, settings):
+ settings.OPTIONAL_API_URLPATTERN_PREFIX = ''
+ assert URLModificationMiddleware._convert_named_url(url) == url
+
+ settings.OPTIONAL_API_URLPATTERN_PREFIX = 'foo'
+ assert URLModificationMiddleware._convert_named_url(url) == url
+
+ def test_named_org(self):
+ test_org = Organization.objects.create(name='test_org')
+
+ assert URLModificationMiddleware._convert_named_url('/api/v2/organizations/test_org/') == f'/api/v2/organizations/{test_org.pk}/'
+
+ def test_named_org_optional_api_urlpattern_prefix_interaction(self, settings):
+ settings.OPTIONAL_API_URLPATTERN_PREFIX = 'bar'
+ test_org = Organization.objects.create(name='test_org')
+
+ assert URLModificationMiddleware._convert_named_url('/api/bar/v2/organizations/test_org/') == f'/api/bar/v2/organizations/{test_org.pk}/'
+
+ @pytest.mark.parametrize("prefix", ['', 'bar'])
+ def test_named_org_not_found(self, prefix, settings):
+ settings.OPTIONAL_API_URLPATTERN_PREFIX = prefix
+ if prefix:
+ prefix += '/'
+
+ assert URLModificationMiddleware._convert_named_url(f'/api/{prefix}v2/organizations/does-not-exist/') == f'/api/{prefix}v2/organizations/0/'
+
+ @pytest.mark.parametrize("prefix", ['', 'bar'])
+ def test_named_sub_resource(self, prefix, settings):
+ settings.OPTIONAL_API_URLPATTERN_PREFIX = prefix
+ test_org = Organization.objects.create(name='test_org')
+ if prefix:
+ prefix += '/'
+
+ assert (
+ URLModificationMiddleware._convert_named_url(f'/api/{prefix}v2/organizations/test_org/inventories/')
+ == f'/api/{prefix}v2/organizations/{test_org.pk}/inventories/'
+ )
+
+ def test_named_job_template(self):
+ org = Organization.objects.create(name='test_org')
+ tpl = JobTemplate.objects.create(name='test_tpl', organization=org)
+
+ # first, cause a '404' - we want to verify that no state from previous requests is carried over when named
+ # urls are resolved
+ assert URLModificationMiddleware._convert_named_url('/api/v2/job_templates/test/tpl++test_org/') == '/api/v2/job_templates/test/tpl++test_org/'
+
+ # try to resolve a valid url - it should succeed
+ assert URLModificationMiddleware._convert_named_url('/api/v2/job_templates/test_tpl++test_org/') == f'/api/v2/job_templates/{tpl.pk}/'
diff --git a/awx/main/tests/functional/test_notifications.py b/awx/main/tests/functional/test_notifications.py
index 08036db97c90..cf93030ac6ac 100644
--- a/awx/main/tests/functional/test_notifications.py
+++ b/awx/main/tests/functional/test_notifications.py
@@ -16,8 +16,7 @@
@pytest.mark.django_db
def test_get_notification_template_list(get, user, notification_template):
url = reverse('api:notification_template_list')
- response = get(url, user('admin', True))
- assert response.status_code == 200
+ response = get(url, user('admin', True), expect=200)
assert len(response.data['results']) == 1
@@ -35,8 +34,8 @@ def test_basic_parameterization(get, post, user, organization):
notification_configuration=dict(url="http://localhost", disable_ssl_verification=False, headers={"Test": "Header"}),
),
u,
+ expect=201,
)
- assert response.status_code == 201
url = reverse('api:notification_template_detail', kwargs={'pk': response.data['id']})
response = get(url, u)
assert 'related' in response.data
@@ -69,8 +68,8 @@ def assert_send(self, messages):
notification_configuration=dict(account_sid="dummy", account_token="shouldhide", from_number="+19999999999", to_numbers=["9998887777"]),
),
u,
+ expect=201,
)
- assert response.status_code == 201
notification_template_actual = NotificationTemplate.objects.get(id=response.data['id'])
url = reverse('api:notification_template_detail', kwargs={'pk': response.data['id']})
response = get(url, u)
@@ -96,8 +95,8 @@ def test_inherited_notification_templates(get, post, user, organization, project
notification_configuration=dict(url="http://localhost", disable_ssl_verification=False, headers={"Test": "Header"}),
),
u,
+ expect=201,
)
- assert response.status_code == 201
notification_templates.append(response.data['id'])
i = Inventory.objects.create(name='test', organization=organization)
i.save()
@@ -122,8 +121,7 @@ def test_disallow_delete_when_notifications_pending(delete, user, notification_t
u = user('superuser', True)
url = reverse('api:notification_template_detail', kwargs={'pk': notification_template.id})
Notification.objects.create(notification_template=notification_template, status='pending')
- response = delete(url, user=u)
- assert response.status_code == 405
+ delete(url, user=u, expect=405)
@pytest.mark.django_db
@@ -133,9 +131,8 @@ def test_notification_template_list_includes_notification_errors(get, user, noti
Notification.objects.create(notification_template=notification_template, status='successful')
url = reverse('api:notification_template_list')
u = user('superuser', True)
- response = get(url, user=u)
+ response = get(url, user=u, expect=200)
- assert response.status_code == 200
notifications = response.data['results'][0]['summary_fields']['recent_notifications']
assert len(notifications) == 3
statuses = [n['status'] for n in notifications]
@@ -163,8 +160,8 @@ def test_custom_environment_injection(post, user, organization):
notification_configuration=dict(url="https://example.org", disable_ssl_verification=False, http_method="POST", headers={"Test": "Header"}),
),
u,
+ expect=201,
)
- assert response.status_code == 201
template = NotificationTemplate.objects.get(pk=response.data['id'])
with pytest.raises(ConnectionError), override_settings(AWX_TASK_ENV={'HTTPS_PROXY': '192.168.50.100:1234'}), mock.patch.object(
HTTPAdapter, 'send'
@@ -218,3 +215,31 @@ def test_webhook_notification_pointed_to_a_redirect_launch_endpoint(post, admin,
)
assert n1.send("", n1.messages.get("success").get("body")) == 1
+
+
+@pytest.mark.django_db
+def test_update_notification_template(admin, notification_template):
+ notification_template.messages['workflow_approval'] = {
+ "running": {
+ "message": None,
+ "body": None,
+ }
+ }
+ notification_template.save()
+
+ workflow_approval_message = {
+ "approved": {
+ "message": None,
+ "body": None,
+ },
+ "running": {
+ "message": "test-message",
+ "body": None,
+ },
+ }
+ notification_template.messages['workflow_approval'] = workflow_approval_message
+ notification_template.save()
+
+ subevents = sorted(notification_template.messages["workflow_approval"].keys())
+ assert subevents == ["approved", "running"]
+ assert notification_template.messages['workflow_approval'] == workflow_approval_message
diff --git a/awx/main/tests/functional/test_policy.py b/awx/main/tests/functional/test_policy.py
new file mode 100644
index 000000000000..f265a55e6413
--- /dev/null
+++ b/awx/main/tests/functional/test_policy.py
@@ -0,0 +1,631 @@
+import json
+import os
+from unittest import mock
+
+import pytest
+import requests.exceptions
+from django.test import override_settings
+
+from awx.main.models import (
+ Job,
+ Inventory,
+ Project,
+ Organization,
+ JobTemplate,
+ Credential,
+ CredentialType,
+ User,
+ Team,
+ Label,
+ WorkflowJob,
+ WorkflowJobNode,
+ InventorySource,
+)
+from awx.main.exceptions import PolicyEvaluationError
+from awx.main.tasks import policy
+from awx.main.tasks.policy import JobSerializer, OPA_AUTH_TYPES
+
+
+def _parse_exception_message(exception: PolicyEvaluationError):
+ pe_plain = str(exception.value)
+
+ assert "This job cannot be executed due to a policy violation or error. See the following details:" in pe_plain
+
+ violation_message = "This job cannot be executed due to a policy violation or error. See the following details:"
+ return eval(pe_plain.split(violation_message)[1].strip())
+
+
+@pytest.fixture(autouse=True)
+def setup_opa_settings():
+ with override_settings(
+ OPA_HOST='opa.example.com',
+ ):
+ yield
+
+
+@pytest.fixture
+def opa_client():
+ cls_mock = mock.MagicMock(name='OpaClient')
+ instance_mock = cls_mock.return_value
+ instance_mock.__enter__.return_value = instance_mock
+
+ with mock.patch('awx.main.tasks.policy.OpaClient', cls_mock):
+ yield instance_mock
+
+
+@pytest.fixture
+def job():
+ project: Project = Project.objects.create(name='proj1', scm_type='git', scm_branch='main', scm_url='https://git.example.com/proj1')
+ inventory: Inventory = Inventory.objects.create(name='inv1', opa_query_path="inventory/response")
+ org: Organization = Organization.objects.create(name="org1", opa_query_path="organization/response")
+ jt: JobTemplate = JobTemplate.objects.create(name="jt1", opa_query_path="job_template/response")
+ job: Job = Job.objects.create(name='job1', extra_vars="{}", inventory=inventory, project=project, organization=org, job_template=jt)
+ return job
+
+
+@pytest.mark.django_db
+def test_job_serializer():
+ user: User = User.objects.create(username='user1')
+ org: Organization = Organization.objects.create(name='org1')
+
+ team: Team = Team.objects.create(name='team1', organization=org)
+ team.admin_role.members.add(user)
+
+ project: Project = Project.objects.create(name='proj1', scm_type='git', scm_branch='main', scm_url='https://git.example.com/proj1')
+ inventory: Inventory = Inventory.objects.create(name='inv1', description='Demo inventory')
+ inventory_source: InventorySource = InventorySource.objects.create(name='inv-src1', source='file', inventory=inventory)
+ extra_vars = {"FOO": "value1", "BAR": "value2"}
+
+ CredentialType.setup_tower_managed_defaults()
+ cred_type_ssh: CredentialType = CredentialType.objects.get(kind='ssh')
+ cred: Credential = Credential.objects.create(name="cred1", description='Demo credential', credential_type=cred_type_ssh, organization=org)
+
+ label: Label = Label.objects.create(name='label1', organization=org)
+
+ job: Job = Job.objects.create(
+ name='job1', extra_vars=json.dumps(extra_vars), inventory=inventory, project=project, organization=org, created_by=user, launch_type='workflow'
+ )
+ # job.unified_job_node.workflow_job = workflow_job
+ job.credentials.add(cred)
+ job.labels.add(label)
+
+ workflow_job: WorkflowJob = WorkflowJob.objects.create(name='wf-job1')
+ WorkflowJobNode.objects.create(job=job, workflow_job=workflow_job)
+
+ serializer = JobSerializer(instance=job)
+
+ assert serializer.data == {
+ 'id': job.id,
+ 'name': 'job1',
+ 'created': job.created.strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
+ 'created_by': {
+ 'id': user.id,
+ 'username': 'user1',
+ 'is_superuser': False,
+ 'teams': [
+ {'id': team.id, 'name': 'team1'},
+ ],
+ },
+ 'credentials': [
+ {
+ 'id': cred.id,
+ 'name': 'cred1',
+ 'description': 'Demo credential',
+ 'organization': {
+ 'id': org.id,
+ 'name': 'org1',
+ },
+ 'credential_type': cred_type_ssh.id,
+ 'kind': 'ssh',
+ 'managed': False,
+ 'kubernetes': False,
+ 'cloud': False,
+ },
+ ],
+ 'execution_environment': None,
+ 'extra_vars': extra_vars,
+ 'forks': 0,
+ 'hosts_count': 0,
+ 'instance_group': None,
+ 'inventory': {
+ 'id': inventory.id,
+ 'name': 'inv1',
+ 'description': 'Demo inventory',
+ 'kind': '',
+ 'total_hosts': 0,
+ 'total_groups': 0,
+ 'has_inventory_sources': False,
+ 'total_inventory_sources': 0,
+ 'has_active_failures': False,
+ 'hosts_with_active_failures': 0,
+ 'inventory_sources': [
+ {
+ 'id': inventory_source.id,
+ 'name': 'inv-src1',
+ 'source': 'file',
+ 'status': 'never updated',
+ }
+ ],
+ },
+ 'job_template': None,
+ 'job_type': 'run',
+ 'job_type_name': 'job',
+ 'labels': [
+ {
+ 'id': label.id,
+ 'name': 'label1',
+ 'organization': {
+ 'id': org.id,
+ 'name': 'org1',
+ },
+ },
+ ],
+ 'launch_type': 'workflow',
+ 'limit': '',
+ 'launched_by': {},
+ 'organization': {
+ 'id': org.id,
+ 'name': 'org1',
+ },
+ 'playbook': '',
+ 'project': {
+ 'id': project.id,
+ 'name': 'proj1',
+ 'status': 'pending',
+ 'scm_type': 'git',
+ 'scm_url': 'https://git.example.com/proj1',
+ 'scm_branch': 'main',
+ 'scm_refspec': '',
+ 'scm_clean': False,
+ 'scm_track_submodules': False,
+ 'scm_delete_on_update': False,
+ },
+ 'scm_branch': '',
+ 'scm_revision': '',
+ 'workflow_job': {
+ 'id': workflow_job.id,
+ 'name': 'wf-job1',
+ },
+ 'workflow_job_template': None,
+ }
+
+
+@pytest.mark.django_db
+def test_evaluate_policy_missing_opa_query_path_field(opa_client):
+ project: Project = Project.objects.create(name='proj1', scm_type='git', scm_branch='main', scm_url='https://git.example.com/proj1')
+ inventory: Inventory = Inventory.objects.create(name='inv1')
+ org: Organization = Organization.objects.create(name="org1")
+ jt: JobTemplate = JobTemplate.objects.create(name="jt1")
+ job: Job = Job.objects.create(name='job1', extra_vars="{}", inventory=inventory, project=project, organization=org, job_template=jt)
+
+ response = {
+ "result": {
+ "allowed": True,
+ "violations": [],
+ }
+ }
+ opa_client.query_rule.return_value = response
+ try:
+ policy.evaluate_policy(job)
+ except PolicyEvaluationError as e:
+ pytest.fail(f"Must not raise PolicyEvaluationError: {e}")
+
+ assert opa_client.query_rule.call_count == 0
+
+
+@pytest.mark.django_db
+def test_evaluate_policy(opa_client, job):
+ response = {
+ "result": {
+ "allowed": True,
+ "violations": [],
+ }
+ }
+ opa_client.query_rule.return_value = response
+ try:
+ policy.evaluate_policy(job)
+ except PolicyEvaluationError as e:
+ pytest.fail(f"Must not raise PolicyEvaluationError: {e}")
+
+ opa_client.query_rule.assert_has_calls(
+ [
+ mock.call(input_data=mock.ANY, package_path='organization/response'),
+ mock.call(input_data=mock.ANY, package_path='inventory/response'),
+ mock.call(input_data=mock.ANY, package_path='job_template/response'),
+ ],
+ any_order=False,
+ )
+ assert opa_client.query_rule.call_count == 3
+
+
+@pytest.mark.django_db
+def test_evaluate_policy_allowed(opa_client, job):
+ response = {
+ "result": {
+ "allowed": True,
+ "violations": [],
+ }
+ }
+ opa_client.query_rule.return_value = response
+ try:
+ policy.evaluate_policy(job)
+ except PolicyEvaluationError as e:
+ pytest.fail(f"Must not raise PolicyEvaluationError: {e}")
+
+ assert opa_client.query_rule.call_count == 3
+
+
+@pytest.mark.django_db
+def test_evaluate_policy_not_allowed(opa_client, job):
+ response = {
+ "result": {
+ "allowed": False,
+ "violations": ["Access not allowed."],
+ }
+ }
+ opa_client.query_rule.return_value = response
+
+ with pytest.raises(PolicyEvaluationError) as pe:
+ policy.evaluate_policy(job)
+
+ pe_plain = str(pe.value)
+ assert "Errors:" not in pe_plain
+
+ exception = _parse_exception_message(pe)
+
+ assert exception["Violations"]["Organization"] == ["Access not allowed."]
+ assert exception["Violations"]["Inventory"] == ["Access not allowed."]
+ assert exception["Violations"]["Job template"] == ["Access not allowed."]
+
+ assert opa_client.query_rule.call_count == 3
+
+
+@pytest.mark.django_db
+def test_evaluate_policy_not_found(opa_client, job):
+ response = {}
+ opa_client.query_rule.return_value = response
+
+ with pytest.raises(PolicyEvaluationError) as pe:
+ policy.evaluate_policy(job)
+
+ missing_result_property = 'Call to OPA did not return a "result" property. The path refers to an undefined document.'
+
+ exception = _parse_exception_message(pe)
+ assert exception["Errors"]["Organization"] == missing_result_property
+ assert exception["Errors"]["Inventory"] == missing_result_property
+ assert exception["Errors"]["Job template"] == missing_result_property
+
+ assert opa_client.query_rule.call_count == 3
+
+
+@pytest.mark.django_db
+def test_evaluate_policy_server_error(opa_client, job):
+ http_error_msg = '500 Server Error: Internal Server Error for url: https://opa.example.com:8181/v1/data/job_template/response/invalid'
+ error_response = {
+ 'code': 'internal_error',
+ 'message': (
+ '1 error occurred: 1:1: rego_type_error: undefined ref: data.job_template.response.invalid\n\t'
+ 'data.job_template.response.invalid\n\t'
+ ' ^\n\t'
+ ' have: "invalid"\n\t'
+ ' want (one of): ["allowed" "violations"]'
+ ),
+ }
+ response = mock.Mock()
+ response.status_code = requests.codes.internal_server_error
+ response.json.return_value = error_response
+
+ opa_client.query_rule.side_effect = requests.exceptions.HTTPError(http_error_msg, response=response)
+
+ with pytest.raises(PolicyEvaluationError) as pe:
+ policy.evaluate_policy(job)
+
+ exception = _parse_exception_message(pe)
+ assert exception["Errors"]["Organization"] == f'Call to OPA failed. Code: internal_error, Message: {error_response["message"]}'
+ assert exception["Errors"]["Inventory"] == f'Call to OPA failed. Code: internal_error, Message: {error_response["message"]}'
+ assert exception["Errors"]["Job template"] == f'Call to OPA failed. Code: internal_error, Message: {error_response["message"]}'
+
+ assert opa_client.query_rule.call_count == 3
+
+
+@pytest.mark.django_db
+def test_evaluate_policy_invalid_result(opa_client, job):
+ response = {
+ "result": {
+ "absolutely": "no!",
+ }
+ }
+ opa_client.query_rule.return_value = response
+
+ with pytest.raises(PolicyEvaluationError) as pe:
+ policy.evaluate_policy(job)
+
+ invalid_result = 'OPA policy returned invalid result.'
+
+ exception = _parse_exception_message(pe)
+ assert exception["Errors"]["Organization"] == invalid_result
+ assert exception["Errors"]["Inventory"] == invalid_result
+ assert exception["Errors"]["Job template"] == invalid_result
+
+ assert opa_client.query_rule.call_count == 3
+
+
+@pytest.mark.django_db
+def test_evaluate_policy_failed_exception(opa_client, job):
+ error_response = {}
+ response = mock.Mock()
+ response.status_code = requests.codes.internal_server_error
+ response.json.return_value = error_response
+
+ opa_client.query_rule.side_effect = ValueError("Invalid JSON")
+
+ with pytest.raises(PolicyEvaluationError) as pe:
+ policy.evaluate_policy(job)
+
+ opa_failed_exception = 'Call to OPA failed. Exception: Invalid JSON'
+
+ exception = _parse_exception_message(pe)
+ assert exception["Errors"]["Organization"] == opa_failed_exception
+ assert exception["Errors"]["Inventory"] == opa_failed_exception
+ assert exception["Errors"]["Job template"] == opa_failed_exception
+
+ assert opa_client.query_rule.call_count == 3
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize(
+ "settings_kwargs, expected_client_cert, expected_verify, verify_content",
+ [
+ # Case 1: Certificate-based authentication (mTLS)
+ (
+ {
+ "OPA_HOST": "opa.example.com",
+ "OPA_SSL": True,
+ "OPA_AUTH_TYPE": OPA_AUTH_TYPES.CERTIFICATE,
+ "OPA_AUTH_CLIENT_CERT": "-----BEGIN CERTIFICATE-----\nMIICert\n-----END CERTIFICATE-----",
+ "OPA_AUTH_CLIENT_KEY": "-----BEGIN PRIVATE KEY-----\nMIIKey\n-----END PRIVATE KEY-----",
+ "OPA_AUTH_CA_CERT": "-----BEGIN CERTIFICATE-----\nMIICACert\n-----END CERTIFICATE-----",
+ },
+ True, # Client cert should be created
+ "file", # Verify path should be a file
+ "-----BEGIN CERTIFICATE-----", # Expected content in verify file
+ ),
+ # Case 2: SSL with server verification only
+ (
+ {
+ "OPA_HOST": "opa.example.com",
+ "OPA_SSL": True,
+ "OPA_AUTH_TYPE": OPA_AUTH_TYPES.NONE,
+ "OPA_AUTH_CA_CERT": "-----BEGIN CERTIFICATE-----\nMIICACert\n-----END CERTIFICATE-----",
+ },
+ False, # No client cert should be created
+ "file", # Verify path should be a file
+ "-----BEGIN CERTIFICATE-----", # Expected content in verify file
+ ),
+ # Case 3: SSL with system CA store
+ (
+ {
+ "OPA_HOST": "opa.example.com",
+ "OPA_SSL": True,
+ "OPA_AUTH_TYPE": OPA_AUTH_TYPES.NONE,
+ "OPA_AUTH_CA_CERT": "", # No custom CA cert
+ },
+ False, # No client cert should be created
+ True, # Verify path should be True (system CA store)
+ None, # No file to check content
+ ),
+ # Case 4: No SSL
+ (
+ {
+ "OPA_HOST": "opa.example.com",
+ "OPA_SSL": False,
+ "OPA_AUTH_TYPE": OPA_AUTH_TYPES.NONE,
+ },
+ False, # No client cert should be created
+ False, # Verify path should be False (no verification)
+ None, # No file to check content
+ ),
+ ],
+ ids=[
+ "certificate_auth",
+ "ssl_server_verification",
+ "ssl_system_ca_store",
+ "no_ssl",
+ ],
+)
+def test_opa_cert_file(settings_kwargs, expected_client_cert, expected_verify, verify_content):
+ """Parameterized test for the opa_cert_file context manager.
+
+ Tests different configurations:
+ - Certificate-based authentication (mTLS)
+ - SSL with server verification only
+ - SSL with system CA store
+ - No SSL
+ """
+ with override_settings(**settings_kwargs):
+ client_cert_path = None
+ verify_path = None
+
+ with policy.opa_cert_file() as cert_files:
+ client_cert_path, verify_path = cert_files
+
+ # Check client cert based on expected_client_cert
+ if expected_client_cert:
+ assert client_cert_path is not None
+ with open(client_cert_path, 'r') as f:
+ content = f.read()
+ assert "-----BEGIN CERTIFICATE-----" in content
+ assert "-----BEGIN PRIVATE KEY-----" in content
+ else:
+ assert client_cert_path is None
+
+ # Check verify path based on expected_verify
+ if expected_verify == "file":
+ assert verify_path is not None
+ assert os.path.isfile(verify_path)
+ with open(verify_path, 'r') as f:
+ content = f.read()
+ assert verify_content in content
+ else:
+ assert verify_path is expected_verify
+
+ # Verify files are deleted after context manager exits
+ if expected_client_cert:
+ assert not os.path.exists(client_cert_path), "Client cert file was not deleted"
+
+ if expected_verify == "file":
+ assert not os.path.exists(verify_path), "CA cert file was not deleted"
+
+
+@pytest.mark.django_db
+@override_settings(
+ OPA_HOST='opa.example.com',
+ OPA_SSL=False, # SSL disabled
+ OPA_AUTH_TYPE=OPA_AUTH_TYPES.CERTIFICATE, # But cert auth enabled
+ OPA_AUTH_CLIENT_CERT="-----BEGIN CERTIFICATE-----\nMIICert\n-----END CERTIFICATE-----",
+ OPA_AUTH_CLIENT_KEY="-----BEGIN PRIVATE KEY-----\nMIIKey\n-----END PRIVATE KEY-----",
+)
+def test_evaluate_policy_cert_auth_requires_ssl():
+ """Test that policy evaluation raises an error when certificate auth is used without SSL."""
+ project = Project.objects.create(name='proj1')
+ inventory = Inventory.objects.create(name='inv1', opa_query_path="inventory/response")
+ org = Organization.objects.create(name="org1", opa_query_path="organization/response")
+ jt = JobTemplate.objects.create(name="jt1", opa_query_path="job_template/response")
+ job = Job.objects.create(name='job1', extra_vars="{}", inventory=inventory, project=project, organization=org, job_template=jt)
+
+ with pytest.raises(PolicyEvaluationError) as pe:
+ policy.evaluate_policy(job)
+
+ assert "OPA_AUTH_TYPE=Certificate requires OPA_SSL to be enabled" in str(pe.value)
+
+
+@pytest.mark.django_db
+@override_settings(
+ OPA_HOST='opa.example.com',
+ OPA_SSL=True,
+ OPA_AUTH_TYPE=OPA_AUTH_TYPES.CERTIFICATE,
+ OPA_AUTH_CLIENT_CERT="", # Missing client cert
+ OPA_AUTH_CLIENT_KEY="", # Missing client key
+ OPA_AUTH_CA_CERT="", # Missing CA cert
+)
+def test_evaluate_policy_missing_cert_settings():
+ """Test that policy evaluation raises an error when certificate settings are missing."""
+ project = Project.objects.create(name='proj1')
+ inventory = Inventory.objects.create(name='inv1', opa_query_path="inventory/response")
+ org = Organization.objects.create(name="org1", opa_query_path="organization/response")
+ jt = JobTemplate.objects.create(name="jt1", opa_query_path="job_template/response")
+ job = Job.objects.create(name='job1', extra_vars="{}", inventory=inventory, project=project, organization=org, job_template=jt)
+
+ with pytest.raises(PolicyEvaluationError) as pe:
+ policy.evaluate_policy(job)
+
+ error_msg = str(pe.value)
+ assert "Following certificate settings are missing for OPA_AUTH_TYPE=Certificate:" in error_msg
+ assert "OPA_AUTH_CLIENT_CERT" in error_msg
+ assert "OPA_AUTH_CLIENT_KEY" in error_msg
+ assert "OPA_AUTH_CA_CERT" in error_msg
+
+
+@pytest.mark.django_db
+@override_settings(
+ OPA_HOST='opa.example.com',
+ OPA_PORT=8181,
+ OPA_SSL=True,
+ OPA_AUTH_TYPE=OPA_AUTH_TYPES.CERTIFICATE,
+ OPA_AUTH_CLIENT_CERT="-----BEGIN CERTIFICATE-----\nMIICert\n-----END CERTIFICATE-----",
+ OPA_AUTH_CLIENT_KEY="-----BEGIN PRIVATE KEY-----\nMIIKey\n-----END PRIVATE KEY-----",
+ OPA_AUTH_CA_CERT="-----BEGIN CERTIFICATE-----\nMIICACert\n-----END CERTIFICATE-----",
+ OPA_REQUEST_TIMEOUT=2.5,
+ OPA_REQUEST_RETRIES=3,
+)
+def test_opa_client_context_manager_mtls():
+ """Test that opa_client context manager correctly initializes the OPA client."""
+ # Mock the OpaClient class
+ with mock.patch('awx.main.tasks.policy.OpaClient') as mock_opa_client:
+ # Setup the mock
+ mock_instance = mock_opa_client.return_value
+ mock_instance.__enter__.return_value = mock_instance
+ mock_instance._session = mock.MagicMock()
+
+ # Use the context manager
+ with policy.opa_client(headers={'Custom-Header': 'Value'}) as client:
+ # Verify the client was initialized with the correct parameters
+ mock_opa_client.assert_called_once_with(
+ host='opa.example.com',
+ port=8181,
+ headers={'Custom-Header': 'Value'},
+ ssl=True,
+ cert=mock.ANY, # We can't check the exact value as it's a temporary file
+ timeout=2.5,
+ retries=3,
+ )
+
+ # Verify the session properties were set correctly
+ assert client._session.cert is not None
+ assert client._session.verify is not None
+
+ # Check the content of the cert file
+ cert_file_path = client._session.cert
+ assert os.path.isfile(cert_file_path)
+ with open(cert_file_path, 'r') as f:
+ cert_content = f.read()
+ assert "-----BEGIN CERTIFICATE-----" in cert_content
+ assert "MIICert" in cert_content
+ assert "-----BEGIN PRIVATE KEY-----" in cert_content
+ assert "MIIKey" in cert_content
+
+ # Check the content of the verify file
+ verify_file_path = client._session.verify
+ assert os.path.isfile(verify_file_path)
+ with open(verify_file_path, 'r') as f:
+ verify_content = f.read()
+ assert "-----BEGIN CERTIFICATE-----" in verify_content
+ assert "MIICACert" in verify_content
+
+ # Verify the client is the mocked instance
+ assert client is mock_instance
+
+ # Store file paths for checking after context exit
+ cert_path = client._session.cert
+ verify_path = client._session.verify
+
+ # Verify files are deleted after context manager exits
+ assert not os.path.exists(cert_path), "Client cert file was not deleted"
+ assert not os.path.exists(verify_path), "CA cert file was not deleted"
+
+
+@pytest.mark.django_db
+@override_settings(
+ OPA_HOST='opa.example.com',
+ OPA_SSL=True,
+ OPA_AUTH_TYPE=OPA_AUTH_TYPES.TOKEN,
+ OPA_AUTH_TOKEN='secret-token',
+ OPA_AUTH_CUSTOM_HEADERS={'X-Custom': 'Header'},
+)
+def test_opa_client_token_auth():
+ """Test that token authentication correctly adds the Authorization header."""
+ # Create a job for testing
+ project = Project.objects.create(name='proj1')
+ inventory = Inventory.objects.create(name='inv1', opa_query_path="inventory/response")
+ org = Organization.objects.create(name="org1", opa_query_path="organization/response")
+ jt = JobTemplate.objects.create(name="jt1", opa_query_path="job_template/response")
+ job = Job.objects.create(name='job1', extra_vars="{}", inventory=inventory, project=project, organization=org, job_template=jt)
+
+ # Mock the OpaClient class
+ with mock.patch('awx.main.tasks.policy.opa_client') as mock_opa_client_cm:
+ # Setup the mock
+ mock_client = mock.MagicMock()
+ mock_opa_client_cm.return_value.__enter__.return_value = mock_client
+ mock_client.query_rule.return_value = {
+ "result": {
+ "allowed": True,
+ "violations": [],
+ }
+ }
+
+ # Call evaluate_policy
+ policy.evaluate_policy(job)
+
+ # Verify opa_client was called with the correct headers
+ expected_headers = {'X-Custom': 'Header', 'Authorization': 'Bearer secret-token'}
+ mock_opa_client_cm.assert_called_once_with(headers=expected_headers)
diff --git a/awx/main/tests/functional/test_projects.py b/awx/main/tests/functional/test_projects.py
index 0459aaab49ae..7d389d1e1623 100644
--- a/awx/main/tests/functional/test_projects.py
+++ b/awx/main/tests/functional/test_projects.py
@@ -4,7 +4,7 @@
import pytest
from awx.api.versioning import reverse
-from awx.main.models import Project
+from awx.main.models import Project, JobTemplate
from django.core.exceptions import ValidationError
@@ -334,8 +334,71 @@ def test_team_project_list(get, team_project_list):
)
+@pytest.mark.django_db
+def test_project_teams_list_multiple_roles_distinct(get, organization_factory):
+ # test projects with multiple roles on the same team
+ objects = organization_factory(
+ 'org1',
+ superusers=['admin'],
+ teams=['teamA'],
+ projects=['proj1'],
+ roles=[
+ 'teamA.member_role:proj1.admin_role',
+ 'teamA.member_role:proj1.use_role',
+ 'teamA.member_role:proj1.update_role',
+ 'teamA.member_role:proj1.read_role',
+ ],
+ )
+ admin = objects.superusers.admin
+ proj1 = objects.projects.proj1
+
+ res = get(reverse('api:project_teams_list', kwargs={'pk': proj1.pk}), admin).data
+ names = [t['name'] for t in res['results']]
+ assert names == ['teamA']
+
+
+@pytest.mark.django_db
+def test_project_teams_list_multiple_teams(get, organization_factory):
+ # test projects with multiple teams
+ objs = organization_factory(
+ 'org1',
+ superusers=['admin'],
+ teams=['teamA', 'teamB', 'teamC', 'teamD'],
+ projects=['proj1'],
+ roles=[
+ 'teamA.member_role:proj1.admin_role',
+ 'teamB.member_role:proj1.update_role',
+ 'teamC.member_role:proj1.use_role',
+ 'teamD.member_role:proj1.read_role',
+ ],
+ )
+ admin = objs.superusers.admin
+ proj1 = objs.projects.proj1
+
+ res = get(reverse('api:project_teams_list', kwargs={'pk': proj1.pk}), admin).data
+ names = sorted([t['name'] for t in res['results']])
+ assert names == ['teamA', 'teamB', 'teamC', 'teamD']
+
+
+@pytest.mark.django_db
+def test_project_teams_list_no_direct_assignments(get, organization_factory):
+ # test projects with no direct team assignments
+ objects = organization_factory(
+ 'org1',
+ superusers=['admin'],
+ teams=['teamA'],
+ projects=['proj1'],
+ roles=[],
+ )
+ admin = objects.superusers.admin
+ proj1 = objects.projects.proj1
+
+ res = get(reverse('api:project_teams_list', kwargs={'pk': proj1.pk}), admin).data
+ assert res['count'] == 0
+
+
@pytest.mark.parametrize("u,expected_status_code", [('rando', 403), ('org_member', 403), ('org_admin', 201), ('admin', 201)])
-@pytest.mark.django_db()
+@pytest.mark.django_db
def test_create_project(post, organization, org_admin, org_member, admin, rando, u, expected_status_code):
if u == 'rando':
u = rando
@@ -353,11 +416,12 @@ def test_create_project(post, organization, org_admin, org_member, admin, rando,
'organization': organization.id,
},
u,
+ expect=expected_status_code,
)
- print(result.data)
- assert result.status_code == expected_status_code
if expected_status_code == 201:
assert Project.objects.filter(name='Project', organization=organization).exists()
+ elif expected_status_code == 403:
+ assert 'do not have permission' in str(result.data['detail'])
@pytest.mark.django_db
@@ -411,14 +475,14 @@ def test_project_delete(delete, organization, admin_user):
@pytest.mark.parametrize(
- 'order_by, expected_names, expected_ids',
+ 'order_by, expected_names',
[
- ('name', ['alice project', 'bob project', 'shared project'], [1, 2, 3]),
- ('-name', ['shared project', 'bob project', 'alice project'], [3, 2, 1]),
+ ('name', ['alice project', 'bob project', 'shared project']),
+ ('-name', ['shared project', 'bob project', 'alice project']),
],
)
@pytest.mark.django_db
-def test_project_list_ordering_by_name(get, order_by, expected_names, expected_ids, organization_factory):
+def test_project_list_ordering_by_name(get, order_by, expected_names, organization_factory):
'ensure sorted order of project list is maintained correctly when the requested order is invalid or not applicable'
objects = organization_factory(
'org1',
@@ -426,28 +490,44 @@ def test_project_list_ordering_by_name(get, order_by, expected_names, expected_i
superusers=['admin'],
)
project_names = []
- project_ids = []
# TODO: ask for an order by here that doesn't apply
results = get(reverse('api:project_list'), objects.superusers.admin, QUERY_STRING='order_by=%s' % order_by).data['results']
for x in range(len(results)):
project_names.append(results[x]['name'])
- project_ids.append(results[x]['id'])
- assert project_names == expected_names and project_ids == expected_ids
+ assert project_names == expected_names
@pytest.mark.parametrize('order_by', ('name', '-name'))
@pytest.mark.django_db
-def test_project_list_ordering_with_duplicate_names(get, order_by, organization_factory):
+def test_project_list_ordering_with_duplicate_names(get, order_by, admin):
# why? because all the '1' mean that all the names are the same, you can't sort based on that,
# meaning you have to fall back on the default sort order, which in this case, is ID
'ensure sorted order of project list is maintained correctly when the project names the same'
- objects = organization_factory(
- 'org1',
- projects=['1', '1', '1', '1', '1'],
- superusers=['admin'],
- )
+ from awx.main.models import Organization
+
+ projects = []
+ for i in range(5):
+ projects.append(Project.objects.create(name='1', organization=Organization.objects.create(name=f'org{i}')))
project_ids = {}
for x in range(3):
- results = get(reverse('api:project_list'), objects.superusers.admin, QUERY_STRING='order_by=%s' % order_by).data['results']
+ results = get(reverse('api:project_list'), user=admin, QUERY_STRING='order_by=%s' % order_by).data['results']
project_ids[x] = [proj['id'] for proj in results]
- assert project_ids[0] == project_ids[1] == project_ids[2] == [1, 2, 3, 4, 5]
+ assert project_ids[0] == project_ids[1] == project_ids[2]
+ assert project_ids[0] == sorted(project_ids[0])
+ assert set(project_ids[0]) == set([proj.id for proj in projects])
+
+
+@pytest.mark.django_db
+def test_project_failed_update(post, project, admin, inventory):
+ """Test to ensure failed projects with update on launch will create launch rather than error"""
+ jt = JobTemplate.objects.create(project=project, inventory=inventory)
+ # set project to update on launch and set status to failed
+ project.update_fields(scm_update_on_launch=True)
+ project.update()
+ project.project_updates.last().update_fields(status='failed')
+ response = post(reverse('api:job_template_launch', kwargs={'pk': jt.pk}), user=admin, expect=201)
+ assert response.status_code == 201
+ # set project to not update on launch and validate still 400's
+ project.update_fields(scm_update_on_launch=False)
+ response = post(reverse('api:job_template_launch', kwargs={'pk': jt.pk}), user=admin, expect=400)
+ assert response.status_code == 400
diff --git a/awx/main/tests/functional/test_python_requirements.py b/awx/main/tests/functional/test_python_requirements.py
index d363b91db1f4..089638e5c2fa 100644
--- a/awx/main/tests/functional/test_python_requirements.py
+++ b/awx/main/tests/functional/test_python_requirements.py
@@ -5,7 +5,38 @@
from django.conf import settings
-@pytest.mark.skip(reason="This test needs some love")
+def test_bootstrap_consistent():
+ with open('Makefile', 'r') as f:
+ mk_data = f.read()
+ bootstrap_reqs = None
+ for line in mk_data.split('\n'):
+ if line.startswith('VENV_BOOTSTRAP'):
+ parts = line.split()
+ bootstrap_reqs = parts[parts.index('?=') + 1 :]
+ break
+ else:
+ raise RuntimeError('Cound not find bootstrap line')
+
+ req_data = None
+ with open('requirements/requirements.txt', 'r') as f:
+ req_data = f.read()
+
+ different_requirements = []
+ for req in bootstrap_reqs:
+ boot_req_name, _ = req.split('=', 1)
+ for line in req_data.split('\n'):
+ if '=' not in line:
+ continue
+ req_name, _ = line.split('=', 1)
+ if req_name == boot_req_name:
+ if req != line:
+ different_requirements.append((req, line))
+ break
+
+ assert not different_requirements
+
+
+@pytest.mark.xfail(reason="This test needs some love")
def test_env_matches_requirements_txt():
from pip.operations import freeze
@@ -33,7 +64,7 @@ def skip_line(line):
if skip_line(x):
continue
x = x.lower()
- (pkg_name, pkg_version) = x.split('==')
+ pkg_name, pkg_version = x.split('==')
reqs_actual.append([pkg_name, pkg_version])
reqs_expected = []
@@ -49,7 +80,7 @@ def skip_line(line):
Special case pkg_name[pkg_subname]==version
For this case, we strip out [pkg_subname]
'''
- (pkg_name, pkg_version) = line.split('==')
+ pkg_name, pkg_version = line.split('==')
pkg_name = re.sub(r'\[.*\]', '', pkg_name)
reqs_expected.append([pkg_name, pkg_version])
diff --git a/awx/main/tests/functional/test_rbac_core.py b/awx/main/tests/functional/test_rbac_core.py
deleted file mode 100644
index 7029bbe54486..000000000000
--- a/awx/main/tests/functional/test_rbac_core.py
+++ /dev/null
@@ -1,213 +0,0 @@
-import pytest
-
-from awx.main.models import (
- Role,
- Organization,
- Project,
-)
-from awx.main.fields import update_role_parentage_for_instance
-
-
-@pytest.mark.django_db
-def test_auto_inheritance_by_children(organization, alice):
- A = Role.objects.create()
- B = Role.objects.create()
- A.members.add(alice)
-
- assert alice not in organization.admin_role
- assert Organization.accessible_objects(alice, 'admin_role').count() == 0
- A.children.add(B)
- assert alice not in organization.admin_role
- assert Organization.accessible_objects(alice, 'admin_role').count() == 0
- A.children.add(organization.admin_role)
- assert alice in organization.admin_role
- assert Organization.accessible_objects(alice, 'admin_role').count() == 1
- A.children.remove(organization.admin_role)
- assert alice not in organization.admin_role
- B.children.add(organization.admin_role)
- assert alice in organization.admin_role
- B.children.remove(organization.admin_role)
- assert alice not in organization.admin_role
- assert Organization.accessible_objects(alice, 'admin_role').count() == 0
-
- # We've had the case where our pre/post save init handlers in our field descriptors
- # end up creating a ton of role objects because of various not-so-obvious issues
- assert Role.objects.count() < 50
-
-
-@pytest.mark.django_db
-def test_auto_inheritance_by_parents(organization, alice):
- A = Role.objects.create()
- B = Role.objects.create()
- A.members.add(alice)
-
- assert alice not in organization.admin_role
- B.parents.add(A)
- assert alice not in organization.admin_role
- organization.admin_role.parents.add(A)
- assert alice in organization.admin_role
- organization.admin_role.parents.remove(A)
- assert alice not in organization.admin_role
- organization.admin_role.parents.add(B)
- assert alice in organization.admin_role
- organization.admin_role.parents.remove(B)
- assert alice not in organization.admin_role
-
-
-@pytest.mark.django_db
-def test_accessible_objects(organization, alice, bob):
- A = Role.objects.create()
- A.members.add(alice)
- B = Role.objects.create()
- B.members.add(alice)
- B.members.add(bob)
-
- assert Organization.accessible_objects(alice, 'admin_role').count() == 0
- assert Organization.accessible_objects(bob, 'admin_role').count() == 0
- A.children.add(organization.admin_role)
- assert Organization.accessible_objects(alice, 'admin_role').count() == 1
- assert Organization.accessible_objects(bob, 'admin_role').count() == 0
-
-
-@pytest.mark.django_db
-def test_team_symantics(organization, team, alice):
- assert alice not in organization.auditor_role
- team.member_role.children.add(organization.auditor_role)
- assert alice not in organization.auditor_role
- team.member_role.members.add(alice)
- assert alice in organization.auditor_role
- team.member_role.members.remove(alice)
- assert alice not in organization.auditor_role
-
-
-@pytest.mark.django_db
-def test_auto_field_adjustments(organization, inventory, team, alice):
- 'Ensures the auto role reparenting is working correctly through non m2m fields'
- org2 = Organization.objects.create(name='Org 2', description='org 2')
- org2.admin_role.members.add(alice)
- assert alice not in inventory.admin_role
- inventory.organization = org2
- inventory.save()
- assert alice in inventory.admin_role
- inventory.organization = organization
- inventory.save()
- assert alice not in inventory.admin_role
- # assert False
-
-
-@pytest.mark.django_db
-def test_implicit_deletes(alice):
- 'Ensures implicit resources and roles delete themselves'
- delorg = Organization.objects.create(name='test-org')
- child = Role.objects.create()
- child.parents.add(delorg.admin_role)
- delorg.admin_role.members.add(alice)
-
- admin_role_id = delorg.admin_role.id
- auditor_role_id = delorg.auditor_role.id
-
- assert child.ancestors.count() > 1
- assert Role.objects.filter(id=admin_role_id).count() == 1
- assert Role.objects.filter(id=auditor_role_id).count() == 1
- n_alice_roles = alice.roles.count()
- n_system_admin_children = Role.singleton('system_administrator').children.count()
-
- delorg.delete()
-
- assert Role.objects.filter(id=admin_role_id).count() == 0
- assert Role.objects.filter(id=auditor_role_id).count() == 0
- assert alice.roles.count() == (n_alice_roles - 1)
- assert Role.singleton('system_administrator').children.count() == (n_system_admin_children - 1)
- assert child.ancestors.count() == 1
- assert child.ancestors.all()[0] == child
-
-
-@pytest.mark.django_db
-def test_content_object(user):
- 'Ensure our content_object stuf seems to be working'
-
- org = Organization.objects.create(name='test-org')
- assert org.admin_role.content_object.id == org.id
-
-
-@pytest.mark.django_db
-def test_hierarchy_rebuilding_multi_path():
- 'Tests a subdtle cases around role hierarchy rebuilding when you have multiple paths to the same role of different length'
-
- X = Role.objects.create()
- A = Role.objects.create()
- B = Role.objects.create()
- C = Role.objects.create()
- D = Role.objects.create()
-
- A.children.add(B)
- A.children.add(D)
- B.children.add(C)
- C.children.add(D)
-
- assert A.is_ancestor_of(D)
- assert X.is_ancestor_of(D) is False
-
- X.children.add(A)
-
- assert X.is_ancestor_of(D) is True
-
- X.children.remove(A)
-
- # This can be the stickler, the rebuilder needs to ensure that D's role
- # hierarchy is built after both A and C are updated.
- assert X.is_ancestor_of(D) is False
-
-
-@pytest.mark.django_db
-def test_auto_parenting():
- org1 = Organization.objects.create(name='org1')
- org2 = Organization.objects.create(name='org2')
-
- prj1 = Project.objects.create(name='prj1')
- prj2 = Project.objects.create(name='prj2')
-
- assert org1.admin_role.is_ancestor_of(prj1.admin_role) is False
- assert org1.admin_role.is_ancestor_of(prj2.admin_role) is False
- assert org2.admin_role.is_ancestor_of(prj1.admin_role) is False
- assert org2.admin_role.is_ancestor_of(prj2.admin_role) is False
-
- prj1.organization = org1
- prj1.save()
-
- assert org1.admin_role.is_ancestor_of(prj1.admin_role)
- assert org1.admin_role.is_ancestor_of(prj2.admin_role) is False
- assert org2.admin_role.is_ancestor_of(prj1.admin_role) is False
- assert org2.admin_role.is_ancestor_of(prj2.admin_role) is False
-
- prj2.organization = org1
- prj2.save()
-
- assert org1.admin_role.is_ancestor_of(prj1.admin_role)
- assert org1.admin_role.is_ancestor_of(prj2.admin_role)
- assert org2.admin_role.is_ancestor_of(prj1.admin_role) is False
- assert org2.admin_role.is_ancestor_of(prj2.admin_role) is False
-
- prj1.organization = org2
- prj1.save()
-
- assert org1.admin_role.is_ancestor_of(prj1.admin_role) is False
- assert org1.admin_role.is_ancestor_of(prj2.admin_role)
- assert org2.admin_role.is_ancestor_of(prj1.admin_role)
- assert org2.admin_role.is_ancestor_of(prj2.admin_role) is False
-
- prj2.organization = org2
- prj2.save()
-
- assert org1.admin_role.is_ancestor_of(prj1.admin_role) is False
- assert org1.admin_role.is_ancestor_of(prj2.admin_role) is False
- assert org2.admin_role.is_ancestor_of(prj1.admin_role)
- assert org2.admin_role.is_ancestor_of(prj2.admin_role)
-
-
-@pytest.mark.django_db
-def test_update_parents_keeps_teams(team, project):
- project.update_role.parents.add(team.member_role)
- assert team.member_role in project.update_role # test prep sanity check
- update_role_parentage_for_instance(project)
- assert team.member_role in project.update_role # actual assertion
diff --git a/awx/main/tests/functional/test_rbac_instance_groups.py b/awx/main/tests/functional/test_rbac_instance_groups.py
deleted file mode 100644
index 402040ea21c3..000000000000
--- a/awx/main/tests/functional/test_rbac_instance_groups.py
+++ /dev/null
@@ -1,87 +0,0 @@
-import pytest
-
-from awx.main.access import (
- InstanceGroupAccess,
- OrganizationAccess,
- InventoryAccess,
- JobTemplateAccess,
-)
-from awx.main.models import Organization
-
-
-@pytest.mark.django_db
-def test_ig_normal_user_visibility(organization, default_instance_group, user):
- u = user('user', False)
- assert len(InstanceGroupAccess(u).get_queryset()) == 0
- organization.instance_groups.add(default_instance_group)
- organization.member_role.members.add(u)
- assert len(InstanceGroupAccess(u).get_queryset()) == 0
-
-
-@pytest.mark.django_db
-def test_ig_admin_user_visibility(organization, default_instance_group, admin, system_auditor, org_admin):
- assert len(InstanceGroupAccess(admin).get_queryset()) == 1
- assert len(InstanceGroupAccess(system_auditor).get_queryset()) == 1
- assert len(InstanceGroupAccess(org_admin).get_queryset()) == 0
- organization.instance_groups.add(default_instance_group)
- assert len(InstanceGroupAccess(org_admin).get_queryset()) == 1
-
-
-@pytest.mark.django_db
-def test_ig_normal_user_associability(organization, default_instance_group, user):
- u = user('user', False)
- access = OrganizationAccess(u)
- assert not access.can_attach(organization, default_instance_group, 'instance_groups', None)
- organization.instance_groups.add(default_instance_group)
- organization.member_role.members.add(u)
- assert not access.can_attach(organization, default_instance_group, 'instance_groups', None)
-
-
-@pytest.mark.django_db
-def test_access_via_two_organizations(rando, default_instance_group):
- for org_name in ['org1', 'org2']:
- org = Organization.objects.create(name=org_name)
- org.instance_groups.add(default_instance_group)
- org.admin_role.members.add(rando)
- access = InstanceGroupAccess(rando)
- assert list(access.get_queryset()) == [default_instance_group]
-
-
-@pytest.mark.django_db
-def test_ig_associability(organization, default_instance_group, admin, system_auditor, org_admin, org_member, job_template_factory):
- admin_access = OrganizationAccess(admin)
- auditor_access = OrganizationAccess(system_auditor)
- oadmin_access = OrganizationAccess(org_admin)
- omember_access = OrganizationAccess(org_member)
- assert admin_access.can_attach(organization, default_instance_group, 'instance_groups', None)
- assert not oadmin_access.can_attach(organization, default_instance_group, 'instance_groups', None)
- assert not auditor_access.can_attach(organization, default_instance_group, 'instance_groups', None)
- assert not omember_access.can_attach(organization, default_instance_group, 'instance_groups', None)
-
- organization.instance_groups.add(default_instance_group)
-
- assert admin_access.can_unattach(organization, default_instance_group, 'instance_groups', None)
- assert not oadmin_access.can_unattach(organization, default_instance_group, 'instance_groups', None)
- assert not auditor_access.can_unattach(organization, default_instance_group, 'instance_groups', None)
- assert not omember_access.can_unattach(organization, default_instance_group, 'instance_groups', None)
-
- objects = job_template_factory('jt', organization=organization, project='p', inventory='i', credential='c')
- admin_access = InventoryAccess(admin)
- auditor_access = InventoryAccess(system_auditor)
- oadmin_access = InventoryAccess(org_admin)
- omember_access = InventoryAccess(org_member)
-
- assert admin_access.can_attach(objects.inventory, default_instance_group, 'instance_groups', None)
- assert oadmin_access.can_attach(objects.inventory, default_instance_group, 'instance_groups', None)
- assert not auditor_access.can_attach(objects.inventory, default_instance_group, 'instance_groups', None)
- assert not omember_access.can_attach(objects.inventory, default_instance_group, 'instance_groups', None)
-
- admin_access = JobTemplateAccess(admin)
- auditor_access = JobTemplateAccess(system_auditor)
- oadmin_access = JobTemplateAccess(org_admin)
- omember_access = JobTemplateAccess(org_member)
-
- assert admin_access.can_attach(objects.job_template, default_instance_group, 'instance_groups', None)
- assert oadmin_access.can_attach(objects.job_template, default_instance_group, 'instance_groups', None)
- assert not auditor_access.can_attach(objects.job_template, default_instance_group, 'instance_groups', None)
- assert not omember_access.can_attach(objects.job_template, default_instance_group, 'instance_groups', None)
diff --git a/awx/main/tests/functional/test_rbac_migration.py b/awx/main/tests/functional/test_rbac_migration.py
deleted file mode 100644
index 5f1b2633e86e..000000000000
--- a/awx/main/tests/functional/test_rbac_migration.py
+++ /dev/null
@@ -1,75 +0,0 @@
-import pytest
-
-from django.apps import apps
-
-from awx.main.migrations import _rbac as rbac
-from awx.main.models import UnifiedJobTemplate, InventorySource, Inventory, JobTemplate, Project, Organization, User
-
-
-@pytest.mark.django_db
-def test_implied_organization_subquery_inventory():
- orgs = []
- for i in range(3):
- orgs.append(Organization.objects.create(name='foo{}'.format(i)))
- orgs.append(orgs[0])
- for i in range(4):
- org = orgs[i]
- if i == 2:
- inventory = Inventory.objects.create(name='foo{}'.format(i))
- else:
- inventory = Inventory.objects.create(name='foo{}'.format(i), organization=org)
- inv_src = InventorySource.objects.create(name='foo{}'.format(i), inventory=inventory, source='ec2')
- sources = UnifiedJobTemplate.objects.annotate(test_field=rbac.implicit_org_subquery(UnifiedJobTemplate, InventorySource))
- for inv_src in sources:
- assert inv_src.test_field == inv_src.inventory.organization_id
-
-
-@pytest.mark.django_db
-def test_implied_organization_subquery_job_template():
- jts = []
- for i in range(5):
- if i <= 3:
- org = Organization.objects.create(name='foo{}'.format(i))
- else:
- org = None
- if i <= 4:
- proj = Project.objects.create(name='foo{}'.format(i), organization=org)
- else:
- proj = None
- jts.append(JobTemplate.objects.create(name='foo{}'.format(i), project=proj))
- # test case of sharing same org
- jts[2].project.organization = jts[3].project.organization
- jts[2].save()
- ujts = UnifiedJobTemplate.objects.annotate(test_field=rbac.implicit_org_subquery(UnifiedJobTemplate, JobTemplate))
- for jt in ujts:
- if not isinstance(jt, JobTemplate): # some are projects
- assert jt.test_field is None
- else:
- if jt.project is None:
- assert jt.test_field is None
- else:
- assert jt.test_field == jt.project.organization_id
-
-
-@pytest.mark.django_db
-def test_give_explicit_inventory_permission():
- dual_admin = User.objects.create(username='alice')
- inv_admin = User.objects.create(username='bob')
- inv_org = Organization.objects.create(name='inv-org')
- proj_org = Organization.objects.create(name='proj-org')
-
- inv_org.admin_role.members.add(inv_admin, dual_admin)
- proj_org.admin_role.members.add(dual_admin)
-
- proj = Project.objects.create(name="test-proj", organization=proj_org)
- inv = Inventory.objects.create(name='test-inv', organization=inv_org)
-
- jt = JobTemplate.objects.create(name='foo', project=proj, inventory=inv)
-
- assert dual_admin in jt.admin_role
-
- rbac.restore_inventory_admins(apps, None)
-
- assert inv_admin in jt.admin_role.members.all()
- assert dual_admin not in jt.admin_role.members.all()
- assert dual_admin in jt.admin_role
diff --git a/awx/main/tests/functional/test_rbac_oauth.py b/awx/main/tests/functional/test_rbac_oauth.py
deleted file mode 100644
index c55943adeb35..000000000000
--- a/awx/main/tests/functional/test_rbac_oauth.py
+++ /dev/null
@@ -1,247 +0,0 @@
-import pytest
-
-from awx.main.access import (
- OAuth2ApplicationAccess,
- OAuth2TokenAccess,
- ActivityStreamAccess,
-)
-from awx.main.models.oauth import (
- OAuth2Application as Application,
- OAuth2AccessToken as AccessToken,
-)
-from awx.main.models import ActivityStream
-from awx.api.versioning import reverse
-
-
-@pytest.mark.django_db
-class TestOAuth2Application:
- @pytest.mark.parametrize(
- "user_for_access, can_access_list",
- [
- (0, [True, True]),
- (1, [True, True]),
- (2, [True, True]),
- (3, [False, False]),
- ],
- )
- def test_can_read(self, admin, org_admin, org_member, alice, user_for_access, can_access_list, organization):
- user_list = [admin, org_admin, org_member, alice]
- access = OAuth2ApplicationAccess(user_list[user_for_access])
- app_creation_user_list = [admin, org_admin]
- for user, can_access in zip(app_creation_user_list, can_access_list):
- app = Application.objects.create(
- name='test app for {}'.format(user.username),
- user=user,
- client_type='confidential',
- authorization_grant_type='password',
- organization=organization,
- )
- assert access.can_read(app) is can_access
-
- def test_admin_only_can_read(self, user, organization):
- user = user('org-admin', False)
- organization.admin_role.members.add(user)
- access = OAuth2ApplicationAccess(user)
- app = Application.objects.create(
- name='test app for {}'.format(user.username), user=user, client_type='confidential', authorization_grant_type='password', organization=organization
- )
- assert access.can_read(app) is True
-
- def test_app_activity_stream(self, org_admin, alice, organization):
- app = Application.objects.create(
- name='test app for {}'.format(org_admin.username),
- user=org_admin,
- client_type='confidential',
- authorization_grant_type='password',
- organization=organization,
- )
- access = OAuth2ApplicationAccess(org_admin)
- assert access.can_read(app) is True
- access = ActivityStreamAccess(org_admin)
- activity_stream = ActivityStream.objects.filter(o_auth2_application=app).latest('pk')
- assert access.can_read(activity_stream) is True
- access = ActivityStreamAccess(alice)
- assert access.can_read(app) is False
- assert access.can_read(activity_stream) is False
-
- def test_token_activity_stream(self, org_admin, alice, organization, post):
- app = Application.objects.create(
- name='test app for {}'.format(org_admin.username),
- user=org_admin,
- client_type='confidential',
- authorization_grant_type='password',
- organization=organization,
- )
- response = post(reverse('api:o_auth2_application_token_list', kwargs={'pk': app.pk}), {'scope': 'read'}, org_admin, expect=201)
- token = AccessToken.objects.get(token=response.data['token'])
- access = OAuth2ApplicationAccess(org_admin)
- assert access.can_read(app) is True
- access = ActivityStreamAccess(org_admin)
- activity_stream = ActivityStream.objects.filter(o_auth2_access_token=token).latest('pk')
- assert access.can_read(activity_stream) is True
- access = ActivityStreamAccess(alice)
- assert access.can_read(token) is False
- assert access.can_read(activity_stream) is False
-
- def test_can_edit_delete_app_org_admin(self, admin, org_admin, org_member, alice, organization):
- user_list = [admin, org_admin, org_member, alice]
- can_access_list = [True, True, False, False]
- for user, can_access in zip(user_list, can_access_list):
- app = Application.objects.create(
- name='test app for {}'.format(user.username),
- user=org_admin,
- client_type='confidential',
- authorization_grant_type='password',
- organization=organization,
- )
- access = OAuth2ApplicationAccess(user)
- assert access.can_change(app, {}) is can_access
- assert access.can_delete(app) is can_access
-
- def test_can_edit_delete_app_admin(self, admin, org_admin, org_member, alice, organization):
- user_list = [admin, org_admin, org_member, alice]
- can_access_list = [True, True, False, False]
- for user, can_access in zip(user_list, can_access_list):
- app = Application.objects.create(
- name='test app for {}'.format(user.username),
- user=admin,
- client_type='confidential',
- authorization_grant_type='password',
- organization=organization,
- )
- access = OAuth2ApplicationAccess(user)
- assert access.can_change(app, {}) is can_access
- assert access.can_delete(app) is can_access
-
- def test_superuser_can_always_create(self, admin, org_admin, org_member, alice, organization):
- access = OAuth2ApplicationAccess(admin)
- for user in [admin, org_admin, org_member, alice]:
- assert access.can_add(
- {'name': 'test app', 'user': user.pk, 'client_type': 'confidential', 'authorization_grant_type': 'password', 'organization': organization.id}
- )
-
- def test_normal_user_cannot_create(self, admin, org_admin, org_member, alice, organization):
- for access_user in [org_member, alice]:
- access = OAuth2ApplicationAccess(access_user)
- for user in [admin, org_admin, org_member, alice]:
- assert not access.can_add(
- {
- 'name': 'test app',
- 'user': user.pk,
- 'client_type': 'confidential',
- 'authorization_grant_type': 'password',
- 'organization': organization.id,
- }
- )
-
-
-@pytest.mark.django_db
-class TestOAuth2Token:
- def test_can_read_change_delete_app_token(self, post, admin, org_admin, org_member, alice, organization):
- user_list = [admin, org_admin, org_member, alice]
- can_access_list = [True, True, False, False]
- app = Application.objects.create(
- name='test app for {}'.format(admin.username),
- user=admin,
- client_type='confidential',
- authorization_grant_type='password',
- organization=organization,
- )
- response = post(reverse('api:o_auth2_application_token_list', kwargs={'pk': app.pk}), {'scope': 'read'}, admin, expect=201)
- for user, can_access in zip(user_list, can_access_list):
- token = AccessToken.objects.get(token=response.data['token'])
- access = OAuth2TokenAccess(user)
- assert access.can_read(token) is can_access
- assert access.can_change(token, {}) is can_access
- assert access.can_delete(token) is can_access
-
- def test_auditor_can_read(self, post, admin, org_admin, org_member, alice, system_auditor, organization):
- user_list = [admin, org_admin, org_member]
- can_access_list = [True, True, True]
- cannot_access_list = [False, False, False]
- app = Application.objects.create(
- name='test app for {}'.format(admin.username),
- user=admin,
- client_type='confidential',
- authorization_grant_type='password',
- organization=organization,
- )
- for user, can_access, cannot_access in zip(user_list, can_access_list, cannot_access_list):
- response = post(reverse('api:o_auth2_application_token_list', kwargs={'pk': app.pk}), {'scope': 'read'}, user, expect=201)
- token = AccessToken.objects.get(token=response.data['token'])
- access = OAuth2TokenAccess(system_auditor)
- assert access.can_read(token) is can_access
- assert access.can_change(token, {}) is cannot_access
- assert access.can_delete(token) is cannot_access
-
- def test_user_auditor_can_change(self, post, org_member, org_admin, system_auditor, organization):
- app = Application.objects.create(
- name='test app for {}'.format(org_admin.username),
- user=org_admin,
- client_type='confidential',
- authorization_grant_type='password',
- organization=organization,
- )
- response = post(reverse('api:o_auth2_application_token_list', kwargs={'pk': app.pk}), {'scope': 'read'}, org_member, expect=201)
- token = AccessToken.objects.get(token=response.data['token'])
- access = OAuth2TokenAccess(system_auditor)
- assert access.can_read(token) is True
- assert access.can_change(token, {}) is False
- assert access.can_delete(token) is False
- dual_user = system_auditor
- organization.admin_role.members.add(dual_user)
- access = OAuth2TokenAccess(dual_user)
- assert access.can_read(token) is True
- assert access.can_change(token, {}) is True
- assert access.can_delete(token) is True
-
- def test_can_read_change_delete_personal_token_org_member(self, post, admin, org_admin, org_member, alice):
- # Tests who can read a token created by an org-member
- user_list = [admin, org_admin, org_member, alice]
- can_access_list = [True, False, True, False]
- response = post(reverse('api:user_personal_token_list', kwargs={'pk': org_member.pk}), {'scope': 'read'}, org_member, expect=201)
- token = AccessToken.objects.get(token=response.data['token'])
- for user, can_access in zip(user_list, can_access_list):
- access = OAuth2TokenAccess(user)
- assert access.can_read(token) is can_access
- assert access.can_change(token, {}) is can_access
- assert access.can_delete(token) is can_access
-
- def test_can_read_personal_token_creator(self, post, admin, org_admin, org_member, alice):
- # Tests the token's creator can read their tokens
- user_list = [admin, org_admin, org_member, alice]
- can_access_list = [True, True, True, True]
-
- for user, can_access in zip(user_list, can_access_list):
- response = post(reverse('api:user_personal_token_list', kwargs={'pk': user.pk}), {'scope': 'read', 'application': None}, user, expect=201)
- token = AccessToken.objects.get(token=response.data['token'])
- access = OAuth2TokenAccess(user)
- assert access.can_read(token) is can_access
- assert access.can_change(token, {}) is can_access
- assert access.can_delete(token) is can_access
-
- @pytest.mark.parametrize(
- "user_for_access, can_access_list",
- [
- (0, [True, True]),
- (1, [True, True]),
- (2, [True, True]),
- (3, [False, False]),
- ],
- )
- def test_can_create(self, post, admin, org_admin, org_member, alice, user_for_access, can_access_list, organization):
- user_list = [admin, org_admin, org_member, alice]
- for user, can_access in zip(user_list, can_access_list):
- app = Application.objects.create(
- name='test app for {}'.format(user.username),
- user=user,
- client_type='confidential',
- authorization_grant_type='password',
- organization=organization,
- )
- post(
- reverse('api:o_auth2_application_token_list', kwargs={'pk': app.pk}),
- {'scope': 'read'},
- user_list[user_for_access],
- expect=201 if can_access else 403,
- )
diff --git a/awx/main/tests/functional/test_routing.py b/awx/main/tests/functional/test_routing.py
new file mode 100644
index 000000000000..7cb5e9b5874d
--- /dev/null
+++ b/awx/main/tests/functional/test_routing.py
@@ -0,0 +1,90 @@
+import pytest
+
+from django.contrib.auth.models import AnonymousUser
+
+from channels.routing import ProtocolTypeRouter
+from channels.testing.websocket import WebsocketCommunicator
+
+
+from awx.main.consumers import WebsocketSecretAuthHelper
+
+
+@pytest.fixture
+def application():
+ # code in routing hits the db on import because .. settings cache
+ from awx.main.routing import application_func
+
+ yield application_func(ProtocolTypeRouter)
+
+
+@pytest.fixture
+def websocket_server_generator(application):
+ def fn(endpoint):
+ return WebsocketCommunicator(application, endpoint)
+
+ return fn
+
+
+@pytest.mark.asyncio
+@pytest.mark.django_db
+class TestWebsocketRelay:
+ @pytest.fixture
+ def websocket_relay_secret_generator(self, settings):
+ def fn(secret, set_broadcast_websocket_secret=False):
+ secret_backup = settings.BROADCAST_WEBSOCKET_SECRET
+ settings.BROADCAST_WEBSOCKET_SECRET = 'foobar'
+ res = ('secret'.encode('utf-8'), WebsocketSecretAuthHelper.construct_secret().encode('utf-8'))
+ if set_broadcast_websocket_secret is False:
+ settings.BROADCAST_WEBSOCKET_SECRET = secret_backup
+ return res
+
+ return fn
+
+ @pytest.fixture
+ def websocket_relay_secret(self, settings, websocket_relay_secret_generator):
+ return websocket_relay_secret_generator('foobar', set_broadcast_websocket_secret=True)
+
+ async def test_authorized(self, websocket_server_generator, websocket_relay_secret):
+ server = websocket_server_generator('/websocket/relay/')
+
+ server.scope['headers'] = (websocket_relay_secret,)
+ connected, _ = await server.connect()
+ assert connected is True
+
+ async def test_not_authorized(self, websocket_server_generator):
+ server = websocket_server_generator('/websocket/relay/')
+ connected, _ = await server.connect()
+ assert connected is False, "Connection to the relay websocket without auth. We expected the client to be denied."
+
+ async def test_wrong_secret(self, websocket_server_generator, websocket_relay_secret_generator):
+ server = websocket_server_generator('/websocket/relay/')
+
+ server.scope['headers'] = (websocket_relay_secret_generator('foobar', set_broadcast_websocket_secret=False),)
+ connected, _ = await server.connect()
+ assert connected is False
+
+
+@pytest.mark.asyncio
+@pytest.mark.django_db
+class TestWebsocketEventConsumer:
+ async def test_unauthorized_anonymous(self, websocket_server_generator):
+ server = websocket_server_generator('/websocket/')
+
+ server.scope['user'] = AnonymousUser()
+ connected, _ = await server.connect()
+ assert connected is False, "Anonymous user should NOT be allowed to login."
+
+ @pytest.mark.xfail(reason="Ran out of coding time.")
+ async def test_authorized(self, websocket_server_generator, application, admin):
+ server = websocket_server_generator('/websocket/')
+
+ """
+ I ran out of time. Here is what I was thinking ...
+ Inject a valid session into the cookies in the header
+
+ server.scope['headers'] = (
+ (b'cookie', ...),
+ )
+ """
+ connected, _ = await server.connect()
+ assert connected is True, "User should be allowed in via cookies auth via a session key in the cookies"
diff --git a/awx/main/tests/functional/test_tasks.py b/awx/main/tests/functional/test_tasks.py
deleted file mode 100644
index c4d0dac4e362..000000000000
--- a/awx/main/tests/functional/test_tasks.py
+++ /dev/null
@@ -1,89 +0,0 @@
-import pytest
-from unittest import mock
-import os
-import tempfile
-import shutil
-
-from awx.main.tasks.jobs import RunJob
-from awx.main.tasks.system import execution_node_health_check, _cleanup_images_and_files, handle_work_error
-from awx.main.models import Instance, Job, InventoryUpdate, ProjectUpdate
-
-
-@pytest.fixture
-def scm_revision_file(tmpdir_factory):
- # Returns path to temporary testing revision file
- revision_file = tmpdir_factory.mktemp('revisions').join('revision.txt')
- with open(str(revision_file), 'w') as f:
- f.write('1234567890123456789012345678901234567890')
- return os.path.join(revision_file.dirname, 'revision.txt')
-
-
-@pytest.mark.django_db
-@pytest.mark.parametrize('node_type', ('control. hybrid'))
-def test_no_worker_info_on_AWX_nodes(node_type):
- hostname = 'us-south-3-compute.invalid'
- Instance.objects.create(hostname=hostname, node_type=node_type)
- assert execution_node_health_check(hostname) is None
-
-
-@pytest.fixture
-def mock_job_folder(request):
- pdd_path = tempfile.mkdtemp(prefix='awx_123_')
-
- def test_folder_cleanup():
- if os.path.exists(pdd_path):
- shutil.rmtree(pdd_path)
-
- request.addfinalizer(test_folder_cleanup)
-
- return pdd_path
-
-
-@pytest.mark.django_db
-def test_folder_cleanup_stale_file(mock_job_folder, mock_me):
- _cleanup_images_and_files()
- assert os.path.exists(mock_job_folder) # grace period should protect folder from deletion
-
- _cleanup_images_and_files(grace_period=0)
- assert not os.path.exists(mock_job_folder) # should be deleted
-
-
-@pytest.mark.django_db
-def test_folder_cleanup_running_job(mock_job_folder, mock_me):
- me_inst = Instance.objects.create(hostname='local_node', uuid='00000000-0000-0000-0000-000000000000')
- with mock.patch.object(Instance.objects, 'me', return_value=me_inst):
- job = Job.objects.create(id=123, controller_node=me_inst.hostname, status='running')
- _cleanup_images_and_files(grace_period=0)
- assert os.path.exists(mock_job_folder) # running job should prevent folder from getting deleted
-
- job.status = 'failed'
- job.save(update_fields=['status'])
- _cleanup_images_and_files(grace_period=0)
- assert not os.path.exists(mock_job_folder) # job is finished and no grace period, should delete
-
-
-@pytest.mark.django_db
-def test_does_not_run_reaped_job(mocker, mock_me):
- job = Job.objects.create(status='failed', job_explanation='This job has been reaped.')
- mock_run = mocker.patch('awx.main.tasks.jobs.ansible_runner.interface.run')
- try:
- RunJob().run(job.id)
- except Exception:
- pass
- job.refresh_from_db()
- assert job.status == 'failed'
- mock_run.assert_not_called()
-
-
-@pytest.mark.django_db
-def test_handle_work_error_nested(project, inventory_source):
- pu = ProjectUpdate.objects.create(status='failed', project=project, celery_task_id='1234')
- iu = InventoryUpdate.objects.create(status='pending', inventory_source=inventory_source, source='scm')
- job = Job.objects.create(status='pending')
- iu.dependent_jobs.add(pu)
- job.dependent_jobs.add(pu, iu)
- handle_work_error({'type': 'project_update', 'id': pu.id})
- iu.refresh_from_db()
- job.refresh_from_db()
- assert iu.job_explanation == f'Previous Task Failed: {{"job_type": "project_update", "job_name": "", "job_id": "{pu.id}"}}'
- assert job.job_explanation == f'Previous Task Failed: {{"job_type": "inventory_update", "job_name": "", "job_id": "{iu.id}"}}'
diff --git a/awx/main/tests/functional/test_teams.py b/awx/main/tests/functional/test_teams.py
deleted file mode 100644
index eda57579cebd..000000000000
--- a/awx/main/tests/functional/test_teams.py
+++ /dev/null
@@ -1,14 +0,0 @@
-import pytest
-
-
-@pytest.mark.django_db()
-def test_admin_not_member(team):
- """Test to ensure we don't add admin_role as a parent to team.member_role, as
- this creates a cycle with organization administration, which we've decided
- to remove support for
-
- (2016-06-16) I think this might have been resolved. I'm asserting
- this to be true in the mean time.
- """
-
- assert team.admin_role.is_ancestor_of(team.member_role) is True
diff --git a/awx/main/tests/live/pytest.ini b/awx/main/tests/live/pytest.ini
new file mode 100644
index 000000000000..cc1e9c5a91de
--- /dev/null
+++ b/awx/main/tests/live/pytest.ini
@@ -0,0 +1,3 @@
+# This file is needed to undo the pytest settings from the project root
+[pytest]
+addopts = -p no:django -p awx.main.tests.live.pytest_django_config
diff --git a/awx/main/tests/live/pytest_django_config.py b/awx/main/tests/live/pytest_django_config.py
new file mode 100644
index 000000000000..8c4eac2474b5
--- /dev/null
+++ b/awx/main/tests/live/pytest_django_config.py
@@ -0,0 +1,12 @@
+import django
+
+from awx import prepare_env
+
+
+def pytest_load_initial_conftests(args):
+ """Replacement for same-named method in pytest_django plugin
+
+ Instead of setting up a test database, this just sets up Django normally
+ this will give access to the postgres database as-is, for better and worse"""
+ prepare_env()
+ django.setup()
diff --git a/awx/main/tests/live/tests/api/test_uniqueness.py b/awx/main/tests/live/tests/api/test_uniqueness.py
new file mode 100644
index 000000000000..e32b9416bfef
--- /dev/null
+++ b/awx/main/tests/live/tests/api/test_uniqueness.py
@@ -0,0 +1,77 @@
+import multiprocessing
+import json
+
+import pytest
+
+import requests
+from requests.auth import HTTPBasicAuth
+
+from django.db import connection
+
+from awx.main.models import User, JobTemplate
+
+
+def create_in_subprocess(project_id, ready_event, continue_event, admin_auth):
+ connection.connect()
+
+ print('setting ready event')
+ ready_event.set()
+ print('waiting for continue event')
+ continue_event.wait()
+
+ if JobTemplate.objects.filter(name='test_jt_duplicate_name').exists():
+ for jt in JobTemplate.objects.filter(name='test_jt_duplicate_name'):
+ jt.delete()
+ assert JobTemplate.objects.filter(name='test_jt_duplicate_name').count() == 0
+
+ jt_data = {'name': 'test_jt_duplicate_name', 'project': project_id, 'playbook': 'hello_world.yml', 'ask_inventory_on_launch': True}
+ response = requests.post('http://localhost:8013/api/v2/job_templates/', json=jt_data, auth=admin_auth)
+ # should either have a conflict or create
+ assert response.status_code in (400, 201)
+ print(f'Subprocess got {response.status_code}')
+ if response.status_code == 400:
+ print(json.dumps(response.json(), indent=2))
+ return response.status_code
+
+
+@pytest.fixture
+def admin_for_test():
+ user, created = User.objects.get_or_create(username='admin_for_test', defaults={'is_superuser': True})
+ if created:
+ user.set_password('for_test_123!')
+ user.save()
+ print(f'Created user {user.username}')
+ return user
+
+
+@pytest.fixture
+def admin_auth(admin_for_test):
+ return HTTPBasicAuth(admin_for_test.username, 'for_test_123!')
+
+
+def test_jt_duplicate_name(admin_auth, demo_proj):
+ N_processes = 5
+ ready_events = [multiprocessing.Event() for _ in range(N_processes)]
+ continue_event = multiprocessing.Event()
+
+ processes = []
+ for i in range(N_processes):
+ p = multiprocessing.Process(target=create_in_subprocess, args=(demo_proj.id, ready_events[i], continue_event, admin_auth))
+ processes.append(p)
+ p.start()
+
+ # Assure both processes are connected and have loaded their host list
+ for e in ready_events:
+ print('waiting on subprocess ready event')
+ e.wait()
+
+ # Begin the bulk_update queries
+ print('setting the continue event for the workers')
+ continue_event.set()
+
+ # if a Deadloack happens it will probably be surfaced by result here
+ print('waiting on the workers to finish the creation')
+ for p in processes:
+ p.join()
+
+ assert JobTemplate.objects.filter(name='test_jt_duplicate_name').count() == 1
diff --git a/awx/main/tests/live/tests/conftest.py b/awx/main/tests/live/tests/conftest.py
new file mode 100644
index 000000000000..4667dc09f104
--- /dev/null
+++ b/awx/main/tests/live/tests/conftest.py
@@ -0,0 +1,244 @@
+import subprocess
+import time
+import os
+import shutil
+import tempfile
+import logging
+
+import pytest
+
+from django.conf import settings
+from django.core.cache import cache
+
+from awx.api.versioning import reverse
+
+# These tests are invoked from the awx/main/tests/live/ subfolder
+# so any fixtures from higher-up conftest files must be explicitly included
+from awx.main.tests.functional.conftest import * # noqa
+from awx.main.tests.conftest import load_all_credentials # noqa: F401; pylint: disable=unused-import
+from awx.main.tests import data
+
+from awx.main.models import Project, JobTemplate, Organization, Inventory, WorkflowJob, UnifiedJob
+from awx.main.tasks.system import clear_setting_cache
+
+logger = logging.getLogger(__name__)
+
+
+PROJ_DATA = os.path.join(os.path.dirname(data.__file__), 'projects')
+
+
+def _copy_folders(source_path, dest_path, clear=False):
+ "folder-by-folder, copy dirs in the source root dir to the destination root dir"
+ for dirname in os.listdir(source_path):
+ source_dir = os.path.join(source_path, dirname)
+ expected_dir = os.path.join(dest_path, dirname)
+ if clear and os.path.exists(expected_dir):
+ shutil.rmtree(expected_dir)
+ if (not os.path.isdir(source_dir)) or os.path.exists(expected_dir):
+ continue
+ shutil.copytree(source_dir, expected_dir)
+
+
+GIT_COMMANDS = (
+ 'git config --global init.defaultBranch devel; '
+ 'git init; '
+ 'git config user.email jenkins@ansible.com; '
+ 'git config user.name DoneByTest; '
+ 'git add .; '
+ 'git commit -m "initial commit"'
+)
+
+
+@pytest.fixture(scope='session')
+def live_tmp_folder():
+ path = os.path.join(tempfile.gettempdir(), 'live_tests')
+ if os.path.exists(path):
+ shutil.rmtree(path)
+ os.mkdir(path)
+ _copy_folders(PROJ_DATA, path)
+ for dirname in os.listdir(path):
+ source_dir = os.path.join(path, dirname)
+ subprocess.run(GIT_COMMANDS, cwd=source_dir, shell=True)
+ # force invalidation of key before checking it in case it is stale
+ cache.delete_many(['AWX_ISOLATION_SHOW_PATHS'])
+ settings._awx_conf_memoizedcache.clear()
+ if path not in settings.AWX_ISOLATION_SHOW_PATHS:
+ logger.info(f'Modifying settings.AWX_ISOLATION_SHOW_PATHS for live test: {settings.AWX_ISOLATION_SHOW_PATHS + [path]}')
+ settings.AWX_ISOLATION_SHOW_PATHS = settings.AWX_ISOLATION_SHOW_PATHS + [path]
+ cache.delete_many(['AWX_ISOLATION_SHOW_PATHS'])
+ settings._awx_conf_memoizedcache.clear()
+ # cache is cleared in test environment, but need to clear in test environment
+ clear_setting_cache.delay(['AWX_ISOLATION_SHOW_PATHS'])
+ time.sleep(5.0) # for _awx_conf_memoizedcache to expire on all workers
+ else:
+ logger.info(f'Believed that {path} is already in settings.AWX_ISOLATION_SHOW_PATHS: {settings.AWX_ISOLATION_SHOW_PATHS}')
+ return path
+
+
+def wait_to_leave_status(job, status, timeout=30, sleep_time=0.1):
+ """Wait until the job does NOT have the specified status with some timeout
+
+ the default timeout is based on the task manager running a 20 second
+ schedule, and the API does not guarentee working jobs faster than this
+ """
+ start = time.time()
+ while time.time() - start < timeout:
+ job.refresh_from_db()
+ if job.status != status:
+ return
+ time.sleep(sleep_time)
+ raise RuntimeError(f'Job failed to exit {status} in {timeout} seconds. job_explanation={job.job_explanation} tb={job.result_traceback}')
+
+
+def wait_for_events(uj, timeout=2):
+ start = time.time()
+ while uj.event_processing_finished is False:
+ time.sleep(0.2)
+ uj.refresh_from_db()
+ if time.time() - start > timeout:
+ break
+
+
+def unified_job_stdout(uj):
+ if type(uj) is UnifiedJob:
+ uj = uj.get_real_instance()
+ if isinstance(uj, WorkflowJob):
+ outputs = []
+ for node in uj.workflow_job_nodes.all().select_related('job').order_by('id'):
+ if node.job is None:
+ continue
+ outputs.append(
+ 'workflow node {node_id} job {job_id} output:\n{output}'.format(
+ node_id=node.id,
+ job_id=node.job.id,
+ output=unified_job_stdout(node.job),
+ )
+ )
+ return '\n'.join(outputs)
+ wait_for_events(uj)
+ return '\n'.join([event.stdout for event in uj.get_event_queryset().order_by('created')])
+
+
+def wait_for_job(job, final_status='successful', running_timeout=800):
+ wait_to_leave_status(job, 'pending')
+ wait_to_leave_status(job, 'waiting')
+ wait_to_leave_status(job, 'running', timeout=running_timeout)
+
+ assert job.status == final_status, f'Job was not successful id={job.id} status={job.status} tb={job.result_traceback} output=\n{unified_job_stdout(job)}'
+
+
+@pytest.fixture(scope='session')
+def default_org():
+ org = Organization.objects.filter(name='Default').first()
+ if org is None:
+ raise Exception('Tests expect Default org to already be created and it is not')
+ return org
+
+
+@pytest.fixture(scope='session')
+def demo_inv(default_org):
+ inventory, _ = Inventory.objects.get_or_create(name='Demo Inventory', defaults={'organization': default_org})
+ return inventory
+
+
+@pytest.fixture(scope='session')
+def demo_proj(default_org):
+ proj, _ = Project.objects.get_or_create(name='Demo Project', defaults={'organization': default_org})
+ return proj
+
+
+@pytest.fixture
+def podman_image_generator():
+ """
+ Generate a tagless podman image from awx base EE
+ """
+
+ def fn():
+ dockerfile = """
+ FROM quay.io/ansible/awx-ee:latest
+ RUN echo "Hello, Podman!" > /tmp/hello.txt
+ """
+ cmd = ['podman', 'build', '-f', '-'] # Create an image without a tag
+ subprocess.run(cmd, capture_output=True, input=dockerfile, text=True, check=True)
+
+ return fn
+
+
+@pytest.fixture
+def project_factory(post, default_org, admin):
+ def _rf(scm_url=None, local_path=None, **extra_kwargs):
+ proj_kwargs = {}
+ if local_path:
+ # manual path
+ project_name = f'Manual roject {local_path}'
+ proj_kwargs['scm_type'] = ''
+ proj_kwargs['local_path'] = local_path
+ elif scm_url:
+ project_name = f'Project {scm_url}'
+ proj_kwargs['scm_type'] = 'git'
+ proj_kwargs['scm_url'] = scm_url
+ else:
+ raise RuntimeError('Need to provide scm_url or local_path')
+
+ if extra_kwargs:
+ proj_kwargs.update(extra_kwargs)
+
+ proj_kwargs['name'] = project_name
+ proj_kwargs['organization'] = default_org.id
+
+ old_proj = Project.objects.filter(name=project_name).first()
+ if old_proj:
+ logger.info(f'Deleting existing project {project_name}')
+ old_proj.delete()
+
+ result = post(
+ reverse('api:project_list'),
+ proj_kwargs,
+ admin,
+ expect=201,
+ )
+ proj = Project.objects.get(id=result.data['id'])
+ return proj
+
+ return _rf
+
+
+@pytest.fixture
+def run_job_from_playbook(demo_inv, post, admin, project_factory):
+ def _rf(test_name, playbook, local_path=None, scm_url=None, jt_params=None, proj=None, wait=True):
+ jt_name = f'{test_name} JT: {playbook}'
+
+ if not proj:
+ proj = project_factory(scm_url=scm_url, local_path=local_path)
+
+ old_jt = JobTemplate.objects.filter(name=jt_name).first()
+ if old_jt:
+ logger.info(f'Deleting existing JT {jt_name}')
+ old_jt.delete()
+
+ if proj.current_job:
+ wait_for_job(proj.current_job)
+
+ assert proj.get_project_path()
+ assert playbook in proj.playbooks
+
+ jt_data = {'name': jt_name, 'project': proj.id, 'playbook': playbook, 'inventory': demo_inv.id}
+ if jt_params:
+ jt_data.update(jt_params)
+
+ result = post(
+ reverse('api:job_template_list'),
+ jt_data,
+ admin,
+ expect=201,
+ )
+ jt = JobTemplate.objects.get(id=result.data['id'])
+ job = jt.create_unified_job()
+ job.signal_start()
+
+ if wait:
+ wait_for_job(job)
+ assert job.status == 'successful'
+ return {'job': job, 'job_template': jt, 'project': proj}
+
+ return _rf
diff --git a/awx/main/tests/live/tests/dispatcherd/test_connection_recovery.py b/awx/main/tests/live/tests/dispatcherd/test_connection_recovery.py
new file mode 100644
index 000000000000..183d9ca1d5ac
--- /dev/null
+++ b/awx/main/tests/live/tests/dispatcherd/test_connection_recovery.py
@@ -0,0 +1,74 @@
+import time
+
+from dispatcherd.config import settings
+from dispatcherd.factories import get_control_from_settings
+from dispatcherd.utils import serialize_task
+
+from awx.main.models import JobTemplate
+
+from awx.main.tests.data.sleep_task import sleep_break_connection, advisory_lock_exception
+from awx.main.tests.live.tests.conftest import wait_for_job
+
+
+def poll_for_task_finish(task_name):
+ running_tasks = [1]
+ start = time.monotonic()
+ ctl = get_control_from_settings()
+ while running_tasks:
+ responses = ctl.control_with_reply('running')
+ assert len(responses) == 1
+ response = responses[0]
+ response.pop('node_id')
+ running_tasks = [task_data for task_data in response.values() if task_data['task'] == task_name]
+ if time.monotonic() - start > 5.0:
+ assert False, f'Never finished working through tasks: {running_tasks}'
+
+
+def check_jobs_work():
+ jt = JobTemplate.objects.get(name='Demo Job Template')
+ job = jt.create_unified_job()
+ job.signal_start()
+ wait_for_job(job)
+
+
+def test_advisory_lock_error_clears():
+ """Run a task that has an exception while holding advisory_lock
+
+ This is regression testing for a bug in its exception handling
+ expected to be fixed by
+ https://github.com/ansible/django-ansible-base/pull/713
+
+ This is an "easier" test case than the next,
+ because it passes just by fixing the DAB case,
+ and passing this does not generally guarentee that
+ workers will not be left with a connection in a bad state.
+ """
+ min_workers = settings.service['pool_kwargs']['min_workers']
+
+ for i in range(min_workers):
+ advisory_lock_exception.delay()
+
+ task_name = serialize_task(advisory_lock_exception)
+ poll_for_task_finish(task_name)
+
+ # Jobs should still work even after the breaking task has ran
+ check_jobs_work()
+
+
+def test_can_recover_connection():
+ """Run a task that intentionally times out the worker connection
+
+ If no connection fixing is implemented outside of that task scope,
+ then subsequent tasks will all error, thus checking that jobs run,
+ after running the sleep_break_connection task.
+ """
+ min_workers = settings.service['pool_kwargs']['min_workers']
+
+ for i in range(min_workers):
+ sleep_break_connection.delay()
+
+ task_name = serialize_task(sleep_break_connection)
+ poll_for_task_finish(task_name)
+
+ # Jobs should still work even after the breaking task has ran
+ check_jobs_work()
diff --git a/awx/main/tests/live/tests/projects/conftest.py b/awx/main/tests/live/tests/projects/conftest.py
new file mode 100644
index 000000000000..39c8b76fbfad
--- /dev/null
+++ b/awx/main/tests/live/tests/projects/conftest.py
@@ -0,0 +1,14 @@
+import pytest
+import os
+
+from django.conf import settings
+
+from awx.main.tests.live.tests.conftest import _copy_folders, PROJ_DATA
+
+
+@pytest.fixture(scope='session')
+def copy_project_folders():
+ proj_root = settings.PROJECTS_ROOT
+ if not os.path.exists(proj_root):
+ os.mkdir(proj_root)
+ _copy_folders(PROJ_DATA, proj_root, clear=True)
diff --git a/awx/main/tests/live/tests/projects/test_file_projects.py b/awx/main/tests/live/tests/projects/test_file_projects.py
new file mode 100644
index 000000000000..616578b13699
--- /dev/null
+++ b/awx/main/tests/live/tests/projects/test_file_projects.py
@@ -0,0 +1,25 @@
+import os
+import subprocess
+
+import pytest
+
+from awx.main.tests.live.tests.conftest import wait_for_job
+
+
+def test_git_file_project(live_tmp_folder, run_job_from_playbook):
+ run_job_from_playbook('test_git_file_project', 'debug.yml', scm_url=f'file://{live_tmp_folder}/debug')
+
+
+@pytest.mark.parametrize('allow_override', [True, False])
+def test_amend_commit(live_tmp_folder, project_factory, allow_override):
+ proj = project_factory(scm_url=f'file://{live_tmp_folder}/debug', allow_override=allow_override)
+ assert proj.current_job
+ wait_for_job(proj.current_job)
+ assert proj.allow_override is allow_override
+
+ source_dir = os.path.join(live_tmp_folder, 'debug')
+ subprocess.run('git commit --amend --no-edit', cwd=source_dir, shell=True)
+
+ update = proj.update()
+ update.signal_start()
+ wait_for_job(update)
diff --git a/awx/main/tests/live/tests/projects/test_manual_project.py b/awx/main/tests/live/tests/projects/test_manual_project.py
new file mode 100644
index 000000000000..11aeb76cf8b3
--- /dev/null
+++ b/awx/main/tests/live/tests/projects/test_manual_project.py
@@ -0,0 +1,2 @@
+def test_manual_project(copy_project_folders, run_job_from_playbook):
+ run_job_from_playbook('test_manual_project', 'debug.yml', local_path='debug')
diff --git a/awx/main/tests/live/tests/projects/test_requirements.py b/awx/main/tests/live/tests/projects/test_requirements.py
new file mode 100644
index 000000000000..c0d392996907
--- /dev/null
+++ b/awx/main/tests/live/tests/projects/test_requirements.py
@@ -0,0 +1,64 @@
+import os
+import time
+
+import pytest
+
+from django.conf import settings
+
+from awx.main.tests.live.tests.conftest import wait_for_job, wait_for_events
+
+from awx.main.models import Project, SystemJobTemplate, Job
+
+
+@pytest.fixture(scope='session')
+def project_with_requirements(default_org):
+ project, _ = Project.objects.get_or_create(
+ name='project-with-requirements',
+ scm_url='https://github.com/ansible/test-playbooks.git',
+ scm_branch="with_requirements",
+ scm_type='git',
+ organization=default_org,
+ )
+ start = time.time()
+ while time.time() - start < 3.0:
+ if project.current_job or project.last_job or project.last_job_run:
+ break
+ assert project.current_job or project.last_job or project.last_job_run, f'Project never updated id={project.id}'
+ update = project.current_job or project.last_job
+ if update:
+ wait_for_job(update)
+ return project
+
+
+def project_cache_is_populated(project):
+ proj_cache = os.path.join(project.get_cache_path(), project.cache_id)
+ return os.path.exists(proj_cache)
+
+
+def test_cache_is_populated_after_cleanup_job(project_with_requirements):
+ assert project_with_requirements.cache_id is not None # already updated, should be something
+ cache_path = os.path.join(settings.PROJECTS_ROOT, '.__awx_cache')
+ assert os.path.exists(cache_path)
+
+ assert project_cache_is_populated(project_with_requirements)
+
+ cleanup_sjt = SystemJobTemplate.objects.get(name='Cleanup Job Details')
+ cleanup_job = cleanup_sjt.create_unified_job(extra_vars={'days': 0})
+ cleanup_job.signal_start()
+ wait_for_job(cleanup_job)
+
+ project_with_requirements.refresh_from_db()
+ assert project_with_requirements.cache_id is not None
+ update = project_with_requirements.update()
+ wait_for_job(update)
+
+ # Now, we still have a populated cache
+ assert project_cache_is_populated(project_with_requirements)
+
+
+def test_git_file_collection_requirement(live_tmp_folder, copy_project_folders, run_job_from_playbook):
+ # this behaves differently, as use_requirements.yml references only the folder, does not include the github name
+ run_job_from_playbook('test_git_file_collection_requirement', 'use_requirement.yml', scm_url=f'file://{live_tmp_folder}/with_requirements')
+ job = Job.objects.filter(name__icontains='test_git_file_collection_requirement').order_by('-created').first()
+ wait_for_events(job)
+ assert '1234567890' in job.job_events.filter(task='debug variable', event='runner_on_ok').first().stdout
diff --git a/awx/main/tests/live/tests/test_ansible_facts.py b/awx/main/tests/live/tests/test_ansible_facts.py
new file mode 100644
index 000000000000..f6db48345e79
--- /dev/null
+++ b/awx/main/tests/live/tests/test_ansible_facts.py
@@ -0,0 +1,64 @@
+import pytest
+
+from awx.main.tests.live.tests.conftest import wait_for_events
+
+from awx.main.models import Job, Inventory
+
+
+def assert_facts_populated(name):
+ job = Job.objects.filter(name__icontains=name).order_by('-created').first()
+ assert job is not None
+ wait_for_events(job)
+
+ inventory = job.inventory
+ assert inventory.hosts.count() > 0 # sanity
+ for host in inventory.hosts.all():
+ assert host.ansible_facts
+
+
+@pytest.fixture
+def general_facts_test(live_tmp_folder, run_job_from_playbook):
+ def _rf(slug, jt_params):
+ jt_params['use_fact_cache'] = True
+ standard_kwargs = dict(scm_url=f'file://{live_tmp_folder}/facts', jt_params=jt_params)
+
+ # GATHER FACTS
+ name = f'test_gather_ansible_facts_{slug}'
+ run_job_from_playbook(name, 'gather.yml', **standard_kwargs)
+ assert_facts_populated(name)
+
+ # KEEP FACTS
+ name = f'test_clear_ansible_facts_{slug}'
+ run_job_from_playbook(name, 'no_op.yml', **standard_kwargs)
+ assert_facts_populated(name)
+
+ # CLEAR FACTS
+ name = f'test_clear_ansible_facts_{slug}'
+ run_job_from_playbook(name, 'clear.yml', **standard_kwargs)
+ job = Job.objects.filter(name__icontains=name).order_by('-created').first()
+
+ assert job is not None
+ wait_for_events(job)
+ inventory = job.inventory
+ assert inventory.hosts.count() > 0 # sanity
+ for host in inventory.hosts.all():
+ assert not host.ansible_facts
+
+ return _rf
+
+
+def test_basic_ansible_facts(general_facts_test):
+ general_facts_test('basic', {})
+
+
+@pytest.fixture
+def sliced_inventory():
+ inv, _ = Inventory.objects.get_or_create(name='inventory-to-slice')
+ if not inv.hosts.exists():
+ for i in range(10):
+ inv.hosts.create(name=f'sliced_host_{i}')
+ return inv
+
+
+def test_slicing_with_facts(general_facts_test, sliced_inventory):
+ general_facts_test('sliced', {'job_slice_count': 3, 'inventory': sliced_inventory.id})
diff --git a/awx/main/tests/live/tests/test_cleanup_task.py b/awx/main/tests/live/tests/test_cleanup_task.py
new file mode 100644
index 000000000000..e9af90b96196
--- /dev/null
+++ b/awx/main/tests/live/tests/test_cleanup_task.py
@@ -0,0 +1,82 @@
+import os
+import json
+import pytest
+import tempfile
+import subprocess
+
+from unittest import mock
+
+from awx.main.tasks.receptor import _convert_args_to_cli, run_until_complete
+from awx.main.tasks.system import CleanupImagesAndFiles
+from awx.main.models import Instance, JobTemplate
+
+
+def get_podman_images():
+ cmd = ['podman', 'images', '--format', 'json']
+ return json.loads((subprocess.run(cmd, capture_output=True, text=True, check=True)).stdout)
+
+
+def test_folder_cleanup_multiple_running_jobs_execution_node(request):
+ demo_jt = JobTemplate.objects.get(name='Demo Job Template')
+
+ jobs = [demo_jt.create_unified_job(_eager_fields={'status': 'running'}) for i in range(3)]
+
+ def delete_jobs():
+ for job in jobs:
+ job.delete()
+
+ request.addfinalizer(delete_jobs)
+
+ job_dirs = []
+ job_patterns = []
+ for job in jobs:
+ job_pattern = f'awx_{job.id}_1234'
+ job_dir = os.path.join(tempfile.gettempdir(), job_pattern)
+ job_patterns.append(job_pattern)
+ job_dirs.append(job_dir)
+ os.mkdir(job_dir)
+
+ inst = Instance.objects.me()
+ runner_cleanup_kwargs = inst.get_cleanup_task_kwargs(exclude_strings=job_patterns, grace_period=0)
+
+ # We can not call worker_cleanup directly because execution and control nodes are not fungible
+ args = _convert_args_to_cli(runner_cleanup_kwargs)
+ remote_command = ' '.join(args)
+
+ subprocess.call('ansible-runner worker ' + remote_command, shell=True)
+ print('ansible-runner worker ' + remote_command)
+
+ assert [os.path.exists(job_dir) for job_dir in job_dirs] == [True for i in range(3)]
+
+
+@pytest.mark.parametrize(
+ 'worktype',
+ ('remote', 'local'),
+)
+def test_tagless_image(podman_image_generator, worktype: str):
+ """
+ Ensure podman images on Control and Hybrid nodes are deleted during cleanup.
+ """
+ podman_image_generator()
+
+ dangling_image = next((image for image in get_podman_images() if image.get('Dangling', False)), None)
+ assert dangling_image
+
+ instance_me = Instance.objects.me()
+
+ match worktype:
+ case 'local':
+ CleanupImagesAndFiles.run_local(instance_me, image_prune=True)
+ case 'remote':
+ with (
+ mock.patch(
+ 'awx.main.tasks.receptor.run_until_complete', lambda *args, **kwargs: run_until_complete(*args, worktype='local', ttl=None, **kwargs)
+ ),
+ mock.patch('awx.main.tasks.system.CleanupImagesAndFiles.get_execution_instances', lambda: [Instance.objects.me()]),
+ ):
+ CleanupImagesAndFiles.run_remote(instance_me, image_prune=True)
+ case _:
+ raise ValueError(f'worktype "{worktype}" not supported.')
+
+ for image in get_podman_images():
+ assert image['Id'] != dangling_image['Id']
diff --git a/awx/main/tests/live/tests/test_demo_data.py b/awx/main/tests/live/tests/test_demo_data.py
new file mode 100644
index 000000000000..fa9ee5eb978c
--- /dev/null
+++ b/awx/main/tests/live/tests/test_demo_data.py
@@ -0,0 +1,15 @@
+from awx.api.versioning import reverse
+
+from awx.main.models import JobTemplate, Job
+
+from awx.main.tests.live.tests.conftest import wait_for_job
+
+
+def test_launch_demo_jt(post, admin):
+ jt = JobTemplate.objects.get(name='Demo Job Template')
+
+ url = reverse('api:job_template_launch', kwargs={'pk': jt.id})
+
+ r = post(url=url, data={}, user=admin, expect=201)
+ job = Job.objects.get(pk=r.data['id'])
+ wait_for_job(job)
diff --git a/awx/main/tests/live/tests/test_devel_image.py b/awx/main/tests/live/tests/test_devel_image.py
new file mode 100644
index 000000000000..a77039247cc3
--- /dev/null
+++ b/awx/main/tests/live/tests/test_devel_image.py
@@ -0,0 +1,10 @@
+import os
+
+RSYSLOG_CONFIG = '/var/lib/awx/rsyslog/rsyslog.conf'
+
+
+def test_rsyslog_config_readable():
+ with open(RSYSLOG_CONFIG, 'r') as f:
+ content = f.read()
+ assert '/var/lib/awx/rsyslog' in content
+ assert oct(os.stat(RSYSLOG_CONFIG).st_mode) == '0o100640'
diff --git a/awx/main/tests/live/tests/test_host_update_contention.py b/awx/main/tests/live/tests/test_host_update_contention.py
new file mode 100644
index 000000000000..d822fc27c11c
--- /dev/null
+++ b/awx/main/tests/live/tests/test_host_update_contention.py
@@ -0,0 +1,78 @@
+import multiprocessing
+import random
+
+from django.db import connection
+from django.utils.timezone import now
+
+from awx.main.models import Inventory, Host
+from awx.main.utils.db import bulk_update_sorted_by_id
+
+
+def worker_delete_target(ready_event, continue_event, field_name):
+ """Runs the bulk update, will be called in duplicate, in parallel"""
+ inv = Inventory.objects.get(organization__name='Default', name='test_host_update_contention')
+ host_list = list(inv.hosts.all())
+ # Using random.shuffle for non-security-critical shuffling in a test
+ random.shuffle(host_list) # NOSONAR
+ for i, host in enumerate(host_list):
+ setattr(host, field_name, f'my_var: {i}')
+
+ # ready to do the bulk_update
+ print('worker has loaded all the hosts needed')
+ ready_event.set()
+ # wait for the coordination message
+ continue_event.wait()
+
+ # NOTE: did not reproduce the bug without batch_size
+ bulk_update_sorted_by_id(Host, host_list, fields=[field_name], batch_size=100)
+ print('finished doing the bulk update in worker')
+
+
+def test_host_update_contention(default_org):
+ inv_kwargs = dict(organization=default_org, name='test_host_update_contention')
+
+ if Inventory.objects.filter(**inv_kwargs).exists():
+ inv = Inventory.objects.get(**inv_kwargs).delete()
+
+ inv = Inventory.objects.create(**inv_kwargs)
+ right_now = now()
+ hosts = [Host(inventory=inv, name=f'host-{i}', created=right_now, modified=right_now) for i in range(1000)]
+ print('bulk creating hosts')
+ Host.objects.bulk_create(hosts)
+
+ # sanity check
+ for host in hosts:
+ assert not host.variables
+
+ # Force our worker pool to make their own connection
+ connection.close()
+
+ ready_events = [multiprocessing.Event() for _ in range(2)]
+ continue_event = multiprocessing.Event()
+
+ print('spawning processes for concurrent bulk updates')
+ processes = []
+ fields = ['variables', 'ansible_facts']
+ for i in range(2):
+ p = multiprocessing.Process(target=worker_delete_target, args=(ready_events[i], continue_event, fields[i]))
+ processes.append(p)
+ p.start()
+
+ # Assure both processes are connected and have loaded their host list
+ for e in ready_events:
+ print('waiting on subprocess ready event')
+ e.wait()
+
+ # Begin the bulk_update queries
+ print('setting the continue event for the workers')
+ continue_event.set()
+
+ # if a Deadloack happens it will probably be surfaced by result here
+ print('waiting on the workers to finish the bulk_update')
+ for p in processes:
+ p.join()
+
+ print('checking workers have variables set')
+ for host in inv.hosts.all():
+ assert host.variables.startswith('my_var:')
+ assert host.ansible_facts.startswith('my_var:')
diff --git a/awx/main/tests/live/tests/test_indirect_host_counting.py b/awx/main/tests/live/tests/test_indirect_host_counting.py
new file mode 100644
index 000000000000..2843eaeba535
--- /dev/null
+++ b/awx/main/tests/live/tests/test_indirect_host_counting.py
@@ -0,0 +1,66 @@
+import yaml
+import time
+
+from awx.main.tests.live.tests.conftest import wait_for_events
+from awx.main.tasks.host_indirect import build_indirect_host_data, save_indirect_host_entries
+from awx.main.models.indirect_managed_node_audit import IndirectManagedNodeAudit
+from awx.main.models import Job
+
+
+def test_indirect_host_counting(live_tmp_folder, run_job_from_playbook):
+ run_job_from_playbook('test_indirect_host_counting', 'run_task.yml', scm_url=f'file://{live_tmp_folder}/test_host_query')
+ job = Job.objects.filter(name__icontains='test_indirect_host_counting').order_by('-created').first()
+ wait_for_events(job) # We must wait for events because system tasks iterate on job.job_events.filter(...)
+
+ # Data matches to awx/main/tests/data/projects/host_query/extensions/audit/event_query.yml
+ # this just does things in-line to be a more localized test for the immediate testing
+ module_jq_str = '{name: .name, canonical_facts: {host_name: .direct_host_name}, facts: {device_type: .device_type}}'
+ event_query = {'demo.query.example': {'query': module_jq_str}}
+
+ # Run the task logic directly with local data
+ results = build_indirect_host_data(job, event_query)
+ assert len(results) == 1
+ host_audit_entry = results[0]
+
+ canonical_facts = {'host_name': 'foo_host_default'}
+ facts = {'device_type': 'Fake Host'}
+
+ # Asserts on data that will match to the input jq string from above
+ assert host_audit_entry.canonical_facts == canonical_facts
+ assert host_audit_entry.facts == facts
+
+ # Test collection of data
+ assert 'demo.query' in job.installed_collections
+ assert 'host_query' in job.installed_collections['demo.query']
+ hq_text = job.installed_collections['demo.query']['host_query']
+ hq_data = yaml.safe_load(hq_text)
+ assert hq_data == {'demo.query.example': {'query': module_jq_str}}
+
+ assert job.ansible_version
+
+ # Poll for events finishing processing, because background task requires this
+ for _ in range(10):
+ if job.job_events.count() >= job.emitted_events:
+ break
+ time.sleep(0.2)
+ else:
+ raise RuntimeError(f'job id={job.id} never processed events')
+
+ # Task might not run due to race condition, so make it run here
+ job.refresh_from_db()
+ if job.event_queries_processed is False:
+ save_indirect_host_entries.delay(job.id, wait_for_events=False)
+
+ # event_queries_processed only assures the task has started, it might take a minor amount of time to finish
+ for _ in range(10):
+ if IndirectManagedNodeAudit.objects.filter(job=job).exists():
+ break
+ time.sleep(0.2)
+ else:
+ raise RuntimeError(f'No IndirectManagedNodeAudit records ever populated for job_id={job.id}')
+
+ assert IndirectManagedNodeAudit.objects.filter(job=job).count() == 1
+ host_audit = IndirectManagedNodeAudit.objects.filter(job=job).first()
+ assert host_audit.canonical_facts == canonical_facts
+ assert host_audit.facts == facts
+ assert host_audit.organization == job.organization
diff --git a/awx/main/tests/live/tests/test_inventory_vars.py b/awx/main/tests/live/tests/test_inventory_vars.py
new file mode 100644
index 000000000000..611a8fffd92d
--- /dev/null
+++ b/awx/main/tests/live/tests/test_inventory_vars.py
@@ -0,0 +1,223 @@
+import subprocess
+import time
+import os.path
+from urllib.parse import urlsplit
+
+import pytest
+from unittest import mock
+
+from awx.main.models.projects import Project
+from awx.main.models.organization import Organization
+from awx.main.models.inventory import Inventory, InventorySource
+from awx.main.tests.live.tests.conftest import wait_for_job
+
+NAME_PREFIX = "test-ivu"
+GIT_REPO_FOLDER = "inventory_vars"
+
+
+def create_new_by_name(model, **kwargs):
+ """
+ Create a new model instance. Delete an existing instance first.
+
+ :param model: The Django model.
+ :param dict kwargs: The keyword arguments required to create a model
+ instance. Must contain at least `name`.
+ :return: The model instance.
+ """
+ name = kwargs["name"]
+ try:
+ instance = model.objects.get(name=name)
+ except model.DoesNotExist:
+ pass
+ else:
+ print(f"FORCE DELETE {name}")
+ instance.delete()
+ finally:
+ instance = model.objects.create(**kwargs)
+ return instance
+
+
+def wait_for_update(instance, timeout=3.0):
+ """Wait until the last update of *instance* is finished."""
+ start = time.time()
+ while time.time() - start < timeout:
+ if instance.current_job or instance.last_job or instance.last_job_run:
+ break
+ time.sleep(0.2)
+ assert instance.current_job or instance.last_job or instance.last_job_run, f'Instance never updated id={instance.id}'
+ update = instance.current_job or instance.last_job
+ if update:
+ wait_for_job(update)
+
+
+def change_source_vars_and_update(invsrc, group_vars):
+ """
+ Change the variables content of an inventory source and update its
+ inventory.
+
+ Does not return before the inventory update is finished.
+
+ :param invsrc: The inventory source instance.
+ :param dict group_vars: The variables for various groups. Format::
+
+ {
+ : {: , : , ..}, :
+ {: , : , ..}, ..
+ }
+
+ :return: None
+ """
+ project = invsrc.source_project
+ repo_path = urlsplit(project.scm_url).path
+ filepath = os.path.join(repo_path, invsrc.source_path)
+ # print(f"change_source_vars_and_update: {project=} {repo_path=} {filepath=}")
+ with open(filepath, "w") as fp:
+ for group, variables in group_vars.items():
+ fp.write(f"[{group}:vars]\n")
+ for name, value in variables.items():
+ fp.write(f"{name}={value}\n")
+ subprocess.run('git add .; git commit -m "Update variables in invsrc.source_path"', cwd=repo_path, shell=True)
+ # Update the project to sync the changed repo contents.
+ project.update()
+ wait_for_update(project)
+ # Update the inventory from the changed source.
+ invsrc.update()
+ wait_for_update(invsrc)
+
+
+@pytest.fixture
+def organization():
+ name = f"{NAME_PREFIX}-org"
+ instance = create_new_by_name(Organization, name=name, description=f"Description for {name}")
+ yield instance
+ instance.delete()
+
+
+@pytest.fixture
+def project(organization, live_tmp_folder):
+ name = f"{NAME_PREFIX}-project"
+ instance = create_new_by_name(
+ Project,
+ name=name,
+ description=f"Description for {name}",
+ organization=organization,
+ scm_url=f"file://{live_tmp_folder}/{GIT_REPO_FOLDER}",
+ scm_type="git",
+ )
+ yield instance
+ instance.delete()
+
+
+@pytest.fixture
+def inventory(organization):
+ name = f"{NAME_PREFIX}-inventory"
+ instance = create_new_by_name(
+ Inventory,
+ name=name,
+ description=f"Description for {name}",
+ organization=organization,
+ )
+ yield instance
+ instance.delete()
+
+
+@pytest.fixture
+def inventory_source(inventory, project):
+ name = f"{NAME_PREFIX}-invsrc"
+ inv_src = InventorySource(
+ name=name,
+ source_project=project,
+ source="scm",
+ source_path="inventory_var_deleted_in_source.ini",
+ inventory=inventory,
+ overwrite_vars=True,
+ )
+ with mock.patch('awx.main.models.unified_jobs.UnifiedJobTemplate.update'):
+ inv_src.save()
+ yield inv_src
+ inv_src.delete()
+
+
+@pytest.fixture
+def inventory_source_factory(inventory, project):
+ """
+ Use this fixture if you want to use multiple inventory sources for the same
+ inventory in your test.
+ """
+ # https://docs.pytest.org/en/stable/how-to/fixtures.html#factories-as-fixtures
+
+ created = []
+ # repo_path = f"{live_tmp_folder}/{GIT_REPO_FOLDER}"
+
+ def _factory(inventory_file, name):
+ # Make sure the inventory file exists before the inventory source
+ # instance is created.
+ #
+ # Note: The current implementation of the inventory source object allows
+ # to create an instance even when the inventory source file does not
+ # exist. If this behaviour changes, uncomment the following code block
+ # and add the fixture `live_tmp_folder` to the factory function
+ # signature.
+ #
+ # inventory_file_path = os.path.join(repo_path, inventory_file) if not
+ # os.path.isfile(inventory_file_path): with open(inventory_file_path,
+ # "w") as fp: pass subprocess.run(f'git add .; git commit -m "Create
+ # {inventory_file_path}"', cwd=repo_path, shell=True)
+ #
+ # Create the inventory source instance.
+ name = f"{NAME_PREFIX}-invsrc-{name}"
+ inv_src = InventorySource(
+ name=name,
+ source_project=project,
+ source="scm",
+ source_path=inventory_file,
+ inventory=inventory,
+ overwrite_vars=True,
+ )
+ with mock.patch('awx.main.models.unified_jobs.UnifiedJobTemplate.update'):
+ inv_src.save()
+ return inv_src
+
+ yield _factory
+ for instance in created:
+ instance.delete()
+
+
+def test_inventory_var_deleted_in_source(inventory, inventory_source):
+ """
+ Verify that a variable which is deleted from its (git-)source between two
+ updates is also deleted from the inventory.
+
+ Verifies https://issues.redhat.com/browse/AAP-17690
+ """
+ inventory_source.update()
+ wait_for_update(inventory_source)
+ assert {"a": "value_a", "b": "value_b"} == Inventory.objects.get(name=inventory.name).variables_dict
+ # Remove variable `a` from source and verify that it is also removed from
+ # the inventory variables.
+ change_source_vars_and_update(inventory_source, {"all": {"b": "value_b"}})
+ assert {"b": "value_b"} == Inventory.objects.get(name=inventory.name).variables_dict
+
+
+def test_inventory_vars_with_multiple_sources(inventory, inventory_source_factory):
+ """
+ Verify a sequence of updates from various sources with changing content.
+ """
+ invsrc_a = inventory_source_factory("invsrc_a.ini", "A")
+ invsrc_b = inventory_source_factory("invsrc_b.ini", "B")
+ invsrc_c = inventory_source_factory("invsrc_c.ini", "C")
+
+ change_source_vars_and_update(invsrc_a, {"all": {"x": "x_from_a", "y": "y_from_a"}})
+ assert {"x": "x_from_a", "y": "y_from_a"} == Inventory.objects.get(name=inventory.name).variables_dict
+ change_source_vars_and_update(invsrc_b, {"all": {"x": "x_from_b", "y": "y_from_b", "z": "z_from_b"}})
+ assert {"x": "x_from_b", "y": "y_from_b", "z": "z_from_b"} == Inventory.objects.get(name=inventory.name).variables_dict
+ change_source_vars_and_update(invsrc_c, {"all": {"x": "x_from_c", "z": "z_from_c"}})
+ assert {"x": "x_from_c", "y": "y_from_b", "z": "z_from_c"} == Inventory.objects.get(name=inventory.name).variables_dict
+ change_source_vars_and_update(invsrc_b, {"all": {}})
+ assert {"x": "x_from_c", "y": "y_from_a", "z": "z_from_c"} == Inventory.objects.get(name=inventory.name).variables_dict
+ change_source_vars_and_update(invsrc_c, {"all": {"z": "z_from_c"}})
+ assert {"x": "x_from_a", "y": "y_from_a", "z": "z_from_c"} == Inventory.objects.get(name=inventory.name).variables_dict
+ change_source_vars_and_update(invsrc_a, {"all": {}})
+ assert {"z": "z_from_c"} == Inventory.objects.get(name=inventory.name).variables_dict
+ change_source_vars_and_update(invsrc_c, {"all": {}})
+ assert {} == Inventory.objects.get(name=inventory.name).variables_dict
diff --git a/awx/main/tests/live/tests/test_job_cancel.py b/awx/main/tests/live/tests/test_job_cancel.py
new file mode 100644
index 000000000000..6a88d4b9a8e1
--- /dev/null
+++ b/awx/main/tests/live/tests/test_job_cancel.py
@@ -0,0 +1,40 @@
+import time
+
+from awx.api.versioning import reverse
+from awx.main.models import Job
+
+from awx.main.tests.live.tests.conftest import wait_for_events
+
+
+def test_cancel_and_delete_job(live_tmp_folder, run_job_from_playbook, post, delete, admin):
+ res = run_job_from_playbook('test_cancel_and_delete_job', 'sleep.yml', scm_url=f'file://{live_tmp_folder}/debug', wait=False)
+ job = res['job']
+ assert job.status == 'pending'
+
+ # Wait for first event so that we can be sure the job is in-progress first
+ start = time.time()
+ timeout = 10.0
+ while not job.job_events.exists():
+ time.sleep(0.2)
+ if time.time() - start > timeout:
+ assert False, f'Did not receive first event for job_id={job.id} in {timeout} seconds'
+
+ # Now cancel the job
+ url = reverse("api:job_cancel", kwargs={'pk': job.pk})
+ post(url, user=admin, expect=202)
+
+ # Job status should change to expected status before infinity
+ start = time.time()
+ timeout = 5.0
+ job.refresh_from_db()
+ while job.status != 'canceled':
+ time.sleep(0.05)
+ job.refresh_from_db(fields=['status'])
+ if time.time() - start > timeout:
+ assert False, f'job_id={job.id} still status={job.status} after {timeout} seconds'
+
+ wait_for_events(job)
+ url = reverse("api:job_detail", kwargs={'pk': job.pk})
+ delete(url, user=admin, expect=204)
+
+ assert not Job.objects.filter(id=job.id).exists()
diff --git a/awx/main/tests/live/tests/test_partitions.py b/awx/main/tests/live/tests/test_partitions.py
new file mode 100644
index 000000000000..f395eda8e13e
--- /dev/null
+++ b/awx/main/tests/live/tests/test_partitions.py
@@ -0,0 +1,23 @@
+from datetime import timedelta
+
+from django.utils.timezone import now
+from django.db import connection
+
+from awx.main.utils.common import create_partition, table_exists
+
+
+def test_table_when_it_exists():
+ with connection.cursor() as cursor:
+ assert table_exists(cursor, 'main_job')
+
+
+def test_table_when_it_does_not_exists():
+ with connection.cursor() as cursor:
+ assert not table_exists(cursor, 'main_not_a_table_check')
+
+
+def test_create_partition_race_condition(mocker):
+ mocker.patch('awx.main.utils.common.table_exists', return_value=False)
+
+ create_partition('main_jobevent', start=now() - timedelta(days=2))
+ create_partition('main_jobevent', start=now() - timedelta(days=2))
diff --git a/awx/main/tests/settings_for_test.py b/awx/main/tests/settings_for_test.py
new file mode 100644
index 000000000000..5634494c3373
--- /dev/null
+++ b/awx/main/tests/settings_for_test.py
@@ -0,0 +1,23 @@
+# Python
+import uuid
+
+# Load development settings for base variables.
+from awx.settings.development import * # NOQA
+
+# Some things make decisions based on settings.SETTINGS_MODULE, so this is done for that
+SETTINGS_MODULE = 'awx.settings.development'
+
+# Use SQLite for unit tests instead of PostgreSQL. If the lines below are
+# commented out, Django will create the test_awx-dev database in PostgreSQL to
+# run unit tests.
+CACHES = {'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'LOCATION': 'unique-{}'.format(str(uuid.uuid4()))}}
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': os.path.join(BASE_DIR, 'awx.sqlite3'), # noqa
+ 'TEST': {
+ # Test database cannot be :memory: for inventory tests.
+ 'NAME': os.path.join(BASE_DIR, 'awx_test.sqlite3') # noqa
+ },
+ }
+}
diff --git a/awx/main/tests/unit/analytics/test_broadcast_websocket.py b/awx/main/tests/unit/analytics/test_broadcast_websocket.py
index cd7f4323b5cc..9aa24e772b2a 100644
--- a/awx/main/tests/unit/analytics/test_broadcast_websocket.py
+++ b/awx/main/tests/unit/analytics/test_broadcast_websocket.py
@@ -1,6 +1,7 @@
import datetime
+from unittest.mock import Mock, patch
-from awx.main.analytics.broadcast_websocket import FixedSlidingWindow
+from awx.main.analytics.broadcast_websocket import FixedSlidingWindow, RelayWebsocketStatsManager
from awx.main.analytics.broadcast_websocket import dt_to_seconds
@@ -59,3 +60,70 @@ def test_record_same_minute_render_diff_minute(self):
assert 20 - i == fsw.render(self.ts(minute=1, second=i, microsecond=0)), "E. Sliding window where 1 record() should drop from the results each time"
assert 0 == fsw.render(self.ts(minute=1, second=20, microsecond=0)), "F. First second one minute after all record() calls"
+
+
+class TestRelayWebsocketStatsManager:
+ """Test Redis client caching in RelayWebsocketStatsManager."""
+
+ def test_get_stats_sync_caches_redis_client(self):
+ """Verify get_stats_sync caches Redis client to avoid creating new connection pools."""
+ # Reset class variable
+ RelayWebsocketStatsManager._redis_client = None
+
+ mock_redis = Mock()
+ mock_redis.get.return_value = b''
+
+ with patch('awx.main.analytics.broadcast_websocket.get_redis_client', return_value=mock_redis) as mock_get_client:
+ # First call should create client
+ RelayWebsocketStatsManager.get_stats_sync()
+ assert mock_get_client.call_count == 1
+
+ # Second call should reuse cached client
+ RelayWebsocketStatsManager.get_stats_sync()
+ assert mock_get_client.call_count == 1 # Still 1, not called again
+
+ # Third call should still reuse cached client
+ RelayWebsocketStatsManager.get_stats_sync()
+ assert mock_get_client.call_count == 1
+
+ # Cleanup
+ RelayWebsocketStatsManager._redis_client = None
+
+ def test_get_stats_sync_returns_parsed_metrics(self):
+ """Verify get_stats_sync returns parsed metric families from Redis."""
+ # Reset class variable
+ RelayWebsocketStatsManager._redis_client = None
+
+ # Sample Prometheus metrics format
+ sample_metrics = b'# HELP test_metric A test metric\n# TYPE test_metric gauge\ntest_metric 42\n'
+
+ mock_redis = Mock()
+ mock_redis.get.return_value = sample_metrics
+
+ with patch('awx.main.analytics.broadcast_websocket.get_redis_client', return_value=mock_redis):
+ result = list(RelayWebsocketStatsManager.get_stats_sync())
+
+ # Should return parsed metric families
+ assert len(result) > 0
+ assert mock_redis.get.called
+
+ # Cleanup
+ RelayWebsocketStatsManager._redis_client = None
+
+ def test_get_stats_sync_handles_empty_redis_data(self):
+ """Verify get_stats_sync handles empty data from Redis gracefully."""
+ # Reset class variable
+ RelayWebsocketStatsManager._redis_client = None
+
+ mock_redis = Mock()
+ mock_redis.get.return_value = None # Redis returns None when key doesn't exist
+
+ with patch('awx.main.analytics.broadcast_websocket.get_redis_client', return_value=mock_redis):
+ result = list(RelayWebsocketStatsManager.get_stats_sync())
+
+ # Should handle empty data gracefully
+ assert result == []
+ assert mock_redis.get.called
+
+ # Cleanup
+ RelayWebsocketStatsManager._redis_client = None
diff --git a/awx/main/tests/unit/api/serializers/test_job_template_serializers.py b/awx/main/tests/unit/api/serializers/test_job_template_serializers.py
index 51e64fd753a1..0a9e31b91cbd 100644
--- a/awx/main/tests/unit/api/serializers/test_job_template_serializers.py
+++ b/awx/main/tests/unit/api/serializers/test_job_template_serializers.py
@@ -76,15 +76,15 @@ def test_callback_absent(self, get_related_mock_and_run, job_template):
class TestJobTemplateSerializerGetSummaryFields:
def test_survey_spec_exists(self, test_get_summary_fields, mocker, job_template):
job_template.survey_spec = {'name': 'blah', 'description': 'blah blah'}
- with mocker.patch.object(JobTemplateSerializer, '_recent_jobs') as mock_rj:
- mock_rj.return_value = []
- test_get_summary_fields(JobTemplateSerializer, job_template, 'survey')
+ mock_rj = mocker.patch.object(JobTemplateSerializer, '_recent_jobs')
+ mock_rj.return_value = []
+ test_get_summary_fields(JobTemplateSerializer, job_template, 'survey')
def test_survey_spec_absent(self, get_summary_fields_mock_and_run, mocker, job_template):
job_template.survey_spec = None
- with mocker.patch.object(JobTemplateSerializer, '_recent_jobs') as mock_rj:
- mock_rj.return_value = []
- summary = get_summary_fields_mock_and_run(JobTemplateSerializer, job_template)
+ mock_rj = mocker.patch.object(JobTemplateSerializer, '_recent_jobs')
+ mock_rj.return_value = []
+ summary = get_summary_fields_mock_and_run(JobTemplateSerializer, job_template)
assert 'survey' not in summary
def test_copy_edit_standard(self, mocker, job_template_factory):
@@ -107,10 +107,10 @@ def test_copy_edit_standard(self, mocker, job_template_factory):
view.kwargs = {}
serializer.context['view'] = view
- with mocker.patch("awx.api.serializers.role_summary_fields_generator", return_value='Can eat pie'):
- with mocker.patch("awx.main.access.JobTemplateAccess.can_change", return_value='foobar'):
- with mocker.patch("awx.main.access.JobTemplateAccess.can_copy", return_value='foo'):
- response = serializer.get_summary_fields(jt_obj)
+ mocker.patch("awx.api.serializers.role_summary_fields_generator", return_value='Can eat pie')
+ mocker.patch("awx.main.access.JobTemplateAccess.can_change", return_value='foobar')
+ mocker.patch("awx.main.access.JobTemplateAccess.can_copy", return_value='foo')
+ response = serializer.get_summary_fields(jt_obj)
assert response['user_capabilities']['copy'] == 'foo'
assert response['user_capabilities']['edit'] == 'foobar'
diff --git a/awx/main/tests/unit/api/serializers/test_token_serializer.py b/awx/main/tests/unit/api/serializers/test_token_serializer.py
deleted file mode 100644
index aa6363d47a10..000000000000
--- a/awx/main/tests/unit/api/serializers/test_token_serializer.py
+++ /dev/null
@@ -1,8 +0,0 @@
-import pytest
-
-from awx.api.serializers import OAuth2TokenSerializer
-
-
-@pytest.mark.parametrize('scope, expect', [('', False), ('read', True), ('read read', False), ('write read', True), ('read rainbow', False)])
-def test_invalid_scopes(scope, expect):
- assert OAuth2TokenSerializer()._is_valid_scope(scope) is expect
diff --git a/awx/main/tests/unit/api/serializers/test_unified_serializers.py b/awx/main/tests/unit/api/serializers/test_unified_serializers.py
index 36558f92cb4f..47451d849a03 100644
--- a/awx/main/tests/unit/api/serializers/test_unified_serializers.py
+++ b/awx/main/tests/unit/api/serializers/test_unified_serializers.py
@@ -39,7 +39,7 @@ def test_unified_job_detail_exclusive_fields():
For each type, assert that the only fields allowed to be exclusive to
detail view are the allowed types
"""
- allowed_detail_fields = frozenset(('result_traceback', 'job_args', 'job_cwd', 'job_env', 'event_processing_finished'))
+ allowed_detail_fields = frozenset(('result_traceback', 'job_args', 'job_cwd', 'job_env', 'event_processing_finished', 'artifacts'))
for cls in UnifiedJob.__subclasses__():
list_serializer = getattr(serializers, '{}ListSerializer'.format(cls.__name__))
detail_serializer = getattr(serializers, '{}Serializer'.format(cls.__name__))
diff --git a/awx/main/tests/unit/api/serializers/test_workflow_serializers.py b/awx/main/tests/unit/api/serializers/test_workflow_serializers.py
index 9e7fe51344e0..218395dc6c62 100644
--- a/awx/main/tests/unit/api/serializers/test_workflow_serializers.py
+++ b/awx/main/tests/unit/api/serializers/test_workflow_serializers.py
@@ -189,8 +189,8 @@ def test_use_db_answer(self, jt, mocker):
serializer = WorkflowJobTemplateNodeSerializer()
wfjt = WorkflowJobTemplate.objects.create(name='fake-wfjt')
serializer.instance = WorkflowJobTemplateNode(workflow_job_template=wfjt, unified_job_template=jt, extra_data={'var1': '$encrypted$foooooo'})
- with mocker.patch('awx.main.models.mixins.decrypt_value', return_value='foo'):
- attrs = serializer.validate({'unified_job_template': jt, 'workflow_job_template': wfjt, 'extra_data': {'var1': '$encrypted$'}})
+ mocker.patch('awx.main.models.mixins.decrypt_value', return_value='foo')
+ attrs = serializer.validate({'unified_job_template': jt, 'workflow_job_template': wfjt, 'extra_data': {'var1': '$encrypted$'}})
assert 'survey_passwords' in attrs
assert 'var1' in attrs['survey_passwords']
assert attrs['extra_data']['var1'] == '$encrypted$foooooo'
diff --git a/awx/main/tests/unit/api/test_fields.py b/awx/main/tests/unit/api/test_fields.py
new file mode 100644
index 000000000000..d6b6ae49d266
--- /dev/null
+++ b/awx/main/tests/unit/api/test_fields.py
@@ -0,0 +1,49 @@
+import pytest
+from collections import OrderedDict
+from unittest import mock
+
+from rest_framework.exceptions import ValidationError
+
+from awx.api.fields import DeprecatedCredentialField
+
+
+class TestDeprecatedCredentialField:
+ """Test that DeprecatedCredentialField handles unexpected input types gracefully."""
+
+ def test_dict_value_raises_validation_error(self):
+ """Passing a dict instead of an integer should return a 400 validation error, not a 500 TypeError."""
+ field = DeprecatedCredentialField()
+ with pytest.raises(ValidationError):
+ field.to_internal_value({"username": "admin", "password": "secret"})
+
+ def test_ordered_dict_value_raises_validation_error(self):
+ """Passing an OrderedDict should return a 400 validation error, not a 500 TypeError."""
+ field = DeprecatedCredentialField()
+ with pytest.raises(ValidationError):
+ field.to_internal_value(OrderedDict([("username", "admin")]))
+
+ def test_list_value_raises_validation_error(self):
+ """Passing a list should return a 400 validation error, not a 500 TypeError."""
+ field = DeprecatedCredentialField()
+ with pytest.raises(ValidationError):
+ field.to_internal_value([1, 2, 3])
+
+ def test_string_value_raises_validation_error(self):
+ """Passing a non-numeric string should return a 400 validation error."""
+ field = DeprecatedCredentialField()
+ with pytest.raises(ValidationError):
+ field.to_internal_value("not_a_number")
+
+ @mock.patch('awx.api.fields.Credential.objects')
+ def test_valid_integer_value_works(self, mock_cred_objects):
+ """Passing a valid integer PK should work when the credential exists."""
+ mock_cred_objects.get.return_value = mock.MagicMock()
+ field = DeprecatedCredentialField()
+ assert field.to_internal_value(42) == 42
+
+ @mock.patch('awx.api.fields.Credential.objects')
+ def test_valid_string_integer_value_works(self, mock_cred_objects):
+ """Passing a numeric string PK should work when the credential exists."""
+ mock_cred_objects.get.return_value = mock.MagicMock()
+ field = DeprecatedCredentialField()
+ assert field.to_internal_value("42") == 42
diff --git a/awx/main/tests/unit/api/test_filters.py b/awx/main/tests/unit/api/test_filters.py
index 7d6501a87161..78cc7401cf15 100644
--- a/awx/main/tests/unit/api/test_filters.py
+++ b/awx/main/tests/unit/api/test_filters.py
@@ -3,15 +3,12 @@
import pytest
# Django
-from django.core.exceptions import FieldDoesNotExist
+from rest_framework.exceptions import PermissionDenied
-from rest_framework.exceptions import PermissionDenied, ParseError
+from ansible_base.rest_filters.rest_framework.field_lookup_backend import FieldLookupBackend
-from awx.api.filters import FieldLookupBackend, OrderByBackend, get_field_from_path
from awx.main.models import (
AdHocCommand,
- ActivityStream,
- Credential,
Job,
JobTemplate,
SystemJob,
@@ -20,94 +17,15 @@
WorkflowJob,
WorkflowJobTemplate,
WorkflowJobOptions,
- InventorySource,
- JobEvent,
)
-from awx.main.models.oauth import OAuth2Application
from awx.main.models.jobs import JobOptions
-def test_related():
- field_lookup = FieldLookupBackend()
- lookup = '__'.join(['inventory', 'organization', 'pk'])
- field, new_lookup = field_lookup.get_field_from_lookup(InventorySource, lookup)
- print(field)
- print(new_lookup)
-
-
-def test_invalid_filter_key():
- field_lookup = FieldLookupBackend()
- # FieldDoesNotExist is caught and converted to ParseError by filter_queryset
- with pytest.raises(FieldDoesNotExist) as excinfo:
- field_lookup.value_to_python(JobEvent, 'event_data.task_action', 'foo')
- assert 'has no field named' in str(excinfo)
-
-
-def test_invalid_field_hop():
- with pytest.raises(ParseError) as excinfo:
- get_field_from_path(Credential, 'organization__description__user')
- assert 'No related model for' in str(excinfo)
-
-
-def test_invalid_order_by_key():
- field_order_by = OrderByBackend()
- with pytest.raises(ParseError) as excinfo:
- [f for f in field_order_by._validate_ordering_fields(JobEvent, ('event_data.task_action',))]
- assert 'has no field named' in str(excinfo)
-
-
-@pytest.mark.parametrize(u"empty_value", [u'', ''])
-def test_empty_in(empty_value):
- field_lookup = FieldLookupBackend()
- with pytest.raises(ValueError) as excinfo:
- field_lookup.value_to_python(JobTemplate, 'project__name__in', empty_value)
- assert 'empty value for __in' in str(excinfo.value)
-
-
-@pytest.mark.parametrize(u"valid_value", [u'foo', u'foo,'])
-def test_valid_in(valid_value):
- field_lookup = FieldLookupBackend()
- value, new_lookup, _ = field_lookup.value_to_python(JobTemplate, 'project__name__in', valid_value)
- assert 'foo' in value
-
-
-def test_invalid_field():
- invalid_field = u"ヽヾ"
- field_lookup = FieldLookupBackend()
- with pytest.raises(ValueError) as excinfo:
- field_lookup.value_to_python(WorkflowJobTemplate, invalid_field, 'foo')
- assert 'is not an allowed field name. Must be ascii encodable.' in str(excinfo.value)
-
-
-def test_valid_iexact():
- field_lookup = FieldLookupBackend()
- value, new_lookup, _ = field_lookup.value_to_python(JobTemplate, 'project__name__iexact', 'foo')
- assert 'foo' in value
-
-
-def test_invalid_iexact():
- field_lookup = FieldLookupBackend()
- with pytest.raises(ValueError) as excinfo:
- field_lookup.value_to_python(Job, 'id__iexact', '1')
- assert 'is not a text field and cannot be filtered by case-insensitive search' in str(excinfo.value)
-
-
-@pytest.mark.parametrize('lookup_suffix', ['', 'contains', 'startswith', 'in'])
-@pytest.mark.parametrize('password_field', Credential.PASSWORD_FIELDS)
-def test_filter_on_password_field(password_field, lookup_suffix):
- field_lookup = FieldLookupBackend()
- lookup = '__'.join(filter(None, [password_field, lookup_suffix]))
- with pytest.raises(PermissionDenied) as excinfo:
- field, new_lookup = field_lookup.get_field_from_lookup(Credential, lookup)
- assert 'not allowed' in str(excinfo.value)
-
-
@pytest.mark.parametrize(
'model, query',
[
(User, 'password__icontains'),
(User, 'settings__value__icontains'),
- (User, 'main_oauth2accesstoken__token__gt'),
(UnifiedJob, 'job_args__icontains'),
(UnifiedJob, 'job_env__icontains'),
(UnifiedJob, 'start_args__icontains'),
@@ -119,8 +37,6 @@ def test_filter_on_password_field(password_field, lookup_suffix):
(WorkflowJob, 'survey_passwords__icontains'),
(JobTemplate, 'survey_spec__icontains'),
(WorkflowJobTemplate, 'survey_spec__icontains'),
- (ActivityStream, 'o_auth2_application__client_secret__gt'),
- (OAuth2Application, 'grant__code__gt'),
],
)
def test_filter_sensitive_fields_and_relations(model, query):
@@ -128,10 +44,3 @@ def test_filter_sensitive_fields_and_relations(model, query):
with pytest.raises(PermissionDenied) as excinfo:
field, new_lookup = field_lookup.get_field_from_lookup(model, query)
assert 'not allowed' in str(excinfo.value)
-
-
-def test_looping_filters_prohibited():
- field_lookup = FieldLookupBackend()
- with pytest.raises(ParseError) as loop_exc:
- field_lookup.get_field_from_lookup(Job, 'job_events__job__job_events')
- assert 'job_events' in str(loop_exc.value)
diff --git a/awx/main/tests/unit/api/test_generics.py b/awx/main/tests/unit/api/test_generics.py
index 6f0982bfd847..05cc72cc1935 100644
--- a/awx/main/tests/unit/api/test_generics.py
+++ b/awx/main/tests/unit/api/test_generics.py
@@ -50,7 +50,7 @@ def test_attach_validate_ok(self, mocker):
mock_request = mocker.MagicMock(data=dict(id=1))
serializer = SubListCreateAttachDetachAPIView()
- (sub_id, res) = serializer.attach_validate(mock_request)
+ sub_id, res = serializer.attach_validate(mock_request)
assert sub_id == 1
assert res is None
@@ -59,12 +59,12 @@ def test_attach_validate_invalid_type(self, mocker):
mock_request = mocker.MagicMock(data=dict(id='foobar'))
serializer = SubListCreateAttachDetachAPIView()
- (sub_id, res) = serializer.attach_validate(mock_request)
+ sub_id, res = serializer.attach_validate(mock_request)
assert type(res) is Response
def test_attach_create_and_associate(self, mocker, get_object_or_400, parent_relationship_factory):
- (serializer, mock_parent_relationship) = parent_relationship_factory(SubListCreateAttachDetachAPIView, 'wife')
+ serializer, mock_parent_relationship = parent_relationship_factory(SubListCreateAttachDetachAPIView, 'wife')
create_return_value = mocker.MagicMock(status_code=status.HTTP_201_CREATED)
serializer.create = mocker.Mock(return_value=create_return_value)
@@ -75,7 +75,7 @@ def test_attach_create_and_associate(self, mocker, get_object_or_400, parent_rel
mock_parent_relationship.wife.add.assert_called_with(get_object_or_400.return_value)
def test_attach_associate_only(self, mocker, get_object_or_400, parent_relationship_factory):
- (serializer, mock_parent_relationship) = parent_relationship_factory(SubListCreateAttachDetachAPIView, 'wife')
+ serializer, mock_parent_relationship = parent_relationship_factory(SubListCreateAttachDetachAPIView, 'wife')
serializer.create = mocker.Mock(return_value=mocker.MagicMock())
mock_request = mocker.MagicMock(data=dict(id=1))
@@ -88,7 +88,7 @@ def test_unattach_validate_ok(self, mocker):
mock_request = mocker.MagicMock(data=dict(id=1))
serializer = SubListCreateAttachDetachAPIView()
- (sub_id, res) = serializer.unattach_validate(mock_request)
+ sub_id, res = serializer.unattach_validate(mock_request)
assert sub_id == 1
assert res is None
@@ -97,7 +97,7 @@ def test_unattach_validate_invalid_type(self, mocker):
mock_request = mocker.MagicMock(data=dict(id='foobar'))
serializer = SubListCreateAttachDetachAPIView()
- (sub_id, res) = serializer.unattach_validate(mock_request)
+ sub_id, res = serializer.unattach_validate(mock_request)
assert type(res) is Response
@@ -105,13 +105,13 @@ def test_unattach_validate_missing_id(self, mocker):
mock_request = mocker.MagicMock(data=dict())
serializer = SubListCreateAttachDetachAPIView()
- (sub_id, res) = serializer.unattach_validate(mock_request)
+ sub_id, res = serializer.unattach_validate(mock_request)
assert sub_id is None
assert type(res) is Response
def test_unattach_by_id_ok(self, mocker, parent_relationship_factory, get_object_or_400):
- (serializer, mock_parent_relationship) = parent_relationship_factory(SubListCreateAttachDetachAPIView, 'wife')
+ serializer, mock_parent_relationship = parent_relationship_factory(SubListCreateAttachDetachAPIView, 'wife')
mock_request = mocker.MagicMock()
mock_sub = mocker.MagicMock(name="object to unattach")
get_object_or_400.return_value = mock_sub
@@ -191,16 +191,16 @@ def mock_view(self, parent=None):
def test_parent_access_check_failed(self, mocker, mock_organization):
mock_access = mocker.MagicMock(__name__='for logger', return_value=False)
- with mocker.patch('awx.main.access.BaseAccess.can_read', mock_access):
- with pytest.raises(PermissionDenied):
- self.mock_view(parent=mock_organization).check_permissions(self.mock_request())
- mock_access.assert_called_once_with(mock_organization)
+ mocker.patch('awx.main.access.BaseAccess.can_read', mock_access)
+ with pytest.raises(PermissionDenied):
+ self.mock_view(parent=mock_organization).check_permissions(self.mock_request())
+ mock_access.assert_called_once_with(mock_organization)
def test_parent_access_check_worked(self, mocker, mock_organization):
mock_access = mocker.MagicMock(__name__='for logger', return_value=True)
- with mocker.patch('awx.main.access.BaseAccess.can_read', mock_access):
- self.mock_view(parent=mock_organization).check_permissions(self.mock_request())
- mock_access.assert_called_once_with(mock_organization)
+ mocker.patch('awx.main.access.BaseAccess.can_read', mock_access)
+ self.mock_view(parent=mock_organization).check_permissions(self.mock_request())
+ mock_access.assert_called_once_with(mock_organization)
def test_related_search_reverse_FK_field():
diff --git a/awx/main/tests/unit/api/test_logger.py b/awx/main/tests/unit/api/test_logger.py
index b56da87da1a7..a3da0d23b829 100644
--- a/awx/main/tests/unit/api/test_logger.py
+++ b/awx/main/tests/unit/api/test_logger.py
@@ -47,7 +47,7 @@
'\n'.join(
[
'template(name="awx" type="string" string="%rawmsg-after-pri%")\nmodule(load="omhttp")',
- 'action(type="omhttp" server="logs-01.loggly.com" serverport="80" usehttps="off" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" action.resumeInterval="5" errorfile="/var/log/tower/rsyslog.err" restpath="inputs/1fd38090-2af1-4e1e-8d80-492899da0f71/tag/http/")', # noqa
+ 'action(type="omhttp" server="logs-01.loggly.com" serverport="80" usehttps="off" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" action.resumeInterval="5" queue.spoolDirectory="/var/lib/awx" queue.filename="awx-external-logger-action-queue" queue.maxDiskSpace="1g" queue.maxFileSize="100m" queue.type="LinkedList" queue.saveOnShutdown="on" queue.syncqueuefiles="on" queue.checkpointInterval="1000" queue.size="131072" queue.highwaterMark="98304" queue.discardMark="117964" queue.discardSeverity="5" errorfile="/var/log/tower/rsyslog.err" restpath="inputs/1fd38090-2af1-4e1e-8d80-492899da0f71/tag/http/")', # noqa
]
),
),
@@ -61,7 +61,7 @@
'\n'.join(
[
'template(name="awx" type="string" string="%rawmsg-after-pri%")',
- 'action(type="omfwd" target="localhost" port="9000" protocol="udp" action.resumeRetryCount="-1" action.resumeInterval="5" template="awx")', # noqa
+ 'action(type="omfwd" target="localhost" port="9000" protocol="udp" action.resumeRetryCount="-1" action.resumeInterval="5" template="awx" queue.spoolDirectory="/var/lib/awx" queue.filename="awx-external-logger-action-queue" queue.maxDiskSpace="1g" queue.maxFileSize="100m" queue.type="LinkedList" queue.saveOnShutdown="on" queue.syncqueuefiles="on" queue.checkpointInterval="1000" queue.size="131072" queue.highwaterMark="98304" queue.discardMark="117964" queue.discardSeverity="5")', # noqa
]
),
),
@@ -75,7 +75,7 @@
'\n'.join(
[
'template(name="awx" type="string" string="%rawmsg-after-pri%")',
- 'action(type="omfwd" target="localhost" port="9000" protocol="tcp" action.resumeRetryCount="-1" action.resumeInterval="5" template="awx")', # noqa
+ 'action(type="omfwd" target="localhost" port="9000" protocol="tcp" action.resumeRetryCount="-1" action.resumeInterval="5" template="awx" queue.spoolDirectory="/var/lib/awx" queue.filename="awx-external-logger-action-queue" queue.maxDiskSpace="1g" queue.maxFileSize="100m" queue.type="LinkedList" queue.saveOnShutdown="on" queue.syncqueuefiles="on" queue.checkpointInterval="1000" queue.size="131072" queue.highwaterMark="98304" queue.discardMark="117964" queue.discardSeverity="5")', # noqa
]
),
),
@@ -89,7 +89,7 @@
'\n'.join(
[
'template(name="awx" type="string" string="%rawmsg-after-pri%")\nmodule(load="omhttp")',
- 'action(type="omhttp" server="yoursplunk" serverport="443" usehttps="on" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" action.resumeInterval="5" errorfile="/var/log/tower/rsyslog.err" restpath="services/collector/event")', # noqa
+ 'action(type="omhttp" server="yoursplunk" serverport="443" usehttps="on" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" action.resumeInterval="5" queue.spoolDirectory="/var/lib/awx" queue.filename="awx-external-logger-action-queue" queue.maxDiskSpace="1g" queue.maxFileSize="100m" queue.type="LinkedList" queue.saveOnShutdown="on" queue.syncqueuefiles="on" queue.checkpointInterval="1000" queue.size="131072" queue.highwaterMark="98304" queue.discardMark="117964" queue.discardSeverity="5" errorfile="/var/log/tower/rsyslog.err" restpath="services/collector/event")', # noqa
]
),
),
@@ -103,7 +103,7 @@
'\n'.join(
[
'template(name="awx" type="string" string="%rawmsg-after-pri%")\nmodule(load="omhttp")',
- 'action(type="omhttp" server="yoursplunk" serverport="80" usehttps="off" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" action.resumeInterval="5" errorfile="/var/log/tower/rsyslog.err" restpath="services/collector/event")', # noqa
+ 'action(type="omhttp" server="yoursplunk" serverport="80" usehttps="off" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" action.resumeInterval="5" queue.spoolDirectory="/var/lib/awx" queue.filename="awx-external-logger-action-queue" queue.maxDiskSpace="1g" queue.maxFileSize="100m" queue.type="LinkedList" queue.saveOnShutdown="on" queue.syncqueuefiles="on" queue.checkpointInterval="1000" queue.size="131072" queue.highwaterMark="98304" queue.discardMark="117964" queue.discardSeverity="5" errorfile="/var/log/tower/rsyslog.err" restpath="services/collector/event")', # noqa
]
),
),
@@ -117,7 +117,7 @@
'\n'.join(
[
'template(name="awx" type="string" string="%rawmsg-after-pri%")\nmodule(load="omhttp")',
- 'action(type="omhttp" server="yoursplunk" serverport="8088" usehttps="on" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" action.resumeInterval="5" errorfile="/var/log/tower/rsyslog.err" restpath="services/collector/event")', # noqa
+ 'action(type="omhttp" server="yoursplunk" serverport="8088" usehttps="on" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" action.resumeInterval="5" queue.spoolDirectory="/var/lib/awx" queue.filename="awx-external-logger-action-queue" queue.maxDiskSpace="1g" queue.maxFileSize="100m" queue.type="LinkedList" queue.saveOnShutdown="on" queue.syncqueuefiles="on" queue.checkpointInterval="1000" queue.size="131072" queue.highwaterMark="98304" queue.discardMark="117964" queue.discardSeverity="5" errorfile="/var/log/tower/rsyslog.err" restpath="services/collector/event")', # noqa
]
),
),
@@ -131,7 +131,7 @@
'\n'.join(
[
'template(name="awx" type="string" string="%rawmsg-after-pri%")\nmodule(load="omhttp")',
- 'action(type="omhttp" server="yoursplunk" serverport="8088" usehttps="on" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" action.resumeInterval="5" errorfile="/var/log/tower/rsyslog.err" restpath="services/collector/event")', # noqa
+ 'action(type="omhttp" server="yoursplunk" serverport="8088" usehttps="on" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" action.resumeInterval="5" queue.spoolDirectory="/var/lib/awx" queue.filename="awx-external-logger-action-queue" queue.maxDiskSpace="1g" queue.maxFileSize="100m" queue.type="LinkedList" queue.saveOnShutdown="on" queue.syncqueuefiles="on" queue.checkpointInterval="1000" queue.size="131072" queue.highwaterMark="98304" queue.discardMark="117964" queue.discardSeverity="5" errorfile="/var/log/tower/rsyslog.err" restpath="services/collector/event")', # noqa
]
),
),
@@ -145,7 +145,7 @@
'\n'.join(
[
'template(name="awx" type="string" string="%rawmsg-after-pri%")\nmodule(load="omhttp")',
- 'action(type="omhttp" server="yoursplunk.org" serverport="8088" usehttps="on" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" action.resumeInterval="5" errorfile="/var/log/tower/rsyslog.err" restpath="services/collector/event")', # noqa
+ 'action(type="omhttp" server="yoursplunk.org" serverport="8088" usehttps="on" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" action.resumeInterval="5" queue.spoolDirectory="/var/lib/awx" queue.filename="awx-external-logger-action-queue" queue.maxDiskSpace="1g" queue.maxFileSize="100m" queue.type="LinkedList" queue.saveOnShutdown="on" queue.syncqueuefiles="on" queue.checkpointInterval="1000" queue.size="131072" queue.highwaterMark="98304" queue.discardMark="117964" queue.discardSeverity="5" errorfile="/var/log/tower/rsyslog.err" restpath="services/collector/event")', # noqa
]
),
),
@@ -159,7 +159,7 @@
'\n'.join(
[
'template(name="awx" type="string" string="%rawmsg-after-pri%")\nmodule(load="omhttp")',
- 'action(type="omhttp" server="yoursplunk.org" serverport="8088" usehttps="off" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" action.resumeInterval="5" errorfile="/var/log/tower/rsyslog.err" restpath="services/collector/event")', # noqa
+ 'action(type="omhttp" server="yoursplunk.org" serverport="8088" usehttps="off" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" action.resumeInterval="5" queue.spoolDirectory="/var/lib/awx" queue.filename="awx-external-logger-action-queue" queue.maxDiskSpace="1g" queue.maxFileSize="100m" queue.type="LinkedList" queue.saveOnShutdown="on" queue.syncqueuefiles="on" queue.checkpointInterval="1000" queue.size="131072" queue.highwaterMark="98304" queue.discardMark="117964" queue.discardSeverity="5" errorfile="/var/log/tower/rsyslog.err" restpath="services/collector/event")', # noqa
]
),
),
@@ -173,7 +173,7 @@
'\n'.join(
[
'template(name="awx" type="string" string="%rawmsg-after-pri%")\nmodule(load="omhttp")',
- 'action(type="omhttp" server="endpoint5.collection.us2.sumologic.com" serverport="443" usehttps="on" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" action.resumeInterval="5" errorfile="/var/log/tower/rsyslog.err" restpath="receiver/v1/http/ZaVnC4dhaV0qoiETY0MrM3wwLoDgO1jFgjOxE6-39qokkj3LGtOroZ8wNaN2M6DtgYrJZsmSi4-36_Up5TbbN_8hosYonLKHSSOSKY845LuLZBCBwStrHQ==")', # noqa
+ 'action(type="omhttp" server="endpoint5.collection.us2.sumologic.com" serverport="443" usehttps="on" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" action.resumeInterval="5" queue.spoolDirectory="/var/lib/awx" queue.filename="awx-external-logger-action-queue" queue.maxDiskSpace="1g" queue.maxFileSize="100m" queue.type="LinkedList" queue.saveOnShutdown="on" queue.syncqueuefiles="on" queue.checkpointInterval="1000" queue.size="131072" queue.highwaterMark="98304" queue.discardMark="117964" queue.discardSeverity="5" errorfile="/var/log/tower/rsyslog.err" restpath="receiver/v1/http/ZaVnC4dhaV0qoiETY0MrM3wwLoDgO1jFgjOxE6-39qokkj3LGtOroZ8wNaN2M6DtgYrJZsmSi4-36_Up5TbbN_8hosYonLKHSSOSKY845LuLZBCBwStrHQ==")', # noqa
]
),
),
diff --git a/awx/main/tests/unit/api/test_schema.py b/awx/main/tests/unit/api/test_schema.py
new file mode 100644
index 000000000000..83ade9e16cc9
--- /dev/null
+++ b/awx/main/tests/unit/api/test_schema.py
@@ -0,0 +1,424 @@
+import copy
+import warnings
+from unittest.mock import Mock, patch
+
+from rest_framework.permissions import IsAuthenticated
+
+from awx.api.schema import (
+ CustomAutoSchema,
+ AuthenticatedSpectacularAPIView,
+ AuthenticatedSpectacularSwaggerView,
+ AuthenticatedSpectacularRedocView,
+ filter_credential_type_schema,
+)
+
+
+class TestCustomAutoSchema:
+ """Unit tests for CustomAutoSchema class."""
+
+ def test_get_tags_with_swagger_topic(self):
+ """Test get_tags returns swagger_topic when available."""
+ view = Mock()
+ view.swagger_topic = 'custom_topic'
+ view.get_serializer = Mock(return_value=Mock())
+
+ schema = CustomAutoSchema()
+ schema.view = view
+
+ tags = schema.get_tags()
+ assert tags == ['Custom_Topic']
+
+ def test_get_tags_with_serializer_meta_model(self):
+ """Test get_tags returns model verbose_name_plural from serializer."""
+ # Create a mock model with verbose_name_plural
+ mock_model = Mock()
+ mock_model._meta.verbose_name_plural = 'test models'
+
+ # Create a mock serializer with Meta.model
+ mock_serializer = Mock()
+ mock_serializer.Meta.model = mock_model
+
+ view = Mock(spec=[]) # View without swagger_topic
+ view.get_serializer = Mock(return_value=mock_serializer)
+
+ schema = CustomAutoSchema()
+ schema.view = view
+
+ tags = schema.get_tags()
+ assert tags == ['Test Models']
+
+ def test_get_tags_with_view_model(self):
+ """Test get_tags returns model verbose_name_plural from view."""
+ # Create a mock model with verbose_name_plural
+ mock_model = Mock()
+ mock_model._meta.verbose_name_plural = 'view models'
+
+ view = Mock(spec=['model']) # View without swagger_topic or get_serializer
+ view.model = mock_model
+
+ schema = CustomAutoSchema()
+ schema.view = view
+
+ tags = schema.get_tags()
+ assert tags == ['View Models']
+
+ def test_get_tags_without_get_serializer(self):
+ """Test get_tags when view doesn't have get_serializer method."""
+ mock_model = Mock()
+ mock_model._meta.verbose_name_plural = 'test objects'
+
+ view = Mock(spec=['model'])
+ view.model = mock_model
+
+ schema = CustomAutoSchema()
+ schema.view = view
+
+ tags = schema.get_tags()
+ assert tags == ['Test Objects']
+
+ def test_get_tags_serializer_exception_with_warning(self):
+ """Test get_tags handles exception in get_serializer with warning."""
+ mock_model = Mock()
+ mock_model._meta.verbose_name_plural = 'fallback models'
+
+ view = Mock(spec=['get_serializer', 'model', '__class__'])
+ view.__class__.__name__ = 'TestView'
+ view.get_serializer = Mock(side_effect=Exception('Serializer error'))
+ view.model = mock_model
+
+ schema = CustomAutoSchema()
+ schema.view = view
+
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ tags = schema.get_tags()
+
+ # Check that a warning was raised
+ assert len(w) == 1
+ assert 'TestView.get_serializer() raised an exception' in str(w[0].message)
+
+ # Should still get tags from view.model
+ assert tags == ['Fallback Models']
+
+ def test_get_tags_serializer_without_meta_model(self):
+ """Test get_tags when serializer doesn't have Meta.model."""
+ mock_serializer = Mock(spec=[]) # No Meta attribute
+
+ view = Mock(spec=['get_serializer'])
+ view.__class__.__name__ = 'NoMetaView'
+ view.get_serializer = Mock(return_value=mock_serializer)
+
+ schema = CustomAutoSchema()
+ schema.view = view
+
+ with patch.object(CustomAutoSchema.__bases__[0], 'get_tags', return_value=['Default Tag']) as mock_super:
+ tags = schema.get_tags()
+ mock_super.assert_called_once()
+ assert tags == ['Default Tag']
+
+ def test_get_tags_fallback_to_super(self):
+ """Test get_tags falls back to parent class method."""
+ view = Mock(spec=['get_serializer'])
+ view.get_serializer = Mock(return_value=Mock(spec=[]))
+
+ schema = CustomAutoSchema()
+ schema.view = view
+
+ with patch.object(CustomAutoSchema.__bases__[0], 'get_tags', return_value=['Super Tag']) as mock_super:
+ tags = schema.get_tags()
+ mock_super.assert_called_once()
+ assert tags == ['Super Tag']
+
+ def test_get_tags_empty_with_warning(self):
+ """Test get_tags returns 'api' fallback when no tags can be determined."""
+ view = Mock(spec=['get_serializer'])
+ view.__class__.__name__ = 'EmptyView'
+ view.get_serializer = Mock(return_value=Mock(spec=[]))
+
+ schema = CustomAutoSchema()
+ schema.view = view
+
+ with patch.object(CustomAutoSchema.__bases__[0], 'get_tags', return_value=[]):
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ tags = schema.get_tags()
+
+ # Check that a warning was raised
+ assert len(w) == 1
+ assert 'Could not determine tags for EmptyView' in str(w[0].message)
+
+ # Should fallback to 'api'
+ assert tags == ['api']
+
+ def test_get_tags_swagger_topic_title_case(self):
+ """Test that swagger_topic is properly title-cased."""
+ view = Mock()
+ view.swagger_topic = 'multi_word_topic'
+ view.get_serializer = Mock(return_value=Mock())
+
+ schema = CustomAutoSchema()
+ schema.view = view
+
+ tags = schema.get_tags()
+ assert tags == ['Multi_Word_Topic']
+
+ def test_is_deprecated_true(self):
+ """Test is_deprecated returns True when view has deprecated=True."""
+ view = Mock()
+ view.deprecated = True
+
+ schema = CustomAutoSchema()
+ schema.view = view
+
+ assert schema.is_deprecated() is True
+
+ def test_is_deprecated_false(self):
+ """Test is_deprecated returns False when view has deprecated=False."""
+ view = Mock()
+ view.deprecated = False
+
+ schema = CustomAutoSchema()
+ schema.view = view
+
+ assert schema.is_deprecated() is False
+
+ def test_is_deprecated_missing_attribute(self):
+ """Test is_deprecated returns False when view doesn't have deprecated attribute."""
+ view = Mock(spec=[])
+
+ schema = CustomAutoSchema()
+ schema.view = view
+
+ assert schema.is_deprecated() is False
+
+ def test_get_tags_serializer_meta_without_model(self):
+ """Test get_tags when serializer has Meta but no model attribute."""
+ mock_serializer = Mock()
+ mock_serializer.Meta = Mock(spec=[]) # Meta exists but no model
+
+ mock_model = Mock()
+ mock_model._meta.verbose_name_plural = 'backup models'
+
+ view = Mock(spec=['get_serializer', 'model'])
+ view.get_serializer = Mock(return_value=mock_serializer)
+ view.model = mock_model
+
+ schema = CustomAutoSchema()
+ schema.view = view
+
+ tags = schema.get_tags()
+ # Should fall back to view.model
+ assert tags == ['Backup Models']
+
+ def test_get_tags_complex_scenario_exception_recovery(self):
+ """Test complex scenario where serializer fails but view.model exists."""
+ mock_model = Mock()
+ mock_model._meta.verbose_name_plural = 'recovery models'
+
+ view = Mock(spec=['get_serializer', 'model', '__class__'])
+ view.__class__.__name__ = 'ComplexView'
+ view.get_serializer = Mock(side_effect=ValueError('Invalid serializer'))
+ view.model = mock_model
+
+ schema = CustomAutoSchema()
+ schema.view = view
+
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ tags = schema.get_tags()
+
+ # Should have warned about the exception
+ assert len(w) == 1
+ assert 'ComplexView.get_serializer() raised an exception' in str(w[0].message)
+
+ # But still recovered and got tags from view.model
+ assert tags == ['Recovery Models']
+
+ def test_get_tags_priority_order(self):
+ """Test that get_tags respects priority: swagger_topic > serializer.Meta.model > view.model."""
+ # Set up a view with all three options
+ mock_model_view = Mock()
+ mock_model_view._meta.verbose_name_plural = 'view models'
+
+ mock_model_serializer = Mock()
+ mock_model_serializer._meta.verbose_name_plural = 'serializer models'
+
+ mock_serializer = Mock()
+ mock_serializer.Meta.model = mock_model_serializer
+
+ view = Mock()
+ view.swagger_topic = 'priority_topic'
+ view.get_serializer = Mock(return_value=mock_serializer)
+ view.model = mock_model_view
+
+ schema = CustomAutoSchema()
+ schema.view = view
+
+ tags = schema.get_tags()
+ # swagger_topic should take priority
+ assert tags == ['Priority_Topic']
+
+
+class TestAuthenticatedSchemaViews:
+ """Unit tests for authenticated schema view classes."""
+
+ def test_authenticated_spectacular_api_view_requires_authentication(self):
+ """Test that AuthenticatedSpectacularAPIView requires authentication."""
+ assert IsAuthenticated in AuthenticatedSpectacularAPIView.permission_classes
+
+ def test_authenticated_spectacular_swagger_view_requires_authentication(self):
+ """Test that AuthenticatedSpectacularSwaggerView requires authentication."""
+ assert IsAuthenticated in AuthenticatedSpectacularSwaggerView.permission_classes
+
+ def test_authenticated_spectacular_redoc_view_requires_authentication(self):
+ """Test that AuthenticatedSpectacularRedocView requires authentication."""
+ assert IsAuthenticated in AuthenticatedSpectacularRedocView.permission_classes
+
+
+class TestFilterCredentialTypeSchema:
+ """Unit tests for filter_credential_type_schema postprocessing hook."""
+
+ def test_filters_both_schemas_correctly(self):
+ """Test that both CredentialTypeRequest and PatchedCredentialTypeRequest schemas are filtered."""
+ result = {
+ 'components': {
+ 'schemas': {
+ 'CredentialTypeRequest': {
+ 'properties': {
+ 'kind': {
+ 'enum': [
+ 'ssh',
+ 'vault',
+ 'net',
+ 'scm',
+ 'cloud',
+ 'registry',
+ 'token',
+ 'insights',
+ 'external',
+ 'kubernetes',
+ 'galaxy',
+ 'cryptography',
+ None,
+ ],
+ 'type': 'string',
+ }
+ }
+ },
+ 'PatchedCredentialTypeRequest': {
+ 'properties': {
+ 'kind': {
+ 'enum': [
+ 'ssh',
+ 'vault',
+ 'net',
+ 'scm',
+ 'cloud',
+ 'registry',
+ 'token',
+ 'insights',
+ 'external',
+ 'kubernetes',
+ 'galaxy',
+ 'cryptography',
+ None,
+ ],
+ 'type': 'string',
+ }
+ }
+ },
+ }
+ }
+ }
+
+ returned = filter_credential_type_schema(result, None, None, None)
+
+ # POST/PUT schema: no None (required field)
+ assert result['components']['schemas']['CredentialTypeRequest']['properties']['kind']['enum'] == ['cloud', 'net']
+ assert result['components']['schemas']['CredentialTypeRequest']['properties']['kind']['description'] == "* `cloud` - Cloud\\n* `net` - Network"
+
+ # PATCH schema: includes None (optional field)
+ assert result['components']['schemas']['PatchedCredentialTypeRequest']['properties']['kind']['enum'] == ['cloud', 'net', None]
+ assert result['components']['schemas']['PatchedCredentialTypeRequest']['properties']['kind']['description'] == "* `cloud` - Cloud\\n* `net` - Network"
+
+ # Other properties should be preserved
+ assert result['components']['schemas']['CredentialTypeRequest']['properties']['kind']['type'] == 'string'
+
+ # Function should return the result
+ assert returned is result
+
+ def test_handles_empty_result(self):
+ """Test graceful handling when result dict is empty."""
+ result = {}
+ original = copy.deepcopy(result)
+
+ returned = filter_credential_type_schema(result, None, None, None)
+
+ assert result == original
+ assert returned is result
+
+ def test_handles_missing_enum(self):
+ """Test that schemas without enum key are not modified."""
+ result = {'components': {'schemas': {'CredentialTypeRequest': {'properties': {'kind': {'type': 'string', 'description': 'Some description'}}}}}}
+ original = copy.deepcopy(result)
+
+ filter_credential_type_schema(result, None, None, None)
+
+ assert result == original
+
+ def test_filters_only_target_schemas(self):
+ """Test that only CredentialTypeRequest schemas are modified, not others."""
+ result = {
+ 'components': {
+ 'schemas': {
+ 'CredentialTypeRequest': {'properties': {'kind': {'enum': ['ssh', 'cloud', 'net', None]}}},
+ 'OtherSchema': {'properties': {'kind': {'enum': ['option1', 'option2']}}},
+ }
+ }
+ }
+
+ other_schema_before = copy.deepcopy(result['components']['schemas']['OtherSchema'])
+
+ filter_credential_type_schema(result, None, None, None)
+
+ # CredentialTypeRequest should be filtered (no None for required field)
+ assert result['components']['schemas']['CredentialTypeRequest']['properties']['kind']['enum'] == ['cloud', 'net']
+
+ # OtherSchema should be unchanged
+ assert result['components']['schemas']['OtherSchema'] == other_schema_before
+
+ def test_handles_only_one_schema_present(self):
+ """Test that function works when only one target schema is present."""
+ result = {'components': {'schemas': {'CredentialTypeRequest': {'properties': {'kind': {'enum': ['ssh', 'cloud', 'net', None]}}}}}}
+
+ filter_credential_type_schema(result, None, None, None)
+
+ assert result['components']['schemas']['CredentialTypeRequest']['properties']['kind']['enum'] == ['cloud', 'net']
+
+ def test_handles_missing_properties(self):
+ """Test graceful handling when schema has no properties key."""
+ result = {'components': {'schemas': {'CredentialTypeRequest': {}}}}
+ original = copy.deepcopy(result)
+
+ filter_credential_type_schema(result, None, None, None)
+
+ assert result == original
+
+ def test_differentiates_required_vs_optional_fields(self):
+ """Test that CredentialTypeRequest excludes None but PatchedCredentialTypeRequest includes it."""
+ result = {
+ 'components': {
+ 'schemas': {
+ 'CredentialTypeRequest': {'properties': {'kind': {'enum': ['ssh', 'vault', 'net', 'scm', 'cloud', 'registry', None]}}},
+ 'PatchedCredentialTypeRequest': {'properties': {'kind': {'enum': ['ssh', 'vault', 'net', 'scm', 'cloud', 'registry', None]}}},
+ }
+ }
+ }
+
+ filter_credential_type_schema(result, None, None, None)
+
+ # POST/PUT schema: no None (required field)
+ assert result['components']['schemas']['CredentialTypeRequest']['properties']['kind']['enum'] == ['cloud', 'net']
+
+ # PATCH schema: includes None (optional field)
+ assert result['components']['schemas']['PatchedCredentialTypeRequest']['properties']['kind']['enum'] == ['cloud', 'net', None]
diff --git a/awx/main/tests/unit/api/test_views.py b/awx/main/tests/unit/api/test_views.py
index 950ae57af4a7..503ad6e854dd 100644
--- a/awx/main/tests/unit/api/test_views.py
+++ b/awx/main/tests/unit/api/test_views.py
@@ -50,6 +50,7 @@ def test_get_endpoints(self, mocker):
'activity_stream',
'workflow_job_templates',
'workflow_jobs',
+ 'analytics',
]
view = ApiVersionRootView()
ret = view.get(mocker.MagicMock())
@@ -65,7 +66,7 @@ def test_inherited_mixin_unattach(self):
mock_request = mock.MagicMock()
super(JobTemplateLabelList, view).unattach(mock_request, None, None)
- assert mixin_unattach.called_with(mock_request, None, None)
+ mixin_unattach.assert_called_with(mock_request, None, None)
class TestInventoryInventorySourcesUpdate:
@@ -107,15 +108,16 @@ def exclude(self, **kwargs):
mock_request = mocker.MagicMock()
mock_request.user.can_access.return_value = can_access
- with mocker.patch.object(InventoryInventorySourcesUpdate, 'get_object', return_value=obj):
- with mocker.patch.object(InventoryInventorySourcesUpdate, 'get_serializer_context', return_value=None):
- with mocker.patch('awx.api.serializers.InventoryUpdateDetailSerializer') as serializer_class:
- serializer = serializer_class.return_value
- serializer.to_representation.return_value = {}
+ mocker.patch.object(InventoryInventorySourcesUpdate, 'get_object', return_value=obj)
+ mocker.patch.object(InventoryInventorySourcesUpdate, 'get_serializer_context', return_value=None)
+ serializer_class = mocker.patch('awx.api.serializers.InventoryUpdateDetailSerializer')
- view = InventoryInventorySourcesUpdate()
- response = view.post(mock_request)
- assert response.data == expected
+ serializer = serializer_class.return_value
+ serializer.to_representation.return_value = {}
+
+ view = InventoryInventorySourcesUpdate()
+ response = view.post(mock_request)
+ assert response.data == expected
class TestSurveySpecValidation:
diff --git a/awx/main/tests/unit/commands/test_dispatcherctl.py b/awx/main/tests/unit/commands/test_dispatcherctl.py
new file mode 100644
index 000000000000..50804577c36e
--- /dev/null
+++ b/awx/main/tests/unit/commands/test_dispatcherctl.py
@@ -0,0 +1,92 @@
+import io
+
+import pytest
+
+from django.core.management.base import CommandError
+
+from awx.main.management.commands import dispatcherctl
+
+
+@pytest.fixture(autouse=True)
+def clear_dispatcher_env(monkeypatch, mocker):
+ monkeypatch.delenv('DISPATCHERD_CONFIG_FILE', raising=False)
+ mocker.patch.object(dispatcherctl.logging, 'basicConfig')
+ mocker.patch.object(dispatcherctl, 'connection', mocker.Mock(vendor='postgresql'))
+
+
+def test_dispatcherctl_runs_control_with_generated_config(mocker):
+ command = dispatcherctl.Command()
+ command.stdout = io.StringIO()
+
+ data = {'foo': 'bar'}
+ mocker.patch.object(dispatcherctl, '_build_command_data_from_args', return_value=data)
+ dispatcher_setup = mocker.patch.object(dispatcherctl, 'dispatcher_setup')
+ config_data = {'setting': 'value'}
+ mocker.patch.object(dispatcherctl, 'get_dispatcherd_config', return_value=config_data)
+
+ control = mocker.Mock()
+ control.control_with_reply.return_value = [{'status': 'ok'}]
+ mocker.patch.object(dispatcherctl, 'get_control_from_settings', return_value=control)
+ mocker.patch.object(dispatcherctl.yaml, 'dump', return_value='payload\n')
+
+ command.handle(
+ command='running',
+ config=dispatcherctl.DEFAULT_CONFIG_FILE,
+ expected_replies=1,
+ log_level='INFO',
+ )
+
+ dispatcher_setup.assert_called_once_with(config_data)
+ control.control_with_reply.assert_called_once_with('running', data=data, expected_replies=1)
+ assert command.stdout.getvalue() == 'payload\n'
+
+
+def test_dispatcherctl_rejects_custom_config_path():
+ command = dispatcherctl.Command()
+ command.stdout = io.StringIO()
+
+ with pytest.raises(CommandError):
+ command.handle(
+ command='running',
+ config='/tmp/dispatcher.yml',
+ expected_replies=1,
+ log_level='INFO',
+ )
+
+
+def test_dispatcherctl_rejects_sqlite_db(mocker):
+ command = dispatcherctl.Command()
+ command.stdout = io.StringIO()
+
+ mocker.patch.object(dispatcherctl, 'connection', mocker.Mock(vendor='sqlite'))
+
+ with pytest.raises(CommandError, match='sqlite3'):
+ command.handle(
+ command='running',
+ config=dispatcherctl.DEFAULT_CONFIG_FILE,
+ expected_replies=1,
+ log_level='INFO',
+ )
+
+
+def test_dispatcherctl_raises_when_replies_missing(mocker):
+ command = dispatcherctl.Command()
+ command.stdout = io.StringIO()
+
+ mocker.patch.object(dispatcherctl, '_build_command_data_from_args', return_value={})
+ mocker.patch.object(dispatcherctl, 'dispatcher_setup')
+ mocker.patch.object(dispatcherctl, 'get_dispatcherd_config', return_value={})
+ control = mocker.Mock()
+ control.control_with_reply.return_value = [{'status': 'ok'}]
+ mocker.patch.object(dispatcherctl, 'get_control_from_settings', return_value=control)
+ mocker.patch.object(dispatcherctl.yaml, 'dump', return_value='- status: ok\n')
+
+ with pytest.raises(CommandError):
+ command.handle(
+ command='running',
+ config=dispatcherctl.DEFAULT_CONFIG_FILE,
+ expected_replies=2,
+ log_level='INFO',
+ )
+
+ control.control_with_reply.assert_called_once_with('running', data={}, expected_replies=2)
diff --git a/awx/main/tests/unit/models/test_credential.py b/awx/main/tests/unit/models/test_credential.py
index 0dc8daff3356..81f243ddefd5 100644
--- a/awx/main/tests/unit/models/test_credential.py
+++ b/awx/main/tests/unit/models/test_credential.py
@@ -4,6 +4,8 @@
from awx.main.models import Credential, CredentialType
+from django.apps import apps
+
@pytest.mark.django_db
def test_unique_hash_with_unicode():
@@ -16,3 +18,32 @@ def test_custom_cred_with_empty_encrypted_field():
ct = CredentialType(name='My Custom Cred', kind='custom', inputs={'fields': [{'id': 'some_field', 'label': 'My Field', 'secret': True}]})
cred = Credential(id=4, name='Testing 1 2 3', credential_type=ct, inputs={})
assert cred.encrypt_field('some_field', None) is None
+
+
+@pytest.mark.parametrize(
+ (
+ 'apps',
+ 'app_config',
+ ),
+ [
+ (
+ apps,
+ None,
+ ),
+ (
+ None,
+ apps.get_app_config('main'),
+ ),
+ ],
+)
+def test__get_credential_type_class(apps, app_config):
+ ct = CredentialType._get_credential_type_class(apps=apps, app_config=app_config)
+ assert ct.__name__ == 'CredentialType'
+
+
+def test__get_credential_type_class_invalid_params():
+ with pytest.raises(ValueError) as e:
+ CredentialType._get_credential_type_class(apps=apps, app_config=apps.get_app_config('main'))
+
+ assert type(e.value) is ValueError
+ assert str(e.value) == 'Expected only apps or app_config to be defined, not both'
diff --git a/awx/main/tests/unit/models/test_events.py b/awx/main/tests/unit/models/test_events.py
index a38df57fff6c..920b8572ea12 100644
--- a/awx/main/tests/unit/models/test_events.py
+++ b/awx/main/tests/unit/models/test_events.py
@@ -1,5 +1,5 @@
from datetime import datetime
-from django.utils.timezone import utc
+from datetime import timezone
import pytest
from awx.main.models import JobEvent, ProjectUpdateEvent, AdHocCommandEvent, InventoryUpdateEvent, SystemJobEvent
@@ -18,7 +18,7 @@
@pytest.mark.parametrize('created', [datetime(2018, 1, 1).isoformat(), datetime(2018, 1, 1)])
def test_event_parse_created(job_identifier, cls, created):
event = cls.create_from_data(**{job_identifier: 123, 'created': created})
- assert event.created == datetime(2018, 1, 1).replace(tzinfo=utc)
+ assert event.created == datetime(2018, 1, 1).replace(tzinfo=timezone.utc)
@pytest.mark.parametrize(
diff --git a/awx/main/tests/unit/models/test_jobs.py b/awx/main/tests/unit/models/test_jobs.py
index 2f030a57c32c..ff1887f34edb 100644
--- a/awx/main/tests/unit/models/test_jobs.py
+++ b/awx/main/tests/unit/models/test_jobs.py
@@ -1,97 +1,139 @@
# -*- coding: utf-8 -*-
import json
import os
-import time
-
import pytest
from awx.main.models import (
- Job,
Inventory,
Host,
)
+from awx.main.tasks.facts import start_fact_cache, finish_fact_cache
+
+from django.utils.timezone import now
+
+from datetime import timedelta
+
+import time
@pytest.fixture
-def hosts(inventory):
+def ref_time():
+ return now() - timedelta(seconds=5)
+
+
+@pytest.fixture
+def hosts(ref_time):
+ inventory = Inventory(id=5)
return [
- Host(name='host1', ansible_facts={"a": 1, "b": 2}, inventory=inventory),
- Host(name='host2', ansible_facts={"a": 1, "b": 2}, inventory=inventory),
- Host(name='host3', ansible_facts={"a": 1, "b": 2}, inventory=inventory),
- Host(name=u'Iñtërnâtiônàlizætiøn', ansible_facts={"a": 1, "b": 2}, inventory=inventory),
+ Host(name='host1', ansible_facts={"a": 1, "b": 2}, ansible_facts_modified=ref_time, inventory=inventory),
+ Host(name='host2', ansible_facts={"a": 1, "b": 2}, ansible_facts_modified=ref_time, inventory=inventory),
+ Host(name='host3', ansible_facts={"a": 1, "b": 2}, ansible_facts_modified=ref_time, inventory=inventory),
+ Host(name=u'Iñtërnâtiônàlizætiøn', ansible_facts={"a": 1, "b": 2}, ansible_facts_modified=ref_time, inventory=inventory),
]
-@pytest.fixture
-def inventory():
- return Inventory(id=5)
+def test_start_job_fact_cache(hosts, tmpdir):
+ # Create artifacts dir inside tmpdir
+ artifacts_dir = tmpdir.mkdir("artifacts")
+ # Assign a mock inventory ID
+ inventory_id = 42
-@pytest.fixture
-def job(mocker, hosts, inventory):
- j = Job(inventory=inventory, id=2)
- j._get_inventory_hosts = mocker.Mock(return_value=hosts)
- return j
+ # Call the function WITHOUT log_data — the decorator handles it
+ start_fact_cache(hosts, artifacts_dir=str(artifacts_dir), timeout=0, inventory_id=inventory_id)
+
+ # Fact files are written into artifacts_dir/fact_cache/
+ fact_cache_dir = os.path.join(artifacts_dir, 'fact_cache')
+
+ for host in hosts:
+ filepath = os.path.join(fact_cache_dir, host.name)
+ assert os.path.exists(filepath)
+ with open(filepath, 'r', encoding='utf-8') as f:
+ assert json.load(f) == host.ansible_facts
+
+
+def test_fact_cache_with_invalid_path_traversal(tmpdir):
+ hosts = [
+ Host(
+ name='../foo',
+ ansible_facts={"a": 1, "b": 2},
+ ),
+ ]
+ artifacts_dir = tmpdir.mkdir("artifacts")
+ inventory_id = 42
+
+ start_fact_cache(hosts, artifacts_dir=str(artifacts_dir), timeout=0, inventory_id=inventory_id)
+
+ # Fact cache directory (safe location)
+ fact_cache_dir = os.path.join(artifacts_dir, 'fact_cache')
+
+ # The bad host name should not produce a file
+ assert not os.path.exists(os.path.join(fact_cache_dir, '../foo'))
+ # Make sure the fact_cache dir exists and is still empty
+ assert os.listdir(fact_cache_dir) == []
-def test_start_job_fact_cache(hosts, job, inventory, tmpdir):
+
+def test_start_job_fact_cache_past_timeout(hosts, tmpdir):
fact_cache = os.path.join(tmpdir, 'facts')
- last_modified = job.start_job_fact_cache(fact_cache, timeout=0)
+ start_fact_cache(hosts, fact_cache, timeout=2)
+
+ for host in hosts:
+ assert not os.path.exists(os.path.join(fact_cache, host.name))
+ ret = start_fact_cache(hosts, fact_cache, timeout=2)
+ assert ret is None
+
+
+def test_start_job_fact_cache_within_timeout(hosts, tmpdir):
+ artifacts_dir = tmpdir.mkdir("artifacts")
+
+ # The hosts fixture was modified 5s ago, which is less than 7s
+ start_fact_cache(hosts, str(artifacts_dir), timeout=7)
+ fact_cache_dir = os.path.join(artifacts_dir, 'fact_cache')
for host in hosts:
- filepath = os.path.join(fact_cache, host.name)
+ filepath = os.path.join(fact_cache_dir, host.name)
assert os.path.exists(filepath)
with open(filepath, 'r') as f:
- assert f.read() == json.dumps(host.ansible_facts)
- assert os.path.getmtime(filepath) <= last_modified
+ assert json.load(f) == host.ansible_facts
-def test_fact_cache_with_invalid_path_traversal(job, inventory, tmpdir, mocker):
- job._get_inventory_hosts = mocker.Mock(
- return_value=[
- Host(
- name='../foo',
- ansible_facts={"a": 1, "b": 2},
- ),
- ]
- )
-
+def test_finish_job_fact_cache_clear(hosts, mocker, ref_time, tmpdir):
fact_cache = os.path.join(tmpdir, 'facts')
- job.start_job_fact_cache(fact_cache, timeout=0)
- # a file called "foo" should _not_ be written outside the facts dir
- assert os.listdir(os.path.join(fact_cache, '..')) == ['facts']
+ start_fact_cache(hosts, fact_cache, timeout=0)
+ bulk_update = mocker.patch('awx.main.tasks.facts.bulk_update_sorted_by_id')
-def test_finish_job_fact_cache_with_existing_data(job, hosts, inventory, mocker, tmpdir):
- fact_cache = os.path.join(tmpdir, 'facts')
- last_modified = job.start_job_fact_cache(fact_cache, timeout=0)
+ # Mock the os.path.exists behavior for host deletion
+ # Let's assume the fact file for hosts[1] is missing.
+ mocker.patch('os.path.exists', side_effect=lambda path: hosts[1].name not in path)
- bulk_update = mocker.patch('django.db.models.query.QuerySet.bulk_update')
+ # Simulate one host's fact file getting deleted manually
+ host_to_delete_filepath = os.path.join(fact_cache, hosts[1].name)
- ansible_facts_new = {"foo": "bar"}
- filepath = os.path.join(fact_cache, hosts[1].name)
- with open(filepath, 'w') as f:
- f.write(json.dumps(ansible_facts_new))
- f.flush()
- # I feel kind of gross about calling `os.utime` by hand, but I noticed
- # that in our container-based dev environment, the resolution for
- # `os.stat()` after a file write was over a second, and I don't want to put
- # a sleep() in this test
- new_modification_time = time.time() + 3600
- os.utime(filepath, (new_modification_time, new_modification_time))
+ # Simulate the file being removed by checking existence first, to avoid FileNotFoundError
+ if os.path.exists(host_to_delete_filepath):
+ os.remove(host_to_delete_filepath)
- job.finish_job_fact_cache(fact_cache, last_modified)
+ finish_fact_cache(fact_cache)
+ # Simulate side effects that would normally be applied during bulk update
+ hosts[1].ansible_facts = {}
+ hosts[1].ansible_facts_modified = now()
+
+ # Verify facts are preserved for hosts with valid cache files
for host in (hosts[0], hosts[2], hosts[3]):
assert host.ansible_facts == {"a": 1, "b": 2}
- assert host.ansible_facts_modified is None
- assert hosts[1].ansible_facts == ansible_facts_new
- bulk_update.assert_called_once_with([hosts[1]], ['ansible_facts', 'ansible_facts_modified'])
+ assert host.ansible_facts_modified == ref_time
+ assert hosts[1].ansible_facts_modified > ref_time
+ # Current implementation skips the call entirely if hosts_to_update == []
+ bulk_update.assert_not_called()
-def test_finish_job_fact_cache_with_bad_data(job, hosts, inventory, mocker, tmpdir):
+
+def test_finish_job_fact_cache_with_bad_data(hosts, mocker, tmpdir):
fact_cache = os.path.join(tmpdir, 'facts')
- last_modified = job.start_job_fact_cache(fact_cache, timeout=0)
+ start_fact_cache(hosts, fact_cache, timeout=0)
bulk_update = mocker.patch('django.db.models.query.QuerySet.bulk_update')
@@ -103,22 +145,6 @@ def test_finish_job_fact_cache_with_bad_data(job, hosts, inventory, mocker, tmpd
new_modification_time = time.time() + 3600
os.utime(filepath, (new_modification_time, new_modification_time))
- job.finish_job_fact_cache(fact_cache, last_modified)
+ finish_fact_cache(fact_cache)
bulk_update.assert_not_called()
-
-
-def test_finish_job_fact_cache_clear(job, hosts, inventory, mocker, tmpdir):
- fact_cache = os.path.join(tmpdir, 'facts')
- last_modified = job.start_job_fact_cache(fact_cache, timeout=0)
-
- bulk_update = mocker.patch('django.db.models.query.QuerySet.bulk_update')
-
- os.remove(os.path.join(fact_cache, hosts[1].name))
- job.finish_job_fact_cache(fact_cache, last_modified)
-
- for host in (hosts[0], hosts[2], hosts[3]):
- assert host.ansible_facts == {"a": 1, "b": 2}
- assert host.ansible_facts_modified is None
- assert hosts[1].ansible_facts == {}
- bulk_update.assert_called_once_with([hosts[1]], ['ansible_facts', 'ansible_facts_modified'])
diff --git a/awx/main/tests/unit/models/test_label.py b/awx/main/tests/unit/models/test_label.py
index e049a8857867..8017782ffa5c 100644
--- a/awx/main/tests/unit/models/test_label.py
+++ b/awx/main/tests/unit/models/test_label.py
@@ -11,7 +11,6 @@
WorkflowJobNode,
)
-
mock_query_set = mock.MagicMock()
mock_objects = mock.MagicMock(filter=mock.MagicMock(return_value=mock_query_set))
diff --git a/awx/main/tests/unit/models/test_receptor_address.py b/awx/main/tests/unit/models/test_receptor_address.py
new file mode 100644
index 000000000000..f18e1a9018a1
--- /dev/null
+++ b/awx/main/tests/unit/models/test_receptor_address.py
@@ -0,0 +1,32 @@
+from awx.main.models import ReceptorAddress
+import pytest
+
+ReceptorAddress()
+
+
+@pytest.mark.parametrize(
+ 'address, protocol, port, websocket_path, expected',
+ [
+ ('foo', 'tcp', 27199, '', 'foo:27199'),
+ ('bar', 'ws', 6789, '', 'wss://bar:6789'),
+ ('mal', 'ws', 6789, 'path', 'wss://mal:6789/path'),
+ ('example.com', 'ws', 443, 'path', 'wss://example.com:443/path'),
+ ],
+)
+def test_get_full_address(address, protocol, port, websocket_path, expected):
+ receptor_address = ReceptorAddress(address=address, protocol=protocol, port=port, websocket_path=websocket_path)
+ assert receptor_address.get_full_address() == expected
+
+
+@pytest.mark.parametrize(
+ 'protocol, expected',
+ [
+ ('tcp', 'tcp-peer'),
+ ('ws', 'ws-peer'),
+ ('wss', 'ws-peer'),
+ ('foo', None),
+ ],
+)
+def test_get_peer_type(protocol, expected):
+ receptor_address = ReceptorAddress(protocol=protocol)
+ assert receptor_address.get_peer_type() == expected
diff --git a/awx/main/tests/unit/models/test_survey_models.py b/awx/main/tests/unit/models/test_survey_models.py
index 57058930eacc..6d9eb5dec999 100644
--- a/awx/main/tests/unit/models/test_survey_models.py
+++ b/awx/main/tests/unit/models/test_survey_models.py
@@ -18,7 +18,7 @@ def __call__(self, value):
@pytest.mark.survey
-class SurveyVariableValidation:
+class TestSurveyVariableValidation:
def test_survey_answers_as_string(self, job_template_factory):
objects = job_template_factory('job-template-with-survey', survey=[{'variable': 'var1', 'type': 'text'}], persisted=False)
jt = objects.job_template
@@ -57,7 +57,7 @@ def test_job_template_survey_variable_validation(self, job_template_factory):
accepted, rejected, errors = obj.accept_or_ignore_variables({"a": 5})
assert rejected == {"a": 5}
assert accepted == {}
- assert str(errors[0]) == "Value 5 for 'a' expected to be a string."
+ assert str(errors['variables_needed_to_start'][0]) == "Value 5 for 'a' expected to be a string."
def test_job_template_survey_default_variable_validation(self, job_template_factory):
objects = job_template_factory(
@@ -88,7 +88,7 @@ def test_job_template_survey_default_variable_validation(self, job_template_fact
obj.survey_enabled = True
accepted, _, errors = obj.accept_or_ignore_variables({"a": 2})
- assert accepted == {{"a": 2.0}}
+ assert accepted == {"a": 2.0}
assert not errors
@@ -176,22 +176,22 @@ def test_display_survey_spec_encrypts_default(survey_spec_factory):
@pytest.mark.survey
@pytest.mark.parametrize(
- "question_type,default,min,max,expect_use,expect_value",
+ "question_type,default,min,max,expect_valid,expect_use,expect_value",
[
- ("text", "", 0, 0, True, ''), # default used
- ("text", "", 1, 0, False, 'N/A'), # value less than min length
- ("password", "", 1, 0, False, 'N/A'), # passwords behave the same as text
- ("multiplechoice", "", 0, 0, False, 'N/A'), # historical bug
- ("multiplechoice", "zeb", 0, 0, False, 'N/A'), # zeb not in choices
- ("multiplechoice", "coffee", 0, 0, True, 'coffee'),
- ("multiselect", None, 0, 0, False, 'N/A'), # NOTE: Behavior is arguable, value of [] may be prefered
- ("multiselect", "", 0, 0, False, 'N/A'),
- ("multiselect", ["zeb"], 0, 0, False, 'N/A'),
- ("multiselect", ["milk"], 0, 0, True, ["milk"]),
- ("multiselect", ["orange\nmilk"], 0, 0, False, 'N/A'), # historical bug
+ ("text", "", 0, 0, True, False, 'N/A'), # valid but empty default not sent for optional question
+ ("text", "", 1, 0, False, False, 'N/A'), # value less than min length
+ ("password", "", 1, 0, False, False, 'N/A'), # passwords behave the same as text
+ ("multiplechoice", "", 0, 0, False, False, 'N/A'), # historical bug
+ ("multiplechoice", "zeb", 0, 0, False, False, 'N/A'), # zeb not in choices
+ ("multiplechoice", "coffee", 0, 0, True, True, 'coffee'),
+ ("multiselect", None, 0, 0, False, False, 'N/A'), # NOTE: Behavior is arguable, value of [] may be prefered
+ ("multiselect", "", 0, 0, False, False, 'N/A'),
+ ("multiselect", ["zeb"], 0, 0, False, False, 'N/A'),
+ ("multiselect", ["milk"], 0, 0, True, True, ["milk"]),
+ ("multiselect", ["orange\nmilk"], 0, 0, False, False, 'N/A'), # historical bug
],
)
-def test_optional_survey_question_defaults(survey_spec_factory, question_type, default, min, max, expect_use, expect_value):
+def test_optional_survey_question_defaults(survey_spec_factory, question_type, default, min, max, expect_valid, expect_use, expect_value):
spec = survey_spec_factory(
[
{
@@ -208,7 +208,7 @@ def test_optional_survey_question_defaults(survey_spec_factory, question_type, d
jt = JobTemplate(name="test-jt", survey_spec=spec, survey_enabled=True)
defaulted_extra_vars = jt._update_unified_job_kwargs({}, {})
element = spec['spec'][0]
- if expect_use:
+ if expect_valid:
assert jt._survey_element_validation(element, {element['variable']: element['default']}) == []
else:
assert jt._survey_element_validation(element, {element['variable']: element['default']})
@@ -218,6 +218,28 @@ def test_optional_survey_question_defaults(survey_spec_factory, question_type, d
assert 'c' not in defaulted_extra_vars['extra_vars']
+@pytest.mark.survey
+def test_optional_survey_empty_default_with_runtime_extra_var(survey_spec_factory):
+ """When a user explicitly provides an empty string at runtime for an optional
+ survey question, the variable should still be included in extra_vars."""
+ spec = survey_spec_factory(
+ [
+ {
+ "required": False,
+ "default": "",
+ "choices": "",
+ "variable": "c",
+ "min": 0,
+ "max": 0,
+ "type": "text",
+ },
+ ]
+ )
+ jt = JobTemplate(name="test-jt", survey_spec=spec, survey_enabled=True)
+ defaulted_extra_vars = jt._update_unified_job_kwargs({}, {'extra_vars': json.dumps({'c': ''})})
+ assert json.loads(defaulted_extra_vars['extra_vars'])['c'] == ''
+
+
@pytest.mark.survey
@pytest.mark.parametrize(
"question_type,default,maxlen,kwargs,expected",
diff --git a/awx/main/tests/unit/models/test_unified_job_unit.py b/awx/main/tests/unit/models/test_unified_job_unit.py
index c7f62225c8e1..2fa8807dff74 100644
--- a/awx/main/tests/unit/models/test_unified_job_unit.py
+++ b/awx/main/tests/unit/models/test_unified_job_unit.py
@@ -1,4 +1,3 @@
-import pytest
from unittest import mock
from awx.main.models import UnifiedJob, UnifiedJobTemplate, WorkflowJob, WorkflowJobNode, WorkflowApprovalTemplate, Job, User, Project, JobTemplate, Inventory
@@ -22,52 +21,6 @@ def test_unified_job_workflow_attributes():
assert job.workflow_job_id == 1
-def mock_on_commit(f):
- f()
-
-
-@pytest.fixture
-def unified_job(mocker):
- mocker.patch.object(UnifiedJob, 'can_cancel', return_value=True)
- j = UnifiedJob()
- j.status = 'pending'
- j.cancel_flag = None
- j.save = mocker.MagicMock()
- j.websocket_emit_status = mocker.MagicMock()
- j.fallback_cancel = mocker.MagicMock()
- return j
-
-
-def test_cancel(unified_job):
- with mock.patch('awx.main.models.unified_jobs.connection.on_commit', wraps=mock_on_commit):
- unified_job.cancel()
-
- assert unified_job.cancel_flag is True
- assert unified_job.status == 'canceled'
- assert unified_job.job_explanation == ''
- # Note: the websocket emit status check is just reflecting the state of the current code.
- # Some more thought may want to go into only emitting canceled if/when the job record
- # status is changed to canceled. Unlike, currently, where it's emitted unconditionally.
- unified_job.websocket_emit_status.assert_called_with("canceled")
- assert [(args, kwargs) for args, kwargs in unified_job.save.call_args_list] == [
- ((), {'update_fields': ['cancel_flag', 'start_args']}),
- ((), {'update_fields': ['status']}),
- ]
-
-
-def test_cancel_job_explanation(unified_job):
- job_explanation = 'giggity giggity'
-
- with mock.patch('awx.main.models.unified_jobs.connection.on_commit'):
- unified_job.cancel(job_explanation=job_explanation)
-
- assert unified_job.job_explanation == job_explanation
- assert [(args, kwargs) for args, kwargs in unified_job.save.call_args_list] == [
- ((), {'update_fields': ['cancel_flag', 'start_args', 'job_explanation']}),
- ((), {'update_fields': ['status']}),
- ]
-
-
def test_organization_copy_to_jobs():
"""
All unified job types should infer their organization from their template organization
@@ -107,7 +60,11 @@ def test_job_metavars(self):
result_hash['{}_user_id'.format(name)] = 47
result_hash['{}_inventory_id'.format(name)] = 45
result_hash['{}_inventory_name'.format(name)] = 'example-inv'
- assert Job(name='fake-job', pk=42, id=42, launch_type='manual', created_by=maker, inventory=inv).awx_meta_vars() == result_hash
+ result_hash['{}_execution_node'.format(name)] = 'example-exec-node'
+ assert (
+ Job(name='fake-job', pk=42, id=42, launch_type='manual', created_by=maker, inventory=inv, execution_node='example-exec-node').awx_meta_vars()
+ == result_hash
+ )
def test_project_update_metavars(self):
data = Job(
diff --git a/awx/main/tests/unit/models/test_workflow_unit.py b/awx/main/tests/unit/models/test_workflow_unit.py
index dc01c3301f6d..7ac2009403de 100644
--- a/awx/main/tests/unit/models/test_workflow_unit.py
+++ b/awx/main/tests/unit/models/test_workflow_unit.py
@@ -155,35 +155,35 @@ def test_node_getter_and_setters():
class TestWorkflowJobCreate:
def test_create_no_prompts(self, wfjt_node_no_prompts, workflow_job_unit, mocker):
mock_create = mocker.MagicMock()
- with mocker.patch('awx.main.models.WorkflowJobNode.objects.create', mock_create):
- wfjt_node_no_prompts.create_workflow_job_node(workflow_job=workflow_job_unit)
- mock_create.assert_called_once_with(
- all_parents_must_converge=False,
- extra_data={},
- survey_passwords={},
- char_prompts=wfjt_node_no_prompts.char_prompts,
- inventory=None,
- unified_job_template=wfjt_node_no_prompts.unified_job_template,
- workflow_job=workflow_job_unit,
- identifier=mocker.ANY,
- execution_environment=None,
- )
+ mocker.patch('awx.main.models.WorkflowJobNode.objects.create', mock_create)
+ wfjt_node_no_prompts.create_workflow_job_node(workflow_job=workflow_job_unit)
+ mock_create.assert_called_once_with(
+ all_parents_must_converge=False,
+ extra_data={},
+ survey_passwords={},
+ char_prompts=wfjt_node_no_prompts.char_prompts,
+ inventory=None,
+ unified_job_template=wfjt_node_no_prompts.unified_job_template,
+ workflow_job=workflow_job_unit,
+ identifier=mocker.ANY,
+ execution_environment=None,
+ )
def test_create_with_prompts(self, wfjt_node_with_prompts, workflow_job_unit, credential, mocker):
mock_create = mocker.MagicMock()
- with mocker.patch('awx.main.models.WorkflowJobNode.objects.create', mock_create):
- wfjt_node_with_prompts.create_workflow_job_node(workflow_job=workflow_job_unit)
- mock_create.assert_called_once_with(
- all_parents_must_converge=False,
- extra_data={},
- survey_passwords={},
- char_prompts=wfjt_node_with_prompts.char_prompts,
- inventory=wfjt_node_with_prompts.inventory,
- unified_job_template=wfjt_node_with_prompts.unified_job_template,
- workflow_job=workflow_job_unit,
- identifier=mocker.ANY,
- execution_environment=None,
- )
+ mocker.patch('awx.main.models.WorkflowJobNode.objects.create', mock_create)
+ wfjt_node_with_prompts.create_workflow_job_node(workflow_job=workflow_job_unit)
+ mock_create.assert_called_once_with(
+ all_parents_must_converge=False,
+ extra_data={},
+ survey_passwords={},
+ char_prompts=wfjt_node_with_prompts.char_prompts,
+ inventory=wfjt_node_with_prompts.inventory,
+ unified_job_template=wfjt_node_with_prompts.unified_job_template,
+ workflow_job=workflow_job_unit,
+ identifier=mocker.ANY,
+ execution_environment=None,
+ )
@pytest.mark.django_db
diff --git a/awx/main/tests/unit/notifications/test_awssns.py b/awx/main/tests/unit/notifications/test_awssns.py
new file mode 100644
index 000000000000..0d18821fe3e3
--- /dev/null
+++ b/awx/main/tests/unit/notifications/test_awssns.py
@@ -0,0 +1,26 @@
+from unittest import mock
+from django.core.mail.message import EmailMessage
+
+import awx.main.notifications.awssns_backend as awssns_backend
+
+
+def test_send_messages():
+ with mock.patch('awx.main.notifications.awssns_backend.AWSSNSBackend._sns_publish') as sns_publish_mock:
+ aws_region = 'us-east-1'
+ sns_topic = f"arn:aws:sns:{aws_region}:111111111111:topic-mock"
+ backend = awssns_backend.AWSSNSBackend(aws_region=aws_region, aws_access_key_id=None, aws_secret_access_key=None, aws_session_token=None)
+ message = EmailMessage(
+ 'test subject',
+ {'body': 'test body'},
+ [],
+ [
+ sns_topic,
+ ],
+ )
+ sent_messages = backend.send_messages(
+ [
+ message,
+ ]
+ )
+ sns_publish_mock.assert_called_once_with(topic_arn=sns_topic, message=message.body)
+ assert sent_messages == 1
diff --git a/awx/main/tests/unit/notifications/test_grafana.py b/awx/main/tests/unit/notifications/test_grafana.py
index 70750e33150b..d4b9c31aa2d3 100644
--- a/awx/main/tests/unit/notifications/test_grafana.py
+++ b/awx/main/tests/unit/notifications/test_grafana.py
@@ -13,7 +13,7 @@ def test_send_messages():
m['started'] = dt.datetime.utcfromtimestamp(60).isoformat()
m['finished'] = dt.datetime.utcfromtimestamp(120).isoformat()
m['subject'] = "test subject"
- backend = grafana_backend.GrafanaBackend("testapikey")
+ backend = grafana_backend.GrafanaBackend("testapikey", dashboardId='', panelId='')
message = EmailMessage(
m['subject'],
{"started": m['started'], "finished": m['finished']},
@@ -43,7 +43,7 @@ def test_send_messages_with_no_verify_ssl():
m['started'] = dt.datetime.utcfromtimestamp(60).isoformat()
m['finished'] = dt.datetime.utcfromtimestamp(120).isoformat()
m['subject'] = "test subject"
- backend = grafana_backend.GrafanaBackend("testapikey", grafana_no_verify_ssl=True)
+ backend = grafana_backend.GrafanaBackend("testapikey", dashboardId='', panelId='', grafana_no_verify_ssl=True)
message = EmailMessage(
m['subject'],
{"started": m['started'], "finished": m['finished']},
@@ -74,7 +74,7 @@ def test_send_messages_with_dashboardid(dashboardId):
m['started'] = dt.datetime.utcfromtimestamp(60).isoformat()
m['finished'] = dt.datetime.utcfromtimestamp(120).isoformat()
m['subject'] = "test subject"
- backend = grafana_backend.GrafanaBackend("testapikey", dashboardId=dashboardId)
+ backend = grafana_backend.GrafanaBackend("testapikey", dashboardId=dashboardId, panelId='')
message = EmailMessage(
m['subject'],
{"started": m['started'], "finished": m['finished']},
@@ -97,7 +97,7 @@ def test_send_messages_with_dashboardid(dashboardId):
assert sent_messages == 1
-@pytest.mark.parametrize("panelId", [42, 0])
+@pytest.mark.parametrize("panelId", ['42', '0'])
def test_send_messages_with_panelid(panelId):
with mock.patch('awx.main.notifications.grafana_backend.requests') as requests_mock:
requests_mock.post.return_value.status_code = 200
@@ -105,7 +105,7 @@ def test_send_messages_with_panelid(panelId):
m['started'] = dt.datetime.utcfromtimestamp(60).isoformat()
m['finished'] = dt.datetime.utcfromtimestamp(120).isoformat()
m['subject'] = "test subject"
- backend = grafana_backend.GrafanaBackend("testapikey", dashboardId=None, panelId=panelId)
+ backend = grafana_backend.GrafanaBackend("testapikey", dashboardId='', panelId=panelId)
message = EmailMessage(
m['subject'],
{"started": m['started'], "finished": m['finished']},
@@ -122,7 +122,7 @@ def test_send_messages_with_panelid(panelId):
requests_mock.post.assert_called_once_with(
'https://example.com/api/annotations',
headers={'Content-Type': 'application/json', 'Authorization': 'Bearer testapikey'},
- json={'text': 'test subject', 'isRegion': True, 'timeEnd': 120000, 'panelId': panelId, 'time': 60000},
+ json={'text': 'test subject', 'isRegion': True, 'timeEnd': 120000, 'panelId': int(panelId), 'time': 60000},
verify=True,
)
assert sent_messages == 1
@@ -135,7 +135,7 @@ def test_send_messages_with_bothids():
m['started'] = dt.datetime.utcfromtimestamp(60).isoformat()
m['finished'] = dt.datetime.utcfromtimestamp(120).isoformat()
m['subject'] = "test subject"
- backend = grafana_backend.GrafanaBackend("testapikey", dashboardId=42, panelId=42)
+ backend = grafana_backend.GrafanaBackend("testapikey", dashboardId='42', panelId='42')
message = EmailMessage(
m['subject'],
{"started": m['started'], "finished": m['finished']},
@@ -158,6 +158,36 @@ def test_send_messages_with_bothids():
assert sent_messages == 1
+def test_send_messages_with_emptyids():
+ with mock.patch('awx.main.notifications.grafana_backend.requests') as requests_mock:
+ requests_mock.post.return_value.status_code = 200
+ m = {}
+ m['started'] = dt.datetime.utcfromtimestamp(60).isoformat()
+ m['finished'] = dt.datetime.utcfromtimestamp(120).isoformat()
+ m['subject'] = "test subject"
+ backend = grafana_backend.GrafanaBackend("testapikey", dashboardId='', panelId='')
+ message = EmailMessage(
+ m['subject'],
+ {"started": m['started'], "finished": m['finished']},
+ [],
+ [
+ 'https://example.com',
+ ],
+ )
+ sent_messages = backend.send_messages(
+ [
+ message,
+ ]
+ )
+ requests_mock.post.assert_called_once_with(
+ 'https://example.com/api/annotations',
+ headers={'Content-Type': 'application/json', 'Authorization': 'Bearer testapikey'},
+ json={'text': 'test subject', 'isRegion': True, 'timeEnd': 120000, 'time': 60000},
+ verify=True,
+ )
+ assert sent_messages == 1
+
+
def test_send_messages_with_tags():
with mock.patch('awx.main.notifications.grafana_backend.requests') as requests_mock:
requests_mock.post.return_value.status_code = 200
@@ -165,7 +195,7 @@ def test_send_messages_with_tags():
m['started'] = dt.datetime.utcfromtimestamp(60).isoformat()
m['finished'] = dt.datetime.utcfromtimestamp(120).isoformat()
m['subject'] = "test subject"
- backend = grafana_backend.GrafanaBackend("testapikey", dashboardId=None, panelId=None, annotation_tags=["ansible"])
+ backend = grafana_backend.GrafanaBackend("testapikey", dashboardId='', panelId='', annotation_tags=["ansible"])
message = EmailMessage(
m['subject'],
{"started": m['started'], "finished": m['finished']},
diff --git a/awx/main/tests/unit/notifications/test_webhook.py b/awx/main/tests/unit/notifications/test_webhook.py
index b2c92c59ab39..4abbf45b70ee 100644
--- a/awx/main/tests/unit/notifications/test_webhook.py
+++ b/awx/main/tests/unit/notifications/test_webhook.py
@@ -226,3 +226,140 @@ def test_send_messages_with_additional_headers():
allow_redirects=False,
)
assert sent_messages == 1
+
+
+def test_send_messages_with_redirects_ok():
+ with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
+ 'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
+ ) as version_mock:
+ # First two calls return redirects, third call returns 200
+ requests_mock.post.side_effect = [
+ mock.Mock(status_code=301, headers={"Location": "http://redirect1.com"}),
+ mock.Mock(status_code=307, headers={"Location": "http://redirect2.com"}),
+ mock.Mock(status_code=200),
+ ]
+ version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
+ backend = webhook_backend.WebhookBackend('POST', None)
+ message = EmailMessage(
+ 'test subject',
+ {'text': 'test body'},
+ [],
+ [
+ 'http://example.com',
+ ],
+ )
+ sent_messages = backend.send_messages(
+ [
+ message,
+ ]
+ )
+ assert requests_mock.post.call_count == 3
+ requests_mock.post.assert_called_with(
+ url='http://redirect2.com',
+ auth=None,
+ data=json.dumps({'text': 'test body'}, ensure_ascii=False).encode('utf-8'),
+ headers={'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'},
+ verify=True,
+ allow_redirects=False,
+ )
+ assert sent_messages == 1
+
+
+def test_send_messages_with_redirects_blank():
+ with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
+ 'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
+ ) as version_mock, mock.patch('awx.main.notifications.webhook_backend.logger') as logger_mock:
+ # First call returns a redirect with Location header, second call returns 301 but NO Location header
+ requests_mock.post.side_effect = [
+ mock.Mock(status_code=301, headers={"Location": "http://redirect1.com"}),
+ mock.Mock(status_code=301, headers={}), # 301 with no Location header
+ ]
+ version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
+ backend = webhook_backend.WebhookBackend('POST', None, fail_silently=True)
+ message = EmailMessage(
+ 'test subject',
+ {'text': 'test body'},
+ [],
+ [
+ 'http://example.com',
+ ],
+ )
+ sent_messages = backend.send_messages(
+ [
+ message,
+ ]
+ )
+ # Should make 2 requests (initial + 1 redirect attempt)
+ assert requests_mock.post.call_count == 2
+ # The error message should be logged
+ logger_mock.error.assert_called_once()
+ error_call_args = logger_mock.error.call_args[0][0]
+ assert "redirect to a blank URL" in error_call_args
+ assert sent_messages == 0
+
+
+def test_send_messages_with_redirects_max_retries_exceeded():
+ with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
+ 'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
+ ) as version_mock, mock.patch('awx.main.notifications.webhook_backend.logger') as logger_mock:
+ # Return MAX_RETRIES (5) redirect responses to exceed the retry limit
+ requests_mock.post.side_effect = [
+ mock.Mock(status_code=301, headers={"Location": "http://redirect1.com"}),
+ mock.Mock(status_code=301, headers={"Location": "http://redirect2.com"}),
+ mock.Mock(status_code=307, headers={"Location": "http://redirect3.com"}),
+ mock.Mock(status_code=301, headers={"Location": "http://redirect4.com"}),
+ mock.Mock(status_code=307, headers={"Location": "http://redirect5.com"}),
+ ]
+ version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
+ backend = webhook_backend.WebhookBackend('POST', None, fail_silently=True)
+ message = EmailMessage(
+ 'test subject',
+ {'text': 'test body'},
+ [],
+ [
+ 'http://example.com',
+ ],
+ )
+ sent_messages = backend.send_messages(
+ [
+ message,
+ ]
+ )
+ # Should make exactly 5 requests (MAX_RETRIES)
+ assert requests_mock.post.call_count == 5
+ # The error message should be logged for exceeding max retries
+ logger_mock.error.assert_called_once()
+ error_call_args = logger_mock.error.call_args[0][0]
+ assert "max number of retries" in error_call_args
+ assert "[5]" in error_call_args
+ assert sent_messages == 0
+
+
+def test_send_messages_with_error_status_code():
+ with mock.patch('awx.main.notifications.webhook_backend.requests') as requests_mock, mock.patch(
+ 'awx.main.notifications.webhook_backend.get_awx_http_client_headers'
+ ) as version_mock, mock.patch('awx.main.notifications.webhook_backend.logger') as logger_mock:
+ # Return a 404 error status code
+ requests_mock.post.return_value = mock.Mock(status_code=404)
+ version_mock.return_value = {'Content-Type': 'application/json', 'User-Agent': 'AWX 0.0.1.dev (open)'}
+ backend = webhook_backend.WebhookBackend('POST', None, fail_silently=True)
+ message = EmailMessage(
+ 'test subject',
+ {'text': 'test body'},
+ [],
+ [
+ 'http://example.com',
+ ],
+ )
+ sent_messages = backend.send_messages(
+ [
+ message,
+ ]
+ )
+ # Should make exactly 1 request
+ assert requests_mock.post.call_count == 1
+ # The error message should be logged
+ logger_mock.error.assert_called_once()
+ error_call_args = logger_mock.error.call_args[0][0]
+ assert "Error sending webhook notification: 404" in error_call_args
+ assert sent_messages == 0
diff --git a/awx/main/tests/unit/scheduler/test_dag_simple.py b/awx/main/tests/unit/scheduler/test_dag_simple.py
index 4bb141815754..1cea21e92d27 100644
--- a/awx/main/tests/unit/scheduler/test_dag_simple.py
+++ b/awx/main/tests/unit/scheduler/test_dag_simple.py
@@ -39,6 +39,6 @@ def simple_cycle_1(node_generator):
def test_has_cycle(simple_cycle_1):
- (g, nodes) = simple_cycle_1
+ g, nodes = simple_cycle_1
assert g.has_cycle() is True
diff --git a/awx/main/tests/unit/scheduler/test_dag_workflow.py b/awx/main/tests/unit/scheduler/test_dag_workflow.py
index a3225b76a3e7..b4681e6b90a8 100644
--- a/awx/main/tests/unit/scheduler/test_dag_workflow.py
+++ b/awx/main/tests/unit/scheduler/test_dag_workflow.py
@@ -86,13 +86,13 @@ def workflow_dag_root_children(self, wf_node_generator):
return (g, wf_root_nodes, wf_leaf_nodes)
def test_get_root_nodes(self, workflow_dag_root_children):
- (g, wf_root_nodes, ignore) = workflow_dag_root_children
+ g, wf_root_nodes, ignore = workflow_dag_root_children
assert set([n.id for n in wf_root_nodes]) == set([n['node_object'].id for n in g.get_root_nodes()])
class TestDNR:
def test_mark_dnr_nodes(self, workflow_dag_1):
- (g, nodes) = workflow_dag_1
+ g, nodes = workflow_dag_1
r'''
0
@@ -166,7 +166,7 @@ def simple_all_convergence(self, wf_node_generator):
return (g, nodes)
def test_simple_all_convergence(self, simple_all_convergence):
- (g, nodes) = simple_all_convergence
+ g, nodes = simple_all_convergence
dnr_nodes = g.mark_dnr_nodes()
assert 0 == len(dnr_nodes), "no nodes should be marked DNR"
@@ -197,7 +197,7 @@ def workflow_all_converge_1(self, wf_node_generator):
return (g, nodes)
def test_all_converge_edge_case_1(self, workflow_all_converge_1):
- (g, nodes) = workflow_all_converge_1
+ g, nodes = workflow_all_converge_1
dnr_nodes = g.mark_dnr_nodes()
assert 2 == len(dnr_nodes), "node[1] and node[2] should be marked DNR"
assert nodes[1] == dnr_nodes[0], "Node 1 should be marked DNR"
@@ -233,7 +233,7 @@ def workflow_all_converge_2(self, wf_node_generator):
return (g, nodes)
def test_all_converge_edge_case_2(self, workflow_all_converge_2):
- (g, nodes) = workflow_all_converge_2
+ g, nodes = workflow_all_converge_2
dnr_nodes = g.mark_dnr_nodes()
assert 1 == len(dnr_nodes), "1 and only 1 node should be marked DNR"
assert nodes[2] == dnr_nodes[0], "Node 3 should be marked DNR"
@@ -268,7 +268,7 @@ def workflow_all_converge_will_run(self, wf_node_generator):
return (g, nodes)
def test_workflow_all_converge_will_run(self, workflow_all_converge_will_run):
- (g, nodes) = workflow_all_converge_will_run
+ g, nodes = workflow_all_converge_will_run
dnr_nodes = g.mark_dnr_nodes()
assert 0 == len(dnr_nodes), "No nodes should get marked DNR"
@@ -306,7 +306,7 @@ def workflow_all_converge_dnr(self, wf_node_generator):
return (g, nodes)
def test_workflow_all_converge_while_parent_runs(self, workflow_all_converge_dnr):
- (g, nodes) = workflow_all_converge_dnr
+ g, nodes = workflow_all_converge_dnr
dnr_nodes = g.mark_dnr_nodes()
assert 0 == len(dnr_nodes), "No nodes should get marked DNR"
@@ -315,7 +315,7 @@ def test_workflow_all_converge_while_parent_runs(self, workflow_all_converge_dnr
def test_workflow_all_converge_with_incorrect_parent(self, workflow_all_converge_dnr):
# Another tick of the scheduler
- (g, nodes) = workflow_all_converge_dnr
+ g, nodes = workflow_all_converge_dnr
nodes[1].job.status = 'successful'
dnr_nodes = g.mark_dnr_nodes()
assert 1 == len(dnr_nodes), "1 and only 1 node should be marked DNR"
@@ -326,7 +326,7 @@ def test_workflow_all_converge_with_incorrect_parent(self, workflow_all_converge
def test_workflow_all_converge_runs(self, workflow_all_converge_dnr):
# Trick the scheduler again to make sure the convergence node acutally runs
- (g, nodes) = workflow_all_converge_dnr
+ g, nodes = workflow_all_converge_dnr
nodes[1].job.status = 'failed'
dnr_nodes = g.mark_dnr_nodes()
assert 0 == len(dnr_nodes), "No nodes should be marked DNR"
@@ -375,7 +375,7 @@ def workflow_all_converge_deep_dnr_tree(self, wf_node_generator):
return (g, nodes)
def test_workflow_all_converge_deep_dnr_tree(self, workflow_all_converge_deep_dnr_tree):
- (g, nodes) = workflow_all_converge_deep_dnr_tree
+ g, nodes = workflow_all_converge_deep_dnr_tree
dnr_nodes = g.mark_dnr_nodes()
assert 4 == len(dnr_nodes), "All nodes w/ no jobs should be marked DNR"
@@ -391,7 +391,7 @@ def test_workflow_all_converge_deep_dnr_tree(self, workflow_all_converge_deep_dn
class TestIsWorkflowDone:
@pytest.fixture
def workflow_dag_2(self, workflow_dag_1):
- (g, nodes) = workflow_dag_1
+ g, nodes = workflow_dag_1
r'''
S0
/\
@@ -416,7 +416,7 @@ def workflow_dag_2(self, workflow_dag_1):
@pytest.fixture
def workflow_dag_failed(self, workflow_dag_1):
- (g, nodes) = workflow_dag_1
+ g, nodes = workflow_dag_1
r'''
S0
/\
@@ -453,7 +453,7 @@ def workflow_dag_canceled(self, wf_node_generator):
@pytest.fixture
def workflow_dag_failure(self, workflow_dag_canceled):
- (g, nodes) = workflow_dag_canceled
+ g, nodes = workflow_dag_canceled
nodes[0].job.status = 'failed'
return (g, nodes)
@@ -463,7 +463,7 @@ def test_done(self, workflow_dag_2):
assert g.is_workflow_done() is False
def test_workflow_done_and_failed(self, workflow_dag_failed):
- (g, nodes) = workflow_dag_failed
+ g, nodes = workflow_dag_failed
assert g.is_workflow_done() is True
assert g.has_workflow_failed() == (
@@ -477,7 +477,7 @@ def test_workflow_done_and_failed(self, workflow_dag_failed):
)
def test_is_workflow_done_no_unified_job_tempalte_end(self, workflow_dag_failed):
- (g, nodes) = workflow_dag_failed
+ g, nodes = workflow_dag_failed
nodes[2].unified_job_template = None
@@ -492,7 +492,7 @@ def test_is_workflow_done_no_unified_job_tempalte_end(self, workflow_dag_failed)
)
def test_is_workflow_done_no_unified_job_tempalte_begin(self, workflow_dag_1):
- (g, nodes) = workflow_dag_1
+ g, nodes = workflow_dag_1
nodes[0].unified_job_template = None
g.mark_dnr_nodes()
@@ -508,7 +508,7 @@ def test_is_workflow_done_no_unified_job_tempalte_begin(self, workflow_dag_1):
)
def test_canceled_should_fail(self, workflow_dag_canceled):
- (g, nodes) = workflow_dag_canceled
+ g, nodes = workflow_dag_canceled
assert g.has_workflow_failed() == (
True,
@@ -521,7 +521,7 @@ def test_canceled_should_fail(self, workflow_dag_canceled):
)
def test_failure_should_fail(self, workflow_dag_failure):
- (g, nodes) = workflow_dag_failure
+ g, nodes = workflow_dag_failure
assert g.has_workflow_failed() == (
True,
@@ -555,13 +555,13 @@ def workflow_dag_canceled(self, wf_node_generator):
return (g, nodes)
def test_cancel_still_runs_children(self, workflow_dag_canceled):
- (g, nodes) = workflow_dag_canceled
+ g, nodes = workflow_dag_canceled
g.mark_dnr_nodes()
assert set([nodes[1], nodes[2]]) == set(g.bfs_nodes_to_run())
-@pytest.mark.skip(reason="Run manually to re-generate doc images")
+@pytest.mark.xfail(reason="Run manually to re-generate doc images")
class TestDocsExample:
@pytest.fixture
def complex_dag(self, wf_node_generator):
@@ -587,7 +587,7 @@ def complex_dag(self, wf_node_generator):
return (g, nodes)
def test_dnr_step(self, complex_dag):
- (g, nodes) = complex_dag
+ g, nodes = complex_dag
base_dir = '/awx_devel'
g.generate_graphviz_plot(file_name=os.path.join(base_dir, "workflow_step0.gv"))
diff --git a/awx/main/tests/unit/settings/test_defaults.py b/awx/main/tests/unit/settings/test_defaults.py
index a7f5eeeca8db..10cb5561a7f4 100644
--- a/awx/main/tests/unit/settings/test_defaults.py
+++ b/awx/main/tests/unit/settings/test_defaults.py
@@ -1,20 +1,19 @@
import pytest
from django.conf import settings
-from datetime import timedelta
@pytest.mark.parametrize(
- "job_name,function_path",
+ "task_name",
[
- ('tower_scheduler', 'awx.main.tasks.system.awx_periodic_scheduler'),
+ 'awx.main.tasks.system.awx_periodic_scheduler',
],
)
-def test_CELERYBEAT_SCHEDULE(mocker, job_name, function_path):
- assert job_name in settings.CELERYBEAT_SCHEDULE
- assert 'schedule' in settings.CELERYBEAT_SCHEDULE[job_name]
- assert type(settings.CELERYBEAT_SCHEDULE[job_name]['schedule']) is timedelta
- assert settings.CELERYBEAT_SCHEDULE[job_name]['task'] == function_path
+def test_DISPATCHER_SCHEDULE(mocker, task_name):
+ assert task_name in settings.DISPATCHER_SCHEDULE
+ assert 'schedule' in settings.DISPATCHER_SCHEDULE[task_name]
+ assert type(settings.DISPATCHER_SCHEDULE[task_name]['schedule']) in (int, float)
+ assert settings.DISPATCHER_SCHEDULE[task_name]['task'] == task_name
# Ensures that the function exists
- mocker.patch(function_path)
+ mocker.patch(task_name)
diff --git a/awx/main/tests/unit/settings/test_k8s_resource_setttings.py b/awx/main/tests/unit/settings/test_k8s_resource_setttings.py
index a2899a8561fe..65fa45d95a10 100644
--- a/awx/main/tests/unit/settings/test_k8s_resource_setttings.py
+++ b/awx/main/tests/unit/settings/test_k8s_resource_setttings.py
@@ -36,7 +36,9 @@ def test_SYSTEM_TASK_ABS_MEM_conversion(value, converted_value, mem_capacity):
mock_settings.IS_K8S = True
assert convert_mem_str_to_bytes(value) == converted_value
assert get_corrected_memory(-1) == converted_value
- assert get_mem_effective_capacity(-1) == mem_capacity
+ assert get_mem_effective_capacity(1, is_control_node=True) == mem_capacity
+ # SYSTEM_TASK_ABS_MEM should not effect memory and capacity for execution nodes
+ assert get_mem_effective_capacity(2147483648, is_control_node=False) == 20
@pytest.mark.parametrize(
@@ -58,4 +60,6 @@ def test_SYSTEM_TASK_ABS_CPU_conversion(value, converted_value, cpu_capacity):
mock_settings.SYSTEM_TASK_FORKS_CPU = 4
assert convert_cpu_str_to_decimal_cpu(value) == converted_value
assert get_corrected_cpu(-1) == converted_value
- assert get_cpu_effective_capacity(-1) == cpu_capacity
+ assert get_cpu_effective_capacity(-1, is_control_node=True) == cpu_capacity
+ # SYSTEM_TASK_ABS_CPU should not effect cpu count and capacity for execution nodes
+ assert get_cpu_effective_capacity(2.0, is_control_node=False) == 8
diff --git a/awx/main/tests/unit/tasks/test_host_indirect_unit.py b/awx/main/tests/unit/tasks/test_host_indirect_unit.py
new file mode 100644
index 000000000000..2b128ca6fafe
--- /dev/null
+++ b/awx/main/tests/unit/tasks/test_host_indirect_unit.py
@@ -0,0 +1,56 @@
+import copy
+
+import pytest
+
+from awx.main.tasks.host_indirect import get_hashable_form
+
+
+class TestHashableForm:
+ @pytest.mark.parametrize(
+ 'data',
+ [
+ {'a': 'b'},
+ ['a', 'b'],
+ ('a', 'b'),
+ {'a': {'b': 'c'}},
+ {'a': ['b', 'c']},
+ {'a': ('b', 'c')},
+ ['a', ['b', 'c']],
+ ['a', ('b', 'c')],
+ ['a', {'b': 'c'}],
+ ],
+ )
+ def test_compare_equal_data(self, data):
+ other_data = copy.deepcopy(data)
+ # A tuple of scalars may be cached so ids could legitimately be the same
+ if data != ('a', 'b'):
+ assert id(data) != id(other_data) # sanity
+ assert id(get_hashable_form(data)) != id(get_hashable_form(data))
+
+ assert get_hashable_form(data) == get_hashable_form(data)
+ assert hash(get_hashable_form(data)) == hash(get_hashable_form(data))
+
+ assert get_hashable_form(data) in {get_hashable_form(data): 1} # test lookup hit
+
+ @pytest.mark.parametrize(
+ 'data, other_data',
+ [
+ [{'a': 'b'}, {'a': 'c'}],
+ [{'a': 'b'}, {'a': 'b', 'c': 'd'}],
+ [['a', 'b'], ['a', 'c']],
+ [('a', 'b'), ('a', 'c')],
+ [{'a': {'b': 'c'}}, {'a': {'b': 'd'}}],
+ [{'a': ['b', 'c']}, {'a': ['b', 'd']}],
+ [{'a': ('b', 'c')}, {'a': ('b', 'd')}],
+ [['a', ['b', 'c']], ['a', ['b', 'd']]],
+ [['a', ('b', 'c')], ['a', ('b', 'd')]],
+ [['a', {'b': 'c'}], ['a', {'b': 'd'}]],
+ ],
+ )
+ def test_compare_different_data(self, data, other_data):
+ assert data != other_data # sanity, otherwise why test this?
+ assert get_hashable_form(data) != get_hashable_form(other_data)
+ assert hash(get_hashable_form(data)) != hash(get_hashable_form(other_data))
+
+ assert get_hashable_form(other_data) not in {get_hashable_form(data): 1} # test lookup miss
+ assert get_hashable_form(data) not in {get_hashable_form(other_data): 1}
diff --git a/awx/main/tests/unit/tasks/test_jobs.py b/awx/main/tests/unit/tasks/test_jobs.py
new file mode 100644
index 000000000000..b678add12d1e
--- /dev/null
+++ b/awx/main/tests/unit/tasks/test_jobs.py
@@ -0,0 +1,485 @@
+# -*- coding: utf-8 -*-
+import os
+import tempfile
+import shutil
+
+import pytest
+from unittest import mock
+
+from awx.main.models import (
+ Inventory,
+ Host,
+)
+
+from django.utils.timezone import now
+from django.db.models.query import QuerySet
+
+from awx.main.models import (
+ Job,
+ Organization,
+ Project,
+ JobTemplate,
+ UnifiedJobTemplate,
+ InstanceGroup,
+ ExecutionEnvironment,
+ ProjectUpdate,
+ InventoryUpdate,
+ InventorySource,
+ AdHocCommand,
+)
+from awx.main.tasks import jobs
+from ansible_base.lib.workload_identity.controller import AutomationControllerJobScope
+
+
+@pytest.fixture
+def private_data_dir():
+ private_data = tempfile.mkdtemp(prefix='awx_')
+ for subfolder in ('inventory', 'env'):
+ runner_subfolder = os.path.join(private_data, subfolder)
+ os.makedirs(runner_subfolder, exist_ok=True)
+ yield private_data
+ shutil.rmtree(private_data, True)
+
+
+@mock.patch('awx.main.tasks.facts.settings')
+@mock.patch('awx.main.tasks.jobs.create_partition', return_value=True)
+def test_pre_post_run_hook_facts(mock_create_partition, mock_facts_settings, private_data_dir, execution_environment):
+ # Create mocked inventory and host queryset
+ inventory = mock.MagicMock(spec=Inventory, pk=1)
+ host1 = mock.MagicMock(spec=Host, id=1, name='host1', ansible_facts={"a": 1, "b": 2}, ansible_facts_modified=now(), inventory=inventory)
+ host2 = mock.MagicMock(spec=Host, id=2, name='host2', ansible_facts={"a": 1, "b": 2}, ansible_facts_modified=now(), inventory=inventory)
+
+ # Mock hosts queryset
+ hosts = [host1, host2]
+ qs_hosts = mock.MagicMock(spec=QuerySet)
+ qs_hosts._result_cache = hosts
+ qs_hosts.only.return_value = hosts
+ qs_hosts.count.side_effect = lambda: len(qs_hosts._result_cache)
+ inventory.hosts = qs_hosts
+
+ # Create mocked job object
+ org = mock.MagicMock(spec=Organization, pk=1)
+ proj = mock.MagicMock(spec=Project, pk=1, organization=org)
+ job = mock.MagicMock(
+ spec=Job,
+ use_fact_cache=True,
+ project=proj,
+ organization=org,
+ job_slice_number=1,
+ job_slice_count=1,
+ inventory=inventory,
+ execution_environment=execution_environment,
+ )
+ job.get_hosts_for_fact_cache = Job.get_hosts_for_fact_cache.__get__(job)
+ job.job_env.get = mock.MagicMock(return_value=private_data_dir)
+
+ # Mock RunJob task
+ mock_facts_settings.ANSIBLE_FACT_CACHE_TIMEOUT = False
+ task = jobs.RunJob()
+ task.instance = job
+ task.update_model = mock.Mock(return_value=job)
+ task.model.objects.get = mock.Mock(return_value=job)
+
+ # Run pre_run_hook
+ task.facts_write_time = task.pre_run_hook(job, private_data_dir)
+
+ # Add a third mocked host
+ host3 = mock.MagicMock(spec=Host, id=3, name='host3', ansible_facts={"added": True}, ansible_facts_modified=now(), inventory=inventory)
+ qs_hosts._result_cache.append(host3)
+ assert inventory.hosts.count() == 3
+
+ # Run post_run_hook
+ task.runner_callback.artifacts_processed = mock.MagicMock(return_value=True)
+ task.post_run_hook(job, "success")
+
+ # Verify final host facts
+ assert qs_hosts._result_cache[2].ansible_facts == {"added": True}
+
+
+@mock.patch('awx.main.tasks.facts.bulk_update_sorted_by_id')
+@mock.patch('awx.main.tasks.facts.settings')
+@mock.patch('awx.main.tasks.jobs.create_partition', return_value=True)
+def test_pre_post_run_hook_facts_deleted_sliced(mock_create_partition, mock_facts_settings, private_data_dir, execution_environment):
+ # Fully mocked inventory
+ mock_inventory = mock.MagicMock(spec=Inventory)
+
+ # Create 999 mocked Host instances
+ hosts = []
+ for i in range(999):
+ host = mock.MagicMock(spec=Host)
+ host.id = i
+ host.name = f'host{i}'
+ host.ansible_facts = {"a": 1, "b": 2}
+ host.ansible_facts_modified = now()
+ host.inventory = mock_inventory
+ hosts.append(host)
+
+ # Mock inventory.hosts behavior
+ mock_qs_hosts = mock.MagicMock()
+ mock_qs_hosts.only.return_value = hosts
+ mock_qs_hosts.count.return_value = 999
+ mock_inventory.hosts = mock_qs_hosts
+
+ # Mock Organization and Project
+ org = mock.MagicMock(spec=Organization)
+ proj = mock.MagicMock(spec=Project)
+ proj.organization = org
+
+ # Mock job object
+ job = mock.MagicMock(spec=Job)
+ job.use_fact_cache = True
+ job.project = proj
+ job.organization = org
+ job.job_slice_number = 1
+ job.job_slice_count = 3
+ job.execution_environment = execution_environment
+ job.inventory = mock_inventory
+ job.job_env.get.return_value = private_data_dir
+
+ # Bind actual method for host filtering
+ job.get_hosts_for_fact_cache = Job.get_hosts_for_fact_cache.__get__(job)
+
+ # Mock task instance
+ mock_facts_settings.ANSIBLE_FACT_CACHE_TIMEOUT = False
+ task = jobs.RunJob()
+ task.instance = job
+ task.update_model = mock.Mock(return_value=job)
+ task.model.objects.get = mock.Mock(return_value=job)
+
+ # Call pre_run_hook
+ task.facts_write_time = task.pre_run_hook(job, private_data_dir)
+
+ # Simulate one host deletion
+ hosts.pop(1)
+ mock_qs_hosts.count.return_value = 998
+
+ # Call post_run_hook
+ task.runner_callback.artifacts_processed = mock.MagicMock(return_value=True)
+ task.post_run_hook(job, "success")
+
+ # Assert that ansible_facts were preserved
+ for host in hosts:
+ assert host.ansible_facts == {"a": 1, "b": 2}
+
+ # Add expected failure cases
+ failures = []
+ for host in hosts:
+ try:
+ assert host.ansible_facts == {"a": 1, "b": 2, "unexpected_key": "bad"}
+ except AssertionError:
+ failures.append(f"Host named {host.name} has facts {host.ansible_facts}")
+
+ assert len(failures) > 0, f"Failures occurred for the following hosts: {failures}"
+
+
+@mock.patch('awx.main.tasks.facts.bulk_update_sorted_by_id')
+@mock.patch('awx.main.tasks.facts.settings')
+def test_invalid_host_facts(mock_facts_settings, bulk_update_sorted_by_id, private_data_dir, execution_environment):
+ inventory = Inventory(pk=1)
+ mock_inventory = mock.MagicMock(spec=Inventory, wraps=inventory)
+ mock_inventory._state = mock.MagicMock()
+
+ hosts = [
+ Host(id=0, name='host0', ansible_facts={"a": 1, "b": 2}, ansible_facts_modified=now(), inventory=mock_inventory),
+ Host(id=1, name='host1', ansible_facts={"a": 1, "b": 2, "unexpected_key": "bad"}, ansible_facts_modified=now(), inventory=mock_inventory),
+ ]
+ mock_inventory.hosts = hosts
+
+ failures = []
+ for host in mock_inventory.hosts:
+ assert "a" in host.ansible_facts
+ if "unexpected_key" in host.ansible_facts:
+ failures.append(host.name)
+
+ mock_facts_settings.SOME_SETTING = True
+ bulk_update_sorted_by_id(Host, mock_inventory.hosts, fields=['ansible_facts'])
+
+ with pytest.raises(pytest.fail.Exception):
+ if failures:
+ pytest.fail(f" {len(failures)} facts cleared failures : {','.join(failures)}")
+
+
+@pytest.mark.parametrize(
+ "job_attrs,expected_claims",
+ [
+ (
+ {
+ 'id': 100,
+ 'name': 'Test Job',
+ 'job_type': 'run',
+ 'launch_type': 'manual',
+ 'playbook': 'site.yml',
+ 'organization': Organization(id=1, name='Test Org'),
+ 'inventory': Inventory(id=2, name='Test Inventory'),
+ 'project': Project(id=3, name='Test Project'),
+ 'execution_environment': ExecutionEnvironment(id=4, name='Test EE'),
+ 'job_template': JobTemplate(id=5, name='Test Job Template'),
+ 'unified_job_template': UnifiedJobTemplate(pk=6, id=6, name='Test Unified Job Template'),
+ 'instance_group': InstanceGroup(id=7, name='Test Instance Group'),
+ },
+ {
+ AutomationControllerJobScope.CLAIM_JOB_ID: 100,
+ AutomationControllerJobScope.CLAIM_JOB_NAME: 'Test Job',
+ AutomationControllerJobScope.CLAIM_JOB_TYPE: 'run',
+ AutomationControllerJobScope.CLAIM_LAUNCH_TYPE: 'manual',
+ AutomationControllerJobScope.CLAIM_PLAYBOOK_NAME: 'site.yml',
+ AutomationControllerJobScope.CLAIM_ORGANIZATION_NAME: 'Test Org',
+ AutomationControllerJobScope.CLAIM_ORGANIZATION_ID: 1,
+ AutomationControllerJobScope.CLAIM_INVENTORY_NAME: 'Test Inventory',
+ AutomationControllerJobScope.CLAIM_INVENTORY_ID: 2,
+ AutomationControllerJobScope.CLAIM_EXECUTION_ENVIRONMENT_NAME: 'Test EE',
+ AutomationControllerJobScope.CLAIM_EXECUTION_ENVIRONMENT_ID: 4,
+ AutomationControllerJobScope.CLAIM_PROJECT_NAME: 'Test Project',
+ AutomationControllerJobScope.CLAIM_PROJECT_ID: 3,
+ AutomationControllerJobScope.CLAIM_JOB_TEMPLATE_NAME: 'Test Job Template',
+ AutomationControllerJobScope.CLAIM_JOB_TEMPLATE_ID: 5,
+ AutomationControllerJobScope.CLAIM_UNIFIED_JOB_TEMPLATE_NAME: 'Test Unified Job Template',
+ AutomationControllerJobScope.CLAIM_UNIFIED_JOB_TEMPLATE_ID: 6,
+ AutomationControllerJobScope.CLAIM_INSTANCE_GROUP_NAME: 'Test Instance Group',
+ AutomationControllerJobScope.CLAIM_INSTANCE_GROUP_ID: 7,
+ },
+ ),
+ (
+ {'id': 100, 'name': 'Test', 'job_type': 'run', 'launch_type': 'manual', 'organization': Organization(id=1, name='')},
+ {
+ AutomationControllerJobScope.CLAIM_JOB_ID: 100,
+ AutomationControllerJobScope.CLAIM_JOB_NAME: 'Test',
+ AutomationControllerJobScope.CLAIM_JOB_TYPE: 'run',
+ AutomationControllerJobScope.CLAIM_LAUNCH_TYPE: 'manual',
+ AutomationControllerJobScope.CLAIM_ORGANIZATION_ID: 1,
+ AutomationControllerJobScope.CLAIM_ORGANIZATION_NAME: '',
+ AutomationControllerJobScope.CLAIM_PLAYBOOK_NAME: '',
+ },
+ ),
+ ],
+)
+def test_populate_claims_for_workload(job_attrs, expected_claims):
+ job = Job()
+
+ for attr, value in job_attrs.items():
+ setattr(job, attr, value)
+
+ claims = jobs.populate_claims_for_workload(job)
+ assert claims == expected_claims
+
+
+@pytest.mark.parametrize(
+ "workload_attrs,expected_claims",
+ [
+ (
+ {
+ 'id': 200,
+ 'name': 'Git Sync',
+ 'job_type': 'check',
+ 'launch_type': 'sync',
+ 'organization': Organization(id=1, name='Test Org'),
+ 'project': Project(pk=3, id=3, name='Test Project'),
+ 'unified_job_template': Project(pk=3, id=3, name='Test Project'),
+ 'execution_environment': ExecutionEnvironment(id=4, name='Test EE'),
+ 'instance_group': InstanceGroup(id=7, name='Test Instance Group'),
+ },
+ {
+ AutomationControllerJobScope.CLAIM_JOB_ID: 200,
+ AutomationControllerJobScope.CLAIM_JOB_NAME: 'Git Sync',
+ AutomationControllerJobScope.CLAIM_JOB_TYPE: 'check',
+ AutomationControllerJobScope.CLAIM_LAUNCH_TYPE: 'sync',
+ AutomationControllerJobScope.CLAIM_LAUNCHED_BY_NAME: 'Test Project',
+ AutomationControllerJobScope.CLAIM_LAUNCHED_BY_ID: 3,
+ AutomationControllerJobScope.CLAIM_ORGANIZATION_NAME: 'Test Org',
+ AutomationControllerJobScope.CLAIM_ORGANIZATION_ID: 1,
+ AutomationControllerJobScope.CLAIM_PROJECT_NAME: 'Test Project',
+ AutomationControllerJobScope.CLAIM_PROJECT_ID: 3,
+ AutomationControllerJobScope.CLAIM_UNIFIED_JOB_TEMPLATE_NAME: 'Test Project',
+ AutomationControllerJobScope.CLAIM_UNIFIED_JOB_TEMPLATE_ID: 3,
+ AutomationControllerJobScope.CLAIM_EXECUTION_ENVIRONMENT_NAME: 'Test EE',
+ AutomationControllerJobScope.CLAIM_EXECUTION_ENVIRONMENT_ID: 4,
+ AutomationControllerJobScope.CLAIM_INSTANCE_GROUP_NAME: 'Test Instance Group',
+ AutomationControllerJobScope.CLAIM_INSTANCE_GROUP_ID: 7,
+ },
+ ),
+ (
+ {
+ 'id': 201,
+ 'name': 'Minimal Project Update',
+ 'job_type': 'run',
+ 'launch_type': 'manual',
+ },
+ {
+ AutomationControllerJobScope.CLAIM_JOB_ID: 201,
+ AutomationControllerJobScope.CLAIM_JOB_NAME: 'Minimal Project Update',
+ AutomationControllerJobScope.CLAIM_JOB_TYPE: 'run',
+ AutomationControllerJobScope.CLAIM_LAUNCH_TYPE: 'manual',
+ },
+ ),
+ ],
+)
+def test_populate_claims_for_project_update(workload_attrs, expected_claims):
+ project_update = ProjectUpdate()
+ for attr, value in workload_attrs.items():
+ setattr(project_update, attr, value)
+
+ claims = jobs.populate_claims_for_workload(project_update)
+ assert claims == expected_claims
+
+
+@pytest.mark.parametrize(
+ "workload_attrs,expected_claims",
+ [
+ (
+ {
+ 'id': 300,
+ 'name': 'AWS Sync',
+ 'launch_type': 'scheduled',
+ 'organization': Organization(id=1, name='Test Org'),
+ 'inventory': Inventory(id=2, name='AWS Inventory'),
+ 'unified_job_template': InventorySource(pk=8, id=8, name='AWS Source'),
+ 'execution_environment': ExecutionEnvironment(id=4, name='Test EE'),
+ 'instance_group': InstanceGroup(id=7, name='Test Instance Group'),
+ },
+ {
+ AutomationControllerJobScope.CLAIM_JOB_ID: 300,
+ AutomationControllerJobScope.CLAIM_JOB_NAME: 'AWS Sync',
+ AutomationControllerJobScope.CLAIM_LAUNCH_TYPE: 'scheduled',
+ AutomationControllerJobScope.CLAIM_ORGANIZATION_NAME: 'Test Org',
+ AutomationControllerJobScope.CLAIM_ORGANIZATION_ID: 1,
+ AutomationControllerJobScope.CLAIM_INVENTORY_NAME: 'AWS Inventory',
+ AutomationControllerJobScope.CLAIM_INVENTORY_ID: 2,
+ AutomationControllerJobScope.CLAIM_UNIFIED_JOB_TEMPLATE_NAME: 'AWS Source',
+ AutomationControllerJobScope.CLAIM_UNIFIED_JOB_TEMPLATE_ID: 8,
+ AutomationControllerJobScope.CLAIM_EXECUTION_ENVIRONMENT_NAME: 'Test EE',
+ AutomationControllerJobScope.CLAIM_EXECUTION_ENVIRONMENT_ID: 4,
+ AutomationControllerJobScope.CLAIM_INSTANCE_GROUP_NAME: 'Test Instance Group',
+ AutomationControllerJobScope.CLAIM_INSTANCE_GROUP_ID: 7,
+ },
+ ),
+ (
+ {
+ 'id': 301,
+ 'name': 'Minimal Inventory Update',
+ 'launch_type': 'manual',
+ },
+ {
+ AutomationControllerJobScope.CLAIM_JOB_ID: 301,
+ AutomationControllerJobScope.CLAIM_JOB_NAME: 'Minimal Inventory Update',
+ AutomationControllerJobScope.CLAIM_LAUNCH_TYPE: 'manual',
+ },
+ ),
+ ],
+)
+def test_populate_claims_for_inventory_update(workload_attrs, expected_claims):
+ inventory_update = InventoryUpdate()
+ for attr, value in workload_attrs.items():
+ setattr(inventory_update, attr, value)
+
+ claims = jobs.populate_claims_for_workload(inventory_update)
+ assert claims == expected_claims
+
+
+@pytest.mark.parametrize(
+ "workload_attrs,expected_claims",
+ [
+ (
+ {
+ 'id': 400,
+ 'name': 'Ping All Hosts',
+ 'job_type': 'run',
+ 'launch_type': 'manual',
+ 'organization': Organization(id=1, name='Test Org'),
+ 'inventory': Inventory(id=2, name='Test Inventory'),
+ 'execution_environment': ExecutionEnvironment(id=4, name='Test EE'),
+ 'instance_group': InstanceGroup(id=7, name='Test Instance Group'),
+ },
+ {
+ AutomationControllerJobScope.CLAIM_JOB_ID: 400,
+ AutomationControllerJobScope.CLAIM_JOB_NAME: 'Ping All Hosts',
+ AutomationControllerJobScope.CLAIM_JOB_TYPE: 'run',
+ AutomationControllerJobScope.CLAIM_LAUNCH_TYPE: 'manual',
+ AutomationControllerJobScope.CLAIM_ORGANIZATION_NAME: 'Test Org',
+ AutomationControllerJobScope.CLAIM_ORGANIZATION_ID: 1,
+ AutomationControllerJobScope.CLAIM_INVENTORY_NAME: 'Test Inventory',
+ AutomationControllerJobScope.CLAIM_INVENTORY_ID: 2,
+ AutomationControllerJobScope.CLAIM_EXECUTION_ENVIRONMENT_NAME: 'Test EE',
+ AutomationControllerJobScope.CLAIM_EXECUTION_ENVIRONMENT_ID: 4,
+ AutomationControllerJobScope.CLAIM_INSTANCE_GROUP_NAME: 'Test Instance Group',
+ AutomationControllerJobScope.CLAIM_INSTANCE_GROUP_ID: 7,
+ },
+ ),
+ (
+ {
+ 'id': 401,
+ 'name': 'Minimal Ad Hoc',
+ 'job_type': 'run',
+ 'launch_type': 'manual',
+ },
+ {
+ AutomationControllerJobScope.CLAIM_JOB_ID: 401,
+ AutomationControllerJobScope.CLAIM_JOB_NAME: 'Minimal Ad Hoc',
+ AutomationControllerJobScope.CLAIM_JOB_TYPE: 'run',
+ AutomationControllerJobScope.CLAIM_LAUNCH_TYPE: 'manual',
+ },
+ ),
+ ],
+)
+def test_populate_claims_for_adhoc_command(workload_attrs, expected_claims):
+ adhoc_command = AdHocCommand()
+ for attr, value in workload_attrs.items():
+ setattr(adhoc_command, attr, value)
+
+ claims = jobs.populate_claims_for_workload(adhoc_command)
+ assert claims == expected_claims
+
+
+@mock.patch('awx.main.tasks.jobs.get_workload_identity_client')
+def test_retrieve_workload_identity_jwt_returns_jwt_from_client(mock_get_client):
+ """retrieve_workload_identity_jwt returns the JWT string from the client."""
+ mock_client = mock.MagicMock()
+ mock_response = mock.MagicMock()
+ mock_response.jwt = 'eyJ.test.jwt'
+ mock_client.request_workload_jwt.return_value = mock_response
+ mock_get_client.return_value = mock_client
+
+ unified_job = Job()
+ unified_job.id = 42
+ unified_job.name = 'Test Job'
+ unified_job.launch_type = 'manual'
+ unified_job.organization = Organization(id=1, name='Test Org')
+ unified_job.unified_job_template = None
+ unified_job.instance_group = None
+
+ result = jobs.retrieve_workload_identity_jwt(unified_job, audience='https://api.example.com', scope='aap_controller_automation_job')
+
+ assert result == 'eyJ.test.jwt'
+ mock_client.request_workload_jwt.assert_called_once()
+ call_kwargs = mock_client.request_workload_jwt.call_args[1]
+ assert call_kwargs['audience'] == 'https://api.example.com'
+ assert call_kwargs['scope'] == 'aap_controller_automation_job'
+ assert 'claims' in call_kwargs
+ assert call_kwargs['claims'][AutomationControllerJobScope.CLAIM_JOB_ID] == 42
+ assert call_kwargs['claims'][AutomationControllerJobScope.CLAIM_JOB_NAME] == 'Test Job'
+
+
+@mock.patch('awx.main.tasks.jobs.get_workload_identity_client')
+def test_retrieve_workload_identity_jwt_passes_audience_and_scope(mock_get_client):
+ """retrieve_workload_identity_jwt passes audience and scope to the client."""
+ mock_client = mock.MagicMock()
+ mock_client.request_workload_jwt.return_value = mock.MagicMock(jwt='token')
+ mock_get_client.return_value = mock_client
+
+ unified_job = mock.MagicMock()
+ audience = 'custom_audience'
+ scope = 'custom_scope'
+ with mock.patch('awx.main.tasks.jobs.populate_claims_for_workload', return_value={'job_id': 1}):
+ jobs.retrieve_workload_identity_jwt(unified_job, audience=audience, scope=scope)
+
+ mock_client.request_workload_jwt.assert_called_once_with(claims={'job_id': 1}, scope=scope, audience=audience)
+
+
+@mock.patch('awx.main.tasks.jobs.get_workload_identity_client')
+def test_retrieve_workload_identity_jwt_raises_when_client_not_configured(mock_get_client):
+ """retrieve_workload_identity_jwt raises RuntimeError when client is None."""
+ mock_get_client.return_value = None
+
+ unified_job = mock.MagicMock()
+
+ with pytest.raises(RuntimeError, match="Workload identity client is not configured"):
+ jobs.retrieve_workload_identity_jwt(unified_job, audience='test_audience', scope='test_scope')
diff --git a/awx/main/tests/unit/tasks/test_signals.py b/awx/main/tests/unit/tasks/test_signals.py
index a435b8a66039..f089ea749da9 100644
--- a/awx/main/tests/unit/tasks/test_signals.py
+++ b/awx/main/tests/unit/tasks/test_signals.py
@@ -1,8 +1,51 @@
import signal
+import functools
from awx.main.tasks.signals import signal_state, signal_callback, with_signal_handling
+def pytest_sigint():
+ pytest_sigint.called_count += 1
+
+
+def pytest_sigterm():
+ pytest_sigterm.called_count += 1
+
+
+def pytest_sigusr1():
+ pytest_sigusr1.called_count += 1
+
+
+def tmp_signals_for_test(func):
+ """
+ When we run our internal signal handlers, it will call the original signal
+ handlers when its own work is finished.
+ This would crash the test runners normally, because those methods will
+ shut down the process.
+ So this is a decorator to safely replace existing signal handlers
+ with new signal handlers that do nothing so that tests do not crash.
+ """
+
+ @functools.wraps(func)
+ def wrapper():
+ original_sigterm = signal.getsignal(signal.SIGTERM)
+ original_sigint = signal.getsignal(signal.SIGINT)
+ original_sigusr1 = signal.getsignal(signal.SIGUSR1)
+ signal.signal(signal.SIGTERM, pytest_sigterm)
+ signal.signal(signal.SIGINT, pytest_sigint)
+ signal.signal(signal.SIGUSR1, pytest_sigusr1)
+ pytest_sigterm.called_count = 0
+ pytest_sigint.called_count = 0
+ pytest_sigusr1.called_count = 0
+ func()
+ signal.signal(signal.SIGTERM, original_sigterm)
+ signal.signal(signal.SIGINT, original_sigint)
+ signal.signal(signal.SIGUSR1, original_sigusr1)
+
+ return wrapper
+
+
+@tmp_signals_for_test
def test_outer_inner_signal_handling():
"""
Even if the flag is set in the outer context, its value should persist in the inner context
@@ -15,17 +58,24 @@ def f2():
@with_signal_handling
def f1():
assert signal_callback() is False
- signal_state.set_flag()
+ signal_state.set_signal_flag(for_signal=signal.SIGTERM)
assert signal_callback()
f2()
original_sigterm = signal.getsignal(signal.SIGTERM)
assert signal_callback() is False
+ assert pytest_sigterm.called_count == 0
+ assert pytest_sigint.called_count == 0
+ assert pytest_sigusr1.called_count == 0
f1()
assert signal_callback() is False
assert signal.getsignal(signal.SIGTERM) is original_sigterm
+ assert pytest_sigterm.called_count == 1
+ assert pytest_sigint.called_count == 0
+ assert pytest_sigusr1.called_count == 0
+@tmp_signals_for_test
def test_inner_outer_signal_handling():
"""
Even if the flag is set in the inner context, its value should persist in the outer context
@@ -34,7 +84,7 @@ def test_inner_outer_signal_handling():
@with_signal_handling
def f2():
assert signal_callback() is False
- signal_state.set_flag()
+ signal_state.set_signal_flag(for_signal=signal.SIGINT)
assert signal_callback()
@with_signal_handling
@@ -45,6 +95,33 @@ def f1():
original_sigterm = signal.getsignal(signal.SIGTERM)
assert signal_callback() is False
+ assert pytest_sigterm.called_count == 0
+ assert pytest_sigint.called_count == 0
+ assert pytest_sigusr1.called_count == 0
f1()
assert signal_callback() is False
assert signal.getsignal(signal.SIGTERM) is original_sigterm
+ assert pytest_sigterm.called_count == 0
+ assert pytest_sigint.called_count == 1
+ assert pytest_sigusr1.called_count == 0
+
+
+@tmp_signals_for_test
+def test_sigusr1_signal_handling():
+ @with_signal_handling
+ def f1():
+ assert signal_callback() is False
+ signal_state.set_signal_flag(for_signal=signal.SIGUSR1)
+ assert signal_callback()
+
+ original_sigusr1 = signal.getsignal(signal.SIGUSR1)
+ assert signal_callback() is False
+ assert pytest_sigterm.called_count == 0
+ assert pytest_sigint.called_count == 0
+ assert pytest_sigusr1.called_count == 0
+ f1()
+ assert signal_callback() is False
+ assert signal.getsignal(signal.SIGUSR1) is original_sigusr1
+ assert pytest_sigterm.called_count == 0
+ assert pytest_sigint.called_count == 0
+ assert pytest_sigusr1.called_count == 1
diff --git a/awx/main/tests/unit/tasks/test_system.py b/awx/main/tests/unit/tasks/test_system.py
new file mode 100644
index 000000000000..c567dc48336d
--- /dev/null
+++ b/awx/main/tests/unit/tasks/test_system.py
@@ -0,0 +1,64 @@
+import pytest
+from unittest.mock import MagicMock, patch
+from awx.main.tasks.system import update_inventory_computed_fields
+from awx.main.models import Inventory
+from django.db import DatabaseError
+
+
+@pytest.fixture
+def mock_logger():
+ with patch("awx.main.tasks.system.logger") as logger:
+ yield logger
+
+
+@pytest.fixture
+def mock_inventory():
+ return MagicMock(spec=Inventory)
+
+
+def test_update_inventory_computed_fields_existing_inventory(mock_logger, mock_inventory):
+ # Mocking the Inventory.objects.filter method to return a non-empty queryset
+ with patch("awx.main.tasks.system.Inventory.objects.filter") as mock_filter:
+ mock_filter.return_value.exists.return_value = True
+ mock_filter.return_value.__getitem__.return_value = mock_inventory
+
+ # Mocking the update_computed_fields method
+ with patch.object(mock_inventory, "update_computed_fields") as mock_update_computed_fields:
+ update_inventory_computed_fields(1)
+
+ # Assertions
+ mock_filter.assert_called_once_with(id=1)
+ mock_update_computed_fields.assert_called_once()
+
+ # You can add more assertions based on your specific requirements
+
+
+def test_update_inventory_computed_fields_missing_inventory(mock_logger):
+ # Mocking the Inventory.objects.filter method to return an empty queryset
+ with patch("awx.main.tasks.system.Inventory.objects.filter") as mock_filter:
+ mock_filter.return_value.exists.return_value = False
+
+ update_inventory_computed_fields(1)
+
+ # Assertions
+ mock_filter.assert_called_once_with(id=1)
+ mock_logger.error.assert_called_once_with("Update Inventory Computed Fields failed due to missing inventory: 1")
+
+
+def test_update_inventory_computed_fields_database_error_nosqlstate(mock_logger, mock_inventory):
+ # Mocking the Inventory.objects.filter method to return a non-empty queryset
+ with patch("awx.main.tasks.system.Inventory.objects.filter") as mock_filter:
+ mock_filter.return_value.exists.return_value = True
+ mock_filter.return_value.__getitem__.return_value = mock_inventory
+
+ # Mocking the update_computed_fields method
+ with patch.object(mock_inventory, "update_computed_fields") as mock_update_computed_fields:
+ # Simulating the update_computed_fields method to explicitly raise a DatabaseError
+ mock_update_computed_fields.side_effect = DatabaseError("Some error")
+
+ update_inventory_computed_fields(1)
+
+ # Assertions
+ mock_filter.assert_called_once_with(id=1)
+ mock_update_computed_fields.assert_called_once()
+ mock_inventory.update_computed_fields.assert_called_once()
diff --git a/awx/main/tests/unit/test_access.py b/awx/main/tests/unit/test_access.py
index 0059cb498400..08e1e66ab59a 100644
--- a/awx/main/tests/unit/test_access.py
+++ b/awx/main/tests/unit/test_access.py
@@ -5,7 +5,7 @@
from django.forms.models import model_to_dict
from rest_framework.exceptions import ParseError
-from awx.main.access import BaseAccess, check_superuser, JobTemplateAccess, WorkflowJobTemplateAccess, SystemJobTemplateAccess, vars_are_encrypted
+from awx.main.access import BaseAccess, check_superuser, JobTemplateAccess, WorkflowJobTemplateAccess, vars_are_encrypted
from awx.main.models import (
Credential,
@@ -239,14 +239,3 @@ def can_copy(self, obj):
foo = object()
foo_capabilities = foo_access.get_user_capabilities(foo, ['edit', 'copy'])
assert foo_capabilities == {'edit': 'bar', 'copy': 'foo'}
-
-
-def test_system_job_template_can_start(mocker):
- user = mocker.MagicMock(spec=User, id=1, is_system_auditor=True, is_superuser=False)
- assert user.is_system_auditor
- access = SystemJobTemplateAccess(user)
- assert not access.can_start(None)
-
- user.is_superuser = True
- access = SystemJobTemplateAccess(user)
- assert access.can_start(None)
diff --git a/awx/main/tests/unit/test_db.py b/awx/main/tests/unit/test_db.py
index ce0b8bbeccb8..b1ffbfc0d8c8 100644
--- a/awx/main/tests/unit/test_db.py
+++ b/awx/main/tests/unit/test_db.py
@@ -9,7 +9,6 @@
import awx
from awx.main.db.profiled_pg.base import RecordedQueryLog
-
QUERY = {'sql': 'SELECT * FROM main_job', 'time': '.01'}
EXPLAIN = 'Seq Scan on public.main_job (cost=0.00..1.18 rows=18 width=86)'
diff --git a/awx/main/tests/unit/test_redact.py b/awx/main/tests/unit/test_redact.py
index c5585ff75cda..f175cbbf7a55 100644
--- a/awx/main/tests/unit/test_redact.py
+++ b/awx/main/tests/unit/test_redact.py
@@ -36,8 +36,7 @@
TEST_CLEARTEXT.append(
{
'uri': uri,
- 'text': textwrap.dedent(
- """\
+ 'text': textwrap.dedent("""\
PLAY [all] ********************************************************************
TASK: [delete project directory before update] ********************************
@@ -59,9 +58,7 @@
localhost : ok=0 changed=0 unreachable=0 failed=1
- """
- % (uri.username, uri.password, str(uri), str(uri))
- ),
+ """ % (uri.username, uri.password, str(uri), str(uri))),
'host_occurrences': 2,
}
)
@@ -70,8 +67,7 @@
TEST_CLEARTEXT.append(
{
'uri': uri,
- 'text': textwrap.dedent(
- """\
+ 'text': textwrap.dedent("""\
TASK: [update project using git] **
failed: [localhost] => {"cmd": "/usr/bin/git ls-remote https://REDACTED:********", "failed": true, "rc": 128}
stderr: error: Couldn't resolve host '@%s' while accessing %s
@@ -81,9 +77,7 @@
msg: error: Couldn't resolve host '@%s' while accessing %s
fatal: HTTP request failed
- """
- % (uri.host, str(uri), uri.host, str(uri))
- ),
+ """ % (uri.host, str(uri), uri.host, str(uri))),
'host_occurrences': 4,
}
)
diff --git a/awx/main/tests/unit/test_settings.py b/awx/main/tests/unit/test_settings.py
index 19b90099a175..ee517d6a870e 100644
--- a/awx/main/tests/unit/test_settings.py
+++ b/awx/main/tests/unit/test_settings.py
@@ -1,8 +1,93 @@
-from split_settings.tools import include
+LOCAL_SETTINGS = (
+ 'ALLOWED_HOSTS',
+ 'BROADCAST_WEBSOCKET_PORT',
+ 'BROADCAST_WEBSOCKET_VERIFY_CERT',
+ 'BROADCAST_WEBSOCKET_PROTOCOL',
+ 'BROADCAST_WEBSOCKET_SECRET',
+ 'DATABASES',
+ 'CACHES',
+ 'DEBUG',
+ 'NAMED_URL_GRAPH',
+ # Platform flags are managed by the platform flags system and have environment-specific defaults
+ 'FEATURE_INDIRECT_NODE_COUNTING_ENABLED',
+)
def test_postprocess_auth_basic_enabled():
- locals().update({'__file__': __file__})
+ """The final loaded settings should have basic auth enabled."""
+ from awx.settings import REST_FRAMEWORK
- include('../../../settings/defaults.py', scope=locals())
- assert 'awx.api.authentication.LoggedBasicAuthentication' in locals()['REST_FRAMEWORK']['DEFAULT_AUTHENTICATION_CLASSES']
+ assert 'awx.api.authentication.LoggedBasicAuthentication' in REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES']
+
+
+def test_default_settings():
+ """Ensure that all default settings are present in the snapshot."""
+ from django.conf import settings
+
+ for k in dir(settings):
+ if k not in settings.DEFAULTS_SNAPSHOT or k in LOCAL_SETTINGS:
+ continue
+ default_val = getattr(settings.default_settings, k, None)
+ snapshot_val = settings.DEFAULTS_SNAPSHOT[k]
+ assert default_val == snapshot_val, f'Setting for {k} does not match snapshot:\nsnapshot: {snapshot_val}\ndefault: {default_val}'
+
+
+def test_django_conf_settings_is_awx_settings():
+ """Ensure that the settings loaded from dynaconf are the same as the settings delivered to django."""
+ from django.conf import settings
+ from awx.settings import REST_FRAMEWORK
+
+ assert settings.REST_FRAMEWORK == REST_FRAMEWORK
+
+
+def test_dynaconf_is_awx_settings():
+ """Ensure that the settings loaded from dynaconf are the same as the settings delivered to django."""
+ from django.conf import settings
+ from awx.settings import REST_FRAMEWORK
+
+ assert settings.DYNACONF.REST_FRAMEWORK == REST_FRAMEWORK
+
+
+def test_development_settings_can_be_directly_imported(monkeypatch):
+ """Ensure that the development settings can be directly imported."""
+ monkeypatch.setenv('AWX_MODE', 'development')
+ from django.conf import settings
+ from awx.settings.development import REST_FRAMEWORK
+ from awx.settings.development import DEBUG # actually set on defaults.py and not overridden in development.py
+
+ assert settings.REST_FRAMEWORK == REST_FRAMEWORK
+ assert DEBUG is True
+
+
+def test_merge_application_name():
+ """Ensure that the merge_application_name function works as expected."""
+ from awx.settings.functions import merge_application_name
+
+ settings = {
+ "DATABASES__default__ENGINE": "django.db.backends.postgresql",
+ "CLUSTER_HOST_ID": "test-cluster-host-id",
+ }
+ result = merge_application_name(settings)["DATABASES__default__OPTIONS__application_name"]
+ assert result.startswith("awx-")
+ assert "test-cluster" in result
+
+
+def test_development_defaults_feature_flags(monkeypatch):
+ """Ensure that development_defaults.py sets the correct feature flags."""
+ monkeypatch.setenv('AWX_MODE', 'development')
+
+ # Import the development_defaults module directly to trigger coverage of the new lines
+ import importlib.util
+ import os
+
+ spec = importlib.util.spec_from_file_location("development_defaults", os.path.join(os.path.dirname(__file__), "../../../settings/development_defaults.py"))
+ development_defaults = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(development_defaults)
+
+ # Also import through the development settings to ensure both paths are tested
+ from awx.settings.development import FEATURE_INDIRECT_NODE_COUNTING_ENABLED
+
+ # Verify the feature flags are set correctly in both the module and settings
+ assert hasattr(development_defaults, 'FEATURE_INDIRECT_NODE_COUNTING_ENABLED')
+ assert development_defaults.FEATURE_INDIRECT_NODE_COUNTING_ENABLED is True
+ assert FEATURE_INDIRECT_NODE_COUNTING_ENABLED is True
diff --git a/awx/main/tests/unit/test_tasks.py b/awx/main/tests/unit/test_tasks.py
index bfec59b6163c..ce4515b88c4a 100644
--- a/awx/main/tests/unit/test_tasks.py
+++ b/awx/main/tests/unit/test_tasks.py
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
-import configparser
import json
import os
import shutil
@@ -10,7 +9,8 @@
from unittest import mock
import pytest
import yaml
-import jinja2
+
+from awx_plugins.interfaces._temporary_private_container_api import CONTAINER_ROOT
from django.conf import settings
@@ -37,7 +37,6 @@
from awx.main.tasks import jobs, system, receptor
from awx.main.utils import encrypt_field, encrypt_value
from awx.main.utils.safe_yaml import SafeLoader
-from awx.main.utils.execution_environments import CONTAINER_ROOT
from awx.main.utils.licensing import Licenser
from awx.main.constants import JOB_VARIABLE_PREFIXES
@@ -108,7 +107,7 @@ def job():
@pytest.fixture
def adhoc_job():
- return AdHocCommand(pk=1, id=1, inventory=Inventory())
+ return AdHocCommand(pk=1, id=1, inventory=Inventory(), status='waiting')
@pytest.fixture
@@ -137,17 +136,10 @@ def test_send_notifications_not_list():
def test_send_notifications_job_id(mocker):
- with mocker.patch('awx.main.models.UnifiedJob.objects.get'):
- system.send_notifications([], job_id=1)
- assert UnifiedJob.objects.get.called
- assert UnifiedJob.objects.get.called_with(id=1)
-
-
-def test_work_success_callback_missing_job():
- task_data = {'type': 'project_update', 'id': 9999}
- with mock.patch('django.db.models.query.QuerySet.get') as get_mock:
- get_mock.side_effect = ProjectUpdate.DoesNotExist()
- assert system.handle_work_success(task_data) is None
+ mocker.patch('awx.main.models.UnifiedJob.objects.get')
+ system.send_notifications([], job_id=1)
+ assert UnifiedJob.objects.get.called
+ UnifiedJob.objects.get.assert_called_with(id=1)
@mock.patch('awx.main.models.UnifiedJob.objects.get')
@@ -164,7 +156,7 @@ def test_send_notifications_list(mock_notifications_filter, mock_job_get, mocker
assert mock_notifications[0].save.called
assert mock_job.notifications.add.called
- assert mock_job.notifications.add.called_with(*mock_notifications)
+ mock_job.notifications.add.assert_called_with(*mock_notifications)
@pytest.mark.parametrize(
@@ -371,7 +363,7 @@ class TestExtraVarSanitation(TestJobExecution):
# are deemed trustable, because they can only be added by users w/ enough
# privilege to add/modify a Job Template)
- UNSAFE = '{{ lookup(' 'pipe' ',' 'ls -la' ') }}'
+ UNSAFE = "{{ lookup('pipe', 'ls -la') }}"
def test_vars_unsafe_by_default(self, job, private_data_dir, mock_me):
job.created_by = User(pk=123, username='angry-spud')
@@ -380,8 +372,8 @@ def test_vars_unsafe_by_default(self, job, private_data_dir, mock_me):
task = jobs.RunJob()
task.build_extra_vars_file(job, private_data_dir)
- fd = open(os.path.join(private_data_dir, 'env', 'extravars'))
- extra_vars = yaml.load(fd, Loader=SafeLoader)
+ with open(os.path.join(private_data_dir, 'env', 'extravars')) as fd:
+ extra_vars = yaml.load(fd, Loader=SafeLoader)
# ensure that strings are marked as unsafe
for name in JOB_VARIABLE_PREFIXES:
@@ -399,8 +391,8 @@ def test_launchtime_vars_unsafe(self, job, private_data_dir, mock_me):
task.build_extra_vars_file(job, private_data_dir)
- fd = open(os.path.join(private_data_dir, 'env', 'extravars'))
- extra_vars = yaml.load(fd, Loader=SafeLoader)
+ with open(os.path.join(private_data_dir, 'env', 'extravars')) as fd:
+ extra_vars = yaml.load(fd, Loader=SafeLoader)
assert extra_vars['msg'] == self.UNSAFE
assert hasattr(extra_vars['msg'], '__UNSAFE__')
@@ -410,8 +402,8 @@ def test_nested_launchtime_vars_unsafe(self, job, private_data_dir, mock_me):
task.build_extra_vars_file(job, private_data_dir)
- fd = open(os.path.join(private_data_dir, 'env', 'extravars'))
- extra_vars = yaml.load(fd, Loader=SafeLoader)
+ with open(os.path.join(private_data_dir, 'env', 'extravars')) as fd:
+ extra_vars = yaml.load(fd, Loader=SafeLoader)
assert extra_vars['msg'] == {'a': [self.UNSAFE]}
assert hasattr(extra_vars['msg']['a'][0], '__UNSAFE__')
@@ -421,8 +413,8 @@ def test_allowed_jt_extra_vars(self, job, private_data_dir, mock_me):
task.build_extra_vars_file(job, private_data_dir)
- fd = open(os.path.join(private_data_dir, 'env', 'extravars'))
- extra_vars = yaml.load(fd, Loader=SafeLoader)
+ with open(os.path.join(private_data_dir, 'env', 'extravars')) as fd:
+ extra_vars = yaml.load(fd, Loader=SafeLoader)
assert extra_vars['msg'] == self.UNSAFE
assert not hasattr(extra_vars['msg'], '__UNSAFE__')
@@ -433,8 +425,8 @@ def test_nested_allowed_vars(self, job, private_data_dir, mock_me):
task.build_extra_vars_file(job, private_data_dir)
- fd = open(os.path.join(private_data_dir, 'env', 'extravars'))
- extra_vars = yaml.load(fd, Loader=SafeLoader)
+ with open(os.path.join(private_data_dir, 'env', 'extravars')) as fd:
+ extra_vars = yaml.load(fd, Loader=SafeLoader)
assert extra_vars['msg'] == {'a': {'b': [self.UNSAFE]}}
assert not hasattr(extra_vars['msg']['a']['b'][0], '__UNSAFE__')
@@ -447,8 +439,8 @@ def test_sensitive_values_dont_leak(self, job, private_data_dir, mock_me):
task.build_extra_vars_file(job, private_data_dir)
- fd = open(os.path.join(private_data_dir, 'env', 'extravars'))
- extra_vars = yaml.load(fd, Loader=SafeLoader)
+ with open(os.path.join(private_data_dir, 'env', 'extravars')) as fd:
+ extra_vars = yaml.load(fd, Loader=SafeLoader)
assert extra_vars['msg'] == 'other-value'
assert hasattr(extra_vars['msg'], '__UNSAFE__')
@@ -462,13 +454,14 @@ def test_overwritten_jt_extra_vars(self, job, private_data_dir, mock_me):
task.build_extra_vars_file(job, private_data_dir)
- fd = open(os.path.join(private_data_dir, 'env', 'extravars'))
- extra_vars = yaml.load(fd, Loader=SafeLoader)
+ with open(os.path.join(private_data_dir, 'env', 'extravars')) as fd:
+ extra_vars = yaml.load(fd, Loader=SafeLoader)
assert extra_vars['msg'] == self.UNSAFE
assert hasattr(extra_vars['msg'], '__UNSAFE__')
class TestGenericRun:
+ @pytest.mark.django_db(reset_sequences=True)
def test_generic_failure(self, patch_Job, execution_environment, mock_me, mock_create_partition):
job = Job(status='running', inventory=Inventory(), project=Project(local_path='/projects/_23_foo'))
job.websocket_emit_status = mock.Mock()
@@ -480,7 +473,7 @@ def test_generic_failure(self, patch_Job, execution_environment, mock_me, mock_c
task.model.objects.get = mock.Mock(return_value=job)
task.build_private_data_files = mock.Mock(side_effect=OSError())
- with mock.patch('awx.main.tasks.jobs.shutil.copytree'):
+ with mock.patch('awx.main.tasks.jobs.shutil.copytree'), mock.patch('awx.main.tasks.jobs.evaluate_policy'):
with pytest.raises(Exception):
task.run(1)
@@ -489,26 +482,6 @@ def test_generic_failure(self, patch_Job, execution_environment, mock_me, mock_c
assert update_model_call['status'] == 'error'
assert update_model_call['emitted_events'] == 0
- def test_cancel_flag(self, job, update_model_wrapper, execution_environment, mock_me, mock_create_partition):
- job.status = 'running'
- job.cancel_flag = True
- job.websocket_emit_status = mock.Mock()
- job.send_notification_templates = mock.Mock()
- job.execution_environment = execution_environment
-
- task = jobs.RunJob()
- task.instance = job
- task.update_model = mock.Mock(wraps=update_model_wrapper)
- task.model.objects.get = mock.Mock(return_value=job)
- task.build_private_data_files = mock.Mock()
-
- with mock.patch('awx.main.tasks.jobs.shutil.copytree'):
- with pytest.raises(Exception):
- task.run(1)
-
- for c in [mock.call(1, start_args='', status='canceled')]:
- assert c in task.update_model.call_args_list
-
def test_event_count(self, mock_me):
task = jobs.RunJob()
task.runner_callback.dispatcher = mock.MagicMock()
@@ -573,6 +546,7 @@ def test_survey_extra_vars(self, mock_me):
private_data_dir, extra_vars, safe_dict = call_args
assert extra_vars['super_secret'] == "CLASSIFIED"
+ @pytest.mark.django_db
def test_awx_task_env(self, patch_Job, private_data_dir, execution_environment, mock_me):
job = Job(project=Project(), inventory=Inventory())
job.execution_environment = execution_environment
@@ -597,6 +571,8 @@ def test_options_jinja_usage(self, adhoc_job, adhoc_update_model_wrapper, mock_m
adhoc_job.send_notification_templates = mock.Mock()
task = jobs.RunAdHocCommand()
+ adhoc_job.status = 'running' # to bypass status flip
+ task.instance = adhoc_job # to bypass fetch
task.update_model = mock.Mock(wraps=adhoc_update_model_wrapper)
task.model.objects.get = mock.Mock(return_value=adhoc_job)
task.build_inventory = mock.Mock()
@@ -863,202 +839,6 @@ def test_multi_vault_password_ask(self, private_data_dir, job, mock_me):
assert '--vault-id dev@prompt' in ' '.join(args)
assert '--vault-id prod@prompt' in ' '.join(args)
- @pytest.mark.parametrize("verify", (True, False))
- def test_k8s_credential(self, job, private_data_dir, verify, mock_me):
- k8s = CredentialType.defaults['kubernetes_bearer_token']()
- inputs = {
- 'host': 'https://example.org/',
- 'bearer_token': 'token123',
- }
- if verify:
- inputs['verify_ssl'] = True
- inputs['ssl_ca_cert'] = 'CERTDATA'
- credential = Credential(
- pk=1,
- credential_type=k8s,
- inputs=inputs,
- )
- credential.inputs['bearer_token'] = encrypt_field(credential, 'bearer_token')
- job.credentials.add(credential)
-
- env = {}
- safe_env = {}
- credential.credential_type.inject_credential(credential, env, safe_env, [], private_data_dir)
-
- assert env['K8S_AUTH_HOST'] == 'https://example.org/'
- assert env['K8S_AUTH_API_KEY'] == 'token123'
-
- if verify:
- assert env['K8S_AUTH_VERIFY_SSL'] == 'True'
- local_path = to_host_path(env['K8S_AUTH_SSL_CA_CERT'], private_data_dir)
- cert = open(local_path, 'r').read()
- assert cert == 'CERTDATA'
- else:
- assert env['K8S_AUTH_VERIFY_SSL'] == 'False'
- assert 'K8S_AUTH_SSL_CA_CERT' not in env
-
- assert safe_env['K8S_AUTH_API_KEY'] == HIDDEN_PASSWORD
-
- def test_aws_cloud_credential(self, job, private_data_dir, mock_me):
- aws = CredentialType.defaults['aws']()
- credential = Credential(pk=1, credential_type=aws, inputs={'username': 'bob', 'password': 'secret'})
- credential.inputs['password'] = encrypt_field(credential, 'password')
- job.credentials.add(credential)
-
- env = {}
- safe_env = {}
- credential.credential_type.inject_credential(credential, env, safe_env, [], private_data_dir)
-
- assert env['AWS_ACCESS_KEY_ID'] == 'bob'
- assert env['AWS_SECRET_ACCESS_KEY'] == 'secret'
- assert 'AWS_SECURITY_TOKEN' not in env
- assert safe_env['AWS_SECRET_ACCESS_KEY'] == HIDDEN_PASSWORD
-
- def test_aws_cloud_credential_with_sts_token(self, private_data_dir, job, mock_me):
- aws = CredentialType.defaults['aws']()
- credential = Credential(pk=1, credential_type=aws, inputs={'username': 'bob', 'password': 'secret', 'security_token': 'token'})
- for key in ('password', 'security_token'):
- credential.inputs[key] = encrypt_field(credential, key)
- job.credentials.add(credential)
-
- env = {}
- safe_env = {}
- credential.credential_type.inject_credential(credential, env, safe_env, [], private_data_dir)
-
- assert env['AWS_ACCESS_KEY_ID'] == 'bob'
- assert env['AWS_SECRET_ACCESS_KEY'] == 'secret'
- assert env['AWS_SECURITY_TOKEN'] == 'token'
- assert safe_env['AWS_SECRET_ACCESS_KEY'] == HIDDEN_PASSWORD
-
- @pytest.mark.parametrize("cred_env_var", ['GCE_CREDENTIALS_FILE_PATH', 'GOOGLE_APPLICATION_CREDENTIALS'])
- def test_gce_credentials(self, cred_env_var, private_data_dir, job, mock_me):
- gce = CredentialType.defaults['gce']()
- credential = Credential(pk=1, credential_type=gce, inputs={'username': 'bob', 'project': 'some-project', 'ssh_key_data': self.EXAMPLE_PRIVATE_KEY})
- credential.inputs['ssh_key_data'] = encrypt_field(credential, 'ssh_key_data')
- job.credentials.add(credential)
-
- env = {}
- safe_env = {}
- credential.credential_type.inject_credential(credential, env, safe_env, [], private_data_dir)
- runner_path = env[cred_env_var]
- local_path = to_host_path(runner_path, private_data_dir)
- json_data = json.load(open(local_path, 'rb'))
- assert json_data['type'] == 'service_account'
- assert json_data['private_key'] == self.EXAMPLE_PRIVATE_KEY
- assert json_data['client_email'] == 'bob'
- assert json_data['project_id'] == 'some-project'
-
- def test_azure_rm_with_tenant(self, private_data_dir, job, mock_me):
- azure = CredentialType.defaults['azure_rm']()
- credential = Credential(
- pk=1, credential_type=azure, inputs={'client': 'some-client', 'secret': 'some-secret', 'tenant': 'some-tenant', 'subscription': 'some-subscription'}
- )
- credential.inputs['secret'] = encrypt_field(credential, 'secret')
- job.credentials.add(credential)
-
- env = {}
- safe_env = {}
- credential.credential_type.inject_credential(credential, env, safe_env, [], private_data_dir)
-
- assert env['AZURE_CLIENT_ID'] == 'some-client'
- assert env['AZURE_SECRET'] == 'some-secret'
- assert env['AZURE_TENANT'] == 'some-tenant'
- assert env['AZURE_SUBSCRIPTION_ID'] == 'some-subscription'
- assert safe_env['AZURE_SECRET'] == HIDDEN_PASSWORD
-
- def test_azure_rm_with_password(self, private_data_dir, job, mock_me):
- azure = CredentialType.defaults['azure_rm']()
- credential = Credential(
- pk=1, credential_type=azure, inputs={'subscription': 'some-subscription', 'username': 'bob', 'password': 'secret', 'cloud_environment': 'foobar'}
- )
- credential.inputs['password'] = encrypt_field(credential, 'password')
- job.credentials.add(credential)
-
- env = {}
- safe_env = {}
- credential.credential_type.inject_credential(credential, env, safe_env, [], private_data_dir)
-
- assert env['AZURE_SUBSCRIPTION_ID'] == 'some-subscription'
- assert env['AZURE_AD_USER'] == 'bob'
- assert env['AZURE_PASSWORD'] == 'secret'
- assert env['AZURE_CLOUD_ENVIRONMENT'] == 'foobar'
- assert safe_env['AZURE_PASSWORD'] == HIDDEN_PASSWORD
-
- def test_vmware_credentials(self, private_data_dir, job, mock_me):
- vmware = CredentialType.defaults['vmware']()
- credential = Credential(pk=1, credential_type=vmware, inputs={'username': 'bob', 'password': 'secret', 'host': 'https://example.org'})
- credential.inputs['password'] = encrypt_field(credential, 'password')
- job.credentials.add(credential)
-
- env = {}
- safe_env = {}
- credential.credential_type.inject_credential(credential, env, safe_env, [], private_data_dir)
-
- assert env['VMWARE_USER'] == 'bob'
- assert env['VMWARE_PASSWORD'] == 'secret'
- assert env['VMWARE_HOST'] == 'https://example.org'
- assert safe_env['VMWARE_PASSWORD'] == HIDDEN_PASSWORD
-
- def test_openstack_credentials(self, private_data_dir, job, mock_me):
- task = jobs.RunJob()
- task.instance = job
- openstack = CredentialType.defaults['openstack']()
- credential = Credential(
- pk=1, credential_type=openstack, inputs={'username': 'bob', 'password': 'secret', 'project': 'tenant-name', 'host': 'https://keystone.example.org'}
- )
- credential.inputs['password'] = encrypt_field(credential, 'password')
- job.credentials.add(credential)
-
- private_data_files, ssh_key_data = task.build_private_data_files(job, private_data_dir)
- env = task.build_env(job, private_data_dir, private_data_files=private_data_files)
- credential.credential_type.inject_credential(credential, env, {}, [], private_data_dir)
-
- config_loc = to_host_path(env['OS_CLIENT_CONFIG_FILE'], private_data_dir)
- shade_config = open(config_loc, 'r').read()
- assert shade_config == '\n'.join(
- [
- 'clouds:',
- ' devstack:',
- ' auth:',
- ' auth_url: https://keystone.example.org',
- ' password: secret',
- ' project_name: tenant-name',
- ' username: bob',
- ' verify: true',
- '',
- ]
- )
-
- @pytest.mark.parametrize("ca_file", [None, '/path/to/some/file'])
- def test_rhv_credentials(self, private_data_dir, job, ca_file, mock_me):
- rhv = CredentialType.defaults['rhv']()
- inputs = {
- 'host': 'some-ovirt-host.example.org',
- 'username': 'bob',
- 'password': 'some-pass',
- }
- if ca_file:
- inputs['ca_file'] = ca_file
- credential = Credential(pk=1, credential_type=rhv, inputs=inputs)
- credential.inputs['password'] = encrypt_field(credential, 'password')
- job.credentials.add(credential)
-
- env = {}
- safe_env = {}
- credential.credential_type.inject_credential(credential, env, safe_env, [], private_data_dir)
-
- config = configparser.ConfigParser()
- host_path = to_host_path(env['OVIRT_INI_PATH'], private_data_dir)
- config.read(host_path)
- assert config.get('ovirt', 'ovirt_url') == 'some-ovirt-host.example.org'
- assert config.get('ovirt', 'ovirt_username') == 'bob'
- assert config.get('ovirt', 'ovirt_password') == 'some-pass'
- if ca_file:
- assert config.get('ovirt', 'ovirt_ca_file') == ca_file
- else:
- with pytest.raises(configparser.NoOptionError):
- config.get('ovirt', 'ovirt_ca_file')
-
@pytest.mark.parametrize(
'authorize, expected_authorize',
[
@@ -1067,6 +847,7 @@ def test_rhv_credentials(self, private_data_dir, job, ca_file, mock_me):
[None, '0'],
],
)
+ @pytest.mark.django_db
def test_net_credentials(self, authorize, expected_authorize, job, private_data_dir, mock_me):
task = jobs.RunJob()
task.instance = job
@@ -1089,259 +870,10 @@ def test_net_credentials(self, authorize, expected_authorize, job, private_data_
assert env['ANSIBLE_NET_AUTHORIZE'] == expected_authorize
if authorize:
assert env['ANSIBLE_NET_AUTH_PASS'] == 'authorizeme'
- assert open(env['ANSIBLE_NET_SSH_KEYFILE'], 'r').read() == self.EXAMPLE_PRIVATE_KEY
+ with open(env['ANSIBLE_NET_SSH_KEYFILE'], 'r') as f:
+ assert f.read() == self.EXAMPLE_PRIVATE_KEY
assert safe_env['ANSIBLE_NET_PASSWORD'] == HIDDEN_PASSWORD
- def test_custom_environment_injectors_with_jinja_syntax_error(self, private_data_dir, mock_me):
- some_cloud = CredentialType(
- kind='cloud',
- name='SomeCloud',
- managed=False,
- inputs={'fields': [{'id': 'api_token', 'label': 'API Token', 'type': 'string'}]},
- injectors={'env': {'MY_CLOUD_API_TOKEN': '{{api_token.foo()}}'}},
- )
- credential = Credential(pk=1, credential_type=some_cloud, inputs={'api_token': 'ABC123'})
-
- with pytest.raises(jinja2.exceptions.UndefinedError):
- credential.credential_type.inject_credential(credential, {}, {}, [], private_data_dir)
-
- def test_custom_environment_injectors(self, private_data_dir, mock_me):
- some_cloud = CredentialType(
- kind='cloud',
- name='SomeCloud',
- managed=False,
- inputs={'fields': [{'id': 'api_token', 'label': 'API Token', 'type': 'string'}]},
- injectors={'env': {'MY_CLOUD_API_TOKEN': '{{api_token}}'}},
- )
- credential = Credential(pk=1, credential_type=some_cloud, inputs={'api_token': 'ABC123'})
-
- env = {}
- credential.credential_type.inject_credential(credential, env, {}, [], private_data_dir)
-
- assert env['MY_CLOUD_API_TOKEN'] == 'ABC123'
-
- def test_custom_environment_injectors_with_boolean_env_var(self, private_data_dir, mock_me):
- some_cloud = CredentialType(
- kind='cloud',
- name='SomeCloud',
- managed=False,
- inputs={'fields': [{'id': 'turbo_button', 'label': 'Turbo Button', 'type': 'boolean'}]},
- injectors={'env': {'TURBO_BUTTON': '{{turbo_button}}'}},
- )
- credential = Credential(pk=1, credential_type=some_cloud, inputs={'turbo_button': True})
-
- env = {}
- credential.credential_type.inject_credential(credential, env, {}, [], private_data_dir)
-
- assert env['TURBO_BUTTON'] == str(True)
-
- def test_custom_environment_injectors_with_reserved_env_var(self, private_data_dir, job, mock_me):
- task = jobs.RunJob()
- task.instance = job
- some_cloud = CredentialType(
- kind='cloud',
- name='SomeCloud',
- managed=False,
- inputs={'fields': [{'id': 'api_token', 'label': 'API Token', 'type': 'string'}]},
- injectors={'env': {'JOB_ID': 'reserved'}},
- )
- credential = Credential(pk=1, credential_type=some_cloud, inputs={'api_token': 'ABC123'})
- job.credentials.add(credential)
-
- env = task.build_env(job, private_data_dir)
-
- assert env['JOB_ID'] == str(job.pk)
-
- def test_custom_environment_injectors_with_secret_field(self, private_data_dir, mock_me):
- some_cloud = CredentialType(
- kind='cloud',
- name='SomeCloud',
- managed=False,
- inputs={'fields': [{'id': 'password', 'label': 'Password', 'type': 'string', 'secret': True}]},
- injectors={'env': {'MY_CLOUD_PRIVATE_VAR': '{{password}}'}},
- )
- credential = Credential(pk=1, credential_type=some_cloud, inputs={'password': 'SUPER-SECRET-123'})
- credential.inputs['password'] = encrypt_field(credential, 'password')
-
- env = {}
- safe_env = {}
- credential.credential_type.inject_credential(credential, env, safe_env, [], private_data_dir)
-
- assert env['MY_CLOUD_PRIVATE_VAR'] == 'SUPER-SECRET-123'
- assert 'SUPER-SECRET-123' not in safe_env.values()
- assert safe_env['MY_CLOUD_PRIVATE_VAR'] == HIDDEN_PASSWORD
-
- def test_custom_environment_injectors_with_extra_vars(self, private_data_dir, job, mock_me):
- task = jobs.RunJob()
- some_cloud = CredentialType(
- kind='cloud',
- name='SomeCloud',
- managed=False,
- inputs={'fields': [{'id': 'api_token', 'label': 'API Token', 'type': 'string'}]},
- injectors={'extra_vars': {'api_token': '{{api_token}}'}},
- )
- credential = Credential(pk=1, credential_type=some_cloud, inputs={'api_token': 'ABC123'})
- job.credentials.add(credential)
-
- args = task.build_args(job, private_data_dir, {})
- credential.credential_type.inject_credential(credential, {}, {}, args, private_data_dir)
- extra_vars = parse_extra_vars(args, private_data_dir)
-
- assert extra_vars["api_token"] == "ABC123"
- assert hasattr(extra_vars["api_token"], '__UNSAFE__')
-
- def test_custom_environment_injectors_with_boolean_extra_vars(self, job, private_data_dir, mock_me):
- task = jobs.RunJob()
- some_cloud = CredentialType(
- kind='cloud',
- name='SomeCloud',
- managed=False,
- inputs={'fields': [{'id': 'turbo_button', 'label': 'Turbo Button', 'type': 'boolean'}]},
- injectors={'extra_vars': {'turbo_button': '{{turbo_button}}'}},
- )
- credential = Credential(pk=1, credential_type=some_cloud, inputs={'turbo_button': True})
- job.credentials.add(credential)
-
- args = task.build_args(job, private_data_dir, {})
- credential.credential_type.inject_credential(credential, {}, {}, args, private_data_dir)
- extra_vars = parse_extra_vars(args, private_data_dir)
-
- assert extra_vars["turbo_button"] == "True"
- return ['successful', 0]
-
- def test_custom_environment_injectors_with_nested_extra_vars(self, private_data_dir, job, mock_me):
- task = jobs.RunJob()
- some_cloud = CredentialType(
- kind='cloud',
- name='SomeCloud',
- managed=False,
- inputs={'fields': [{'id': 'host', 'label': 'Host', 'type': 'string'}]},
- injectors={'extra_vars': {'auth': {'host': '{{host}}'}}},
- )
- credential = Credential(pk=1, credential_type=some_cloud, inputs={'host': 'example.com'})
- job.credentials.add(credential)
-
- args = task.build_args(job, private_data_dir, {})
- credential.credential_type.inject_credential(credential, {}, {}, args, private_data_dir)
- extra_vars = parse_extra_vars(args, private_data_dir)
-
- assert extra_vars["auth"]["host"] == "example.com"
-
- def test_custom_environment_injectors_with_templated_extra_vars_key(self, private_data_dir, job, mock_me):
- task = jobs.RunJob()
- some_cloud = CredentialType(
- kind='cloud',
- name='SomeCloud',
- managed=False,
- inputs={'fields': [{'id': 'environment', 'label': 'Environment', 'type': 'string'}, {'id': 'host', 'label': 'Host', 'type': 'string'}]},
- injectors={'extra_vars': {'{{environment}}_auth': {'host': '{{host}}'}}},
- )
- credential = Credential(pk=1, credential_type=some_cloud, inputs={'environment': 'test', 'host': 'example.com'})
- job.credentials.add(credential)
-
- args = task.build_args(job, private_data_dir, {})
- credential.credential_type.inject_credential(credential, {}, {}, args, private_data_dir)
- extra_vars = parse_extra_vars(args, private_data_dir)
-
- assert extra_vars["test_auth"]["host"] == "example.com"
-
- def test_custom_environment_injectors_with_complicated_boolean_template(self, job, private_data_dir, mock_me):
- task = jobs.RunJob()
- some_cloud = CredentialType(
- kind='cloud',
- name='SomeCloud',
- managed=False,
- inputs={'fields': [{'id': 'turbo_button', 'label': 'Turbo Button', 'type': 'boolean'}]},
- injectors={'extra_vars': {'turbo_button': '{% if turbo_button %}FAST!{% else %}SLOW!{% endif %}'}},
- )
- credential = Credential(pk=1, credential_type=some_cloud, inputs={'turbo_button': True})
- job.credentials.add(credential)
-
- args = task.build_args(job, private_data_dir, {})
- credential.credential_type.inject_credential(credential, {}, {}, args, private_data_dir)
- extra_vars = parse_extra_vars(args, private_data_dir)
-
- assert extra_vars["turbo_button"] == "FAST!"
-
- def test_custom_environment_injectors_with_secret_extra_vars(self, job, private_data_dir, mock_me):
- """
- extra_vars that contain secret field values should be censored in the DB
- """
- task = jobs.RunJob()
- some_cloud = CredentialType(
- kind='cloud',
- name='SomeCloud',
- managed=False,
- inputs={'fields': [{'id': 'password', 'label': 'Password', 'type': 'string', 'secret': True}]},
- injectors={'extra_vars': {'password': '{{password}}'}},
- )
- credential = Credential(pk=1, credential_type=some_cloud, inputs={'password': 'SUPER-SECRET-123'})
- credential.inputs['password'] = encrypt_field(credential, 'password')
- job.credentials.add(credential)
-
- args = task.build_args(job, private_data_dir, {})
- credential.credential_type.inject_credential(credential, {}, {}, args, private_data_dir)
-
- extra_vars = parse_extra_vars(args, private_data_dir)
- assert extra_vars["password"] == "SUPER-SECRET-123"
-
- def test_custom_environment_injectors_with_file(self, private_data_dir, mock_me):
- some_cloud = CredentialType(
- kind='cloud',
- name='SomeCloud',
- managed=False,
- inputs={'fields': [{'id': 'api_token', 'label': 'API Token', 'type': 'string'}]},
- injectors={'file': {'template': '[mycloud]\n{{api_token}}'}, 'env': {'MY_CLOUD_INI_FILE': '{{tower.filename}}'}},
- )
- credential = Credential(pk=1, credential_type=some_cloud, inputs={'api_token': 'ABC123'})
-
- env = {}
- credential.credential_type.inject_credential(credential, env, {}, [], private_data_dir)
-
- path = to_host_path(env['MY_CLOUD_INI_FILE'], private_data_dir)
- assert open(path, 'r').read() == '[mycloud]\nABC123'
-
- def test_custom_environment_injectors_with_unicode_content(self, private_data_dir, mock_me):
- value = 'Iñtërnâtiônàlizætiøn'
- some_cloud = CredentialType(
- kind='cloud',
- name='SomeCloud',
- managed=False,
- inputs={'fields': []},
- injectors={'file': {'template': value}, 'env': {'MY_CLOUD_INI_FILE': '{{tower.filename}}'}},
- )
- credential = Credential(
- pk=1,
- credential_type=some_cloud,
- )
-
- env = {}
- credential.credential_type.inject_credential(credential, env, {}, [], private_data_dir)
-
- path = to_host_path(env['MY_CLOUD_INI_FILE'], private_data_dir)
- assert open(path, 'r').read() == value
-
- def test_custom_environment_injectors_with_files(self, private_data_dir, mock_me):
- some_cloud = CredentialType(
- kind='cloud',
- name='SomeCloud',
- managed=False,
- inputs={'fields': [{'id': 'cert', 'label': 'Certificate', 'type': 'string'}, {'id': 'key', 'label': 'Key', 'type': 'string'}]},
- injectors={
- 'file': {'template.cert': '[mycert]\n{{cert}}', 'template.key': '[mykey]\n{{key}}'},
- 'env': {'MY_CERT_INI_FILE': '{{tower.filename.cert}}', 'MY_KEY_INI_FILE': '{{tower.filename.key}}'},
- },
- )
- credential = Credential(pk=1, credential_type=some_cloud, inputs={'cert': 'CERT123', 'key': 'KEY123'})
-
- env = {}
- credential.credential_type.inject_credential(credential, env, {}, [], private_data_dir)
-
- cert_path = to_host_path(env['MY_CERT_INI_FILE'], private_data_dir)
- key_path = to_host_path(env['MY_KEY_INI_FILE'], private_data_dir)
- assert open(cert_path, 'r').read() == '[mycert]\nCERT123'
- assert open(key_path, 'r').read() == '[mykey]\nKEY123'
-
def test_multi_cloud(self, private_data_dir, mock_me):
gce = CredentialType.defaults['gce']()
gce_credential = Credential(pk=1, credential_type=gce, inputs={'username': 'bob', 'project': 'some-project', 'ssh_key_data': self.EXAMPLE_PRIVATE_KEY})
@@ -1363,7 +895,8 @@ def test_multi_cloud(self, private_data_dir, mock_me):
# Because this is testing a mix of multiple cloud creds, we are not going to test the GOOGLE_APPLICATION_CREDENTIALS here
path = to_host_path(env['GCE_CREDENTIALS_FILE_PATH'], private_data_dir)
- json_data = json.load(open(path, 'rb'))
+ with open(path, 'rb') as f:
+ json_data = json.load(f)
assert json_data['type'] == 'service_account'
assert json_data['private_key'] == self.EXAMPLE_PRIVATE_KEY
assert json_data['client_email'] == 'bob'
@@ -1371,6 +904,7 @@ def test_multi_cloud(self, private_data_dir, mock_me):
assert safe_env['AZURE_PASSWORD'] == HIDDEN_PASSWORD
+ @pytest.mark.django_db
def test_awx_task_env(self, settings, private_data_dir, job, mock_me):
settings.AWX_TASK_ENV = {'FOO': 'BAR'}
task = jobs.RunJob()
@@ -1556,6 +1090,70 @@ def test_awx_task_env(self, project_update, settings, private_data_dir, scm_type
assert env['FOO'] == 'BAR'
+@pytest.mark.django_db
+class TestProjectUpdateRefspec(TestJobExecution):
+ @pytest.fixture
+ def project_update(self, execution_environment):
+ org = Organization(pk=1)
+ proj = Project(pk=1, organization=org, allow_override=True)
+ project_update = ProjectUpdate(pk=1, project=proj, scm_type='git')
+ project_update.websocket_emit_status = mock.Mock()
+ project_update.execution_environment = execution_environment
+ return project_update
+
+ def test_refspec_with_allow_override_includes_plus_prefix(self, project_update, private_data_dir, mock_me):
+ """Test that refspec includes + prefix to allow non-fast-forward updates when allow_override is True"""
+ task = jobs.RunProjectUpdate()
+ task.instance = project_update
+
+ # Call build_extra_vars_file which sets the refspec
+ with mock.patch.object(Licenser, 'validate', lambda *args, **kw: {}):
+ task.build_extra_vars_file(project_update, private_data_dir)
+
+ # Read the extra vars file to check the refspec
+ with open(os.path.join(private_data_dir, 'env', 'extravars')) as fd:
+ extra_vars = yaml.load(fd, Loader=SafeLoader)
+
+ # Verify the refspec includes the + prefix for force updates
+ assert 'scm_refspec' in extra_vars
+ assert extra_vars['scm_refspec'] == '+refs/heads/*:refs/remotes/origin/*'
+
+ def test_custom_refspec_not_overridden(self, project_update, private_data_dir, mock_me):
+ """Test that custom user-provided refspec is not overridden"""
+ task = jobs.RunProjectUpdate()
+ task.instance = project_update
+ project_update.scm_refspec = 'refs/pull/*/head:refs/remotes/origin/pr/*'
+
+ with mock.patch.object(Licenser, 'validate', lambda *args, **kw: {}):
+ task.build_extra_vars_file(project_update, private_data_dir)
+
+ with open(os.path.join(private_data_dir, 'env', 'extravars')) as fd:
+ extra_vars = yaml.load(fd, Loader=SafeLoader)
+
+ # Custom refspec should be preserved
+ assert extra_vars['scm_refspec'] == 'refs/pull/*/head:refs/remotes/origin/pr/*'
+
+ def test_no_refspec_without_allow_override(self, execution_environment, private_data_dir, mock_me):
+ """Test that no refspec is set when allow_override is False"""
+ org = Organization(pk=1)
+ proj = Project(pk=1, organization=org, allow_override=False)
+ project_update = ProjectUpdate(pk=1, project=proj, scm_type='git')
+ project_update.websocket_emit_status = mock.Mock()
+ project_update.execution_environment = execution_environment
+
+ task = jobs.RunProjectUpdate()
+ task.instance = project_update
+
+ with mock.patch.object(Licenser, 'validate', lambda *args, **kw: {}):
+ task.build_extra_vars_file(project_update, private_data_dir)
+
+ with open(os.path.join(private_data_dir, 'env', 'extravars')) as fd:
+ extra_vars = yaml.load(fd, Loader=SafeLoader)
+
+ # No refspec should be set
+ assert 'scm_refspec' not in extra_vars
+
+
class TestInventoryUpdateCredentials(TestJobExecution):
@pytest.fixture
def inventory_update(self, execution_environment):
@@ -1716,7 +1314,8 @@ def run(expected_gce_zone):
credential.credential_type.inject_credential(credential, env, safe_env, [], private_data_dir)
assert env['GCE_ZONE'] == expected_gce_zone
- json_data = json.load(open(env[cred_env_var], 'rb'))
+ with open(env[cred_env_var], 'rb') as f:
+ json_data = json.load(f)
assert json_data['type'] == 'service_account'
assert json_data['private_key'] == self.EXAMPLE_PRIVATE_KEY
assert json_data['client_email'] == 'bob'
@@ -1745,7 +1344,8 @@ def get_cred():
env = task.build_env(inventory_update, private_data_dir, private_data_files)
path = to_host_path(env['OS_CLIENT_CONFIG_FILE'], private_data_dir)
- shade_config = open(path, 'r').read()
+ with open(path, 'r') as f:
+ shade_config = f.read()
assert (
'\n'.join(
[
@@ -1908,8 +1508,8 @@ def test_fcntl_ioerror():
@mock.patch('os.open')
-@mock.patch('logging.getLogger')
-def test_acquire_lock_open_fail_logged(logging_getLogger, os_open, mock_me):
+@mock.patch('awx.main.tasks.jobs.logger')
+def test_acquire_lock_open_fail_logged(logger_mock, os_open, mock_me):
err = OSError()
err.errno = 3
err.strerror = 'dummy message'
@@ -1919,21 +1519,18 @@ def test_acquire_lock_open_fail_logged(logging_getLogger, os_open, mock_me):
os_open.side_effect = err
- logger = mock.Mock()
- logging_getLogger.return_value = logger
-
ProjectUpdate = jobs.RunProjectUpdate()
with pytest.raises(OSError):
ProjectUpdate.acquire_lock(instance)
- assert logger.err.called_with("I/O error({0}) while trying to open lock file [{1}]: {2}".format(3, 'this_file_does_not_exist', 'dummy message'))
+ logger_mock.error.assert_called_with("I/O error({0}) while trying to open lock file [{1}]: {2}".format(3, 'this_file_does_not_exist', 'dummy message'))
@mock.patch('os.open')
@mock.patch('os.close')
-@mock.patch('logging.getLogger')
+@mock.patch('awx.main.tasks.jobs.logger')
@mock.patch('fcntl.lockf')
-def test_acquire_lock_acquisition_fail_logged(fcntl_lockf, logging_getLogger, os_close, os_open, mock_me):
+def test_acquire_lock_acquisition_fail_logged(fcntl_lockf, logger_mock, os_close, os_open, mock_me):
err = IOError()
err.errno = 3
err.strerror = 'dummy message'
@@ -1944,16 +1541,15 @@ def test_acquire_lock_acquisition_fail_logged(fcntl_lockf, logging_getLogger, os
os_open.return_value = 3
- logger = mock.Mock()
- logging_getLogger.return_value = logger
-
fcntl_lockf.side_effect = err
ProjectUpdate = jobs.RunProjectUpdate()
with pytest.raises(IOError):
ProjectUpdate.acquire_lock(instance)
os_close.assert_called_with(3)
- assert logger.err.called_with("I/O error({0}) while trying to acquire lock on file [{1}]: {2}".format(3, 'this_file_does_not_exist', 'dummy message'))
+ logger_mock.error.assert_called_with(
+ "I/O error({0}) while trying to acquire lock on file [{1}]: {2}".format(3, 'this_file_does_not_exist', 'dummy message')
+ )
@pytest.mark.parametrize('injector_cls', [cls for cls in ManagedCredentialType.registry.values() if cls.injectors])
@@ -2008,7 +1604,7 @@ def test_project_update_no_ee(mock_me):
with pytest.raises(RuntimeError) as e:
task.build_env(job, {})
- assert 'The project could not sync because there is no Execution Environment' in str(e.value)
+ assert 'The ProjectUpdate could not run because there is no Execution Environment' in str(e.value)
@pytest.mark.parametrize(
diff --git a/awx/main/tests/unit/test_validators.py b/awx/main/tests/unit/test_validators.py
index 925ea64335eb..2512ab28af73 100644
--- a/awx/main/tests/unit/test_validators.py
+++ b/awx/main/tests/unit/test_validators.py
@@ -132,6 +132,25 @@ def test_cert_with_key():
assert not pem_objects[1]['key_enc']
+def test_ssh_key_with_whitespace():
+ # Test that SSH keys with leading/trailing whitespace/newlines are properly sanitized
+ # This addresses issue #14219 where copy-paste can introduce hidden newlines
+ valid_key_with_whitespace = "\n\n" + TEST_SSH_KEY_DATA + "\n\n"
+ pem_objects = validate_ssh_private_key(valid_key_with_whitespace)
+ assert pem_objects[0]['key_type'] == 'rsa'
+ assert not pem_objects[0]['key_enc']
+
+ # Test with just leading whitespace
+ valid_key_leading = "\n\n\n" + TEST_SSH_KEY_DATA
+ pem_objects = validate_ssh_private_key(valid_key_leading)
+ assert pem_objects[0]['key_type'] == 'rsa'
+
+ # Test with just trailing whitespace
+ valid_key_trailing = TEST_SSH_KEY_DATA + "\n\n\n"
+ pem_objects = validate_ssh_private_key(valid_key_trailing)
+ assert pem_objects[0]['key_type'] == 'rsa'
+
+
@pytest.mark.parametrize(
"var_str",
[
diff --git a/awx/main/tests/unit/test_views.py b/awx/main/tests/unit/test_views.py
index e9e2c67baf3a..371e44157ab5 100644
--- a/awx/main/tests/unit/test_views.py
+++ b/awx/main/tests/unit/test_views.py
@@ -10,7 +10,6 @@
from awx.api.views import JobList
from awx.api.generics import ListCreateAPIView, SubListAttachDetachAPIView
-
HTTP_METHOD_NAMES = [
'get',
'post',
@@ -88,6 +87,6 @@ def test_global_creation_always_possible(all_views):
creatable_view = View
if not creatable or not global_view:
continue
- assert 'POST' in global_view().allowed_methods, 'Resource {} should be creatable in global list view {}. ' 'Can be created now in {}'.format(
+ assert 'POST' in global_view().allowed_methods, 'Resource {} should be creatable in global list view {}. Can be created now in {}'.format(
model, global_view, creatable_view
)
diff --git a/awx/main/tests/unit/utils/test_analytics_proxy.py b/awx/main/tests/unit/utils/test_analytics_proxy.py
new file mode 100644
index 000000000000..0a49c33cb97a
--- /dev/null
+++ b/awx/main/tests/unit/utils/test_analytics_proxy.py
@@ -0,0 +1,112 @@
+import pytest
+import requests
+from unittest import mock
+
+from awx.main.utils.analytics_proxy import OIDCClient, TokenType, TokenError
+
+MOCK_TOKEN_RESPONSE = {
+ 'access_token': 'bob-access-token',
+ 'expires_in': 500,
+ 'refresh_expires_in': 900,
+ 'token_type': 'Bearer',
+ 'not-before-policy': 6,
+ 'scope': 'fake-scope1, fake-scope2',
+}
+
+
+@pytest.fixture
+def oidc_client():
+ '''
+ oidc client instantiation fixture.
+ '''
+ return OIDCClient(
+ 'fake-client-id',
+ 'fake-client-secret',
+ 'https://my-token-url.com/get/a/token/',
+ ['api.console'],
+ )
+
+
+@pytest.fixture
+def token():
+ '''
+ Create Token class out of example OIDC token response.
+ '''
+ return OIDCClient._json_response_to_token(MOCK_TOKEN_RESPONSE)
+
+
+def test_generate_access_token(oidc_client):
+ with mock.patch(
+ 'awx.main.utils.analytics_proxy.requests.post',
+ return_value=mock.Mock(json=lambda: MOCK_TOKEN_RESPONSE, raise_for_status=mock.Mock(return_value=None)), # No exception raised
+ ):
+ oidc_client._generate_access_token()
+
+ assert oidc_client.token
+ assert oidc_client.token.access_token == 'bob-access-token'
+ assert oidc_client.token.expires_in == 500
+ assert oidc_client.token.refresh_expires_in == 900
+ assert oidc_client.token.token_type == TokenType.BEARER
+ assert oidc_client.token.not_before_policy == 6
+ assert oidc_client.token.scope == 'fake-scope1, fake-scope2'
+
+
+def test_token_generation_error(oidc_client):
+ '''
+ Check that TokenError is raised for failure in token generation process
+ '''
+ exception_404 = requests.HTTPError('404 Client Error: Not Found for url')
+ with mock.patch(
+ 'awx.main.utils.analytics_proxy.requests.post',
+ return_value=mock.Mock(status_code=404, json=mock.Mock(return_value={'error': 'Not Found'}), raise_for_status=mock.Mock(side_effect=exception_404)),
+ ):
+ with pytest.raises(TokenError) as exc_info:
+ oidc_client._generate_access_token()
+
+ assert exc_info.value.__cause__ == exception_404
+
+
+def test_make_request(oidc_client, token):
+ '''
+ Check that make_request makes an http request with a generated token.
+ '''
+
+ def fake_generate_access_token():
+ oidc_client.token = token
+
+ with (
+ mock.patch.object(oidc_client, '_generate_access_token', side_effect=fake_generate_access_token),
+ mock.patch('awx.main.utils.analytics_proxy.requests.request') as mock_request,
+ ):
+ oidc_client.make_request('GET', 'https://does_not_exist.com')
+
+ mock_request.assert_called_with(
+ 'GET',
+ 'https://does_not_exist.com',
+ headers={
+ 'Authorization': f'Bearer {token.access_token}',
+ 'Accept': 'application/json',
+ },
+ )
+
+
+def test_make_request_existing_token(oidc_client, token):
+ '''
+ Check that make_request does not try and generate a token.
+ '''
+ oidc_client.token = token
+
+ with (
+ mock.patch.object(oidc_client, '_generate_access_token', side_effect=RuntimeError('expected not to be called')),
+ mock.patch('awx.main.utils.analytics_proxy.requests.request') as mock_request,
+ ):
+ oidc_client.make_request('GET', 'https://does_not_exist.com')
+
+ mock_request.assert_called_with(
+ 'GET',
+ 'https://does_not_exist.com',
+ headers={
+ 'Authorization': f'Bearer {token.access_token}',
+ 'Accept': 'application/json',
+ },
+ )
diff --git a/awx/main/tests/unit/utils/test_common.py b/awx/main/tests/unit/utils/test_common.py
index cc8f65bf9333..b5983497b20a 100644
--- a/awx/main/tests/unit/utils/test_common.py
+++ b/awx/main/tests/unit/utils/test_common.py
@@ -12,6 +12,8 @@
from rest_framework.exceptions import ParseError
+from ansible_base.lib.utils.models import get_type_for_model
+
from awx.main.utils import common
from awx.api.validators import HostnameRegexValidator
@@ -106,7 +108,7 @@ def test_set_environ():
# Cases relied on for scheduler dependent jobs list
@pytest.mark.parametrize('model,name', TEST_MODELS)
def test_get_type_for_model(model, name):
- assert common.get_type_for_model(model) == name
+ assert get_type_for_model(model) == name
def test_get_model_for_invalid_type():
@@ -119,6 +121,10 @@ def test_get_model_for_valid_type(model_type, model_class):
assert common.get_model_for_type(model_type) == model_class
+def test_is_testing():
+ assert common.is_testing() is True
+
+
@pytest.mark.parametrize("model_type,model_class", [(name, cls) for cls, name in TEST_MODELS])
def test_get_capacity_type(model_type, model_class):
if model_type in ('job', 'ad_hoc_command', 'inventory_update', 'job_template'):
@@ -234,7 +240,15 @@ def test_extract_ansible_vars():
('git', 'https://example.com/bar.git', 'user', 'pw', True, False, 'https://user:pw@example.com/bar.git'),
('git', 'https://example@example.com/bar.git', False, 'something', True, False, 'https://example.com/bar.git'),
# Special github/bitbucket cases
- ('git', 'notgit@github.com:ansible/awx.git', True, True, True, False, ValueError('Username must be "git" for SSH access to github.com.')),
+ (
+ 'git',
+ 'notgit@github.com:ansible/awx.git',
+ True,
+ True,
+ True,
+ False,
+ ValueError('Username must be "git" for SSH access to github.com.'),
+ ),
(
'git',
'notgit@bitbucket.org:does-not-exist/example.git',
@@ -312,22 +326,18 @@ def test_hostame_regex_validator_default_constructor(self, regex_expr, re_flags)
def test_good_call(self, regex_expr, re_flags):
h = HostnameRegexValidator(regex=regex_expr, flags=re_flags)
- assert (h("192.168.56.101"), None)
+ assert h("192.168.56.101") is None
def test_bad_call(self, regex_expr, re_flags):
h = HostnameRegexValidator(regex=regex_expr, flags=re_flags)
- try:
+ with pytest.raises(ValidationError, match=r"^\['illegal characters detected in hostname=@#\$%\)\$#\(TUFAS_DG. Please verify.'\]$"):
h("@#$%)$#(TUFAS_DG")
- except ValidationError as e:
- assert e.message is not None
def test_good_call_with_inverse(self, regex_expr, re_flags, inverse_match=True):
h = HostnameRegexValidator(regex=regex_expr, flags=re_flags, inverse_match=inverse_match)
- try:
+ with pytest.raises(ValidationError, match=r"^\['Enter a valid value.'\]$"):
h("1.2.3.4")
- except ValidationError as e:
- assert e.message is not None
def test_bad_call_with_inverse(self, regex_expr, re_flags, inverse_match=True):
h = HostnameRegexValidator(regex=regex_expr, flags=re_flags, inverse_match=inverse_match)
- assert (h("@#$%)$#(TUFAS_DG"), None)
+ assert h("@#$%)$#(TUFAS_DG") is None
diff --git a/awx/main/tests/unit/utils/test_execution_environments.py b/awx/main/tests/unit/utils/test_execution_environments.py
index 04a8b05f5f72..1e41890e5f86 100644
--- a/awx/main/tests/unit/utils/test_execution_environments.py
+++ b/awx/main/tests/unit/utils/test_execution_environments.py
@@ -4,8 +4,7 @@
import pytest
-from awx.main.utils.execution_environments import to_container_path
-
+from awx_plugins.interfaces._temporary_private_container_api import get_incontainer_path
private_data_dir = '/tmp/pdd_iso/awx_xxx'
@@ -22,7 +21,7 @@
],
)
def test_switch_paths(container_path, host_path):
- assert to_container_path(host_path, private_data_dir) == container_path
+ assert get_incontainer_path(host_path, private_data_dir) == container_path
def test_symlink_isolation_dir(request):
@@ -40,7 +39,7 @@ def remove_folders():
pdd = f'{dst_path}/awx_xxx'
- assert to_container_path(f'{pdd}/env/tmp1234', pdd) == '/runner/env/tmp1234'
+ assert get_incontainer_path(f'{pdd}/env/tmp1234', pdd) == '/runner/env/tmp1234'
@pytest.mark.parametrize(
@@ -53,4 +52,4 @@ def remove_folders():
)
def test_invalid_host_path(host_path):
with pytest.raises(RuntimeError):
- to_container_path(host_path, private_data_dir)
+ get_incontainer_path(host_path, private_data_dir)
diff --git a/awx/main/tests/unit/utils/test_filters.py b/awx/main/tests/unit/utils/test_filters.py
index ef0abb80d3f1..c2b23de0eea0 100644
--- a/awx/main/tests/unit/utils/test_filters.py
+++ b/awx/main/tests/unit/utils/test_filters.py
@@ -68,7 +68,9 @@ def __init__(self):
@mock.patch('awx.main.utils.filters.get_model', return_value=mockHost())
class TestSmartFilterQueryFromString:
- @mock.patch('awx.api.filters.get_fields_from_path', lambda model, path: ([model], path)) # disable field filtering, because a__b isn't a real Host field
+ @mock.patch(
+ 'ansible_base.rest_filters.rest_framework.field_lookup_backend.get_fields_from_path', lambda model, path, **kwargs: ([model], path)
+ ) # disable field filtering, because a__b isn't a real Host field
@pytest.mark.parametrize(
"filter_string,q_expected",
[
@@ -93,7 +95,7 @@ def test_query_generated(self, mock_get_host_model, filter_string, q_expected):
@pytest.mark.parametrize(
"filter_string",
[
- 'ansible_facts__facts__facts__blank=' 'ansible_facts__a__b__c__ space =ggg',
+ 'ansible_facts__facts__facts__blank=ansible_facts__a__b__c__ space =ggg',
],
)
def test_invalid_filter_strings(self, mock_get_host_model, filter_string):
@@ -104,7 +106,7 @@ def test_invalid_filter_strings(self, mock_get_host_model, filter_string):
@pytest.mark.parametrize(
"filter_string",
[
- 'created_by__password__icontains=pbkdf2' 'search=foo or created_by__password__icontains=pbkdf2',
+ 'created_by__password__icontains=pbkdf2search=foo or created_by__password__icontains=pbkdf2',
'created_by__password__icontains=pbkdf2 or search=foo',
],
)
@@ -116,8 +118,8 @@ def test_forbidden_filter_string(self, mock_get_host_model, filter_string):
@pytest.mark.parametrize(
"filter_string,q_expected",
[
- (u'(a=abc\u1F5E3def)', Q(**{u"a": u"abc\u1F5E3def"})),
- (u'(ansible_facts__a=abc\u1F5E3def)', Q(**{u"ansible_facts__contains": {u"a": u"abc\u1F5E3def"}})),
+ (u'(a=abc\u1f5e3def)', Q(**{u"a": u"abc\u1f5e3def"})),
+ (u'(ansible_facts__a=abc\u1f5e3def)', Q(**{u"ansible_facts__contains": {u"a": u"abc\u1f5e3def"}})),
],
)
def test_unicode(self, mock_get_host_model, filter_string, q_expected):
diff --git a/awx/main/tests/unit/utils/test_inventory_vars.py b/awx/main/tests/unit/utils/test_inventory_vars.py
new file mode 100644
index 000000000000..8ba55c900e17
--- /dev/null
+++ b/awx/main/tests/unit/utils/test_inventory_vars.py
@@ -0,0 +1,110 @@
+"""
+Test utility functions and classes for inventory variable handling.
+"""
+
+import pytest
+
+from awx.main.utils.inventory_vars import InventoryVariable
+from awx.main.utils.inventory_vars import InventoryGroupVariables
+
+
+def test_inventory_variable_update_basic():
+ """Test basic functionality of an inventory variable."""
+ x = InventoryVariable("x")
+ assert x.has_no_source
+ x.update(1, 101)
+ assert str(x) == "1"
+ x.update(2, 102)
+ assert str(x) == "2"
+ x.update(3, 103)
+ assert str(x) == "3"
+ x.delete(102)
+ assert str(x) == "3"
+ x.delete(103)
+ assert str(x) == "1"
+ x.delete(101)
+ assert x.value is None
+ assert x.has_no_source
+
+
+@pytest.mark.parametrize(
+ "updates", # (, , )
+ [
+ ((101, 1, 1),),
+ ((101, 1, 1), (101, None, None)),
+ ((101, 1, 1), (102, 2, 2), (102, None, 1)),
+ ((101, 1, 1), (102, 2, 2), (101, None, 2), (102, None, None)),
+ (
+ (101, 0, 0),
+ (101, 1, 1),
+ (102, 2, 2),
+ (103, 3, 3),
+ (102, None, 3),
+ (103, None, 1),
+ (101, None, None),
+ ),
+ ],
+)
+def test_inventory_variable_update(updates: tuple[int, int | None, int | None]):
+ """
+ Test if the variable value is set correctly on a sequence of updates.
+
+ For this test, the value `None` implies the deletion of the source.
+ """
+ x = InventoryVariable("x")
+ for src_id, value, expected_value in updates:
+ if value is None:
+ x.delete(src_id)
+ else:
+ x.update(value, src_id)
+ assert x.value == expected_value
+
+
+def test_inventory_group_variables_update_basic():
+ """Test basic functionality of an inventory variables update."""
+ vars = InventoryGroupVariables(1)
+ vars.update_from_src({"x": 1, "y": 2}, 101)
+ assert vars == {"x": 1, "y": 2}
+
+
+@pytest.mark.parametrize(
+ "updates", # (, : dict, : dict)
+ [
+ ((101, {"x": 1, "y": 1}, {"x": 1, "y": 1}),),
+ (
+ (101, {"x": 1, "y": 1}, {"x": 1, "y": 1}),
+ (102, {}, {"x": 1, "y": 1}),
+ ),
+ (
+ (101, {"x": 1, "y": 1}, {"x": 1, "y": 1}),
+ (102, {"x": 2}, {"x": 2, "y": 1}),
+ ),
+ (
+ (101, {"x": 1, "y": 1}, {"x": 1, "y": 1}),
+ (102, {"x": 2, "y": 2}, {"x": 2, "y": 2}),
+ ),
+ (
+ (101, {"x": 1, "y": 1}, {"x": 1, "y": 1}),
+ (102, {"x": 2, "z": 2}, {"x": 2, "y": 1, "z": 2}),
+ ),
+ (
+ (101, {"x": 1, "y": 1}, {"x": 1, "y": 1}),
+ (102, {"x": 2, "z": 2}, {"x": 2, "y": 1, "z": 2}),
+ (102, {}, {"x": 1, "y": 1}),
+ ),
+ (
+ (101, {"x": 1, "y": 1}, {"x": 1, "y": 1}),
+ (102, {"x": 2, "z": 2}, {"x": 2, "y": 1, "z": 2}),
+ (103, {"x": 3}, {"x": 3, "y": 1, "z": 2}),
+ (101, {}, {"x": 3, "z": 2}),
+ ),
+ ],
+)
+def test_inventory_group_variables_update(updates: tuple[int, int | None, int | None]):
+ """
+ Test if the group vars are set correctly on various update sequences.
+ """
+ groupvars = InventoryGroupVariables(2)
+ for src_id, vars, expected_vars in updates:
+ groupvars.update_from_src(vars, src_id)
+ assert groupvars == expected_vars
diff --git a/awx/main/tests/unit/utils/test_receptor.py b/awx/main/tests/unit/utils/test_receptor.py
index 0a7e182070c7..123044fcad1b 100644
--- a/awx/main/tests/unit/utils/test_receptor.py
+++ b/awx/main/tests/unit/utils/test_receptor.py
@@ -3,7 +3,7 @@
def test_file_cleanup_scenario():
args = _convert_args_to_cli({'exclude_strings': ['awx_423_', 'awx_582_'], 'file_pattern': '/tmp/awx_*_*'})
- assert ' '.join(args) == 'cleanup --exclude-strings=awx_423_ awx_582_ --file-pattern=/tmp/awx_*_*'
+ assert ' '.join(args) == 'cleanup --exclude-strings "awx_423_" "awx_582_" --file-pattern=/tmp/awx_*_*'
def test_image_cleanup_scenario():
@@ -17,5 +17,6 @@ def test_image_cleanup_scenario():
}
)
assert (
- ' '.join(args) == 'cleanup --remove-images=quay.invalid/foo/bar:latest quay.invalid/foo/bar:devel --image-prune --process-isolation-executable=podman'
+ ' '.join(args)
+ == 'cleanup --remove-images "quay.invalid/foo/bar:latest" "quay.invalid/foo/bar:devel" --image-prune --process-isolation-executable=podman'
)
diff --git a/awx/main/tests/unit/utils/test_redis.py b/awx/main/tests/unit/utils/test_redis.py
new file mode 100644
index 000000000000..23e0940fc032
--- /dev/null
+++ b/awx/main/tests/unit/utils/test_redis.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2025 Ansible, Inc.
+# All Rights Reserved
+
+from django.test.utils import override_settings
+
+from awx.main.utils.redis import get_redis_client, get_redis_client_async
+from redis.exceptions import BusyLoadingError, ConnectionError, TimeoutError
+from redis.backoff import ExponentialBackoff
+
+
+class TestRedisRetryConfiguration:
+ """Verify Redis retry configuration is applied to connection objects."""
+
+ def test_retry_configuration_applied_to_client(self, settings):
+ """Verify all retry settings are applied to the connection pool."""
+ # Test sync client
+ client = get_redis_client()
+ retry = client.connection_pool.connection_kwargs['retry']
+ backoff = retry._backoff
+ retry_errors = client.connection_pool.connection_kwargs['retry_on_error']
+
+ # Assert provided values match values on the object
+ assert retry._retries == settings.REDIS_RETRY_COUNT == 3
+ assert isinstance(backoff, ExponentialBackoff)
+ assert backoff._base == settings.REDIS_BACKOFF_BASE == 0.5
+ assert backoff._cap == settings.REDIS_BACKOFF_CAP == 1.0
+ assert BusyLoadingError in retry_errors
+ assert ConnectionError in retry_errors
+ assert TimeoutError in retry_errors
+
+ # Test async client has same config
+ client_async = get_redis_client_async()
+ retry_async = client_async.connection_pool.connection_kwargs['retry']
+ backoff_async = retry_async._backoff
+ retry_errors_async = client_async.connection_pool.connection_kwargs['retry_on_error']
+
+ assert retry_async._retries == settings.REDIS_RETRY_COUNT
+ assert backoff_async._base == settings.REDIS_BACKOFF_BASE
+ assert backoff_async._cap == settings.REDIS_BACKOFF_CAP
+ assert ConnectionError in retry_errors_async
+
+ @override_settings(REDIS_RETRY_COUNT=5)
+ def test_override_settings_applied_to_client(self):
+ """Verify override_settings changes are applied to client object."""
+ client = get_redis_client()
+ retry = client.connection_pool.connection_kwargs['retry']
+
+ assert retry._retries == 5
+
+ @override_settings(REDIS_BACKOFF_CAP=2.0, REDIS_BACKOFF_BASE=1.0)
+ def test_override_backoff_settings_applied_to_client(self):
+ """Verify override_settings for backoff parameters are applied to client object."""
+ client = get_redis_client()
+ retry = client.connection_pool.connection_kwargs['retry']
+ backoff = retry._backoff
+
+ # Assert provided values match values on object
+ assert backoff._cap == 2.0
+ assert backoff._base == 1.0
diff --git a/awx/main/tests/unit/utils/test_reload.py b/awx/main/tests/unit/utils/test_reload.py
index 5f8c7b95e35b..2b41a5fef0c1 100644
--- a/awx/main/tests/unit/utils/test_reload.py
+++ b/awx/main/tests/unit/utils/test_reload.py
@@ -7,15 +7,15 @@ def test_produce_supervisor_command(mocker):
mock_process = mocker.MagicMock()
mock_process.communicate = communicate_mock
Popen_mock = mocker.MagicMock(return_value=mock_process)
- with mocker.patch.object(reload.subprocess, 'Popen', Popen_mock):
- reload.supervisor_service_command("restart")
- reload.subprocess.Popen.assert_called_once_with(
- [
- 'supervisorctl',
- 'restart',
- 'tower-processes:*',
- ],
- stderr=-1,
- stdin=-1,
- stdout=-1,
- )
+ mocker.patch.object(reload.subprocess, 'Popen', Popen_mock)
+ reload.supervisor_service_command("restart")
+ reload.subprocess.Popen.assert_called_once_with(
+ [
+ 'supervisorctl',
+ 'restart',
+ 'tower-processes:*',
+ ],
+ stderr=-1,
+ stdin=-1,
+ stdout=-1,
+ )
diff --git a/awx/main/tests/unit/utils/test_schedule_fast_forward.py b/awx/main/tests/unit/utils/test_schedule_fast_forward.py
new file mode 100644
index 000000000000..be1bdae53eff
--- /dev/null
+++ b/awx/main/tests/unit/utils/test_schedule_fast_forward.py
@@ -0,0 +1,176 @@
+import pytest
+import datetime
+import dateutil
+
+from django.utils.timezone import now
+
+from awx.main.models.schedules import _fast_forward_rrule, Schedule
+from dateutil.rrule import HOURLY, MINUTELY, MONTHLY
+
+REF_DT = datetime.datetime(2024, 1, 1, tzinfo=datetime.timezone.utc)
+
+
+@pytest.mark.parametrize(
+ 'rrulestr',
+ [
+ pytest.param('DTSTART;TZID=America/New_York:20201118T200000 RRULE:FREQ=MINUTELY;INTERVAL=5', id='every-5-min'),
+ pytest.param('DTSTART;TZID=America/New_York:20201118T200000 RRULE:FREQ=HOURLY;INTERVAL=5', id='every-5-hours'),
+ pytest.param('DTSTART;TZID=America/New_York:20201118T200000 RRULE:FREQ=YEARLY;INTERVAL=5', id='every-5-years'),
+ pytest.param(
+ 'DTSTART;TZID=America/New_York:20201118T200000 RRULE:FREQ=MINUTELY;INTERVAL=5;WKST=SU;BYMONTH=2,3;BYMONTHDAY=18;BYHOUR=5;BYMINUTE=35;BYSECOND=0',
+ id='every-5-minutes-at-5:35:00-am-on-the-18th-day-of-feb-or-march-with-week-starting-on-sundays',
+ ),
+ pytest.param(
+ 'DTSTART;TZID=America/New_York:20201118T200000 RRULE:FREQ=HOURLY;INTERVAL=5;WKST=SU;BYMONTH=2,3;BYHOUR=5',
+ id='every-5-hours-at-5-am-in-feb-or-march-with-week-starting-on-sundays',
+ ),
+ ],
+)
+def test_fast_forwarded_rrule_matches_original_occurrence(rrulestr):
+ '''
+ Assert that the resulting fast forwarded date is included in the original rrule
+ occurrence list
+ '''
+ rruleset = Schedule.rrulestr(rrulestr, ref_dt=REF_DT)
+
+ gen = rruleset.xafter(REF_DT, count=200)
+ occurrences = [i for i in gen]
+
+ orig_rruleset = dateutil.rrule.rrulestr(rrulestr, forceset=True)
+ gen = orig_rruleset.xafter(REF_DT, count=200)
+ orig_occurrences = [i for i in gen]
+
+ assert occurrences == orig_occurrences
+
+
+@pytest.mark.parametrize(
+ 'ref_dt',
+ [
+ pytest.param(datetime.datetime(2024, 12, 1, 0, 0, tzinfo=datetime.timezone.utc), id='ref-dt-out-of-dst'),
+ pytest.param(datetime.datetime(2024, 6, 1, 0, 0, tzinfo=datetime.timezone.utc), id='ref-dt-in-dst'),
+ ],
+)
+@pytest.mark.parametrize(
+ 'rrulestr',
+ [
+ pytest.param('DTSTART;TZID=America/New_York:20240118T200000 RRULE:FREQ=MINUTELY;INTERVAL=10', id='rrule-out-of-dst'),
+ pytest.param('DTSTART;TZID=America/New_York:20240318T000000 RRULE:FREQ=MINUTELY;INTERVAL=10', id='rrule-in-dst'),
+ pytest.param(
+ 'DTSTART;TZID=Europe/Lisbon:20230703T005800 RRULE:INTERVAL=10;FREQ=MINUTELY;BYHOUR=9,10,11,12,13,14,15,16,17,18,19,20,21', id='rrule-in-dst-by-hour'
+ ),
+ ],
+)
+def test_fast_forward_across_dst(rrulestr, ref_dt):
+ '''
+ Ensure fast forward works across daylight savings boundaries
+ "in dst" means between March and November
+ "out of dst" means between November and March the following year
+
+ Assert that the resulting fast forwarded date is included in the original rrule
+ occurrence list
+ '''
+ rruleset = Schedule.rrulestr(rrulestr, ref_dt=ref_dt)
+
+ gen = rruleset.xafter(ref_dt, count=200)
+ occurrences = [i for i in gen]
+
+ orig_rruleset = dateutil.rrule.rrulestr(rrulestr, forceset=True)
+ gen = orig_rruleset.xafter(ref_dt, count=200)
+ orig_occurrences = [i for i in gen]
+
+ assert occurrences == orig_occurrences
+
+
+def test_fast_forward_rrule_hours():
+ '''
+ Generate an rrule for each hour of the day
+
+ Assert that the resulting fast forwarded date is included in the original rrule
+ occurrence list
+ '''
+ rrulestr_prefix = 'DTSTART;TZID=America/New_York:20201118T200000 RRULE:FREQ=HOURLY;'
+ for interval in range(1, 24):
+ rrulestr = f"{rrulestr_prefix}INTERVAL={interval}"
+ rruleset = Schedule.rrulestr(rrulestr, ref_dt=REF_DT)
+
+ gen = rruleset.xafter(REF_DT, count=200)
+ occurrences = [i for i in gen]
+
+ orig_rruleset = dateutil.rrule.rrulestr(rrulestr, forceset=True)
+ gen = orig_rruleset.xafter(REF_DT, count=200)
+ orig_occurrences = [i for i in gen]
+
+ assert occurrences == orig_occurrences
+
+
+def test_multiple_rrules():
+ '''
+ Create an rruleset that contains multiple rrules and an exrule
+ rruleA: freq HOURLY interval 5, dtstart should be fast forwarded
+ rruleB: freq HOURLY interval 7, dtstart should be fast forwarded
+ rruleC: freq MONTHLY interval 1, dtstart should not be fast forwarded
+ exruleA: freq HOURLY interval 5, dtstart should be fast forwarded
+ '''
+ rrulestr = '''DTSTART;TZID=America/New_York:20201118T200000
+ RRULE:FREQ=HOURLY;INTERVAL=5
+ RRULE:FREQ=HOURLY;INTERVAL=7
+ RRULE:FREQ=MONTHLY
+ EXRULE:FREQ=HOURLY;INTERVAL=5;BYDAY=MO,TU,WE'''
+ rruleset = Schedule.rrulestr(rrulestr, ref_dt=REF_DT)
+
+ rruleA, rruleB, rruleC = rruleset._rrule
+ exruleA = rruleset._exrule[0]
+
+ # assert that each rrule has its own dtstart
+ assert rruleA._dtstart != rruleB._dtstart
+ assert rruleA._dtstart != rruleC._dtstart
+
+ assert exruleA._dtstart == rruleA._dtstart
+
+ # the new dtstart should be within INTERVAL amount of hours from REF_DT
+ assert (REF_DT - rruleA._dtstart) < datetime.timedelta(hours=6)
+ assert (REF_DT - rruleB._dtstart) < datetime.timedelta(hours=8)
+ assert (REF_DT - exruleA._dtstart) < datetime.timedelta(hours=6)
+
+ # the freq=monthly rrule's dtstart should not have changed
+ dateutil_rruleset = dateutil.rrule.rrulestr(rrulestr, forceset=True)
+ assert rruleC._dtstart == dateutil_rruleset._rrule[2]._dtstart
+
+ gen = rruleset.xafter(REF_DT, count=200)
+ occurrences = [i for i in gen]
+
+ orig_rruleset = dateutil.rrule.rrulestr(rrulestr, forceset=True)
+ gen = orig_rruleset.xafter(REF_DT, count=200)
+ orig_occurrences = [i for i in gen]
+
+ assert occurrences == orig_occurrences
+
+
+def test_future_date_does_not_fast_forward():
+ dtstart = now() + datetime.timedelta(days=30)
+ rrule = dateutil.rrule.rrule(freq=HOURLY, interval=7, dtstart=dtstart)
+ new_rrule = _fast_forward_rrule(rrule, ref_dt=REF_DT)
+ assert new_rrule == rrule
+
+
+def test_rrule_with_count_does_not_fast_forward():
+ rrule = dateutil.rrule.rrule(freq=MINUTELY, interval=5, count=1, dtstart=REF_DT)
+
+ assert rrule == _fast_forward_rrule(rrule, ref_dt=REF_DT)
+
+
+@pytest.mark.parametrize(
+ ('freq', 'interval'),
+ [
+ pytest.param(MINUTELY, 15.5555, id="freq-MINUTELY-interval-15.5555"),
+ pytest.param(MONTHLY, 1, id="freq-MONTHLY-interval-1"),
+ ],
+)
+def test_does_not_fast_forward(freq, interval):
+ '''
+ Assert a couple of rrules that should not be fast forwarded
+ '''
+ dtstart = REF_DT - datetime.timedelta(days=30)
+ rrule = dateutil.rrule.rrule(freq=freq, interval=interval, dtstart=dtstart)
+
+ assert rrule == _fast_forward_rrule(rrule, ref_dt=REF_DT)
diff --git a/awx/main/tests/unit/utils/test_validate_rh.py b/awx/main/tests/unit/utils/test_validate_rh.py
new file mode 100644
index 000000000000..65052bbdefff
--- /dev/null
+++ b/awx/main/tests/unit/utils/test_validate_rh.py
@@ -0,0 +1,154 @@
+from unittest.mock import patch
+from awx.main.utils.licensing import Licenser
+
+
+def test_validate_rh_basic_auth_rhsm():
+ """
+ Assert get_rhsm_subs is called when
+ - basic_auth=True
+ - host is subscription.rhsm.redhat.com
+ """
+ licenser = Licenser()
+
+ with patch.object(licenser, 'get_host_from_rhsm_config', return_value='https://subscription.rhsm.redhat.com') as mock_get_host, patch.object(
+ licenser, 'get_rhsm_subs', return_value=[]
+ ) as mock_get_rhsm, patch.object(licenser, 'get_satellite_subs') as mock_get_satellite, patch.object(
+ licenser, 'get_crc_subs'
+ ) as mock_get_crc, patch.object(
+ licenser, 'generate_license_options_from_entitlements'
+ ) as mock_generate:
+
+ licenser.validate_rh('testuser', 'testpass', basic_auth=True)
+
+ # Assert the correct methods were called
+ mock_get_host.assert_called_once()
+ mock_get_rhsm.assert_called_once_with('https://subscription.rhsm.redhat.com', 'testuser', 'testpass')
+ mock_get_satellite.assert_not_called()
+ mock_get_crc.assert_not_called()
+ mock_generate.assert_called_once_with([], is_candlepin=True)
+
+
+def test_validate_rh_basic_auth_satellite():
+ """
+ Assert get_satellite_subs is called when
+ - basic_auth=True
+ - custom satellite host
+ """
+ licenser = Licenser()
+
+ with patch.object(licenser, 'get_host_from_rhsm_config', return_value='https://satellite.example.com') as mock_get_host, patch.object(
+ licenser, 'get_rhsm_subs'
+ ) as mock_get_rhsm, patch.object(licenser, 'get_satellite_subs', return_value=[]) as mock_get_satellite, patch.object(
+ licenser, 'get_crc_subs'
+ ) as mock_get_crc, patch.object(
+ licenser, 'generate_license_options_from_entitlements'
+ ) as mock_generate:
+
+ licenser.validate_rh('testuser', 'testpass', basic_auth=True)
+
+ # Assert the correct methods were called
+ mock_get_host.assert_called_once()
+ mock_get_rhsm.assert_not_called()
+ mock_get_satellite.assert_called_once_with('https://satellite.example.com', 'testuser', 'testpass')
+ mock_get_crc.assert_not_called()
+ mock_generate.assert_called_once_with([], is_candlepin=True)
+
+
+def test_validate_rh_service_account_crc():
+ """
+ Assert get_crc_subs is called when
+ - basic_auth=False
+ """
+ licenser = Licenser()
+
+ with patch('awx.main.utils.licensing.settings') as mock_settings, patch.object(licenser, 'get_host_from_rhsm_config') as mock_get_host, patch.object(
+ licenser, 'get_rhsm_subs'
+ ) as mock_get_rhsm, patch.object(licenser, 'get_satellite_subs') as mock_get_satellite, patch.object(
+ licenser, 'get_crc_subs', return_value=[]
+ ) as mock_get_crc, patch.object(
+ licenser, 'generate_license_options_from_entitlements'
+ ) as mock_generate:
+
+ mock_settings.SUBSCRIPTIONS_RHSM_URL = 'https://console.redhat.com/api/rhsm/v1/subscriptions'
+
+ licenser.validate_rh('client_id', 'client_secret', basic_auth=False)
+
+ # Assert the correct methods were called
+ mock_get_host.assert_not_called()
+ mock_get_rhsm.assert_not_called()
+ mock_get_satellite.assert_not_called()
+ mock_get_crc.assert_called_once_with('https://console.redhat.com/api/rhsm/v1/subscriptions', 'client_id', 'client_secret')
+ mock_generate.assert_called_once_with([], is_candlepin=False)
+
+
+def test_validate_rh_missing_user_raises_error():
+ """Test validate_rh raises ValueError when user is missing"""
+ licenser = Licenser()
+
+ with patch.object(licenser, 'get_host_from_rhsm_config', return_value='https://subscription.rhsm.redhat.com'):
+ try:
+ licenser.validate_rh(None, 'testpass', basic_auth=True)
+ assert False, "Expected ValueError to be raised"
+ except ValueError as e:
+ assert 'subscriptions_client_id or subscriptions_username is required' in str(e)
+
+
+def test_validate_rh_missing_password_raises_error():
+ """Test validate_rh raises ValueError when password is missing"""
+ licenser = Licenser()
+
+ with patch.object(licenser, 'get_host_from_rhsm_config', return_value='https://subscription.rhsm.redhat.com'):
+ try:
+ licenser.validate_rh('testuser', None, basic_auth=True)
+ assert False, "Expected ValueError to be raised"
+ except ValueError as e:
+ assert 'subscriptions_client_secret or subscriptions_password is required' in str(e)
+
+
+def test_validate_rh_no_host_fallback_to_candlepin():
+ """Test validate_rh falls back to REDHAT_CANDLEPIN_HOST when no host from config
+ - basic_auth=True
+ - no host from config
+ - REDHAT_CANDLEPIN_HOST is set
+ """
+ licenser = Licenser()
+
+ with patch('awx.main.utils.licensing.settings') as mock_settings, patch.object(
+ licenser, 'get_host_from_rhsm_config', return_value=None
+ ) as mock_get_host, patch.object(licenser, 'get_rhsm_subs', return_value=[]) as mock_get_rhsm, patch.object(
+ licenser, 'get_satellite_subs', return_value=[]
+ ) as mock_get_satellite, patch.object(
+ licenser, 'get_crc_subs'
+ ) as mock_get_crc, patch.object(
+ licenser, 'generate_license_options_from_entitlements'
+ ) as mock_generate:
+
+ mock_settings.REDHAT_CANDLEPIN_HOST = 'https://candlepin.example.com'
+ licenser.validate_rh('testuser', 'testpass', basic_auth=True)
+
+ # Assert the correct methods were called
+ mock_get_host.assert_called_once()
+ mock_get_rhsm.assert_not_called()
+ mock_get_satellite.assert_called_once_with('https://candlepin.example.com', 'testuser', 'testpass')
+ mock_get_crc.assert_not_called()
+ mock_generate.assert_called_once_with([], is_candlepin=True)
+
+
+def test_validate_rh_empty_credentials_basic_auth():
+ """Test validate_rh with empty string credentials raises ValueError"""
+ licenser = Licenser()
+
+ with patch.object(licenser, 'get_host_from_rhsm_config', return_value='https://subscription.rhsm.redhat.com'):
+ # Test empty user
+ try:
+ licenser.validate_rh(None, 'testpass', basic_auth=True)
+ assert False, "Expected ValueError to be raised"
+ except ValueError as e:
+ assert 'subscriptions_client_id or subscriptions_username is required' in str(e)
+
+ # Test empty password
+ try:
+ licenser.validate_rh('testuser', None, basic_auth=True)
+ assert False, "Expected ValueError to be raised"
+ except ValueError as e:
+ assert 'subscriptions_client_secret or subscriptions_password is required' in str(e)
diff --git a/awx/main/utils/__init__.py b/awx/main/utils/__init__.py
index 2ffec9d8b617..af7473b0bd9e 100644
--- a/awx/main/utils/__init__.py
+++ b/awx/main/utils/__init__.py
@@ -3,6 +3,7 @@
# AWX
from awx.main.utils.common import * # noqa
+from awx.main.utils.redis import get_redis_client, get_redis_client_async # noqa
from awx.main.utils.encryption import ( # noqa
get_encryption_key,
encrypt_field,
diff --git a/awx/main/utils/analytics_proxy.py b/awx/main/utils/analytics_proxy.py
new file mode 100644
index 000000000000..a6a599942e21
--- /dev/null
+++ b/awx/main/utils/analytics_proxy.py
@@ -0,0 +1,188 @@
+'''
+Proxy requests Analytics requests
+'''
+
+import time
+
+from enum import Enum
+
+from typing import Optional, Any
+
+import requests
+
+DEFAULT_OIDC_TOKEN_ENDPOINT = 'https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/token'
+
+
+class TokenError(requests.RequestException):
+ '''
+ Raised when token generation request fails.
+
+ Useful for differentiating request failure for make_request() vs.
+ other requests issued to get a token i.e.:
+
+ try:
+ client = OIDCClient(...)
+ client.make_request(...)
+ except TokenError as e:
+ print(f"Token generation failed due to {e.__cause__}")
+ except requests.RequestException:
+ print("API request failed)
+ '''
+
+ def __init__(self, message="Token generation request failed", response=None):
+ super().__init__(message)
+ self.response = response # Store the response for debugging
+
+
+def _now(reason: str):
+ '''
+ Wrapper for time. Helps with testing.
+ '''
+ return int(time.time())
+
+
+class TokenType(Enum):
+ '''
+ Access token type as returned by the remote API.
+ '''
+
+ BEARER = 'Bearer'
+
+
+class Token:
+ '''
+ Token data generated by OIDC response.
+ '''
+
+ access_token: str
+ expires_in: int
+ refresh_expires_in: int
+ token_type: TokenType
+ not_before_policy: int # not-before-policy
+ scope: str
+
+ def __init__(
+ self,
+ access_token: str,
+ expires_in: int,
+ refresh_expires_in: int,
+ token_type: TokenType,
+ not_before_policy: int,
+ scope: str,
+ ):
+ self.access_token = access_token
+ self.expires_in = expires_in
+ self.refresh_expires_in = refresh_expires_in
+ self.token_type = token_type
+ self.not_before_policy = not_before_policy
+ self.scope = scope
+
+ self._now = _now(reason='token-creation')
+
+ @property
+ def expires_at(self) -> int:
+ '''
+ Unix timestamp in seconds of when the token expires.
+ '''
+ return self._now + self.expires_in
+
+ def is_expired(self) -> bool:
+ '''
+ Check if the token is expired.
+ '''
+ return _now(reason='token-expiration-check') >= self.expires_at
+
+
+class OIDCClient:
+ '''
+ Wraps requests library make_request() and manages OIDC access token.
+ '''
+
+ def __init__(
+ self,
+ client_id: str,
+ client_secret: str,
+ token_url: str = DEFAULT_OIDC_TOKEN_ENDPOINT,
+ scopes: list[str] = None,
+ base_url: str = '',
+ ) -> None:
+ self.client_id: str = client_id
+ self.client_secret: str = client_secret
+ self.token_url: str = token_url
+ if scopes is None:
+ scopes = ['api.console']
+ self.scopes = scopes
+ self.base_url: str = base_url
+ self.token: Optional[Token] = None
+
+ @classmethod
+ def _json_response_to_token(cls, json_response: Any) -> Token:
+ return Token(
+ access_token=json_response['access_token'],
+ expires_in=json_response['expires_in'],
+ refresh_expires_in=json_response['refresh_expires_in'],
+ token_type=TokenType(json_response['token_type']),
+ not_before_policy=json_response['not-before-policy'],
+ scope=json_response['scope'],
+ )
+
+ def _generate_access_token(self) -> None:
+ '''
+ Fetches the initial access token using client credentials.
+ '''
+ response = requests.post(
+ self.token_url,
+ data={
+ 'grant_type': 'client_credentials',
+ 'client_id': self.client_id,
+ 'client_secret': self.client_secret,
+ 'scope': self.scopes,
+ },
+ headers={'Content-Type': 'application/x-www-form-urlencoded'},
+ )
+ try:
+ response.raise_for_status()
+ except requests.RequestException as e:
+ raise TokenError() from e
+ self.token = OIDCClient._json_response_to_token(response.json())
+
+ def _add_headers(self, headers: dict[str, str]) -> None:
+ '''
+ Add token header
+ '''
+ headers.update(
+ {
+ 'Authorization': f'Bearer {self.token.access_token}',
+ 'Accept': 'application/json',
+ }
+ )
+
+ def _make_request(self, method: str, url: str, headers: dict[str, str], **kwargs: Any) -> requests.Response:
+ '''
+ Actually make an API call.
+ '''
+ self._add_headers(headers)
+ return requests.request(method, url, headers=headers, **kwargs)
+
+ def make_request(self, method: str, endpoint: str, **kwargs: Any) -> requests.Response:
+ '''
+ Makes an authenticated request and refreshes the token if expired.
+ '''
+ has_generated_token = False
+
+ def generate_access_token():
+ self._generate_access_token()
+ return True
+
+ if not self.token or self.token.is_expired():
+ has_generated_token = generate_access_token()
+
+ url = f'{self.base_url}{endpoint}'
+ headers = kwargs.pop('headers', {})
+
+ response = self._make_request(method, url, headers, **kwargs)
+ if not has_generated_token and response.status_code == 401:
+ generate_access_token()
+ response = self._make_request(method, url, headers, **kwargs)
+
+ return response
diff --git a/awx/main/utils/ansible.py b/awx/main/utils/ansible.py
index 64530c53007c..cd99b347de8d 100644
--- a/awx/main/utils/ansible.py
+++ b/awx/main/utils/ansible.py
@@ -48,15 +48,16 @@ def could_be_playbook(project_path, dir_path, filename):
# show up.
matched = False
try:
- for n, line in enumerate(codecs.open(playbook_path, 'r', encoding='utf-8', errors='ignore')):
- if valid_playbook_re.match(line):
- matched = True
- break
- # Any YAML file can also be encrypted with vault;
- # allow these to be used as the main playbook.
- elif n == 0 and line.startswith('$ANSIBLE_VAULT;'):
- matched = True
- break
+ with codecs.open(playbook_path, 'r', encoding='utf-8', errors='ignore') as f:
+ for n, line in enumerate(f):
+ if valid_playbook_re.match(line):
+ matched = True
+ break
+ # Any YAML file can also be encrypted with vault;
+ # allow these to be used as the main playbook.
+ elif n == 0 and line.startswith('$ANSIBLE_VAULT;'):
+ matched = True
+ break
except IOError:
return None
if not matched:
diff --git a/awx/main/utils/common.py b/awx/main/utils/common.py
index dedd02f9957b..a02daef16631 100644
--- a/awx/main/utils/common.py
+++ b/awx/main/utils/common.py
@@ -6,7 +6,7 @@
import json
import yaml
import logging
-import time
+import psycopg
import os
import subprocess
import re
@@ -17,13 +17,15 @@
import contextlib
import tempfile
import functools
+from importlib.metadata import version as _get_version
+from importlib.metadata import entry_points, EntryPoint
# Django
from django.core.exceptions import ObjectDoesNotExist, FieldDoesNotExist
from django.utils.dateparse import parse_datetime
from django.utils.translation import gettext_lazy as _
from django.utils.functional import cached_property
-from django.db import connection, transaction, ProgrammingError
+from django.db import connection, DatabaseError, transaction, ProgrammingError, IntegrityError
from django.db.models.fields.related import ForeignObjectRel, ManyToManyField
from django.db.models.fields.related_descriptors import ForwardManyToOneDescriptor, ManyToManyDescriptor
from django.db.models.query import QuerySet
@@ -41,6 +43,9 @@
# AWX
from awx.conf.license import get_license
+# ansible-runner
+from ansible_runner.utils.capacity import get_mem_in_bytes, get_cpu_count
+
logger = logging.getLogger('awx.main.utils')
__all__ = [
@@ -52,12 +57,10 @@
'get_awx_http_client_headers',
'get_awx_version',
'update_scm_url',
- 'get_type_for_model',
'get_model_for_type',
'copy_model_by_class',
'copy_m2m_relationships',
'prefetch_page_capabilities',
- 'to_python_boolean',
'datetime_hook',
'ignore_inventory_computed_fields',
'ignore_inventory_group_removal',
@@ -80,7 +83,6 @@
'set_environ',
'IllegalArgumentError',
'get_custom_venv_choices',
- 'get_external_account',
'ScheduleTaskManager',
'ScheduleDependencyManager',
'ScheduleWorkflowManager',
@@ -90,7 +92,7 @@
'deepmerge',
'get_event_partition_epoch',
'cleanup_new_process',
- 'log_excess_runtime',
+ 'unified_job_class_to_event_table_name',
]
@@ -110,18 +112,6 @@ def get_object_or_400(klass, *args, **kwargs):
raise ParseError(*e.args)
-def to_python_boolean(value, allow_none=False):
- value = str(value)
- if value.lower() in ('true', '1', 't'):
- return True
- elif value.lower() in ('false', '0', 'f'):
- return False
- elif allow_none and value.lower() in ('none', 'null'):
- return None
- else:
- raise ValueError(_(u'Unable to convert "%s" to boolean') % value)
-
-
def datetime_hook(d):
new_d = {}
for key, value in d.items():
@@ -150,7 +140,7 @@ def underscore_to_camelcase(s):
@functools.cache
def is_testing(argv=None):
'''Return True if running django or py.test unit tests.'''
- if 'PYTEST_CURRENT_TEST' in os.environ.keys():
+ if os.environ.get('DJANGO_SETTINGS_MODULE') == 'awx.main.tests.settings_for_test':
return True
argv = sys.argv if argv is None else argv
if len(argv) >= 1 and ('py.test' in argv[0] or 'py/test.py' in argv[0]):
@@ -160,6 +150,14 @@ def is_testing(argv=None):
return False
+def bypass_in_test(func):
+ def fn(*args, **kwargs):
+ if not is_testing():
+ return func(*args, **kwargs)
+
+ return fn
+
+
class RequireDebugTrueOrTest(logging.Filter):
"""
Logging filter to output when in DEBUG mode or running tests.
@@ -235,9 +233,7 @@ def get_awx_version():
from awx import __version__
try:
- import pkg_resources
-
- return pkg_resources.require('awx')[0].version
+ return _get_version('awx')
except Exception:
return __version__
@@ -367,7 +363,7 @@ def get_allowed_fields(obj, serializer_mapping):
else:
allowed_fields = [x.name for x in obj._meta.fields]
- ACTIVITY_STREAM_FIELD_EXCLUSIONS = {'user': ['last_login'], 'oauth2accesstoken': ['last_used'], 'oauth2application': ['client_secret']}
+ ACTIVITY_STREAM_FIELD_EXCLUSIONS = {'user': ['last_login']}
model_name = obj._meta.model_name
fields_excluded = ACTIVITY_STREAM_FIELD_EXCLUSIONS.get(model_name, [])
# see definition of from_db for CredentialType
@@ -569,14 +565,6 @@ def copy_m2m_relationships(obj1, obj2, fields, kwargs=None):
dest_field.add(*list(src_field_value.all().values_list('id', flat=True)))
-def get_type_for_model(model):
- """
- Return type name for a given model class.
- """
- opts = model._meta.concrete_model._meta
- return camelcase_to_underscore(opts.object_name)
-
-
def get_model_for_type(type_name):
"""
Return model class for a given type name.
@@ -717,7 +705,7 @@ def parse_yaml_or_json(vars_str, silent_failure=True):
if silent_failure:
return {}
raise ParseError(
- _('Cannot parse as JSON (error: {json_error}) or ' 'YAML (error: {yaml_error}).').format(json_error=str(json_err), yaml_error=str(yaml_err))
+ _('Cannot parse as JSON (error: {json_error}) or YAML (error: {yaml_error}).').format(json_error=str(json_err), yaml_error=str(yaml_err))
)
return vars_dict
@@ -768,14 +756,13 @@ def get_corrected_cpu(cpu_count): # formerlly get_cpu_capacity
return cpu_count # no correction
-def get_cpu_effective_capacity(cpu_count):
+def get_cpu_effective_capacity(cpu_count, is_control_node=False):
from django.conf import settings
- cpu_count = get_corrected_cpu(cpu_count)
-
settings_forkcpu = getattr(settings, 'SYSTEM_TASK_FORKS_CPU', None)
env_forkcpu = os.getenv('SYSTEM_TASK_FORKS_CPU', None)
-
+ if is_control_node:
+ cpu_count = get_corrected_cpu(cpu_count)
if env_forkcpu:
forkcpu = int(env_forkcpu)
elif settings_forkcpu:
@@ -834,6 +821,7 @@ def get_corrected_memory(memory):
# Runner returns memory in bytes
# so we convert memory from settings to bytes as well.
+
if env_absmem is not None:
return convert_mem_str_to_bytes(env_absmem)
elif settings_absmem is not None:
@@ -842,14 +830,13 @@ def get_corrected_memory(memory):
return memory
-def get_mem_effective_capacity(mem_bytes):
+def get_mem_effective_capacity(mem_bytes, is_control_node=False):
from django.conf import settings
- mem_bytes = get_corrected_memory(mem_bytes)
-
settings_mem_mb_per_fork = getattr(settings, 'SYSTEM_TASK_FORKS_MEM', None)
env_mem_mb_per_fork = os.getenv('SYSTEM_TASK_FORKS_MEM', None)
-
+ if is_control_node:
+ mem_bytes = get_corrected_memory(mem_bytes)
if env_mem_mb_per_fork:
mem_mb_per_fork = int(env_mem_mb_per_fork)
elif settings_mem_mb_per_fork:
@@ -1013,9 +1000,15 @@ def getattrd(obj, name, default=NoDefaultProvided):
raise
-def getattr_dne(obj, name, notfound=ObjectDoesNotExist):
+empty = object()
+
+
+def getattr_dne(obj, name, default=empty, notfound=ObjectDoesNotExist):
try:
- return getattr(obj, name)
+ if default is empty:
+ return getattr(obj, name)
+ else:
+ return getattr(obj, name, default)
except notfound:
return None
@@ -1089,29 +1082,6 @@ def has_model_field_prefetched(model_obj, field_name):
return getattr(getattr(model_obj, field_name, None), 'prefetch_cache_name', '') in getattr(model_obj, '_prefetched_objects_cache', {})
-def get_external_account(user):
- from django.conf import settings
-
- account_type = None
- if getattr(settings, 'AUTH_LDAP_SERVER_URI', None):
- try:
- if user.pk and user.profile.ldap_dn and not user.has_usable_password():
- account_type = "ldap"
- except AttributeError:
- pass
- if (
- getattr(settings, 'SOCIAL_AUTH_GOOGLE_OAUTH2_KEY', None)
- or getattr(settings, 'SOCIAL_AUTH_GITHUB_KEY', None)
- or getattr(settings, 'SOCIAL_AUTH_GITHUB_ORG_KEY', None)
- or getattr(settings, 'SOCIAL_AUTH_GITHUB_TEAM_KEY', None)
- or getattr(settings, 'SOCIAL_AUTH_SAML_ENABLED_IDPS', None)
- ) and user.social_auth.all():
- account_type = "social"
- if (getattr(settings, 'RADIUS_SERVER', None) or getattr(settings, 'TACACSPLUS_HOST', None)) and user.enterprise_auth.all():
- account_type = "enterprise"
- return account_type
-
-
class classproperty:
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
@@ -1134,7 +1104,11 @@ def create_temporary_fifo(data):
path = os.path.join(tempfile.mkdtemp(), next(tempfile._get_candidate_names()))
os.mkfifo(path, stat.S_IRUSR | stat.S_IWUSR)
- threading.Thread(target=lambda p, d: open(p, 'wb').write(d), args=(path, data)).start()
+ def tmp_write(path, data):
+ with open(path, 'wb') as f:
+ f.write(data)
+
+ threading.Thread(target=tmp_write, args=(path, data)).start()
return path
@@ -1172,6 +1146,17 @@ def deepmerge(a, b):
return b
+def table_exists(cursor, table_name):
+ cursor.execute(f"SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = '{table_name}');")
+ row = cursor.fetchone()
+ if row is not None:
+ for val in row: # should only have 1
+ if val is True:
+ logger.debug(f'Event partition table {table_name} already exists')
+ return True
+ return False
+
+
def create_partition(tblname, start=None):
"""Creates new partition table for events. By default it covers the current hour."""
if start is None:
@@ -1188,13 +1173,37 @@ def create_partition(tblname, start=None):
try:
with transaction.atomic():
with connection.cursor() as cursor:
+ if table_exists(cursor, f"{tblname}_{partition_label}"):
+ return
+
cursor.execute(
- f'CREATE TABLE IF NOT EXISTS {tblname}_{partition_label} '
- f'PARTITION OF {tblname} '
- f'FOR VALUES FROM (\'{start_timestamp}\') to (\'{end_timestamp}\');'
+ f'CREATE TABLE {tblname}_{partition_label} (LIKE {tblname} INCLUDING DEFAULTS INCLUDING CONSTRAINTS); '
+ f'ALTER TABLE {tblname} ATTACH PARTITION {tblname}_{partition_label} '
+ f'FOR VALUES FROM (\'{start_timestamp}\') TO (\'{end_timestamp}\');'
)
- except ProgrammingError as e:
- logger.debug(f'Caught known error due to existing partition: {e}')
+
+ except (ProgrammingError, IntegrityError) as e:
+ cause = e.__cause__
+ if cause and hasattr(cause, 'sqlstate'):
+ sqlstate = cause.sqlstate
+ if sqlstate is None:
+ raise
+ sqlstate_cls = psycopg.errors.lookup(sqlstate)
+
+ if sqlstate_cls in (psycopg.errors.DuplicateTable, psycopg.errors.DuplicateObject, psycopg.errors.UniqueViolation):
+ logger.info(f'Caught known error due to partition creation race: {e}')
+ else:
+ logger.error('SQL Error state: {} - {}'.format(sqlstate, sqlstate_cls))
+ raise
+ except DatabaseError as e:
+ cause = e.__cause__
+ if cause and hasattr(cause, 'sqlstate'):
+ sqlstate = cause.sqlstate
+ if sqlstate is None:
+ raise
+ sqlstate_str = psycopg.errors.lookup(sqlstate)
+ logger.error('SQL Error state: {} - {}'.format(sqlstate, sqlstate_str))
+ raise
def cleanup_new_process(func):
@@ -1214,32 +1223,44 @@ def wrapper_cleanup_new_process(*args, **kwargs):
return wrapper_cleanup_new_process
-def log_excess_runtime(func_logger, cutoff=5.0, debug_cutoff=5.0, msg=None, add_log_data=False):
- def log_excess_runtime_decorator(func):
- @functools.wraps(func)
- def _new_func(*args, **kwargs):
- start_time = time.time()
- log_data = {'name': repr(func.__name__)}
+def unified_job_class_to_event_table_name(job_class):
+ return f'main_{job_class().event_class.__name__.lower()}'
- if add_log_data:
- return_value = func(*args, log_data=log_data, **kwargs)
- else:
- return_value = func(*args, **kwargs)
- log_data['delta'] = time.time() - start_time
- if isinstance(return_value, dict):
- log_data.update(return_value)
+def load_all_entry_points_for(entry_point_subsections: list[str], /) -> dict[str, EntryPoint]:
+ return {ep.name: ep for entry_point_category in entry_point_subsections for ep in entry_points(group=f'awx_plugins.{entry_point_category}')}
- if msg is None:
- record_msg = 'Running {name} took {delta:.2f}s'
- else:
- record_msg = msg
- if log_data['delta'] > cutoff:
- func_logger.info(record_msg.format(**log_data))
- elif log_data['delta'] > debug_cutoff:
- func_logger.debug(record_msg.format(**log_data))
- return return_value
- return _new_func
+def get_auto_max_workers():
+ """Method we normally rely on to get max_workers
+
+ Uses almost same logic as Instance.local_health_check
+ The important thing is to be MORE than Instance.capacity
+ so that the task-manager does not over-schedule this node
+
+ Ideally we would just use the capacity from the database plus reserve workers,
+ but this poses some bootstrap problems where OCP task containers
+ register themselves after startup
+ """
+ # Get memory from ansible-runner
+ total_memory_gb = get_mem_in_bytes()
+
+ # This may replace memory calculation with a user override
+ corrected_memory = get_corrected_memory(total_memory_gb)
+
+ # Get same number as max forks based on memory, this function takes memory as bytes
+ mem_capacity = get_mem_effective_capacity(corrected_memory, is_control_node=True)
+
+ # Follow same process for CPU capacity constraint
+ cpu_count = get_cpu_count()
+ corrected_cpu = get_corrected_cpu(cpu_count)
+ cpu_capacity = get_cpu_effective_capacity(corrected_cpu, is_control_node=True)
+
+ # Here is what is different from health checks,
+ auto_max = max(mem_capacity, cpu_capacity)
+
+ # add magic number of extra workers to ensure
+ # we have a few extra workers to run the heartbeat
+ auto_max += 7
- return log_excess_runtime_decorator
+ return auto_max
diff --git a/awx/main/utils/db.py b/awx/main/utils/db.py
index 5574d4ea91f2..2078b28d49fb 100644
--- a/awx/main/utils/db.py
+++ b/awx/main/utils/db.py
@@ -1,20 +1,34 @@
# Copyright (c) 2017 Ansible by Red Hat
# All Rights Reserved.
-from itertools import chain
-
-
-def get_all_field_names(model):
- # Implements compatibility with _meta.get_all_field_names
- # See: https://docs.djangoproject.com/en/1.11/ref/models/meta/#migrating-from-the-old-api
- return list(
- set(
- chain.from_iterable(
- (field.name, field.attname) if hasattr(field, 'attname') else (field.name,)
- for field in model._meta.get_fields()
- # For complete backwards compatibility, you may want to exclude
- # GenericForeignKey from the results.
- if not (field.many_to_one and field.related_model is None)
- )
- )
- )
+from awx.settings.application_name import set_application_name
+
+from django.conf import settings
+
+
+def set_connection_name(function):
+ set_application_name(settings.DATABASES, settings.CLUSTER_HOST_ID, function=function)
+
+
+def bulk_update_sorted_by_id(model, objects, fields, batch_size=1000):
+ """
+ Perform a sorted bulk update on model instances to avoid database deadlocks.
+
+ This function was introduced to prevent deadlocks observed in the AWX Controller
+ when concurrent jobs attempt to update different fields on the same `main_hosts` table.
+ Specifically, deadlocks occurred when one process updated `last_job_id` while another
+ simultaneously updated `ansible_facts`.
+
+ By sorting updates ID, we ensure a consistent update order,
+ which helps avoid the row-level locking contention that can lead to deadlocks
+ in PostgreSQL when multiple processes are involved.
+
+ Returns:
+ int: The number of rows affected by the update.
+ """
+ objects = [obj for obj in objects if obj.id is not None]
+ if not objects:
+ return 0 # Return 0 when nothing is updated
+
+ sorted_objects = sorted(objects, key=lambda obj: obj.id)
+ return model.objects.bulk_update(sorted_objects, fields, batch_size=batch_size)
diff --git a/awx/main/utils/encryption.py b/awx/main/utils/encryption.py
index 4272e3e07fc1..d23685d33456 100644
--- a/awx/main/utils/encryption.py
+++ b/awx/main/utils/encryption.py
@@ -9,7 +9,6 @@
from cryptography.hazmat.backends import default_backend
from django.utils.encoding import smart_str, smart_bytes
-
__all__ = ['get_encryption_key', 'encrypt_field', 'decrypt_field', 'encrypt_value', 'decrypt_value', 'encrypt_dict']
logger = logging.getLogger('awx.main.utils.encryption')
diff --git a/awx/main/utils/execution_environments.py b/awx/main/utils/execution_environments.py
index 02e6a8b701f6..111b76acc637 100644
--- a/awx/main/utils/execution_environments.py
+++ b/awx/main/utils/execution_environments.py
@@ -1,13 +1,18 @@
-import os
-from pathlib import Path
+import logging
from django.conf import settings
from awx.main.models.execution_environments import ExecutionEnvironment
+logger = logging.getLogger(__name__)
+
def get_control_plane_execution_environment():
- return ExecutionEnvironment.objects.filter(organization=None, managed=True).first()
+ ee = ExecutionEnvironment.objects.filter(organization=None, managed=True).first()
+ if ee == None:
+ logger.error('Failed to find control plane ee, there are no managed EEs without organizations')
+ raise RuntimeError("Failed to find default control plane EE")
+ return ee
def get_default_execution_environment():
@@ -22,6 +27,7 @@ def get_default_execution_environment():
def get_default_pod_spec():
+ job_label: str = settings.AWX_CONTAINER_GROUP_DEFAULT_JOB_LABEL
ee = get_default_execution_environment()
if ee is None:
raise RuntimeError("Unable to find an execution environment.")
@@ -29,10 +35,30 @@ def get_default_pod_spec():
return {
"apiVersion": "v1",
"kind": "Pod",
- "metadata": {"namespace": settings.AWX_CONTAINER_GROUP_DEFAULT_NAMESPACE},
+ "metadata": {"namespace": settings.AWX_CONTAINER_GROUP_DEFAULT_NAMESPACE, "labels": {job_label: ""}},
"spec": {
"serviceAccountName": "default",
"automountServiceAccountToken": False,
+ "affinity": {
+ "podAntiAffinity": {
+ "preferredDuringSchedulingIgnoredDuringExecution": [
+ {
+ "weight": 100,
+ "podAffinityTerm": {
+ "labelSelector": {
+ "matchExpressions": [
+ {
+ "key": job_label,
+ "operator": "Exists",
+ }
+ ]
+ },
+ "topologyKey": "kubernetes.io/hostname",
+ },
+ }
+ ]
+ }
+ },
"containers": [
{
"image": ee.image,
@@ -43,24 +69,3 @@ def get_default_pod_spec():
],
},
}
-
-
-# this is the root of the private data dir as seen from inside
-# of the container running a job
-CONTAINER_ROOT = '/runner'
-
-
-def to_container_path(path, private_data_dir):
- """Given a path inside of the host machine filesystem,
- this returns the expected path which would be observed by the job running
- inside of the EE container.
- This only handles the volume mount from private_data_dir to /runner
- """
- if not os.path.isabs(private_data_dir):
- raise RuntimeError('The private_data_dir path must be absolute')
- # due to how tempfile.mkstemp works, we are probably passed a resolved path, but unresolved private_data_dir
- resolved_path = Path(path).resolve()
- resolved_pdd = Path(private_data_dir).resolve()
- if resolved_pdd != resolved_path and resolved_pdd not in resolved_path.parents:
- raise RuntimeError(f'Cannot convert path {resolved_path} unless it is a subdir of {resolved_pdd}')
- return str(resolved_path).replace(str(resolved_pdd), CONTAINER_ROOT, 1)
diff --git a/awx/main/utils/external_logging.py b/awx/main/utils/external_logging.py
index 26f434a4e439..81983b85e678 100644
--- a/awx/main/utils/external_logging.py
+++ b/awx/main/utils/external_logging.py
@@ -4,6 +4,7 @@
import urllib.parse as urlparse
from django.conf import settings
+from dispatcherd.publish import task
from awx.main.utils.reload import supervisor_service_command
@@ -16,10 +17,26 @@ def construct_rsyslog_conf_template(settings=settings):
port = getattr(settings, 'LOG_AGGREGATOR_PORT', '')
protocol = getattr(settings, 'LOG_AGGREGATOR_PROTOCOL', '')
timeout = getattr(settings, 'LOG_AGGREGATOR_TCP_TIMEOUT', 5)
- max_disk_space = getattr(settings, 'LOG_AGGREGATOR_MAX_DISK_USAGE_GB', 1)
+ action_queue_size = getattr(settings, 'LOG_AGGREGATOR_ACTION_QUEUE_SIZE', 131072)
+ max_disk_space_action_queue = getattr(settings, 'LOG_AGGREGATOR_ACTION_MAX_DISK_USAGE_GB', 1)
spool_directory = getattr(settings, 'LOG_AGGREGATOR_MAX_DISK_USAGE_PATH', '/var/lib/awx').rstrip('/')
error_log_file = getattr(settings, 'LOG_AGGREGATOR_RSYSLOGD_ERROR_LOG_FILE', '')
+ queue_options = [
+ f'queue.spoolDirectory="{spool_directory}"',
+ 'queue.filename="awx-external-logger-action-queue"',
+ f'queue.maxDiskSpace="{max_disk_space_action_queue}g"', # overall disk space for all queue files
+ 'queue.maxFileSize="100m"', # individual file size
+ 'queue.type="LinkedList"',
+ 'queue.saveOnShutdown="on"',
+ 'queue.syncqueuefiles="on"', # (f)sync when checkpoint occurs
+ 'queue.checkpointInterval="1000"', # Update disk queue every 1000 messages
+ f'queue.size="{action_queue_size}"', # max number of messages in queue
+ f'queue.highwaterMark="{int(action_queue_size * 0.75)}"', # 75% of queue.size
+ f'queue.discardMark="{int(action_queue_size * 0.9)}"', # 90% of queue.size
+ 'queue.discardSeverity="5"', # Only discard notice, info, debug if we must discard anything
+ ]
+
if not os.access(spool_directory, os.W_OK):
spool_directory = '/var/lib/awx'
@@ -31,7 +48,6 @@ def construct_rsyslog_conf_template(settings=settings):
'$WorkDirectory /var/lib/awx/rsyslog',
f'$MaxMessageSize {max_bytes}',
'$IncludeConfig /var/lib/awx/rsyslog/conf.d/*.conf',
- f'main_queue(queue.spoolDirectory="{spool_directory}" queue.maxdiskspace="{max_disk_space}g" queue.type="Disk" queue.filename="awx-external-logger-backlog")', # noqa
'module(load="imuxsock" SysSock.Use="off")',
'input(type="imuxsock" Socket="' + settings.LOGGING['handlers']['external_logger']['address'] + '" unlink="on" RateLimit.Burst="0")',
'template(name="awx" type="string" string="%rawmsg-after-pri%")',
@@ -77,7 +93,7 @@ def escape_quotes(x):
'action.resumeRetryCount="-1"',
'template="awx"',
f'action.resumeInterval="{timeout}"',
- ]
+ ] + queue_options
if error_log_file:
params.append(f'errorfile="{error_log_file}"')
if parsed.path:
@@ -105,15 +121,25 @@ def escape_quotes(x):
params = ' '.join(params)
parts.extend(['module(load="omhttp")', f'action({params})'])
elif protocol and host and port:
- parts.append(
- f'action(type="omfwd" target="{host}" port="{port}" protocol="{protocol}" action.resumeRetryCount="-1" action.resumeInterval="{timeout}" template="awx")' # noqa
- )
+ params = [
+ 'type="omfwd"',
+ f'target="{host}"',
+ f'port="{port}"',
+ f'protocol="{protocol}"',
+ 'action.resumeRetryCount="-1"',
+ f'action.resumeInterval="{timeout}"',
+ 'template="awx"',
+ ] + queue_options
+ params = ' '.join(params)
+ parts.append(f'action({params})')
+
else:
parts.append('action(type="omfile" file="/dev/null")') # rsyslog needs *at least* one valid action to start
tmpl = '\n'.join(parts)
return tmpl
+@task(queue='rsyslog_configurer', timeout=600, on_duplicate='queue_one')
def reconfigure_rsyslog():
tmpl = construct_rsyslog_conf_template()
# Write config to a temp file then move it to preserve atomicity
diff --git a/awx/main/utils/filters.py b/awx/main/utils/filters.py
index 7f9724329b80..389b1f93c401 100644
--- a/awx/main/utils/filters.py
+++ b/awx/main/utils/filters.py
@@ -1,5 +1,7 @@
import re
from functools import reduce
+
+from django.core.exceptions import FieldDoesNotExist
from pyparsing import (
infixNotation,
opAssoc,
@@ -161,7 +163,7 @@ def __init__(self, t):
else:
# detect loops and restrict access to sensitive fields
# this import is intentional here to avoid a circular import
- from awx.api.filters import FieldLookupBackend
+ from ansible_base.rest_filters.rest_framework.field_lookup_backend import FieldLookupBackend
FieldLookupBackend().get_field_from_lookup(Host, k)
kwargs[k] = v
@@ -353,7 +355,7 @@ def query_from_string(cls, filter_string):
try:
res = boolExpr.parseString('(' + filter_string + ')')
- except ParseException:
+ except (ParseException, FieldDoesNotExist):
raise RuntimeError(u"Invalid query %s" % filter_string_raw)
if len(res) > 0:
diff --git a/awx/main/utils/formatters.py b/awx/main/utils/formatters.py
index 783278bd9eb7..45ff3f0d955c 100644
--- a/awx/main/utils/formatters.py
+++ b/awx/main/utils/formatters.py
@@ -3,7 +3,6 @@
from copy import copy
import json
-import json_log_formatter
import logging
import traceback
import socket
@@ -15,15 +14,6 @@
from django.conf import settings
-class JobLifeCycleFormatter(json_log_formatter.JSONFormatter):
- def json_record(self, message: str, extra: dict, record: logging.LogRecord):
- if 'time' not in extra:
- extra['time'] = now()
- if record.exc_info:
- extra['exc_info'] = self.formatException(record.exc_info)
- return extra
-
-
class TimeFormatter(logging.Formatter):
"""
Custom log formatter used for inventory imports
@@ -170,6 +160,11 @@ def reformat_data_for_log(self, raw_data, kind=None):
data = json.loads(data)
data_for_log = {}
+ # For the job_lifecycle logger, copy some raw data fields directly
+ for key in ('lifecycle_data', 'organization_id'):
+ if key in raw_data:
+ data_for_log[key] = raw_data[key]
+
if kind == 'job_events' and raw_data.get('python_objects', {}).get('job_event'):
job_event = raw_data['python_objects']['job_event']
guid = job_event.event_data.pop('guid', None)
@@ -262,8 +257,7 @@ def get_extra_fields(self, record):
return fields
def format(self, record):
- stamp = datetime.utcfromtimestamp(record.created)
- stamp = stamp.replace(tzinfo=tzutc())
+ stamp = datetime.fromtimestamp(record.created, tz=tzutc())
message = {
# Field not included, but exist in related logs
# 'path': record.pathname
@@ -283,6 +277,7 @@ def format(self, record):
message.update(self.get_debug_fields(record))
if settings.LOG_AGGREGATOR_TYPE == 'splunk':
- # splunk messages must have a top level "event" key
- message = {'event': message}
+ # splunk messages must have a top level "event" key when using the /services/collector/event receiver.
+ # The event receiver wont scan an event for a timestamp field therefore a time field must also be supplied containing epoch timestamp
+ message = {'time': record.created, 'event': message}
return self.serialize(message)
diff --git a/awx/main/utils/handlers.py b/awx/main/utils/handlers.py
index aa32c77e8c7f..f6209c755ec1 100644
--- a/awx/main/utils/handlers.py
+++ b/awx/main/utils/handlers.py
@@ -2,10 +2,13 @@
# All Rights Reserved.
# Python
+import base64
import logging
+import logging.handlers
import sys
import traceback
-from datetime import datetime
+import os
+from datetime import datetime, timezone
# Django
from django.conf import settings
@@ -15,6 +18,17 @@
# AWX
from awx.main.exceptions import PostRunError
+# OTEL
+from opentelemetry._logs import set_logger_provider
+from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter as OTLPGrpcLogExporter
+from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter as OTLPHttpLogExporter
+
+from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
+from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
+from opentelemetry.sdk.resources import Resource
+
+__all__ = ['RSysLogHandler', 'SpecialInventoryHandler', 'ColorHandler']
+
class RSysLogHandler(logging.handlers.SysLogHandler):
append_nul = False
@@ -35,7 +49,7 @@ def handleError(self, record):
# because the alternative is blocking the
# socket.send() in the Python process, which we definitely don't
# want to do)
- dt = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
+ dt = datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')
msg = f'{dt} ERROR rsyslogd was unresponsive: '
exc = traceback.format_exc()
try:
@@ -97,39 +111,71 @@ def emit(self, record):
self.event_handler(dispatch_data)
-ColorHandler = logging.StreamHandler
-
if settings.COLOR_LOGS is True:
- try:
- from logutils.colorize import ColorizingStreamHandler
- import colorama
-
- colorama.deinit()
- colorama.init(wrap=False, convert=False, strip=False)
-
- class ColorHandler(ColorizingStreamHandler):
- def colorize(self, line, record):
- # comment out this method if you don't like the job_lifecycle
- # logs rendered with cyan text
- previous_level_map = self.level_map.copy()
- if record.name == "awx.analytics.job_lifecycle":
- self.level_map[logging.INFO] = (None, 'cyan', True)
- msg = super(ColorHandler, self).colorize(line, record)
- self.level_map = previous_level_map
- return msg
-
- def format(self, record):
- message = logging.StreamHandler.format(self, record)
- return '\n'.join([self.colorize(line, record) for line in message.splitlines()])
-
- level_map = {
- logging.DEBUG: (None, 'green', True),
- logging.INFO: (None, None, True),
- logging.WARNING: (None, 'yellow', True),
- logging.ERROR: (None, 'red', True),
- logging.CRITICAL: (None, 'red', True),
- }
-
- except ImportError:
- # logutils is only used for colored logs in the dev environment
- pass
+ from logutils.colorize import ColorizingStreamHandler
+ import colorama
+
+ colorama.deinit()
+ colorama.init(wrap=False, convert=False, strip=False)
+
+ class ColorHandler(ColorizingStreamHandler):
+ def colorize(self, line, record):
+ # comment out this method if you don't like the job_lifecycle
+ # logs rendered with cyan text
+ previous_level_map = self.level_map.copy()
+ if record.name == "awx.analytics.job_lifecycle":
+ self.level_map[logging.INFO] = (None, 'cyan', True)
+ msg = super(ColorHandler, self).colorize(line, record)
+ self.level_map = previous_level_map
+ return msg
+
+ def format(self, record):
+ message = logging.StreamHandler.format(self, record)
+ return '\n'.join([self.colorize(line, record) for line in message.splitlines()])
+
+ level_map = {
+ logging.DEBUG: (None, 'green', True),
+ logging.INFO: (None, None, True),
+ logging.WARNING: (None, 'yellow', True),
+ logging.ERROR: (None, 'red', True),
+ logging.CRITICAL: (None, 'red', True),
+ }
+
+else:
+ ColorHandler = logging.StreamHandler
+
+
+class OTLPHandler(LoggingHandler):
+ def __init__(self, endpoint=None, protocol='grpc', service_name=None, instance_id=None, auth=None, username=None, password=None):
+ if not endpoint:
+ raise ValueError("endpoint required")
+
+ if auth == 'basic' and (username is None or password is None):
+ raise ValueError("auth type basic requires username and passsword parameters")
+
+ self.endpoint = endpoint
+ self.service_name = service_name or (sys.argv[1] if len(sys.argv) > 1 else (sys.argv[0] or 'unknown_service'))
+ self.instance_id = instance_id or os.uname().nodename
+
+ logger_provider = LoggerProvider(
+ resource=Resource.create(
+ {
+ "service.name": self.service_name,
+ "service.instance.id": self.instance_id,
+ }
+ ),
+ )
+ set_logger_provider(logger_provider)
+
+ headers = {}
+ if auth == 'basic':
+ secret = f'{username}:{password}'
+ headers['Authorization'] = "Basic " + base64.b64encode(secret.encode()).decode()
+
+ if protocol == 'grpc':
+ otlp_exporter = OTLPGrpcLogExporter(endpoint=self.endpoint, insecure=True, headers=headers)
+ elif protocol == 'http':
+ otlp_exporter = OTLPHttpLogExporter(endpoint=self.endpoint, headers=headers)
+ logger_provider.add_log_record_processor(BatchLogRecordProcessor(otlp_exporter))
+
+ super().__init__(level=logging.NOTSET, logger_provider=logger_provider)
diff --git a/awx/main/utils/inventory_vars.py b/awx/main/utils/inventory_vars.py
new file mode 100644
index 000000000000..7780a93c73ff
--- /dev/null
+++ b/awx/main/utils/inventory_vars.py
@@ -0,0 +1,276 @@
+import logging
+from typing import TypeAlias, Any
+
+from awx.main.models import InventoryGroupVariablesWithHistory
+
+var_value: TypeAlias = Any
+update_queue: TypeAlias = list[tuple[int, var_value]]
+
+
+logger = logging.getLogger('awx.api.inventory_import')
+
+
+class InventoryVariable:
+ """
+ Represents an inventory variable.
+
+ This class keeps track of the variable updates from different inventory
+ sources.
+ """
+
+ def __init__(self, name: str) -> None:
+ """
+ :param str name: The variable's name.
+ :return: None
+ """
+ self.name = name
+ self._update_queue: update_queue = []
+ """
+ A queue representing updates from inventory sources in the sequence of
+ occurrence.
+
+ The queue is realized as a list of two-tuples containing variable values
+ and their originating inventory source. The last item of the list is
+ considered the top of the queue, and holds the current value of the
+ variable.
+ """
+
+ def reset(self) -> None:
+ """Reset the variable by deleting its history."""
+ self._update_queue = []
+
+ def load(self, updates: update_queue) -> "InventoryVariable":
+ """Load internal state from a list."""
+ self._update_queue = updates
+ return self
+
+ def dump(self) -> update_queue:
+ """Save internal state to a list."""
+ return self._update_queue
+
+ def update(self, value: var_value, invsrc_id: int) -> None:
+ """
+ Update the variable with a new value from an inventory source.
+
+ Updating means that this source is moved to the top of the queue
+ and `value` becomes the new current value.
+
+ :param value: The new value of the variable.
+ :param int invsrc_id: The inventory source of the new variable value.
+ :return: None
+ """
+ logger.debug(f"InventoryVariable().update({value}, {invsrc_id}):")
+ # Move this source to the front of the queue by first deleting a
+ # possibly existing entry, and then add the new entry to the front.
+ self.delete(invsrc_id)
+ self._update_queue.append((invsrc_id, value))
+
+ def delete(self, invsrc_id: int) -> None:
+ """
+ Delete an inventory source from the variable.
+
+ :param int invsrc_id: The inventory source id.
+ :return: None
+ """
+ data_index = self._get_invsrc_index(invsrc_id)
+ # Remove last update from this source, if there was any.
+ if data_index is not None:
+ value = self._update_queue.pop(data_index)[1]
+ logger.debug(f"InventoryVariable().delete({invsrc_id}): {data_index=} {value=}")
+
+ def _get_invsrc_index(self, invsrc_id: int) -> int | None:
+ """Return the inventory source's position in the queue, or `None`."""
+ for i, entry in enumerate(self._update_queue):
+ if entry[0] == invsrc_id:
+ return i
+ return None
+
+ def _get_current_value(self) -> var_value:
+ """
+ Return the current value of the variable, or None if the variable has no
+ history.
+ """
+ return self._update_queue[-1][1] if self._update_queue else None
+
+ @property
+ def value(self) -> var_value:
+ """Read the current value of the variable."""
+ return self._get_current_value()
+
+ @property
+ def has_no_source(self) -> bool:
+ """True, if the variable is orphan, i.e. no source contains this var anymore."""
+ return not self._update_queue
+
+ def __str__(self):
+ """Return the string representation of the current value."""
+ return str(self.value or "")
+
+
+class InventoryGroupVariables(dict):
+ """
+ Represent all inventory variables from one group.
+
+ This dict contains all variables of a inventory group and their current
+ value under consideration of the inventory source update history.
+
+ Note that variables values cannot be `None`, use the empty string to
+ indicate that a variable holds no value. See also `InventoryVariable`.
+ """
+
+ def __init__(self, id: int) -> None:
+ """
+ :param int id: The id of the group object.
+ :return: None
+ """
+ super().__init__()
+ self.id = id
+ # In _vars we keep all sources for a given variable. This enables us to
+ # find the current value for a variable, which is the value from the
+ # latest update which defined this variable.
+ self._vars: dict[str, InventoryVariable] = {}
+
+ def _sync_vars(self) -> None:
+ """
+ Copy the current values of all variables into the internal dict.
+
+ Call this everytime the `_vars` structure has been modified.
+ """
+ for name, inv_var in self._vars.items():
+ self[name] = inv_var.value
+
+ def load_state(self, state: dict[str, update_queue]) -> "InventoryGroupVariables":
+ """Load internal state from a dict."""
+ for name, updates in state.items():
+ self._vars[name] = InventoryVariable(name).load(updates)
+ self._sync_vars()
+ return self
+
+ def save_state(self) -> dict[str, update_queue]:
+ """Return internal state as a dict."""
+ state = {}
+ for name, inv_var in self._vars.items():
+ state[name] = inv_var.dump()
+ return state
+
+ def update_from_src(
+ self,
+ new_vars: dict[str, var_value],
+ source_id: int,
+ overwrite_vars: bool = True,
+ reset: bool = False,
+ ) -> None:
+ """
+ Update with variables from an inventory source.
+
+ Delete all variables for this source which are not in the update vars.
+
+ :param dict new_vars: The variables from the inventory source.
+ :param int invsrc_id: The id of the inventory source for this update.
+ :param bool overwrite_vars: If `True`, delete this source's history
+ entry for variables which are not in this update. If `False`, keep
+ the old updates in the history for such variables. Default is
+ `True`.
+ :param bool reset: If `True`, delete the update history for all existing
+ variables before updating the new vars. Therewith making this update
+ overwrite all history. Default is `False`.
+ :return: None
+ """
+ logger.debug(f"InventoryGroupVariables({self.id}).update_from_src({new_vars=}, {source_id=}, {overwrite_vars=}, {reset=}): {self=}")
+ # Create variables which are newly introduced by this source.
+ for name in new_vars:
+ if name not in self._vars:
+ self._vars[name] = InventoryVariable(name)
+ # Combine the names of the existing vars and the new vars from this update.
+ all_var_names = list(set(list(self.keys()) + list(new_vars.keys())))
+ # In reset-mode, delete all existing vars and their history before
+ # updating.
+ if reset:
+ for name in all_var_names:
+ self._vars[name].reset()
+ # Go through all variables (the existing ones, and the ones added by
+ # this update), delete this source from variables which are not in this
+ # update, and update the value of variables which are part of this
+ # update.
+ for name in all_var_names:
+ # Update or delete source from var (if name not in vars).
+ if name in new_vars:
+ self._vars[name].update(new_vars[name], source_id)
+ elif overwrite_vars:
+ self._vars[name].delete(source_id)
+ # Delete vars which have no source anymore.
+ if self._vars[name].has_no_source:
+ del self._vars[name]
+ del self[name]
+ # After the update, refresh the internal dict with the possibly changed
+ # current values.
+ self._sync_vars()
+ logger.debug(f"InventoryGroupVariables({self.id}).update_from_src(): {self=}")
+
+
+def update_group_variables(
+ group_id: int | None,
+ newvars: dict,
+ dbvars: dict | None,
+ invsrc_id: int,
+ inventory_id: int,
+ overwrite_vars: bool = True,
+ reset: bool = False,
+) -> dict[str, var_value]:
+ """
+ Update the inventory variables of one group.
+
+ Merge the new variables into the existing group variables.
+
+ The update can be triggered either by an inventory update via API, or via a
+ manual edit of the variables field in the awx inventory form.
+
+ TODO: Can we get rid of the dbvars? This is only needed because the new
+ update-var mechanism needs to be properly initialized if the db already
+ contains some variables.
+
+ :param int group_id: The inventory group id (pk). For the 'all'-group use
+ `None`, because this group is not an actual `Group` object in the
+ database.
+ :param dict newvars: The variables contained in this update.
+ :param dict dbvars: The variables which are already stored in the database
+ for this inventory and this group. Can be `None`.
+ :param int invsrc_id: The id of the inventory source. Usually this is the
+ database primary key of the inventory source object, but there is one
+ special id -1 which is used for the initial update from the database and
+ for manual updates via the GUI.
+ :param int inventory_id: The id of the inventory on which this update is
+ applied.
+ :param bool overwrite_vars: If `True`, delete variables which were merged
+ from the same source in a previous update, but are no longer contained
+ in that source. If `False`, such variables would not be removed from the
+ group. Default is `True`.
+ :param bool reset: If `True`, delete all variables from previous updates,
+ therewith making this update overwrite all history. Default is `False`.
+ :return: The variables and their current values as a dict.
+ :rtype: dict
+ """
+ inv_group_vars = InventoryGroupVariables(group_id)
+ # Restore the existing variables state.
+ try:
+ # Get the object for this group from the database.
+ model = InventoryGroupVariablesWithHistory.objects.get(inventory_id=inventory_id, group_id=group_id)
+ except InventoryGroupVariablesWithHistory.DoesNotExist:
+ # If no previous state exists, create a new database object, and
+ # initialize it with the current group variables.
+ model = InventoryGroupVariablesWithHistory(inventory_id=inventory_id, group_id=group_id)
+ if dbvars:
+ inv_group_vars.update_from_src(dbvars, -1) # Assume -1 as inv_source_id for existing vars.
+ else:
+ # Load the group variables state from the database object.
+ inv_group_vars.load_state(model.variables)
+ #
+ logger.debug(f"update_group_variables: before update_from_src {model.variables=}")
+ # Apply the new inventory update onto the group variables.
+ inv_group_vars.update_from_src(newvars, invsrc_id, overwrite_vars, reset)
+ # Save the new variables state.
+ model.variables = inv_group_vars.save_state()
+ model.save()
+ logger.debug(f"update_group_variables: after update_from_src {model.variables=}")
+ logger.debug(f"update_group_variables({group_id=}, {newvars}): {inv_group_vars}")
+ return inv_group_vars
diff --git a/awx/main/utils/licensing.py b/awx/main/utils/licensing.py
index bec953f822df..20417940b8bc 100644
--- a/awx/main/utils/licensing.py
+++ b/awx/main/utils/licensing.py
@@ -15,7 +15,6 @@
import collections
import copy
import io
-import os
import json
import logging
import re
@@ -35,6 +34,11 @@
from django.conf import settings
from django.utils.translation import gettext_lazy as _
+# Shared code for the AWX platform
+from awx_plugins.interfaces._temporary_private_licensing_api import detect_server_product_name
+
+from awx.main.constants import SUBSCRIPTION_USAGE_MODEL_UNIQUE_HOSTS
+from awx.main.utils.analytics_proxy import OIDCClient
MAX_INSTANCES = 9999999
@@ -169,10 +173,17 @@ def _can_aggregate(sub, license):
license.setdefault('sku', sub['pool']['productId'])
license.setdefault('subscription_name', sub['pool']['productName'])
+ license.setdefault('subscription_id', sub['pool']['subscriptionId'])
+ license.setdefault('account_number', sub['pool']['accountNumber'])
license.setdefault('pool_id', sub['pool']['id'])
license.setdefault('product_name', sub['pool']['productName'])
license.setdefault('valid_key', True)
- license.setdefault('license_type', 'enterprise')
+ if sub['pool']['productId'].startswith('S'):
+ license.setdefault('trial', True)
+ license.setdefault('license_type', 'trial')
+ else:
+ license.setdefault('trial', False)
+ license.setdefault('license_type', 'enterprise')
license.setdefault('satellite', False)
# Use the nearest end date
endDate = parse_date(sub['endDate'])
@@ -184,6 +195,16 @@ def _can_aggregate(sub, license):
license['instance_count'] = license.get('instance_count', 0) + instances
license['subscription_name'] = re.sub(r'[\d]* Managed Nodes', '%d Managed Nodes' % license['instance_count'], license['subscription_name'])
+ license['support_level'] = ''
+ license['usage'] = ''
+ for attr in sub['pool'].get('productAttributes', []):
+ if attr.get('name') == 'support_level':
+ license['support_level'] = attr.get('value')
+ elif attr.get('name') == 'usage':
+ license['usage'] = attr.get('value')
+ elif attr.get('name') == 'ph_product_name' and attr.get('value') == 'RHEL Developer':
+ license['license_type'] = 'developer'
+
if not license:
logger.error("No valid subscriptions found in manifest")
self._attrs.update(license)
@@ -198,27 +219,43 @@ def update(self, **kwargs):
kwargs['license_date'] = int(kwargs['license_date'])
self._attrs.update(kwargs)
- def validate_rh(self, user, pw):
+ def get_host_from_rhsm_config(self):
try:
host = 'https://' + str(self.config.get("server", "hostname"))
except Exception:
logger.exception('Cannot access rhsm.conf, make sure subscription manager is installed and configured.')
host = None
+ return host
+
+ def validate_rh(self, user, pw, basic_auth):
+ # if basic auth is True, host is read from rhsm.conf (subscription.rhsm.redhat.com)
+ # if basic auth is False, host is settings.SUBSCRIPTIONS_RHSM_URL (console.redhat.com)
+ # if rhsm.conf is not found, host is settings.REDHAT_CANDLEPIN_HOST (satellite server)
+ if basic_auth:
+ host = self.get_host_from_rhsm_config()
+ if not host:
+ host = getattr(settings, 'REDHAT_CANDLEPIN_HOST', None)
+ else:
+ host = settings.SUBSCRIPTIONS_RHSM_URL
+
if not host:
- host = getattr(settings, 'REDHAT_CANDLEPIN_HOST', None)
+ raise ValueError('Could not get host url for subscriptions')
if not user:
- raise ValueError('subscriptions_username is required')
+ raise ValueError('subscriptions_client_id or subscriptions_username is required')
if not pw:
- raise ValueError('subscriptions_password is required')
+ raise ValueError('subscriptions_client_secret or subscriptions_password is required')
if host and user and pw:
- if 'subscription.rhsm.redhat.com' in host:
- json = self.get_rhsm_subs(host, user, pw)
+ if basic_auth:
+ if 'subscription.rhsm.redhat.com' in host:
+ json = self.get_rhsm_subs(host, user, pw)
+ else:
+ json = self.get_satellite_subs(host, user, pw)
else:
- json = self.get_satellite_subs(host, user, pw)
- return self.generate_license_options_from_entitlements(json)
+ json = self.get_crc_subs(host, user, pw)
+ return self.generate_license_options_from_entitlements(json, is_candlepin=basic_auth)
return []
def get_rhsm_subs(self, host, user, pw):
@@ -240,6 +277,35 @@ def get_rhsm_subs(self, host, user, pw):
json.extend(resp.json())
return json
+ def get_crc_subs(self, host, client_id, client_secret):
+ try:
+ client = OIDCClient(client_id, client_secret)
+ subs = client.make_request(
+ 'GET',
+ host,
+ verify=True,
+ timeout=(31, 31),
+ )
+ except requests.RequestException:
+ logger.warning("Failed to connect to console.redhat.com using Service Account credentials. Falling back to basic auth.")
+ subs = requests.request(
+ 'GET',
+ host,
+ auth=(client_id, client_secret),
+ verify=True,
+ timeout=(31, 31),
+ )
+ subs.raise_for_status()
+ subs_formatted = []
+ for sku in subs.json()['body']:
+ sku_data = {k: v for k, v in sku.items() if k != 'subscriptions'}
+ for sub in sku['subscriptions']:
+ sub_data = sku_data.copy()
+ sub_data['subscriptions'] = sub
+ subs_formatted.append(sub_data)
+
+ return subs_formatted
+
def get_satellite_subs(self, host, user, pw):
port = None
try:
@@ -247,7 +313,7 @@ def get_satellite_subs(self, host, user, pw):
port = str(self.config.get("server", "port"))
except Exception as e:
logger.exception('Unable to read rhsm config to get ca_cert location. {}'.format(str(e)))
- verify = getattr(settings, 'REDHAT_CANDLEPIN_VERIFY', True)
+ verify = True
if port:
host = ':'.join([host, port])
json = []
@@ -276,7 +342,10 @@ def get_satellite_subs(self, host, user, pw):
license['productId'] = sub['product_id']
license['quantity'] = int(sub['quantity'])
license['support_level'] = sub['support_level']
+ license['usage'] = sub.get('usage')
license['subscription_name'] = sub['name']
+ license['subscriptionId'] = sub['subscription_id']
+ license['accountNumber'] = sub['account_number']
license['id'] = sub['upstream_pool_id']
license['endDate'] = sub['end_date']
license['productName'] = "Red Hat Ansible Automation"
@@ -286,11 +355,6 @@ def get_satellite_subs(self, host, user, pw):
json.append(license)
return json
- def is_appropriate_sat_sub(self, sub):
- if 'Red Hat Ansible Automation' not in sub['subscription_name']:
- return False
- return True
-
def is_appropriate_sub(self, sub):
if sub['activeSubscription'] is False:
return False
@@ -300,47 +364,94 @@ def is_appropriate_sub(self, sub):
return True
return False
- def generate_license_options_from_entitlements(self, json):
+ def is_appropriate_sat_sub(self, sub):
+ if 'Red Hat Ansible Automation' not in sub['subscription_name']:
+ return False
+ return True
+
+ def generate_license_options_from_entitlements(self, json, is_candlepin=False):
from dateutil.parser import parse
- ValidSub = collections.namedtuple('ValidSub', 'sku name support_level end_date trial quantity pool_id satellite')
+ ValidSub = collections.namedtuple(
+ 'ValidSub', 'sku name support_level end_date trial developer_license quantity satellite subscription_id account_number usage'
+ )
valid_subs = []
for sub in json:
satellite = sub.get('satellite')
if satellite:
is_valid = self.is_appropriate_sat_sub(sub)
- else:
+ elif is_candlepin:
is_valid = self.is_appropriate_sub(sub)
+ else:
+ # the list of subs from console.redhat.com and subscriptions.rhsm.redhat.com are already valid based on the query params we provided
+ is_valid = True
if is_valid:
try:
- end_date = parse(sub.get('endDate'))
+ if is_candlepin:
+ end_date = parse(sub.get('endDate'))
+ else:
+ end_date = parse(sub['subscriptions']['endDate'])
except Exception:
continue
- now = datetime.utcnow()
+ now = datetime.now(timezone.utc)
now = now.replace(tzinfo=end_date.tzinfo)
if end_date < now:
# If the sub has a past end date, skip it
continue
- try:
- quantity = int(sub['quantity'])
- if quantity == -1:
- # effectively, unlimited
- quantity = MAX_INSTANCES
- except Exception:
- continue
- sku = sub['productId']
- trial = sku.startswith('S') # i.e.,, SER/SVC
- support_level = ''
- pool_id = sub['id']
- if satellite:
- support_level = sub['support_level']
+ developer_license = False
+ support_level = sub.get('support_level', '')
+ account_number = ''
+ usage = sub.get('usage', '')
+ if is_candlepin:
+ try:
+ quantity = int(sub['quantity'])
+ except Exception:
+ continue
+ sku = sub['productId']
+ subscription_id = sub['subscriptionId']
+ sub_name = sub['productName']
+ account_number = sub['accountNumber']
else:
- for attr in sub.get('productAttributes', []):
- if attr.get('name') == 'support_level':
- support_level = attr.get('value')
+ try:
+ # Determine total quantity based on capacity name
+ # if capacity name is Nodes, capacity quantity x subscription quantity
+ # if capacity name is Sockets, capacity quantity / 2 (minimum of 1) x subscription quantity
+ if sub['capacity']['name'] == "Nodes":
+ quantity = int(sub['capacity']['quantity']) * int(sub['subscriptions']['quantity'])
+ elif sub['capacity']['name'] == "Sockets":
+ quantity = max(int(sub['capacity']['quantity']) / 2, 1) * int(sub['subscriptions']['quantity'])
+ else:
+ continue
+ except Exception:
+ continue
+ sku = sub['sku']
+ sub_name = sub['name']
+ support_level = sub['serviceLevel']
+ subscription_id = sub['subscriptions']['number']
+ if sub.get('name') == 'RHEL Developer':
+ developer_license = True
+
+ if quantity == -1:
+ # effectively, unlimited
+ quantity = MAX_INSTANCES
+ trial = sku.startswith('S') # i.e.,, SER/SVC
- valid_subs.append(ValidSub(sku, sub['productName'], support_level, end_date, trial, quantity, pool_id, satellite))
+ valid_subs.append(
+ ValidSub(
+ sku,
+ sub_name,
+ support_level,
+ end_date,
+ trial,
+ developer_license,
+ quantity,
+ satellite,
+ subscription_id,
+ account_number,
+ usage,
+ )
+ )
if valid_subs:
licenses = []
@@ -349,10 +460,13 @@ def generate_license_options_from_entitlements(self, json):
license._attrs['instance_count'] = int(sub.quantity)
license._attrs['sku'] = sub.sku
license._attrs['support_level'] = sub.support_level
+ license._attrs['usage'] = sub.usage
license._attrs['license_type'] = 'enterprise'
if sub.trial:
license._attrs['trial'] = True
license._attrs['license_type'] = 'trial'
+ if sub.developer_license:
+ license._attrs['license_type'] = 'developer'
license._attrs['instance_count'] = min(MAX_INSTANCES, license._attrs['instance_count'])
human_instances = license._attrs['instance_count']
if human_instances == MAX_INSTANCES:
@@ -362,8 +476,11 @@ def generate_license_options_from_entitlements(self, json):
license._attrs['satellite'] = satellite
license._attrs['valid_key'] = True
license.update(license_date=int(sub.end_date.strftime('%s')))
- license.update(pool_id=sub.pool_id)
+ license.update(subscription_id=sub.subscription_id)
+ license.update(account_number=sub.account_number)
licenses.append(license._attrs.copy())
+ # sort by sku
+ licenses.sort(key=lambda x: x['sku'])
return licenses
raise ValueError('No valid Red Hat Ansible Automation subscription could be found for this account.') # noqa
@@ -382,12 +499,28 @@ def validate(self):
current_instances = Host.objects.active_count()
license_date = int(attrs.get('license_date', 0) or 0)
- automated_instances = HostMetric.objects.count()
- first_host = HostMetric.objects.only('first_automation').order_by('first_automation').first()
+
+ subscription_model = getattr(settings, 'SUBSCRIPTION_USAGE_MODEL', '')
+ if subscription_model == SUBSCRIPTION_USAGE_MODEL_UNIQUE_HOSTS:
+ automated_instances = HostMetric.active_objects.count()
+ first_host = HostMetric.active_objects.only('first_automation').order_by('first_automation').first()
+ attrs['deleted_instances'] = HostMetric.objects.filter(deleted=True).count()
+ attrs['reactivated_instances'] = HostMetric.active_objects.filter(deleted_counter__gte=1).count()
+ else:
+ automated_instances = 0
+ first_host = HostMetric.objects.only('first_automation').order_by('first_automation').first()
+ attrs['deleted_instances'] = 0
+ attrs['reactivated_instances'] = 0
+
if first_host:
automated_since = int(first_host.first_automation.timestamp())
else:
- automated_since = int(Instance.objects.order_by('id').first().created.timestamp())
+ try:
+ automated_since = int(Instance.objects.order_by('id').first().created.timestamp())
+ except AttributeError:
+ # In the odd scenario that create_preload_data was not run, there are no hosts
+ # Then we CAN end up here before any instance has registered
+ automated_since = int(time.time())
instance_count = int(attrs.get('instance_count', 0))
attrs['current_instances'] = current_instances
attrs['automated_instances'] = automated_instances
@@ -412,13 +545,9 @@ def get_licenser(*args, **kwargs):
from awx.main.utils.licensing import Licenser, OpenLicense
try:
- if os.path.exists('/var/lib/awx/.tower_version'):
- return Licenser(*args, **kwargs)
- else:
+ if detect_server_product_name() == 'AWX':
return OpenLicense()
+ else:
+ return Licenser(*args, **kwargs)
except Exception as e:
raise ValueError(_('Error importing License: %s') % e)
-
-
-def server_product_name():
- return 'AWX' if isinstance(get_licenser(), OpenLicense) else 'Red Hat Ansible Automation Platform'
diff --git a/awx/main/utils/mem_inventory.py b/awx/main/utils/mem_inventory.py
index 7e6e458cb8f1..3167710d8c5d 100644
--- a/awx/main/utils/mem_inventory.py
+++ b/awx/main/utils/mem_inventory.py
@@ -6,7 +6,6 @@
import logging
from collections import OrderedDict
-
# Logger is used for any data-related messages so that the log level
# can be adjusted on command invocation
logger = logging.getLogger('awx.main.commands.inventory_import')
@@ -253,7 +252,7 @@ def dict_to_mem_data(data, inventory=None):
if isinstance(hv, dict):
host.variables.update(hv)
else:
- logger.warning('Expected dict of vars for ' 'host "%s", got %s instead', hk, str(type(hv)))
+ logger.warning('Expected dict of vars for host "%s", got %s instead', hk, str(type(hv)))
group.add_host(host)
elif isinstance(hosts, (list, tuple)):
for hk in hosts:
@@ -262,13 +261,13 @@ def dict_to_mem_data(data, inventory=None):
continue
group.add_host(host)
else:
- logger.warning('Expected dict or list of "hosts" for ' 'group "%s", got %s instead', k, str(type(hosts)))
+ logger.warning('Expected dict or list of "hosts" for group "%s", got %s instead', k, str(type(hosts)))
# Process group variables.
vars = v.get('vars', {})
if isinstance(vars, dict):
group.variables.update(vars)
else:
- logger.warning('Expected dict of vars for ' 'group "%s", got %s instead', k, str(type(vars)))
+ logger.warning('Expected dict of vars for group "%s", got %s instead', k, str(type(vars)))
# Process child groups.
children = v.get('children', [])
if isinstance(children, (list, tuple)):
@@ -277,7 +276,7 @@ def dict_to_mem_data(data, inventory=None):
if child and c != 'ungrouped':
group.add_child_group(child)
else:
- logger.warning('Expected list of children for ' 'group "%s", got %s instead', k, str(type(children)))
+ logger.warning('Expected list of children for group "%s", got %s instead', k, str(type(children)))
# Load host names from a list.
elif isinstance(v, (list, tuple)):
@@ -288,7 +287,7 @@ def dict_to_mem_data(data, inventory=None):
group.add_host(host)
else:
logger.warning('')
- logger.warning('Expected dict or list for group "%s", ' 'got %s instead', k, str(type(v)))
+ logger.warning('Expected dict or list for group "%s", got %s instead', k, str(type(v)))
if k not in ['all', 'ungrouped']:
inventory.all_group.add_child_group(group)
@@ -299,6 +298,6 @@ def dict_to_mem_data(data, inventory=None):
if isinstance(meta_hostvars, dict):
v.variables.update(meta_hostvars)
else:
- logger.warning('Expected dict of vars for ' 'host "%s", got %s instead', k, str(type(meta_hostvars)))
+ logger.warning('Expected dict of vars for host "%s", got %s instead', k, str(type(meta_hostvars)))
return inventory
diff --git a/awx/main/utils/migration.py b/awx/main/utils/migration.py
new file mode 100644
index 000000000000..10396e7ea358
--- /dev/null
+++ b/awx/main/utils/migration.py
@@ -0,0 +1,14 @@
+from django.db.migrations.executor import MigrationExecutor
+from django.db import connections, DEFAULT_DB_ALIAS
+
+
+def is_database_synchronized(database=DEFAULT_DB_ALIAS):
+ """_summary_
+ Ensure all migrations have ran
+ https://stackoverflow.com/questions/31838882/check-for-pending-django-migrations
+ """
+ connection = connections[database]
+ connection.prepare_database()
+ executor = MigrationExecutor(connection)
+ targets = executor.loader.graph.leaf_nodes()
+ return not executor.migration_plan(targets)
diff --git a/awx/main/utils/named_url_graph.py b/awx/main/utils/named_url_graph.py
index 9d2c0a27c97d..51a85fc68441 100644
--- a/awx/main/utils/named_url_graph.py
+++ b/awx/main/utils/named_url_graph.py
@@ -5,8 +5,6 @@
# Django
from django.db import models
from django.conf import settings
-from django.contrib.contenttypes.models import ContentType
-
NAMED_URL_RES_DILIMITER = "++"
NAMED_URL_RES_INNER_DILIMITER = "+"
@@ -245,6 +243,8 @@ def _generate_configurations(nodes):
def _dfs(configuration, model, graph, dead_ends, new_deadends, parents):
+ from django.contrib.contenttypes.models import ContentType
+
parents.add(model)
fields, fk_names = configuration[model][0][:], configuration[model][1][:]
adj_list = []
@@ -306,3 +306,19 @@ def generate_graph(models):
def reset_counters():
for node in settings.NAMED_URL_GRAPH.values():
node.counter = 0
+
+
+def _customize_graph():
+ from django.contrib.auth.models import User
+ from awx.main.models import Instance, Schedule, UnifiedJobTemplate
+
+ for model in [Schedule, UnifiedJobTemplate]:
+ if model in settings.NAMED_URL_GRAPH:
+ settings.NAMED_URL_GRAPH[model].remove_bindings()
+ settings.NAMED_URL_GRAPH.pop(model)
+ if User not in settings.NAMED_URL_GRAPH:
+ settings.NAMED_URL_GRAPH[User] = GraphNode(User, ['username'], [])
+ settings.NAMED_URL_GRAPH[User].add_bindings()
+ if Instance not in settings.NAMED_URL_GRAPH:
+ settings.NAMED_URL_GRAPH[Instance] = GraphNode(Instance, ['hostname'], [])
+ settings.NAMED_URL_GRAPH[Instance].add_bindings()
diff --git a/awx/main/utils/pglock.py b/awx/main/utils/pglock.py
deleted file mode 100644
index 3d8a00d20a57..000000000000
--- a/awx/main/utils/pglock.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright (c) 2017 Ansible by Red Hat
-# All Rights Reserved.
-
-from contextlib import contextmanager
-
-from django_pglocks import advisory_lock as django_pglocks_advisory_lock
-from django.db import connection
-
-
-@contextmanager
-def advisory_lock(*args, **kwargs):
- if connection.vendor == 'postgresql':
- with django_pglocks_advisory_lock(*args, **kwargs) as internal_lock:
- yield internal_lock
- else:
- yield True
diff --git a/awx/main/utils/plugins.py b/awx/main/utils/plugins.py
new file mode 100644
index 000000000000..31fb2d3963b4
--- /dev/null
+++ b/awx/main/utils/plugins.py
@@ -0,0 +1,87 @@
+# Copyright (c) 2024 Ansible, Inc.
+# All Rights Reserved.
+
+"""
+This module contains the code responsible for extracting the lists of dynamically discovered plugins.
+"""
+
+from functools import cache
+
+
+@cache
+def discover_available_cloud_provider_plugin_names() -> list[str]:
+ """
+ Return a list of cloud plugin names available in runtime.
+
+ The discovery result is cached since it does not change throughout
+ the life cycle of the server run.
+
+ :returns: List of plugin cloud names.
+ :rtype: list[str]
+ """
+ from awx.main.models.inventory import InventorySourceOptions
+
+ plugin_names = list(InventorySourceOptions.injectors.keys())
+
+ plugin_names.remove('constructed')
+
+ return plugin_names
+
+
+@cache
+def compute_cloud_inventory_sources() -> dict[str, str]:
+ """
+ Return a dictionary of cloud provider plugin names
+ available plus source control management and constructed.
+
+ :returns: Dictionary of plugin cloud names plus source control.
+ :rtype: dict[str, str]
+ """
+
+ plugins = discover_available_cloud_provider_plugin_names()
+
+ return dict(zip(plugins, plugins), scm='scm', constructed='constructed')
+
+
+@cache
+def discover_available_cloud_provider_descriptions() -> dict[str, str]:
+ """
+ Return a dictionary of cloud provider plugin descriptions
+ available.
+
+ :returns: Dictionary of plugin cloud descriptions.
+ :rtype: dict[str, str]
+ """
+ from awx.main.models.inventory import InventorySourceOptions
+
+ plugin_description_list = [(plugin_name, plugin.plugin_description) for plugin_name, plugin in InventorySourceOptions.injectors.items()]
+
+ plugin_description = dict(plugin_description_list)
+
+ return plugin_description
+
+
+@cache
+def load_combined_inventory_source_options() -> dict[str, str]:
+ """
+ Return a dictionary of cloud provider plugin names and 'file'.
+
+ The 'file' entry is included separately since it needs to be consumed directly by the serializer.
+
+ :returns: A dictionary of cloud provider plugin names (as both keys and values) plus the 'file' entry.
+ :rtype: dict[str, str]
+ """
+
+ plugins = compute_cloud_inventory_sources()
+
+ plugin_description = discover_available_cloud_provider_descriptions()
+
+ if 'scm' in plugins:
+ plugin_description['scm'] = 'Sourced from a Project'
+
+ if 'file' in plugins:
+ plugin_description['file'] = 'File-based inventory source'
+
+ result = {plugin: plugin_description.get(plugin, plugin) for plugin in plugins}
+
+ return result
diff --git a/awx/main/utils/proxy.py b/awx/main/utils/proxy.py
new file mode 100644
index 000000000000..0676e8f44ae6
--- /dev/null
+++ b/awx/main/utils/proxy.py
@@ -0,0 +1,47 @@
+# Copyright (c) 2024 Ansible, Inc.
+# All Rights Reserved.
+
+
+# DRF
+from rest_framework.request import Request
+
+"""
+Note that these methods operate on request.environ. This data is from uwsgi.
+It is the source data from which request.headers (read-only) is constructed.
+"""
+
+
+def is_proxy_in_headers(request: Request, proxy_list: list[str], headers: list[str]) -> bool:
+ """
+ Determine if the request went through at least one proxy in the list.
+ Example:
+ request.environ = {
+ "HTTP_X_FOO": "8.8.8.8, 192.168.2.1",
+ "REMOTE_ADDR": "192.168.2.1",
+ "REMOTE_HOST": "foobar"
+ }
+ proxy_list = ["192.168.2.1"]
+ headers = ["HTTP_X_FOO", "REMOTE_ADDR", "REMOTE_HOST"]
+
+ The above would return True since 192.168.2.1 is a value for the header HTTP_X_FOO
+
+ request: The DRF/Django request. request.environ dict will be used for searching for proxies
+ proxy_list: A list of known and trusted proxies may be ip or hostnames
+ headers: A list of keys for which to consider values that may contain a proxy
+ """
+
+ remote_hosts = set()
+
+ for header in headers:
+ for value in request.environ.get(header, '').split(','):
+ value = value.strip()
+ if value:
+ remote_hosts.add(value)
+
+ return bool(remote_hosts.intersection(set(proxy_list)))
+
+
+def delete_headers_starting_with_http(request: Request, headers: list[str]):
+ for header in headers:
+ if header.startswith('HTTP_'):
+ request.environ.pop(header, None)
diff --git a/awx/main/utils/redis.py b/awx/main/utils/redis.py
new file mode 100644
index 000000000000..98aa89ba29ce
--- /dev/null
+++ b/awx/main/utils/redis.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2025 Ansible, Inc.
+# All Rights Reserved
+
+"""Redis client utilities with automatic retry on connection errors."""
+
+import redis
+import redis.asyncio
+from django.conf import settings
+from redis.backoff import ExponentialBackoff
+from redis.retry import Retry
+from redis.exceptions import BusyLoadingError, ConnectionError, TimeoutError
+
+
+def _get_redis_pool_kwargs():
+ """
+ Get common Redis connection pool kwargs with retry configuration.
+
+ Returns:
+ dict: Keyword arguments for redis.ConnectionPool.from_url()
+ """
+ retry = Retry(ExponentialBackoff(cap=settings.REDIS_BACKOFF_CAP, base=settings.REDIS_BACKOFF_BASE), retries=settings.REDIS_RETRY_COUNT)
+ return {
+ 'retry': retry,
+ 'retry_on_error': [BusyLoadingError, ConnectionError, TimeoutError],
+ }
+
+
+def get_redis_client():
+ """
+ Create a Redis client with automatic retry on connection errors.
+
+ This function creates a Redis connection with built-in retry logic to handle
+ transient connection failures (like broken pipes, timeouts, etc.) that can occur
+ during long-running operations.
+
+ Based on PR feedback: https://github.com/ansible/awx/pull/16158#issuecomment-3486839154
+ Uses redis-py's built-in retry mechanism instead of custom retry logic.
+
+ Returns:
+ redis.Redis: A Redis client instance configured with retry logic
+
+ Notes:
+ - Uses exponential backoff with configurable retries (REDIS_RETRY_COUNT setting)
+ - Retries on BusyLoadingError, ConnectionError, and TimeoutError
+ - Requires redis-py 7.0+
+ """
+ pool = redis.ConnectionPool.from_url(
+ settings.BROKER_URL,
+ **_get_redis_pool_kwargs(),
+ )
+ return redis.Redis(connection_pool=pool)
+
+
+def get_redis_client_async():
+ """
+ Create an async Redis client with automatic retry on connection errors.
+
+ This is the async version of get_redis_client() for use with asyncio code.
+
+ Returns:
+ redis.asyncio.Redis: An async Redis client instance configured with retry logic
+
+ Notes:
+ - Uses exponential backoff with configurable retries (REDIS_RETRY_COUNT setting)
+ - Retries on BusyLoadingError, ConnectionError, and TimeoutError
+ - Requires redis-py 7.0+
+ """
+ pool = redis.asyncio.ConnectionPool.from_url(
+ settings.BROKER_URL,
+ **_get_redis_pool_kwargs(),
+ )
+ return redis.asyncio.Redis(connection_pool=pool)
diff --git a/awx/main/utils/reload.py b/awx/main/utils/reload.py
index a7c2a1ed99b5..4306a1ae2fd3 100644
--- a/awx/main/utils/reload.py
+++ b/awx/main/utils/reload.py
@@ -6,7 +6,6 @@
import logging
import os
-
logger = logging.getLogger('awx.main.utils.reload')
@@ -17,7 +16,7 @@ def supervisor_service_command(command, service='*', communicate=True):
"""
args = ['supervisorctl']
- supervisor_config_path = os.getenv('SUPERVISOR_WEB_CONFIG_PATH', None)
+ supervisor_config_path = os.getenv('SUPERVISOR_CONFIG_PATH', None)
if supervisor_config_path:
args.extend(['-c', supervisor_config_path])
diff --git a/awx/main/utils/safe_yaml.py b/awx/main/utils/safe_yaml.py
index abf21e3428db..3dbfffc6a177 100644
--- a/awx/main/utils/safe_yaml.py
+++ b/awx/main/utils/safe_yaml.py
@@ -1,7 +1,6 @@
import re
import yaml
-
__all__ = ['safe_dump', 'SafeLoader']
diff --git a/awx/main/utils/update_model.py b/awx/main/utils/update_model.py
index 0b2998561cdf..37f35f7091a0 100644
--- a/awx/main/utils/update_model.py
+++ b/awx/main/utils/update_model.py
@@ -6,7 +6,6 @@
from awx.main.tasks.signals import signal_callback
-
logger = logging.getLogger('awx.main.tasks.utils')
diff --git a/awx/main/validators.py b/awx/main/validators.py
index 751d38060bbf..ad1552996b88 100644
--- a/awx/main/validators.py
+++ b/awx/main/validators.py
@@ -181,6 +181,8 @@ def validate_ssh_private_key(data):
certificates; should handle any valid options for ssh_private_key on a
credential.
"""
+ # Strip leading and trailing whitespace/newlines to handle common copy-paste issues
+ data = data.strip()
return validate_pem(data, min_keys=1)
diff --git a/awx/main/wsbroadcast.py b/awx/main/wsbroadcast.py
deleted file mode 100644
index 2c1f228785f2..000000000000
--- a/awx/main/wsbroadcast.py
+++ /dev/null
@@ -1,208 +0,0 @@
-import json
-import logging
-import asyncio
-
-import aiohttp
-from aiohttp import client_exceptions
-from asgiref.sync import sync_to_async
-
-from channels.layers import get_channel_layer
-
-from django.conf import settings
-from django.apps import apps
-from django.core.serializers.json import DjangoJSONEncoder
-
-from awx.main.analytics.broadcast_websocket import (
- BroadcastWebsocketStats,
- BroadcastWebsocketStatsManager,
-)
-import awx.main.analytics.subsystem_metrics as s_metrics
-
-logger = logging.getLogger('awx.main.wsbroadcast')
-
-
-def wrap_broadcast_msg(group, message: str):
- # TODO: Maybe wrap as "group","message" so that we don't need to
- # encode/decode as json.
- return json.dumps(dict(group=group, message=message), cls=DjangoJSONEncoder)
-
-
-def unwrap_broadcast_msg(payload: dict):
- return (payload['group'], payload['message'])
-
-
-@sync_to_async
-def get_broadcast_hosts():
- Instance = apps.get_model('main', 'Instance')
- instances = (
- Instance.objects.exclude(hostname=Instance.objects.my_hostname())
- .exclude(node_type='execution')
- .exclude(node_type='hop')
- .order_by('hostname')
- .values('hostname', 'ip_address')
- .distinct()
- )
- return {i['hostname']: i['ip_address'] or i['hostname'] for i in instances}
-
-
-def get_local_host():
- Instance = apps.get_model('main', 'Instance')
- return Instance.objects.my_hostname()
-
-
-class WebsocketTask:
- def __init__(
- self,
- name,
- event_loop,
- stats: BroadcastWebsocketStats,
- remote_host: str,
- remote_port: int = settings.BROADCAST_WEBSOCKET_PORT,
- protocol: str = settings.BROADCAST_WEBSOCKET_PROTOCOL,
- verify_ssl: bool = settings.BROADCAST_WEBSOCKET_VERIFY_CERT,
- endpoint: str = 'broadcast',
- ):
- self.name = name
- self.event_loop = event_loop
- self.stats = stats
- self.remote_host = remote_host
- self.remote_port = remote_port
- self.endpoint = endpoint
- self.protocol = protocol
- self.verify_ssl = verify_ssl
- self.channel_layer = None
- self.subsystem_metrics = s_metrics.Metrics(instance_name=name)
-
- async def run_loop(self, websocket: aiohttp.ClientWebSocketResponse):
- raise RuntimeError("Implement me")
-
- async def connect(self, attempt):
- from awx.main.consumers import WebsocketSecretAuthHelper # noqa
-
- logger.debug(f"Connection from {self.name} to {self.remote_host} attempt number {attempt}.")
-
- '''
- Can not put get_channel_layer() in the init code because it is in the init
- path of channel layers i.e. RedisChannelLayer() calls our init code.
- '''
- if not self.channel_layer:
- self.channel_layer = get_channel_layer()
-
- try:
- if attempt > 0:
- await asyncio.sleep(settings.BROADCAST_WEBSOCKET_RECONNECT_RETRY_RATE_SECONDS)
- except asyncio.CancelledError:
- logger.warning(f"Connection from {self.name} to {self.remote_host} cancelled")
- raise
-
- uri = f"{self.protocol}://{self.remote_host}:{self.remote_port}/websocket/{self.endpoint}/"
- timeout = aiohttp.ClientTimeout(total=10)
-
- secret_val = WebsocketSecretAuthHelper.construct_secret()
- try:
- async with aiohttp.ClientSession(headers={'secret': secret_val}, timeout=timeout) as session:
- async with session.ws_connect(uri, ssl=self.verify_ssl, heartbeat=20) as websocket:
- logger.info(f"Connection from {self.name} to {self.remote_host} established.")
- self.stats.record_connection_established()
- attempt = 0
- await self.run_loop(websocket)
- except asyncio.CancelledError:
- # TODO: Check if connected and disconnect
- # Possibly use run_until_complete() if disconnect is async
- logger.warning(f"Connection from {self.name} to {self.remote_host} cancelled.")
- self.stats.record_connection_lost()
- raise
- except client_exceptions.ClientConnectorError as e:
- logger.warning(f"Connection from {self.name} to {self.remote_host} failed: '{e}'.")
- except asyncio.TimeoutError:
- logger.warning(f"Connection from {self.name} to {self.remote_host} timed out.")
- except Exception as e:
- # Early on, this is our canary. I'm not sure what exceptions we can really encounter.
- logger.exception(f"Connection from {self.name} to {self.remote_host} failed for unknown reason: '{e}'.")
- else:
- logger.warning(f"Connection from {self.name} to {self.remote_host} list.")
-
- self.stats.record_connection_lost()
- self.start(attempt=attempt + 1)
-
- def start(self, attempt=0):
- self.async_task = self.event_loop.create_task(self.connect(attempt=attempt))
-
- def cancel(self):
- self.async_task.cancel()
-
-
-class BroadcastWebsocketTask(WebsocketTask):
- async def run_loop(self, websocket: aiohttp.ClientWebSocketResponse):
- async for msg in websocket:
- self.stats.record_message_received()
-
- if msg.type == aiohttp.WSMsgType.ERROR:
- break
- elif msg.type == aiohttp.WSMsgType.TEXT:
- try:
- payload = json.loads(msg.data)
- except json.JSONDecodeError:
- logmsg = "Failed to decode broadcast message"
- if logger.isEnabledFor(logging.DEBUG):
- logmsg = "{} {}".format(logmsg, payload)
- logger.warning(logmsg)
- continue
- (group, message) = unwrap_broadcast_msg(payload)
- if group == "metrics":
- self.subsystem_metrics.store_metrics(message)
- continue
- await self.channel_layer.group_send(group, {"type": "internal.message", "text": message})
-
-
-class BroadcastWebsocketManager(object):
- def __init__(self):
- self.event_loop = asyncio.get_event_loop()
- '''
- {
- 'hostname1': BroadcastWebsocketTask(),
- 'hostname2': BroadcastWebsocketTask(),
- 'hostname3': BroadcastWebsocketTask(),
- }
- '''
- self.broadcast_tasks = dict()
- self.local_hostname = get_local_host()
- self.stats_mgr = BroadcastWebsocketStatsManager(self.event_loop, self.local_hostname)
-
- async def run_per_host_websocket(self):
- while True:
- known_hosts = await get_broadcast_hosts()
- future_remote_hosts = known_hosts.keys()
- current_remote_hosts = self.broadcast_tasks.keys()
- deleted_remote_hosts = set(current_remote_hosts) - set(future_remote_hosts)
- new_remote_hosts = set(future_remote_hosts) - set(current_remote_hosts)
-
- remote_addresses = {k: v.remote_host for k, v in self.broadcast_tasks.items()}
- for hostname, address in known_hosts.items():
- if hostname in self.broadcast_tasks and address != remote_addresses[hostname]:
- deleted_remote_hosts.add(hostname)
- new_remote_hosts.add(hostname)
-
- if deleted_remote_hosts:
- logger.warning(f"Removing {deleted_remote_hosts} from websocket broadcast list")
- if new_remote_hosts:
- logger.warning(f"Adding {new_remote_hosts} to websocket broadcast list")
-
- for h in deleted_remote_hosts:
- self.broadcast_tasks[h].cancel()
- del self.broadcast_tasks[h]
- self.stats_mgr.delete_remote_host_stats(h)
-
- for h in new_remote_hosts:
- stats = self.stats_mgr.new_remote_host_stats(h)
- broadcast_task = BroadcastWebsocketTask(name=self.local_hostname, event_loop=self.event_loop, stats=stats, remote_host=known_hosts[h])
- broadcast_task.start()
- self.broadcast_tasks[h] = broadcast_task
-
- await asyncio.sleep(settings.BROADCAST_WEBSOCKET_NEW_INSTANCE_POLL_RATE_SECONDS)
-
- def start(self):
- self.stats_mgr.start()
-
- self.async_task = self.event_loop.create_task(self.run_per_host_websocket())
- return self.async_task
diff --git a/awx/main/wsrelay.py b/awx/main/wsrelay.py
new file mode 100644
index 000000000000..9e55e3e2c83a
--- /dev/null
+++ b/awx/main/wsrelay.py
@@ -0,0 +1,376 @@
+import json
+import logging
+import asyncio
+from typing import Dict
+from copy import deepcopy
+
+import ipaddress
+
+import aiohttp
+from aiohttp import client_exceptions
+import redis
+
+from channels.layers import get_channel_layer
+
+from django.conf import settings
+from django.apps import apps
+
+import psycopg
+
+from awx.main.analytics.broadcast_websocket import (
+ RelayWebsocketStats,
+ RelayWebsocketStatsManager,
+)
+
+logger = logging.getLogger('awx.main.wsrelay')
+
+
+def wrap_broadcast_msg(group, message: str):
+ # TODO: Maybe wrap as "group","message" so that we don't need to
+ # encode/decode as json.
+ return dict(group=group, message=message)
+
+
+def get_local_host():
+ Instance = apps.get_model('main', 'Instance')
+ return Instance.objects.my_hostname()
+
+
+class WebsocketRelayConnection:
+ def __init__(
+ self,
+ name,
+ stats: RelayWebsocketStats,
+ remote_host: str,
+ remote_port: int = settings.BROADCAST_WEBSOCKET_PORT,
+ protocol: str = settings.BROADCAST_WEBSOCKET_PROTOCOL,
+ verify_ssl: bool = settings.BROADCAST_WEBSOCKET_VERIFY_CERT,
+ ):
+ self.name = name
+ self.stats = stats
+ self.remote_host = remote_host
+ self.remote_port = remote_port
+ self.protocol = protocol
+ self.verify_ssl = verify_ssl
+ self.channel_layer = None
+ self.producers = dict()
+ self.connected = False
+
+ async def run_loop(self, websocket: aiohttp.ClientWebSocketResponse):
+ raise RuntimeError("Implement me")
+
+ async def connect(self):
+ from awx.main.consumers import WebsocketSecretAuthHelper # noqa
+
+ logger.debug(f"Connection attempt from {self.name} to {self.remote_host}")
+
+ '''
+ Can not put get_channel_layer() in the init code because it is in the init
+ path of channel layers i.e. RedisChannelLayer() calls our init code.
+ '''
+ if not self.channel_layer:
+ self.channel_layer = get_channel_layer()
+
+ # figure out if what we have is an ipaddress, IPv6 Addresses must have brackets added for uri
+ uri_hostname = self.remote_host
+ try:
+ # Throws ValueError if self.remote_host is a hostname like example.com, not an IPv4 or IPv6 ip address
+ if isinstance(ipaddress.ip_address(uri_hostname), ipaddress.IPv6Address):
+ uri_hostname = f"[{uri_hostname}]"
+ except ValueError:
+ pass
+
+ uri = f"{self.protocol}://{uri_hostname}:{self.remote_port}/websocket/relay/"
+ timeout = aiohttp.ClientTimeout(total=10)
+
+ secret_val = WebsocketSecretAuthHelper.construct_secret()
+ try:
+ async with aiohttp.ClientSession(headers={'secret': secret_val}, timeout=timeout) as session:
+ async with session.ws_connect(uri, ssl=self.verify_ssl, heartbeat=20) as websocket:
+ logger.info(f"Connection from {self.name} to {self.remote_host} established.")
+ self.stats.record_connection_established()
+ self.connected = True
+ await self.run_connection(websocket)
+ except asyncio.CancelledError:
+ # TODO: Check if connected and disconnect
+ # Possibly use run_until_complete() if disconnect is async
+ logger.warning(f"Connection from {self.name} to {self.remote_host} canceled.")
+ except client_exceptions.ClientConnectorError as e:
+ logger.warning(f"Connection from {self.name} to {self.remote_host} failed: '{e}'.", exc_info=True)
+ except asyncio.TimeoutError:
+ logger.warning(f"Connection from {self.name} to {self.remote_host} timed out.")
+ except Exception as e:
+ # Early on, this is our canary. I'm not sure what exceptions we can really encounter.
+ logger.warning(f"Connection from {self.name} to {self.remote_host} failed for unknown reason: '{e}'.", exc_info=True)
+ else:
+ logger.debug(f"Connection from {self.name} to {self.remote_host} lost, but no exception was raised.")
+ finally:
+ self.connected = False
+ self.stats.record_connection_lost()
+
+ def start(self):
+ self.async_task = asyncio.get_running_loop().create_task(
+ self.connect(),
+ name=f"WebsocketRelayConnection.connect.{self.name}",
+ )
+ return self.async_task
+
+ def cancel(self):
+ self.async_task.cancel()
+
+ async def run_connection(self, websocket: aiohttp.ClientWebSocketResponse):
+ # create a dedicated subsystem metric producer to handle local subsystem
+ # metrics messages
+ # the "metrics" group is not subscribed to in the typical fashion, so we
+ # just explicitly create it
+ producer = asyncio.get_running_loop().create_task(
+ self.run_producer("metrics", websocket, "metrics"),
+ name="WebsocketRelayConnection.run_producer.metrics",
+ )
+ self.producers["metrics"] = {"task": producer, "subscriptions": {"metrics"}}
+ async for msg in websocket:
+ self.stats.record_message_received()
+
+ if msg.type == aiohttp.WSMsgType.ERROR:
+ break
+ elif msg.type == aiohttp.WSMsgType.TEXT:
+ try:
+ payload = json.loads(msg.data)
+ except json.JSONDecodeError:
+ logmsg = "Failed to decode message from web node"
+ if logger.isEnabledFor(logging.DEBUG):
+ logmsg = "{} {}".format(logmsg, payload)
+ logger.warning(logmsg)
+ continue
+
+ if payload.get("type") == "consumer.subscribe":
+ for group in payload['groups']:
+ name = f"{self.remote_host}-{group}"
+ origin_channel = payload['origin_channel']
+ if not self.producers.get(name):
+ producer = asyncio.get_running_loop().create_task(
+ self.run_producer(name, websocket, group),
+ name=f"WebsocketRelayConnection.run_producer.{name}",
+ )
+ self.producers[name] = {"task": producer, "subscriptions": {origin_channel}}
+ logger.debug(f"Producer {name} started.")
+ else:
+ self.producers[name]["subscriptions"].add(origin_channel)
+ logger.debug(f"Connection from {self.name} to {self.remote_host} added subscription to {group}.")
+
+ if payload.get("type") == "consumer.unsubscribe":
+ for group in payload['groups']:
+ name = f"{self.remote_host}-{group}"
+ origin_channel = payload['origin_channel']
+ try:
+ self.producers[name]["subscriptions"].remove(origin_channel)
+ logger.debug(f"Unsubscribed {origin_channel} from {name}")
+ except KeyError:
+ logger.warning(f"Producer {name} not found.")
+
+ async def run_producer(self, name, websocket, group):
+ try:
+ logger.info(f"Starting producer for {name}")
+
+ consumer_channel = await self.channel_layer.new_channel()
+ await self.channel_layer.group_add(group, consumer_channel)
+ logger.debug(f"Producer {name} added to group {group} and is now awaiting messages.")
+
+ while True:
+ try:
+ msg = await asyncio.wait_for(self.channel_layer.receive(consumer_channel), timeout=10)
+ if not msg.get("needs_relay"):
+ # This is added in by emit_channel_notification(). It prevents us from looping
+ # in the event that we are sharing a redis with a web instance. We'll see the
+ # message once (it'll have needs_relay=True), we'll delete that, and then forward
+ # the message along. The web instance will add it back to the same channels group,
+ # but it won't have needs_relay=True, so we'll ignore it.
+ continue
+
+ # We need to copy the message because we're going to delete the needs_relay key
+ # and we don't want to modify the original message because other producers may
+ # still need to act on it. It seems weird, but it's necessary.
+ msg = dict(msg)
+ del msg["needs_relay"]
+ except asyncio.TimeoutError:
+ current_subscriptions = self.producers[name]["subscriptions"]
+ if len(current_subscriptions) == 0:
+ logger.info(f"Producer {name} has no subscribers, shutting down.")
+ return
+
+ continue
+ except redis.exceptions.ConnectionError:
+ logger.info(f"Producer {name} lost connection to Redis, shutting down.")
+ return
+
+ await websocket.send_json(wrap_broadcast_msg(group, msg))
+ except ConnectionResetError:
+ # This can be hit when a web node is scaling down and we try to write to it.
+ # There's really nothing to do in this case and it's a fairly typical thing to happen.
+ # We'll log it as debug, but it's not really a problem.
+ logger.debug(f"Producer {name} connection reset.")
+ pass
+ except Exception:
+ # Note, this is very intentional and important since we do not otherwise
+ # ever check the result of this future. Without this line you will not see an error if
+ # something goes wrong in here.
+ logger.exception(f"Event relay producer {name} crashed")
+ finally:
+ await self.channel_layer.group_discard(group, consumer_channel)
+ del self.producers[name]
+
+
+class WebSocketRelayManager(object):
+ def __init__(self):
+ self.local_hostname = get_local_host()
+ self.relay_connections = dict()
+ # hostname -> ip
+ self.known_hosts: Dict[str, str] = dict()
+
+ async def on_ws_heartbeat(self, conn):
+ await conn.execute("LISTEN web_ws_heartbeat")
+ async for notif in conn.notifies():
+ if notif is None:
+ continue
+ try:
+ if not notif.payload or notif.channel != "web_ws_heartbeat":
+ logger.warning(f"Unexpected channel or missing payload. {notif.channel}, {notif.payload}")
+ continue
+
+ try:
+ payload = json.loads(notif.payload)
+ except json.JSONDecodeError:
+ logmsg = "Failed to decode message from pg_notify channel `web_ws_heartbeat`"
+ if logger.isEnabledFor(logging.DEBUG):
+ logmsg = "{} {}".format(logmsg, payload)
+ logger.warning(logmsg)
+ continue
+
+ # Skip if the message comes from the same host we are running on
+ # In this case, we'll be sharing a redis, no need to relay.
+ if payload.get("hostname") == self.local_hostname:
+ hostname = payload.get("hostname")
+ logger.debug(f"Received a heartbeat request for {hostname}. Skipping as we use redis for local host.")
+ continue
+
+ action = payload.get("action")
+
+ if action in ("online", "offline"):
+ hostname = payload.get("hostname")
+ ip = payload.get("ip") or hostname # try back to hostname if ip isn't supplied
+ if ip is None:
+ logger.warning(f"Received invalid {action} ws_heartbeat, missing hostname and ip: {payload}")
+ continue
+ logger.debug(f"Web host {hostname} ({ip}) {action} heartbeat received.")
+
+ if action == "online":
+ self.known_hosts[hostname] = ip
+ elif action == "offline":
+ await self.cleanup_offline_host(hostname)
+ except Exception as e:
+ # This catch-all is the same as the one above. asyncio will eat the exception
+ # but we want to know about it.
+ logger.exception(f"on_ws_heartbeat exception: {e}")
+
+ async def cleanup_offline_host(self, hostname):
+ """
+ Given a hostname, try to cancel its task/connection and remove it from
+ the list of hosts we know about.
+ If the host isn't in the list, assume that it was already deleted and
+ don't error.
+ """
+ if hostname in self.relay_connections:
+ self.relay_connections[hostname].cancel()
+
+ # Wait for the task to actually run its cancel/completion logic
+ # otherwise it might get GC'd too early when we del it below.
+ # Being GC'd too early could generate a scary message in logs:
+ # "Task was destroyed but it is pending!"
+ try:
+ await asyncio.wait_for(self.relay_connections[hostname].async_task, timeout=10)
+ except asyncio.TimeoutError:
+ logger.warning(f"Tried to cancel relay connection for {hostname} but it timed out during cleanup.")
+ except asyncio.CancelledError:
+ # Handle the case where the task was already canceled by the time we got here.
+ pass
+
+ del self.relay_connections[hostname]
+
+ if hostname in self.known_hosts:
+ del self.known_hosts[hostname]
+
+ try:
+ self.stats_mgr.delete_remote_host_stats(hostname)
+ except KeyError:
+ pass
+
+ async def run(self):
+ self.stats_mgr = RelayWebsocketStatsManager(self.local_hostname)
+ self.stats_mgr.start()
+
+ database_conf = deepcopy(settings.DATABASES['default'])
+ database_conf['OPTIONS'] = deepcopy(database_conf.get('OPTIONS', {}))
+
+ for k, v in settings.LISTENER_DATABASES.get('default', {}).items():
+ if k != 'OPTIONS':
+ database_conf[k] = v
+ for k, v in settings.LISTENER_DATABASES.get('default', {}).get('OPTIONS', {}).items():
+ database_conf['OPTIONS'][k] = v
+
+ if 'PASSWORD' in database_conf:
+ database_conf['OPTIONS']['password'] = database_conf.pop('PASSWORD')
+
+ async_conn = await psycopg.AsyncConnection.connect(
+ dbname=database_conf['NAME'],
+ host=database_conf['HOST'],
+ user=database_conf['USER'],
+ port=database_conf['PORT'],
+ **database_conf.get("OPTIONS", {}),
+ )
+
+ await async_conn.set_autocommit(True)
+ on_ws_heartbeat_task = asyncio.get_running_loop().create_task(
+ self.on_ws_heartbeat(async_conn),
+ name="WebSocketRelayManager.on_ws_heartbeat",
+ )
+
+ # Establishes a websocket connection to /websocket/relay on all API servers
+ while True:
+ if on_ws_heartbeat_task.done():
+ raise Exception("on_ws_heartbeat_task has exited")
+
+ future_remote_hosts = self.known_hosts.keys()
+ current_remote_hosts = self.relay_connections.keys()
+ deleted_remote_hosts = set(current_remote_hosts) - set(future_remote_hosts)
+ new_remote_hosts = set(future_remote_hosts) - set(current_remote_hosts)
+
+ # This loop handles if we get an advertisement from a host we already know about but
+ # the advertisement has a different IP than we are currently connected to.
+ for hostname, address in self.known_hosts.items():
+ if hostname not in self.relay_connections:
+ # We've picked up a new hostname that we don't know about yet.
+ continue
+
+ if address != self.relay_connections[hostname].remote_host:
+ deleted_remote_hosts.add(hostname)
+ new_remote_hosts.add(hostname)
+
+ # Delete any hosts with closed connections
+ for hostname, relay_conn in self.relay_connections.items():
+ if not relay_conn.connected:
+ deleted_remote_hosts.add(hostname)
+
+ if deleted_remote_hosts:
+ logger.info(f"Removing {deleted_remote_hosts} from websocket broadcast list")
+ await asyncio.gather(*[self.cleanup_offline_host(h) for h in deleted_remote_hosts])
+
+ if new_remote_hosts:
+ logger.info(f"Adding {new_remote_hosts} to websocket broadcast list")
+
+ for h in new_remote_hosts:
+ stats = self.stats_mgr.new_remote_host_stats(h)
+ relay_connection = WebsocketRelayConnection(name=self.local_hostname, stats=stats, remote_host=self.known_hosts[h])
+ relay_connection.start()
+ self.relay_connections[h] = relay_connection
+
+ await asyncio.sleep(settings.BROADCAST_WEBSOCKET_NEW_INSTANCE_POLL_RATE_SECONDS)
diff --git a/awx/playbooks/action_plugins/insights.py b/awx/playbooks/action_plugins/insights.py
index 0d9bf6abd7ff..2d6b563c292f 100644
--- a/awx/playbooks/action_plugins/insights.py
+++ b/awx/playbooks/action_plugins/insights.py
@@ -9,6 +9,8 @@
from ansible.plugins.action import ActionBase
+DEFAULT_OIDC_ENDPOINT = 'https://sso.redhat.com/auth/realms/redhat-external'
+
class ActionModule(ActionBase):
def save_playbook(self, proj_path, remediation, content):
@@ -34,31 +36,78 @@ def write_version(self, proj_path, etag):
with open(file_path, 'w') as f:
f.write(etag)
+ def _obtain_auth_token(self, oidc_endpoint, client_id, client_secret):
+ if oidc_endpoint.endswith('/'):
+ oidc_endpoint = oidc_endpoint[:-1]
+ main_url = oidc_endpoint + '/.well-known/openid-configuration'
+ response = requests.get(url=main_url, headers={'Accept': 'application/json'})
+ data = {}
+ if response.status_code != 200:
+ data['failed'] = True
+ data['msg'] = 'Expected {} to return a status code of 200 but returned status code "{}" instead with content "{}".'.format(
+ main_url, response.status_code, response.content
+ )
+ return data
+
+ auth_url = response.json().get('token_endpoint', None)
+ data = {
+ 'grant_type': 'client_credentials',
+ 'scope': 'api.console',
+ 'client_id': client_id,
+ 'client_secret': client_secret,
+ }
+ response = requests.post(url=auth_url, data=data)
+
+ if response.status_code != 200:
+ data['failed'] = True
+ data['msg'] = 'Expected {} to return a status code of 200 but returned status code "{}" instead with content "{}".'.format(
+ auth_url, response.status_code, response.content
+ )
+ else:
+ data['token'] = response.json().get('access_token', None)
+ data['token_type'] = response.json().get('token_type', None)
+ return data
+
def run(self, tmp=None, task_vars=None):
self._supports_check_mode = False
+ session = requests.Session()
result = super(ActionModule, self).run(tmp, task_vars)
insights_url = self._task.args.get('insights_url', None)
- username = self._task.args.get('username', None)
- password = self._task.args.get('password', None)
proj_path = self._task.args.get('project_path', None)
license = self._task.args.get('awx_license_type', None)
awx_version = self._task.args.get('awx_version', None)
+ authentication = self._task.args.get('authentication', None)
+ username = self._task.args.get('username', None)
+ password = self._task.args.get('password', None)
+ client_id = self._task.args.get('client_id', None)
+ client_secret = self._task.args.get('client_secret', None)
+
+ session.headers.update(
+ {
+ 'Content-Type': 'application/json',
+ 'User-Agent': '{} {} ({})'.format('AWX' if license == 'open' else 'Red Hat Ansible Automation Platform', awx_version, license),
+ }
+ )
+
+ if authentication == 'service_account' or (client_id and client_secret):
+ data = self._obtain_auth_token(DEFAULT_OIDC_ENDPOINT, client_id, client_secret)
+ if 'token' not in data:
+ result['failed'] = data['failed']
+ result['msg'] = data['msg']
+ return result
+ session.headers.update({'Authorization': f'{data["token_type"]} {data["token"]}'})
+ elif authentication == 'basic' or (username and password):
+ session.auth = requests.auth.HTTPBasicAuth(username, password)
- session = requests.Session()
- session.auth = requests.auth.HTTPBasicAuth(username, password)
- headers = {
- 'Content-Type': 'application/json',
- 'User-Agent': '{} {} ({})'.format('AWX' if license == 'open' else 'Red Hat Ansible Automation Platform', awx_version, license),
- }
url = '/api/remediations/v1/remediations'
while url:
- res = session.get('{}{}'.format(insights_url, url), headers=headers, timeout=120)
+ res = session.get('{}{}'.format(insights_url, url), timeout=120)
if res.status_code != 200:
result['failed'] = True
- result['msg'] = 'Expected {} to return a status code of 200 but returned status ' 'code "{}" instead with content "{}".'.format(
+ result['msg'] = 'Expected {} to return a status code of 200 but returned status code "{}" instead with content "{}".'.format(
url, res.status_code, res.content
)
return result
@@ -87,7 +136,7 @@ def run(self, tmp=None, task_vars=None):
continue
elif res.status_code != 200:
result['failed'] = True
- result['msg'] = 'Expected {} to return a status code of 200 but returned status ' 'code "{}" instead with content "{}".'.format(
+ result['msg'] = 'Expected {} to return a status code of 200 but returned status code "{}" instead with content "{}".'.format(
playbook_url, res.status_code, res.content
)
return result
diff --git a/awx/playbooks/library/indirect_instance_count.py b/awx/playbooks/library/indirect_instance_count.py
new file mode 100644
index 000000000000..497178abc799
--- /dev/null
+++ b/awx/playbooks/library/indirect_instance_count.py
@@ -0,0 +1,93 @@
+# (C) 2012, Michael DeHaan,
+# (c) 2017 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+DOCUMENTATION = '''
+ callback: host_query
+ type: notification
+ short_description: for demo of indirect host data and counting, this produces collection data
+ version_added: historical
+ description:
+ - Saves collection data to artifacts folder
+ requirements:
+ - Whitelist in configuration
+ - Set AWX_ISOLATED_DATA_DIR, AWX will do this
+'''
+
+import os
+import json
+from importlib.resources import files
+
+from ansible.plugins.callback import CallbackBase
+
+# NOTE: in Ansible 1.2 or later general logging is available without
+# this plugin, just set ANSIBLE_LOG_PATH as an environment variable
+# or log_path in the DEFAULTS section of your ansible configuration
+# file. This callback is an example of per hosts logging for those
+# that want it.
+
+
+# Taken from https://github.com/ansible/ansible/blob/devel/lib/ansible/cli/galaxy.py#L1624
+
+from ansible.cli.galaxy import with_collection_artifacts_manager
+from ansible.release import __version__
+
+from ansible.galaxy.collection import find_existing_collections
+from ansible.utils.collection_loader import AnsibleCollectionConfig
+import ansible.constants as C
+
+
+@with_collection_artifacts_manager
+def list_collections(artifacts_manager=None):
+ artifacts_manager.require_build_metadata = False
+
+ default_collections_path = set(C.COLLECTIONS_PATHS)
+ collections_search_paths = default_collections_path | set(AnsibleCollectionConfig.collection_paths)
+ collections = list(find_existing_collections(list(collections_search_paths), artifacts_manager, dedupe=False))
+ return collections
+
+
+class CallbackModule(CallbackBase):
+ """
+ logs playbook results, per host, in /var/log/ansible/hosts
+ """
+
+ CALLBACK_VERSION = 2.0
+ CALLBACK_TYPE = 'notification'
+ CALLBACK_NAME = 'indirect_instance_count'
+ CALLBACK_NEEDS_WHITELIST = True
+
+ TIME_FORMAT = "%b %d %Y %H:%M:%S"
+ MSG_FORMAT = "%(now)s - %(category)s - %(data)s\n\n"
+
+ def v2_playbook_on_stats(self, stats):
+ artifact_dir = os.getenv('AWX_ISOLATED_DATA_DIR')
+ if not artifact_dir:
+ raise RuntimeError('Only suitable in AWX, did not find private_data_dir')
+
+ collections_print = {}
+ # Loop over collections, from ansible-core these are Candidate objects
+ for candidate in list_collections():
+ collection_print = {
+ 'version': candidate.ver,
+ }
+
+ query_file = files(f'ansible_collections.{candidate.namespace}.{candidate.name}') / 'extensions' / 'audit' / 'event_query.yml'
+ if query_file.exists():
+ with query_file.open('r') as f:
+ collection_print['host_query'] = f.read()
+
+ collections_print[candidate.fqcn] = collection_print
+
+ ansible_data = {'installed_collections': collections_print, 'ansible_version': __version__}
+
+ write_path = os.path.join(artifact_dir, 'ansible_data.json')
+ with open(write_path, "w") as fd:
+ fd.write(json.dumps(ansible_data, indent=2))
+
+ super().v2_playbook_on_stats(stats)
diff --git a/awx/playbooks/project_update.yml b/awx/playbooks/project_update.yml
index 2067e76043cb..1cdf046106e2 100644
--- a/awx/playbooks/project_update.yml
+++ b/awx/playbooks/project_update.yml
@@ -19,48 +19,58 @@
# awx_version: Current running version of the awx or tower as a string
# awx_license_type: "open" for AWX; else presume Tower
# gpg_pubkey: the GPG public key to use for validation, when enabled
+# client_id: Red Hat service account client ID; required for the 'service_account' authentication method used against the Insights API
+# client_secret: Red Hat service account client secret; required for the 'service_account' authentication method used against the Insights API
+# authentication: The authentication method to use against the Insights API
+# client_id and client_secret are required for the 'service_account' authentication method
+# scm_username and scm_password are required for the 'basic' authentication method
- hosts: localhost
gather_facts: false
connection: local
name: Update source tree if necessary
tasks:
-
- - name: delete project directory before update
- command: "find -delete" # volume mounted, cannot delete folder itself
+ - name: Delete project directory before update
+ ansible.builtin.shell: set -o pipefail && find . -delete -print | tail -2 # volume mounted, cannot delete folder itself
+ register: reg
+ changed_when: reg.stdout_lines | length > 1
args:
chdir: "{{ project_path }}"
tags:
- delete
- - block:
- - name: update project using git
- git:
- dest: "{{project_path|quote}}"
- repo: "{{scm_url}}"
- version: "{{scm_branch|quote}}"
- refspec: "{{scm_refspec|default(omit)}}"
- force: "{{scm_clean}}"
- track_submodules: "{{scm_track_submodules|default(omit)}}"
- accept_hostkey: "{{scm_accept_hostkey|default(omit)}}"
+ - name: Update project using git
+ tags:
+ - update_git
+ block:
+ - name: Update project using git
+ ansible.builtin.git:
+ dest: "{{ project_path | quote }}"
+ repo: "{{ scm_url }}"
+ version: "{{ scm_branch | quote }}"
+ refspec: "{{ scm_refspec | default(omit) }}"
+ force: "{{ scm_clean }}"
+ track_submodules: "{{ scm_track_submodules | default(omit) }}"
+ accept_hostkey: "{{ scm_accept_hostkey | default(omit) }}"
register: git_result
- name: Set the git repository version
- set_fact:
+ ansible.builtin.set_fact:
scm_version: "{{ git_result['after'] }}"
when: "'after' in git_result"
- tags:
- - update_git
- - block:
- - name: update project using svn
- subversion:
- dest: "{{project_path|quote}}"
- repo: "{{scm_url|quote}}"
- revision: "{{scm_branch|quote}}"
- force: "{{scm_clean}}"
- username: "{{scm_username|default(omit)}}"
- password: "{{scm_password|default(omit)}}"
+ - name: Update project using svn
+ tags:
+ - update_svn
+ block:
+ - name: Update project using svn
+ ansible.builtin.subversion:
+ dest: "{{ project_path | quote }}"
+ repo: "{{ scm_url | quote }}"
+ revision: "{{ scm_branch | quote }}"
+ force: "{{ scm_clean }}"
+ username: "{{ scm_username | default(omit) }}"
+ password: "{{ scm_password | default(omit) }}"
# must be in_place because folder pre-existing, because it is mounted
in_place: true
environment:
@@ -68,85 +78,93 @@
register: svn_result
- name: Set the svn repository version
- set_fact:
+ ansible.builtin.set_fact:
scm_version: "{{ svn_result['after'] }}"
when: "'after' in svn_result"
- - name: parse subversion version string properly
- set_fact:
- scm_version: "{{scm_version|regex_replace('^.*Revision: ([0-9]+).*$', '\\1')}}"
- tags:
- - update_svn
+ - name: Parse subversion version string properly
+ ansible.builtin.set_fact:
+ scm_version: "{{ scm_version | regex_replace('^.*Revision: ([0-9]+).*$', '\\1') }}"
- - block:
+
+ - name: Project update for Insights
+ tags:
+ - update_insights
+ block:
- name: Ensure the project directory is present
- file:
- dest: "{{project_path|quote}}"
+ ansible.builtin.file:
+ dest: "{{ project_path | quote }}"
state: directory
+ mode: '0755'
- name: Fetch Insights Playbook(s)
insights:
- insights_url: "{{insights_url}}"
- username: "{{scm_username}}"
- password: "{{scm_password}}"
- project_path: "{{project_path}}"
- awx_license_type: "{{awx_license_type}}"
- awx_version: "{{awx_version}}"
+ insights_url: "{{ insights_url }}"
+ username: "{{ scm_username | default(omit) }}"
+ password: "{{ scm_password | default(omit) }}"
+ project_path: "{{ project_path }}"
+ awx_license_type: "{{ awx_license_type }}"
+ awx_version: "{{ awx_version }}"
+ client_id: "{{ client_id | default(omit) }}"
+ client_secret: "{{ client_secret | default(omit) }}"
+ authentication: "{{ authentication | default(omit) }}"
register: results
- name: Save Insights Version
- set_fact:
- scm_version: "{{results.version}}"
+ ansible.builtin.set_fact:
+ scm_version: "{{ results.version }}"
when: results is defined
- tags:
- - update_insights
- - block:
+
+ - name: Update project using archive
+ tags:
+ - update_archive
+ block:
- name: Ensure the project archive directory is present
- file:
- dest: "{{ project_path|quote }}/.archive"
+ ansible.builtin.file:
+ dest: "{{ project_path | quote }}/.archive"
state: directory
+ mode: '0755'
- name: Get archive from url
- get_url:
- url: "{{ scm_url|quote }}"
- dest: "{{ project_path|quote }}/.archive/"
- url_username: "{{ scm_username|default(omit) }}"
- url_password: "{{ scm_password|default(omit) }}"
+ ansible.builtin.get_url:
+ url: "{{ scm_url | quote }}"
+ dest: "{{ project_path | quote }}/.archive/"
+ url_username: "{{ scm_username | default(omit) }}"
+ url_password: "{{ scm_password | default(omit) }}"
force_basic_auth: true
+ mode: '0755'
register: get_archive
- name: Unpack archive
project_archive:
src: "{{ get_archive.dest }}"
- project_path: "{{ project_path|quote }}"
+ project_path: "{{ project_path | quote }}"
force: "{{ scm_clean }}"
when: get_archive.changed or scm_clean
register: unarchived
- name: Find previous archives
- find:
- paths: "{{ project_path|quote }}/.archive/"
+ ansible.builtin.find:
+ paths: "{{ project_path | quote }}/.archive/"
excludes:
- - "{{ get_archive.dest|basename }}"
+ - "{{ get_archive.dest | basename }}"
when: unarchived.changed
register: previous_archive
- name: Remove previous archives
- file:
+ ansible.builtin.file:
path: "{{ item.path }}"
state: absent
loop: "{{ previous_archive.files }}"
- when: previous_archive.files|default([])
+ when: previous_archive.files | default([])
- name: Set scm_version to archive sha1 checksum
- set_fact:
+ ansible.builtin.set_fact:
scm_version: "{{ get_archive.checksum_src }}"
- tags:
- - update_archive
- name: Repository Version
- debug:
+ ansible.builtin.debug:
msg: "Repository Version {{ scm_version }}"
tags:
- update_git
@@ -179,64 +197,94 @@
connection: local
name: Install content with ansible-galaxy command if necessary
vars:
- galaxy_task_env: # configure in settings
- additional_collections_env:
- # These environment variables are used for installing collections, in addition to galaxy_task_env
- # setting the collections paths silences warnings
- ANSIBLE_COLLECTIONS_PATHS: "{{projects_root}}/.__awx_cache/{{local_path}}/stage/requirements_collections"
+ galaxy_task_env: # configured in settings
+ # additional_galaxy_env contains environment variables are used for installing roles and collections and will take precedence over items in galaxy_task_env
+ additional_galaxy_env:
+ # These paths control where ansible-galaxy installs collections and roles on top the filesystem
+ ANSIBLE_COLLECTIONS_PATH: "{{ projects_root }}/.__awx_cache/{{ local_path }}/stage/requirements_collections"
+ ANSIBLE_ROLES_PATH: "{{ projects_root }}/.__awx_cache/{{ local_path }}/stage/requirements_roles"
# Put the local tmp directory in same volume as collection destination
# otherwise, files cannot be moved accross volumes and will cause error
- ANSIBLE_LOCAL_TEMP: "{{projects_root}}/.__awx_cache/{{local_path}}/stage/tmp"
+ ANSIBLE_LOCAL_TEMP: "{{ projects_root }}/.__awx_cache/{{ local_path }}/stage/tmp"
tasks:
-
- name: Check content sync settings
+ when: not roles_enabled | bool and not collections_enabled | bool
+ tags:
+ - install_roles
+ - install_collections
block:
- - debug:
+ - name: Warn about disabled content sync
+ ansible.builtin.debug:
msg: >
Collection and role syncing disabled. Check the AWX_ROLES_ENABLED and
AWX_COLLECTIONS_ENABLED settings and Galaxy credentials on the project's organization.
+ - name: End play due to disabled content sync
+ ansible.builtin.meta: end_play
- - meta: end_play
+ - block:
+ - name: Fetch galaxy roles from roles/requirements.(yml/yaml)
+ ansible.builtin.command:
+ cmd: "ansible-galaxy role install -r {{ req_file }} {{ verbosity }}"
+ register: galaxy_result
+ vars:
+ req_file: "{{ lookup('ansible.builtin.first_found', req_candidates, skip=True) }}"
+ req_candidates:
+ files:
+ - "{{ project_path | quote }}/roles/requirements.yml"
+ - "{{ project_path | quote }}/roles/requirements.yaml"
+ skip: True
+ changed_when: "'was installed successfully' in galaxy_result.stdout"
+ when:
+ - roles_enabled | bool
+ - req_file
+ tags:
+ - install_roles
- when: not roles_enabled|bool and not collections_enabled|bool
- tags:
- - install_roles
- - install_collections
+ - name: Fetch galaxy collections from collections/requirements.(yml/yaml)
+ ansible.builtin.command:
+ cmd: "ansible-galaxy collection install -r {{ req_file }} {{ verbosity }}"
+ register: galaxy_collection_result
+ vars:
+ req_file: "{{ lookup('ansible.builtin.first_found', req_candidates, skip=True) }}"
+ req_candidates:
+ files:
+ - "{{ project_path | quote }}/collections/requirements.yml"
+ - "{{ project_path | quote }}/collections/requirements.yaml"
+ skip: True
+ changed_when: "'Nothing to do.' not in galaxy_collection_result.stdout"
+ when:
+ - "ansible_version.full is version_compare('2.9', '>=')"
+ - collections_enabled | bool
+ - req_file
+ tags:
+ - install_collections
- - name: fetch galaxy roles from requirements.(yml/yaml)
- command: >
- ansible-galaxy role install -r {{ item }}
- --roles-path {{projects_root}}/.__awx_cache/{{local_path}}/stage/requirements_roles
- {{ ' -' + 'v' * ansible_verbosity if ansible_verbosity else '' }}
- args:
- chdir: "{{project_path|quote}}"
- register: galaxy_result
- with_fileglob:
- - "{{project_path|quote}}/roles/requirements.yaml"
- - "{{project_path|quote}}/roles/requirements.yml"
- changed_when: "'was installed successfully' in galaxy_result.stdout"
- environment: "{{ galaxy_task_env }}"
- when: roles_enabled|bool
- tags:
- - install_roles
+ # requirements.yml in project root can be either "old" (roles only) or "new" (collections+roles) format
+ - name: Fetch galaxy roles and collections from requirements.(yml/yaml)
+ ansible.builtin.command:
+ cmd: "ansible-galaxy install -r {{ req_file }} {{ verbosity }}"
+ register: galaxy_combined_result
+ vars:
+ req_file: "{{ lookup('ansible.builtin.first_found', req_candidates, skip=True) }}"
+ req_candidates:
+ files:
+ - "{{ project_path | quote }}/requirements.yaml"
+ - "{{ project_path | quote }}/requirements.yml"
+ skip: True
+ changed_when: "'Nothing to do.' not in galaxy_combined_result.stdout"
+ when:
+ - "ansible_version.full is version_compare('2.10', '>=')"
+ - collections_enabled | bool
+ - roles_enabled | bool
+ - req_file
+ tags:
+ - install_collections
+ - install_roles
+ module_defaults:
+ ansible.builtin.command:
+ chdir: "{{ project_path | quote }}"
- - name: fetch galaxy collections from collections/requirements.(yml/yaml)
- command: >
- ansible-galaxy collection install -r {{ item }}
- --collections-path {{projects_root}}/.__awx_cache/{{local_path}}/stage/requirements_collections
- {{ ' -' + 'v' * ansible_verbosity if ansible_verbosity else '' }}
- args:
- chdir: "{{project_path|quote}}"
- register: galaxy_collection_result
- with_fileglob:
- - "{{project_path|quote}}/collections/requirements.yaml"
- - "{{project_path|quote}}/collections/requirements.yml"
- - "{{project_path|quote}}/requirements.yaml"
- - "{{project_path|quote}}/requirements.yml"
- changed_when: "'Installing ' in galaxy_collection_result.stdout"
- environment: "{{ additional_collections_env | combine(galaxy_task_env) }}"
- when:
- - "ansible_version.full is version_compare('2.9', '>=')"
- - collections_enabled|bool
- tags:
- - install_collections
+ # We combine our additional_galaxy_env into galaxy_task_env so that our values are preferred over anything a user would set
+ environment: "{{ galaxy_task_env | combine(additional_galaxy_env) }}"
+ vars:
+ verbosity: "{{ (ansible_verbosity) | ternary('-'+'v'*ansible_verbosity, '') }}"
diff --git a/awx/resource_api.py b/awx/resource_api.py
new file mode 100644
index 000000000000..10c2eac4ed51
--- /dev/null
+++ b/awx/resource_api.py
@@ -0,0 +1,42 @@
+from ansible_base.resource_registry.registry import ParentResource, ResourceConfig, ServiceAPIConfig, SharedResource
+from ansible_base.rbac.models import RoleDefinition
+
+from ansible_base.resource_registry.shared_types import (
+ FeatureFlagType,
+ RoleDefinitionType,
+ OrganizationType,
+ TeamType,
+ UserType,
+)
+from ansible_base.feature_flags.models import AAPFlag
+from awx.main import models
+
+
+class APIConfig(ServiceAPIConfig):
+ service_type = "awx"
+
+
+RESOURCE_LIST = (
+ ResourceConfig(
+ models.Organization,
+ shared_resource=SharedResource(serializer=OrganizationType, is_provider=False),
+ ),
+ ResourceConfig(
+ models.User,
+ shared_resource=SharedResource(serializer=UserType, is_provider=False),
+ name_field="username",
+ ),
+ ResourceConfig(
+ models.Team,
+ shared_resource=SharedResource(serializer=TeamType, is_provider=False),
+ parent_resources=[ParentResource(model=models.Organization, field_name="organization")],
+ ),
+ ResourceConfig(
+ RoleDefinition,
+ shared_resource=SharedResource(serializer=RoleDefinitionType, is_provider=False),
+ ),
+ ResourceConfig(
+ AAPFlag,
+ shared_resource=SharedResource(serializer=FeatureFlagType, is_provider=False),
+ ),
+)
diff --git a/awx/settings/__init__.py b/awx/settings/__init__.py
index e484e62be15d..2332863d7bb1 100644
--- a/awx/settings/__init__.py
+++ b/awx/settings/__init__.py
@@ -1,2 +1,74 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved.
+import os
+import copy
+from ansible_base.lib.dynamic_config import (
+ factory,
+ export,
+ load_envvars,
+ load_python_file_with_injected_context,
+ load_standard_settings_files,
+)
+from .functions import (
+ assert_production_settings,
+ merge_application_name,
+ add_backwards_compatibility,
+ load_extra_development_files,
+)
+
+add_backwards_compatibility()
+
+# Create a the standard DYNACONF instance which will come with DAB defaults
+# This loads defaults.py and environment specific file e.g: development_defaults.py
+DYNACONF = factory(
+ __name__,
+ "AWX",
+ environments=("development", "production", "quiet", "kube"),
+ settings_files=["defaults.py"],
+)
+
+# Store snapshot before loading any custom config file
+DYNACONF.set(
+ "DEFAULTS_SNAPSHOT",
+ copy.deepcopy(DYNACONF.as_dict(internal=False)),
+ loader_identifier="awx.settings:DEFAULTS_SNAPSHOT",
+)
+
+#############################################################################################
+# Settings loaded before this point will be allowed to be overridden by the database settings
+# Any settings loaded after this point will be marked as as a read_only database setting
+#############################################################################################
+
+# Load extra settings files from the following directories
+# /etc/tower/conf.d/ and /etc/tower/
+# this is the legacy location, kept for backwards compatibility
+settings_dir = os.environ.get('AWX_SETTINGS_DIR', '/etc/tower/conf.d/')
+settings_files_path = os.path.join(settings_dir, '*.py')
+settings_file_path = os.environ.get('AWX_SETTINGS_FILE', '/etc/tower/settings.py')
+load_python_file_with_injected_context(settings_files_path, settings=DYNACONF)
+load_python_file_with_injected_context(settings_file_path, settings=DYNACONF)
+
+# Load extra settings files from the following directories
+# /etc/ansible-automation-platform/{settings,flags,.secrets}.yaml
+# and /etc/ansible-automation-platform/awx/{settings,flags,.secrets}.yaml
+# this is the new standard location for all services
+load_standard_settings_files(DYNACONF)
+
+# Load optional development only settings files
+load_extra_development_files(DYNACONF)
+
+# Check at least one setting file has been loaded in production mode
+assert_production_settings(DYNACONF, settings_dir, settings_file_path)
+
+# Load envvars at the end to allow them to override everything loaded so far
+load_envvars(DYNACONF)
+
+# This must run after all custom settings are loaded
+DYNACONF.update(
+ merge_application_name(DYNACONF),
+ loader_identifier="awx.settings:merge_application_name",
+ merge=True,
+)
+
+# Update django.conf.settings with DYNACONF values
+export(__name__, DYNACONF)
diff --git a/awx/settings/application_name.py b/awx/settings/application_name.py
new file mode 100644
index 000000000000..ac7e40553e00
--- /dev/null
+++ b/awx/settings/application_name.py
@@ -0,0 +1,36 @@
+import os
+import sys
+
+
+def get_service_name(argv):
+ '''
+ Return best-effort guess as to the name of this service
+ '''
+ for arg in argv:
+ if arg == '-m':
+ continue
+ if 'python' in arg:
+ continue
+ if 'manage' in arg:
+ continue
+ if arg.startswith('run_'):
+ return arg[len('run_') :]
+ return arg
+
+
+def get_application_name(CLUSTER_HOST_ID, function=''):
+ if function:
+ function = f'_{function}'
+ return f'awx-{os.getpid()}-{get_service_name(sys.argv)}{function}-{CLUSTER_HOST_ID}'[:63]
+
+
+def set_application_name(DATABASES, CLUSTER_HOST_ID, function=''):
+ """In place modification of DATABASES to set the application name for the connection."""
+ # If settings files were not properly passed DATABASES could be {} at which point we don't need to set the app name.
+ if not DATABASES or 'default' not in DATABASES:
+ return
+
+ if 'sqlite3' in DATABASES['default']['ENGINE']:
+ return
+ options_dict = DATABASES['default'].setdefault('OPTIONS', dict())
+ options_dict['application_name'] = get_application_name(CLUSTER_HOST_ID, function)
diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py
index 4d18540bcda6..f52e2ea13353 100644
--- a/awx/settings/defaults.py
+++ b/awx/settings/defaults.py
@@ -1,23 +1,12 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved.
+# Python
import base64
import os
import re # noqa
-import sys
import tempfile
import socket
-from datetime import timedelta
-
-
-if "pytest" in sys.modules:
- from unittest import mock
-
- with mock.patch('__main__.__builtins__.dir', return_value=[]):
- import ldap
-else:
- import ldap
-
DEBUG = True
SQL_DEBUG = DEBUG
@@ -42,6 +31,18 @@
}
}
+# Special database overrides for dispatcher connections listening to pg_notify
+LISTENER_DATABASES = {
+ 'default': {
+ 'OPTIONS': {
+ 'keepalives': 1,
+ 'keepalives_idle': 5,
+ 'keepalives_interval': 5,
+ 'keepalives_count': 5,
+ },
+ }
+}
+
# Whether or not the deployment is a K8S-based deployment
# In K8S-based deployments, instances have zero capacity - all playbook
# automation is intended to flow through defined Container Groups that
@@ -51,6 +52,7 @@
AWX_CONTAINER_GROUP_K8S_API_TIMEOUT = 10
AWX_CONTAINER_GROUP_DEFAULT_NAMESPACE = os.getenv('MY_POD_NAMESPACE', 'default')
+AWX_CONTAINER_GROUP_DEFAULT_JOB_LABEL = os.getenv('AWX_CONTAINER_GROUP_DEFAULT_JOB_LABEL', 'ansible_job')
# Timeout when waiting for pod to enter running state. If the pod is still in pending state , it will be terminated. Valid time units are "s", "m", "h". Example : "5m" , "10s".
AWX_CONTAINER_GROUP_POD_PENDING_TIMEOUT = "2h"
@@ -77,13 +79,12 @@
# to load the internationalization machinery.
USE_I18N = True
-# If you set this to False, Django will not format dates, numbers and
-# calendars according to the current locale
-USE_L10N = True
-
USE_TZ = True
-STATICFILES_DIRS = [os.path.join(BASE_DIR, 'ui', 'build', 'static'), os.path.join(BASE_DIR, 'static')]
+STATICFILES_DIRS = [
+ os.path.join(BASE_DIR, 'ui', 'build'),
+ os.path.join(BASE_DIR, 'static'),
+]
# Absolute filesystem path to the directory where static file are collected via
# the collectstatic command.
@@ -103,6 +104,7 @@
MEDIA_URL = '/media/'
LOGIN_URL = '/api/login/'
+LOGOUT_ALLOWED_HOSTS = None
# Absolute filesystem path to the directory to host projects (with playbooks).
# This directory should not be web-accessible.
@@ -116,9 +118,6 @@
# Absolute filesystem path to the directory to store logs
LOG_ROOT = '/var/log/tower/'
-# The heartbeat file for the scheduler
-SCHEDULE_METADATA_LOCATION = os.path.join(BASE_DIR, '.tower_cycle')
-
# Django gettext files path: locale//LC_MESSAGES/django.po, django.mo
LOCALE_PATHS = (os.path.join(BASE_DIR, 'locale'),)
@@ -129,6 +128,16 @@
# Note: This setting may be overridden by database settings.
SCHEDULE_MAX_JOBS = 10
+# Bulk API related settings
+# Maximum number of jobs that can be launched in 1 bulk job
+BULK_JOB_MAX_LAUNCH = 100
+
+# Maximum number of host that can be created in 1 bulk host create
+BULK_HOST_MAX_CREATE = 100
+
+# Maximum number of host that can be deleted in 1 bulk host delete
+BULK_HOST_MAX_DELETE = 250
+
SITE_ID = 1
# Make this unique, and don't share it with anybody.
@@ -156,6 +165,11 @@
# REMOTE_HOST_HEADERS will be trusted unconditionally')
PROXY_IP_ALLOWED_LIST = []
+# If we are behind a reverse proxy/load balancer, use this setting to
+# allow the scheme://addresses from which Tower should trust csrf requests from
+# If this setting is an empty list (the default), we will only trust ourself
+CSRF_TRUSTED_ORIGINS = []
+
CUSTOM_VENV_PATHS = []
# Warning: this is a placeholder for a database setting
@@ -203,7 +217,7 @@
# The number of seconds to buffer callback receiver bulk
# writes in memory before flushing via JobEvent.objects.bulk_create()
-JOB_EVENT_BUFFER_SECONDS = 0.1
+JOB_EVENT_BUFFER_SECONDS = 1
# The interval at which callback receiver statistics should be
# recorded
@@ -215,6 +229,9 @@
# The number of job events to migrate per-transaction when moving from int -> bigint
JOB_EVENT_MIGRATION_CHUNK_SIZE = 1000000
+# The prefix of the redis key that stores metrics
+SUBSYSTEM_METRICS_REDIS_KEY_PREFIX = "awx_metrics"
+
# Histogram buckets for the callback_receiver_batch_events_insert_db metric
SUBSYSTEM_METRICS_BATCH_INSERT_BUCKETS = [10, 50, 150, 350, 650, 2000]
@@ -235,6 +252,7 @@
# We have the grace period so the task manager can bail out before the timeout.
TASK_MANAGER_TIMEOUT = 300
TASK_MANAGER_TIMEOUT_GRACE_PERIOD = 60
+TASK_MANAGER_LOCK_TIMEOUT = TASK_MANAGER_TIMEOUT + TASK_MANAGER_TIMEOUT_GRACE_PERIOD
# Number of seconds _in addition to_ the task manager timeout a job can stay
# in waiting without being reaped
@@ -251,6 +269,9 @@
# Note: This setting may be overridden by database settings.
SESSION_COOKIE_AGE = 1800
+# Option to change userLoggedIn cookie SameSite policy.
+USER_COOKIE_SAMESITE = 'Lax'
+
# Name of the cookie that contains the session information.
# Note: Changing this value may require changes to any clients.
SESSION_COOKIE_NAME = 'awx_sessionid'
@@ -283,14 +304,21 @@
'django.template.context_processors.static',
'django.template.context_processors.tz',
'django.contrib.messages.context_processors.messages',
+ 'django.template.context_processors.request',
'awx.ui.context_processors.csp',
'awx.ui.context_processors.version',
- 'social_django.context_processors.backends',
- 'social_django.context_processors.login_redirect',
],
'builtins': ['awx.main.templatetags.swagger'],
+ 'libraries': {
+ "ansible_base.lib.templatetags.requests": "ansible_base.lib.templatetags.requests",
+ "ansible_base.lib.templatetags.util": "ansible_base.lib.templatetags.util",
+ },
},
- 'DIRS': [os.path.join(BASE_DIR, 'templates'), os.path.join(BASE_DIR, 'ui', 'build'), os.path.join(BASE_DIR, 'ui', 'public')],
+ 'DIRS': [
+ os.path.join(BASE_DIR, 'templates'),
+ os.path.join(BASE_DIR, 'ui', 'public'),
+ os.path.join(BASE_DIR, 'ui', 'build', 'awx'),
+ ],
},
]
@@ -308,22 +336,26 @@
# According to channels 4.0 docs you install daphne instead of channels now
'daphne',
'django.contrib.staticfiles',
- 'oauth2_provider',
'rest_framework',
'django_extensions',
'polymorphic',
- 'taggit',
- 'social_django',
'django_guid',
'corsheaders',
'awx.conf',
'awx.main',
'awx.api',
'awx.ui',
- 'awx.sso',
'solo',
+ 'ansible_base.rest_filters',
+ 'ansible_base.jwt_consumer',
+ 'ansible_base.resource_registry',
+ 'ansible_base.rbac',
+ 'ansible_base.feature_flags',
+ 'ansible_base.api_documentation',
+ 'flags',
]
+
INTERNAL_IPS = ('127.0.0.1',)
MAX_PAGE_SIZE = 200
@@ -331,17 +363,11 @@
'DEFAULT_PAGINATION_CLASS': 'awx.api.pagination.Pagination',
'PAGE_SIZE': 25,
'DEFAULT_AUTHENTICATION_CLASSES': (
- 'awx.api.authentication.LoggedOAuth2Authentication',
+ 'ansible_base.jwt_consumer.awx.auth.AwxJWTAuthentication',
'awx.api.authentication.SessionAuthentication',
'awx.api.authentication.LoggedBasicAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': ('awx.api.permissions.ModelAccessPermission',),
- 'DEFAULT_FILTER_BACKENDS': (
- 'awx.api.filters.TypeFilterBackend',
- 'awx.api.filters.FieldLookupBackend',
- 'rest_framework.filters.SearchFilter',
- 'awx.api.filters.OrderByBackend',
- ),
'DEFAULT_PARSER_CLASSES': ('awx.api.parsers.JSONParser',),
'DEFAULT_RENDERER_CLASSES': ('awx.api.renderers.DefaultJSONRenderer', 'awx.api.renderers.BrowsableAPIRenderer'),
'DEFAULT_METADATA_CLASS': 'awx.api.metadata.Metadata',
@@ -349,65 +375,15 @@
'VIEW_DESCRIPTION_FUNCTION': 'awx.api.generics.get_view_description',
'NON_FIELD_ERRORS_KEY': '__all__',
'DEFAULT_VERSION': 'v2',
- # For swagger schema generation
+ # For OpenAPI schema generation with drf-spectacular
# see https://github.com/encode/django-rest-framework/pull/6532
- 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema',
+ 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
# 'URL_FORMAT_OVERRIDE': None,
}
-AUTHENTICATION_BACKENDS = (
- 'awx.sso.backends.LDAPBackend',
- 'awx.sso.backends.LDAPBackend1',
- 'awx.sso.backends.LDAPBackend2',
- 'awx.sso.backends.LDAPBackend3',
- 'awx.sso.backends.LDAPBackend4',
- 'awx.sso.backends.LDAPBackend5',
- 'awx.sso.backends.RADIUSBackend',
- 'awx.sso.backends.TACACSPlusBackend',
- 'social_core.backends.google.GoogleOAuth2',
- 'social_core.backends.github.GithubOAuth2',
- 'social_core.backends.github.GithubOrganizationOAuth2',
- 'social_core.backends.github.GithubTeamOAuth2',
- 'social_core.backends.github_enterprise.GithubEnterpriseOAuth2',
- 'social_core.backends.github_enterprise.GithubEnterpriseOrganizationOAuth2',
- 'social_core.backends.github_enterprise.GithubEnterpriseTeamOAuth2',
- 'social_core.backends.open_id_connect.OpenIdConnectAuth',
- 'social_core.backends.azuread.AzureADOAuth2',
- 'awx.sso.backends.SAMLAuth',
- 'awx.main.backends.AWXModelBackend',
-)
-
-
-# Django OAuth Toolkit settings
-OAUTH2_PROVIDER_APPLICATION_MODEL = 'main.OAuth2Application'
-OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL = 'main.OAuth2AccessToken'
-OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL = 'oauth2_provider.RefreshToken'
-
-OAUTH2_PROVIDER = {'ACCESS_TOKEN_EXPIRE_SECONDS': 31536000000, 'AUTHORIZATION_CODE_EXPIRE_SECONDS': 600, 'REFRESH_TOKEN_EXPIRE_SECONDS': 2628000}
-ALLOW_OAUTH2_FOR_EXTERNAL_USERS = False
-
-# LDAP server (default to None to skip using LDAP authentication).
-# Note: This setting may be overridden by database settings.
-AUTH_LDAP_SERVER_URI = None
-
-# Disable LDAP referrals by default (to prevent certain LDAP queries from
-# hanging with AD).
-# Note: This setting may be overridden by database settings.
-AUTH_LDAP_CONNECTION_OPTIONS = {ldap.OPT_REFERRALS: 0, ldap.OPT_NETWORK_TIMEOUT: 30}
+# SWAGGER_SETTINGS removed - migrated to drf-spectacular (see SPECTACULAR_SETTINGS below)
-# Radius server settings (default to empty string to skip using Radius auth).
-# Note: These settings may be overridden by database settings.
-RADIUS_SERVER = ''
-RADIUS_PORT = 1812
-RADIUS_SECRET = ''
-
-# TACACS+ settings (default host to empty string to skip using TACACS+ auth).
-# Note: These settings may be overridden by database settings.
-TACACSPLUS_HOST = ''
-TACACSPLUS_PORT = 49
-TACACSPLUS_SECRET = ''
-TACACSPLUS_SESSION_TIMEOUT = 5
-TACACSPLUS_AUTH_PROTOCOL = 'ascii'
+AUTHENTICATION_BACKENDS = ('awx.main.backends.AWXModelBackend',)
# Enable / Disable HTTP Basic Authentication used in the API browser
# Note: Session limits are not enforced when using HTTP Basic Authentication.
@@ -437,113 +413,46 @@
EXECUTION_NODE_REMEDIATION_CHECKS = 60 * 30 # once every 30 minutes check if an execution node errors have been resolved
# Amount of time dispatcher will try to reconnect to database for jobs and consuming new work
-DISPATCHER_DB_DOWNTOWN_TOLLERANCE = 40
+DISPATCHER_DB_DOWNTIME_TOLERANCE = 40
BROKER_URL = 'unix:///var/run/redis/redis.sock'
-CELERYBEAT_SCHEDULE = {
- 'tower_scheduler': {'task': 'awx.main.tasks.system.awx_periodic_scheduler', 'schedule': timedelta(seconds=30), 'options': {'expires': 20}},
- 'cluster_heartbeat': {
+REDIS_RETRY_COUNT = 3 # Number of retries for Redis connection errors
+REDIS_BACKOFF_CAP = 1.0 # Maximum backoff delay in seconds for Redis retries
+REDIS_BACKOFF_BASE = 0.5 # Base for exponential backoff calculation for Redis retries
+
+DISPATCHER_SCHEDULE = {
+ 'awx.main.tasks.system.awx_periodic_scheduler': {'task': 'awx.main.tasks.system.awx_periodic_scheduler', 'schedule': 30, 'options': {'expires': 20}},
+ 'awx.main.tasks.system.cluster_node_heartbeat': {
'task': 'awx.main.tasks.system.cluster_node_heartbeat',
- 'schedule': timedelta(seconds=CLUSTER_NODE_HEARTBEAT_PERIOD),
+ 'schedule': CLUSTER_NODE_HEARTBEAT_PERIOD,
'options': {'expires': 50},
},
- 'gather_analytics': {'task': 'awx.main.tasks.system.gather_analytics', 'schedule': timedelta(minutes=5)},
- 'task_manager': {'task': 'awx.main.scheduler.tasks.task_manager', 'schedule': timedelta(seconds=20), 'options': {'expires': 20}},
- 'dependency_manager': {'task': 'awx.main.scheduler.tasks.dependency_manager', 'schedule': timedelta(seconds=20), 'options': {'expires': 20}},
- 'k8s_reaper': {'task': 'awx.main.tasks.system.awx_k8s_reaper', 'schedule': timedelta(seconds=60), 'options': {'expires': 50}},
- 'receptor_reaper': {'task': 'awx.main.tasks.system.awx_receptor_workunit_reaper', 'schedule': timedelta(seconds=60)},
- 'send_subsystem_metrics': {'task': 'awx.main.analytics.analytics_tasks.send_subsystem_metrics', 'schedule': timedelta(seconds=20)},
- 'cleanup_images': {'task': 'awx.main.tasks.system.cleanup_images_and_files', 'schedule': timedelta(hours=3)},
+ 'awx.main.tasks.system.gather_analytics': {'task': 'awx.main.tasks.system.gather_analytics', 'schedule': 300},
+ 'awx.main.scheduler.tasks.task_manager': {'task': 'awx.main.scheduler.tasks.task_manager', 'schedule': 20, 'options': {'expires': 20}},
+ 'awx.main.scheduler.tasks.dependency_manager': {'task': 'awx.main.scheduler.tasks.dependency_manager', 'schedule': 20, 'options': {'expires': 20}},
+ 'awx.main.tasks.system.awx_k8s_reaper': {'task': 'awx.main.tasks.system.awx_k8s_reaper', 'schedule': 60, 'options': {'expires': 50}},
+ 'awx.main.tasks.system.awx_receptor_workunit_reaper': {'task': 'awx.main.tasks.system.awx_receptor_workunit_reaper', 'schedule': 60},
+ 'awx.main.analytics.analytics_tasks.send_subsystem_metrics': {'task': 'awx.main.analytics.analytics_tasks.send_subsystem_metrics', 'schedule': 20},
+ 'awx.main.tasks.system.cleanup_images_and_files': {'task': 'awx.main.tasks.system.cleanup_images_and_files', 'schedule': 10800},
+ 'awx.main.tasks.host_metrics.cleanup_host_metrics': {'task': 'awx.main.tasks.host_metrics.cleanup_host_metrics', 'schedule': 12600},
+ 'awx.main.tasks.host_metrics.host_metric_summary_monthly': {'task': 'awx.main.tasks.host_metrics.host_metric_summary_monthly', 'schedule': 14400},
+ 'awx.main.tasks.system.periodic_resource_sync': {'task': 'awx.main.tasks.system.periodic_resource_sync', 'schedule': 900},
+ 'awx.main.tasks.host_indirect.cleanup_and_save_indirect_host_entries_fallback': {
+ 'task': 'awx.main.tasks.host_indirect.cleanup_and_save_indirect_host_entries_fallback',
+ 'schedule': 3600,
+ },
}
# Django Caching Configuration
DJANGO_REDIS_IGNORE_EXCEPTIONS = True
-CACHES = {'default': {'BACKEND': 'django_redis.cache.RedisCache', 'LOCATION': 'unix:/var/run/redis/redis.sock?db=1'}}
-
-# Social Auth configuration.
-SOCIAL_AUTH_STRATEGY = 'social_django.strategy.DjangoStrategy'
-SOCIAL_AUTH_STORAGE = 'social_django.models.DjangoStorage'
-SOCIAL_AUTH_USER_MODEL = 'auth.User'
-
-_SOCIAL_AUTH_PIPELINE_BASE = (
- 'social_core.pipeline.social_auth.social_details',
- 'social_core.pipeline.social_auth.social_uid',
- 'social_core.pipeline.social_auth.auth_allowed',
- 'social_core.pipeline.social_auth.social_user',
- 'social_core.pipeline.user.get_username',
- 'social_core.pipeline.social_auth.associate_by_email',
- 'social_core.pipeline.user.create_user',
- 'awx.sso.social_base_pipeline.check_user_found_or_created',
- 'social_core.pipeline.social_auth.associate_user',
- 'social_core.pipeline.social_auth.load_extra_data',
- 'awx.sso.social_base_pipeline.set_is_active_for_new_user',
- 'social_core.pipeline.user.user_details',
- 'awx.sso.social_base_pipeline.prevent_inactive_login',
-)
-SOCIAL_AUTH_PIPELINE = _SOCIAL_AUTH_PIPELINE_BASE + ('awx.sso.social_pipeline.update_user_orgs', 'awx.sso.social_pipeline.update_user_teams')
-SOCIAL_AUTH_SAML_PIPELINE = _SOCIAL_AUTH_PIPELINE_BASE + ('awx.sso.saml_pipeline.populate_user', 'awx.sso.saml_pipeline.update_user_flags')
-SAML_AUTO_CREATE_OBJECTS = True
-
-SOCIAL_AUTH_LOGIN_URL = '/'
-SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/sso/complete/'
-SOCIAL_AUTH_LOGIN_ERROR_URL = '/sso/error/'
-SOCIAL_AUTH_INACTIVE_USER_URL = '/sso/inactive/'
-
-SOCIAL_AUTH_RAISE_EXCEPTIONS = False
-SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL = False
-# SOCIAL_AUTH_SLUGIFY_USERNAMES = True
-SOCIAL_AUTH_CLEAN_USERNAMES = True
-
-SOCIAL_AUTH_SANITIZE_REDIRECTS = True
-SOCIAL_AUTH_REDIRECT_IS_HTTPS = False
+CACHES = {'default': {'BACKEND': 'awx.main.cache.AWXRedisCache', 'LOCATION': 'unix:///var/run/redis/redis.sock?db=1'}}
-# Note: These settings may be overridden by database settings.
-SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = ''
-SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = ''
-SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = ['profile']
-
-SOCIAL_AUTH_GITHUB_KEY = ''
-SOCIAL_AUTH_GITHUB_SECRET = ''
-SOCIAL_AUTH_GITHUB_SCOPE = ['user:email', 'read:org']
-
-SOCIAL_AUTH_GITHUB_ORG_KEY = ''
-SOCIAL_AUTH_GITHUB_ORG_SECRET = ''
-SOCIAL_AUTH_GITHUB_ORG_NAME = ''
-SOCIAL_AUTH_GITHUB_ORG_SCOPE = ['user:email', 'read:org']
-
-SOCIAL_AUTH_GITHUB_TEAM_KEY = ''
-SOCIAL_AUTH_GITHUB_TEAM_SECRET = ''
-SOCIAL_AUTH_GITHUB_TEAM_ID = ''
-SOCIAL_AUTH_GITHUB_TEAM_SCOPE = ['user:email', 'read:org']
-
-SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY = ''
-SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET = ''
-SOCIAL_AUTH_GITHUB_ENTERPRISE_SCOPE = ['user:email', 'read:org']
-
-SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY = ''
-SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_SECRET = ''
-SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME = ''
-SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_SCOPE = ['user:email', 'read:org']
-
-SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY = ''
-SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_SECRET = ''
-SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID = ''
-SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_SCOPE = ['user:email', 'read:org']
-
-SOCIAL_AUTH_AZUREAD_OAUTH2_KEY = ''
-SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET = ''
-
-SOCIAL_AUTH_SAML_SP_ENTITY_ID = ''
-SOCIAL_AUTH_SAML_SP_PUBLIC_CERT = ''
-SOCIAL_AUTH_SAML_SP_PRIVATE_KEY = ''
-SOCIAL_AUTH_SAML_ORG_INFO = {}
-SOCIAL_AUTH_SAML_TECHNICAL_CONTACT = {}
-SOCIAL_AUTH_SAML_SUPPORT_CONTACT = {}
-SOCIAL_AUTH_SAML_ENABLED_IDPS = {}
-
-SOCIAL_AUTH_SAML_ORGANIZATION_ATTR = {}
-SOCIAL_AUTH_SAML_TEAM_ATTR = {}
-SOCIAL_AUTH_SAML_USER_FLAGS_BY_ATTR = {}
+ROLE_SINGLETON_USER_RELATIONSHIP = ''
+ROLE_SINGLETON_TEAM_RELATIONSHIP = ''
+
+# We want to short-circuit RBAC methods to get permission to system admins and auditors
+ROLE_BYPASS_SUPERUSER_FLAGS = ['is_superuser']
+ROLE_BYPASS_ACTION_FLAGS = {'view': 'is_system_auditor'}
# Any ANSIBLE_* settings will be passed to the task runner subprocess
# environment
@@ -612,6 +521,10 @@
# Automatically remove nodes that have missed their heartbeats after some time
AWX_AUTO_DEPROVISION_INSTANCES = False
+
+# If True, allow users to be assigned to roles that were created via JWT
+ALLOW_LOCAL_ASSIGNING_JWT_ROLES = True
+
# Enable Pendo on the UI, possible values are 'off', 'anonymous', and 'detailed'
# Note: This setting may be overridden by database settings.
PENDO_TRACKING_STATE = "off"
@@ -669,6 +582,12 @@
VMWARE_VALIDATE_CERTS = False
+# -----------------
+# -- VMware ESXi --
+# -----------------
+# TODO: Verify matches with AAP-53978 solution in awx-plugins
+VMWARE_ESXI_EXCLUDE_EMPTY_GROUPS = True
+
# ---------------------------
# -- Google Compute Engine --
# ---------------------------
@@ -712,10 +631,10 @@
# ---------------------
# ----- Foreman -----
# ---------------------
-SATELLITE6_ENABLED_VAR = 'foreman_enabled'
+SATELLITE6_ENABLED_VAR = 'foreman_enabled,foreman.enabled'
SATELLITE6_ENABLED_VALUE = 'True'
SATELLITE6_EXCLUDE_EMPTY_GROUPS = True
-SATELLITE6_INSTANCE_ID_VAR = 'foreman_id'
+SATELLITE6_INSTANCE_ID_VAR = 'foreman_id,foreman.id'
# SATELLITE6_GROUP_PREFIX and SATELLITE6_GROUP_PATTERNS defined in source vars
# ----------------
@@ -726,6 +645,19 @@
INSIGHTS_INSTANCE_ID_VAR = 'insights_id'
INSIGHTS_EXCLUDE_EMPTY_GROUPS = False
+# ----------------
+# -- Terraform State --
+# ----------------
+# TERRAFORM_ENABLED_VAR =
+# TERRAFORM_ENABLED_VALUE =
+TERRAFORM_INSTANCE_ID_VAR = 'id'
+TERRAFORM_EXCLUDE_EMPTY_GROUPS = True
+
+# ------------------------
+# OpenShift Virtualization
+# ------------------------
+OPENSHIFT_VIRTUALIZATION_EXCLUDE_EMPTY_GROUPS = True
+
# ---------------------
# ----- Custom -----
# ---------------------
@@ -742,6 +674,13 @@
SCM_EXCLUDE_EMPTY_GROUPS = False
# SCM_INSTANCE_ID_VAR =
+# ----------------
+# -- Constructed --
+# ----------------
+CONSTRUCTED_INSTANCE_ID_VAR = 'remote_tower_id'
+
+CONSTRUCTED_EXCLUDE_EMPTY_GROUPS = False
+
# ---------------------
# -- Activity Stream --
# ---------------------
@@ -758,31 +697,26 @@
DISABLE_LOCAL_AUTH = False
# Note: This setting may be overridden by database settings.
-TOWER_URL_BASE = "https://towerhost"
+TOWER_URL_BASE = "https://platformhost"
INSIGHTS_URL_BASE = "https://example.org"
INSIGHTS_AGENT_MIME = 'application/example'
# See https://github.com/ansible/awx-facts-playbooks
INSIGHTS_SYSTEM_ID_FILE = '/etc/redhat-access-insights/machine-id'
-
-TOWER_SETTINGS_MANIFEST = {}
+INSIGHTS_CERT_PATH = "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"
# Settings related to external logger configuration
LOG_AGGREGATOR_ENABLED = False
LOG_AGGREGATOR_TCP_TIMEOUT = 5
LOG_AGGREGATOR_VERIFY_CERT = True
LOG_AGGREGATOR_LEVEL = 'INFO'
-LOG_AGGREGATOR_MAX_DISK_USAGE_GB = 1
+LOG_AGGREGATOR_ACTION_QUEUE_SIZE = 131072
+LOG_AGGREGATOR_ACTION_MAX_DISK_USAGE_GB = 1 # Action queue
LOG_AGGREGATOR_MAX_DISK_USAGE_PATH = '/var/lib/awx'
LOG_AGGREGATOR_RSYSLOGD_DEBUG = False
LOG_AGGREGATOR_RSYSLOGD_ERROR_LOG_FILE = '/var/log/tower/rsyslog.err'
API_400_ERROR_LOG_FORMAT = 'status {status_code} received by user {user_name} attempting to access {url_path} from {remote_addr}'
-# The number of retry attempts for websocket session establishment
-# If you're encountering issues establishing websockets in a cluster,
-# raising this value can help
-CHANNEL_LAYER_RECEIVE_MAX_RETRY = 10
-
ASGI_APPLICATION = "awx.main.routing.application"
CHANNEL_LAYERS = {
@@ -806,7 +740,6 @@
'json': {'()': 'awx.main.utils.formatters.LogstashFormatter'},
'timed_import': {'()': 'awx.main.utils.formatters.TimeFormatter', 'format': '%(relativeSeconds)9.3f %(levelname)-8s %(message)s'},
'dispatcher': {'format': '%(asctime)s %(levelname)-8s [%(guid)s] %(name)s PID:%(process)d %(message)s'},
- 'job_lifecycle': {'()': 'awx.main.utils.formatters.JobLifeCycleFormatter'},
},
# Extended below based on install scenario. You probably don't want to add something directly here.
# See 'handler_config' below.
@@ -827,10 +760,12 @@
'address': '/var/run/awx-rsyslog/rsyslog.sock',
'filters': ['external_log_enabled', 'dynamic_level_filter', 'guid'],
},
+ 'otel': {'class': 'logging.NullHandler'},
},
'loggers': {
'django': {'handlers': ['console']},
'django.request': {'handlers': ['console', 'file', 'tower_warnings'], 'level': 'WARNING'},
+ 'ansible_base': {'handlers': ['console', 'file', 'tower_warnings']},
'daphne': {'handlers': ['console', 'file', 'tower_warnings'], 'level': 'INFO'},
'rest_framework.request': {'handlers': ['console', 'file', 'tower_warnings'], 'level': 'WARNING', 'propagate': False},
'py.warnings': {'handlers': ['console']},
@@ -838,25 +773,28 @@
'awx.conf': {'handlers': ['null'], 'level': 'WARNING'},
'awx.conf.settings': {'handlers': ['null'], 'level': 'WARNING'},
'awx.main': {'handlers': ['null']},
- 'awx.main.commands.run_callback_receiver': {'handlers': ['callback_receiver']}, # level handled by dynamic_level_filter
+ 'awx.main.commands.run_callback_receiver': {'handlers': ['callback_receiver'], 'level': 'INFO'}, # very noisey debug-level logs
'awx.main.dispatch': {'handlers': ['dispatcher']},
'awx.main.consumers': {'handlers': ['console', 'file', 'tower_warnings'], 'level': 'INFO'},
- 'awx.main.wsbroadcast': {'handlers': ['wsbroadcast']},
+ 'awx.main.rsyslog_configurer': {'handlers': ['rsyslog_configurer']},
+ 'awx.main.cache_clear': {'handlers': ['cache_clear']},
+ 'awx.main.ws_heartbeat': {'handlers': ['ws_heartbeat']},
+ 'awx.main.wsrelay': {'handlers': ['wsrelay']},
'awx.main.commands.inventory_import': {'handlers': ['inventory_import'], 'propagate': False},
- 'awx.main.tasks': {'handlers': ['task_system', 'external_logger'], 'propagate': False},
- 'awx.main.analytics': {'handlers': ['task_system', 'external_logger'], 'level': 'INFO', 'propagate': False},
- 'awx.main.scheduler': {'handlers': ['task_system', 'external_logger'], 'propagate': False},
+ 'awx.main.tasks': {'handlers': ['task_system', 'external_logger', 'console'], 'propagate': False},
+ 'awx.main.analytics': {'handlers': ['task_system', 'external_logger', 'console'], 'level': 'INFO', 'propagate': False},
+ 'awx.main.scheduler': {'handlers': ['task_system', 'external_logger', 'console'], 'propagate': False},
'awx.main.access': {'level': 'INFO'}, # very verbose debug-level logs
'awx.main.signals': {'level': 'INFO'}, # very verbose debug-level logs
'awx.api.permissions': {'level': 'INFO'}, # very verbose debug-level logs
'awx.analytics': {'handlers': ['external_logger'], 'level': 'INFO', 'propagate': False},
- 'awx.analytics.broadcast_websocket': {'handlers': ['console', 'file', 'wsbroadcast', 'external_logger'], 'level': 'INFO', 'propagate': False},
+ 'awx.analytics.broadcast_websocket': {'handlers': ['console', 'file', 'wsrelay', 'external_logger'], 'level': 'INFO', 'propagate': False},
'awx.analytics.performance': {'handlers': ['console', 'file', 'tower_warnings', 'external_logger'], 'level': 'DEBUG', 'propagate': False},
- 'awx.analytics.job_lifecycle': {'handlers': ['console', 'job_lifecycle'], 'level': 'DEBUG', 'propagate': False},
- 'django_auth_ldap': {'handlers': ['console', 'file', 'tower_warnings'], 'level': 'DEBUG'},
+ 'awx.analytics.job_lifecycle': {'handlers': ['console', 'job_lifecycle', 'external_logger'], 'level': 'DEBUG', 'propagate': False},
'social': {'handlers': ['console', 'file', 'tower_warnings'], 'level': 'DEBUG'},
'system_tracking_migrations': {'handlers': ['console', 'file', 'tower_warnings'], 'level': 'DEBUG'},
'rbac_migrations': {'handlers': ['console', 'file', 'tower_warnings'], 'level': 'DEBUG'},
+ 'dispatcherd': {'handlers': ['dispatcher', 'console'], 'level': 'INFO'},
},
}
@@ -868,10 +806,13 @@
'tower_warnings': {'filename': 'tower.log'},
'callback_receiver': {'filename': 'callback_receiver.log'},
'dispatcher': {'filename': 'dispatcher.log', 'formatter': 'dispatcher'},
- 'wsbroadcast': {'filename': 'wsbroadcast.log'},
+ 'wsrelay': {'filename': 'wsrelay.log'},
'task_system': {'filename': 'task_system.log'},
'rbac_migrations': {'filename': 'tower_rbac_migrations.log'},
- 'job_lifecycle': {'filename': 'job_lifecycle.log', 'formatter': 'job_lifecycle'},
+ 'job_lifecycle': {'filename': 'job_lifecycle.log'},
+ 'rsyslog_configurer': {'filename': 'rsyslog_configurer.log'},
+ 'cache_clear': {'filename': 'cache_clear.log'},
+ 'ws_heartbeat': {'filename': 'ws_heartbeat.log'},
}
# If running on a VM, we log to files. When running in a container, we log to stdout.
@@ -926,14 +867,25 @@
# Allow ansible-runner to store env folder (may contain sensitive information)
AWX_RUNNER_OMIT_ENV_FILES = True
-# Allow ansible-runner to save ansible output (may cause performance issues)
+# Allow ansible-runner to save ansible output
+# (changing to False may cause performance issues)
AWX_RUNNER_SUPPRESS_OUTPUT_FILE = True
+# https://github.com/ansible/ansible-runner/pull/1191/files
+# Interval in seconds between the last message and keep-alive messages that
+# ansible-runner will send
+AWX_RUNNER_KEEPALIVE_SECONDS = 0
+
# Delete completed work units in receptor
RECEPTOR_RELEASE_WORK = True
+RECEPTOR_KEEP_WORK_ON_ERROR = False
+
+# K8S only. Use receptor_log_level on AWX spec to set this properly
+RECEPTOR_LOG_LEVEL = 'info'
MIDDLEWARE = [
'django_guid.middleware.guid_middleware',
+ 'ansible_base.lib.middleware.logging.log_request.LogTracebackMiddleware',
'awx.main.middleware.SettingsCacheMiddleware',
'awx.main.middleware.TimingMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
@@ -945,7 +897,7 @@
'django.contrib.auth.middleware.AuthenticationMiddleware',
'awx.main.middleware.DisableLocalAuthMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
- 'awx.sso.middleware.SocialAuthMiddleware',
+ 'awx.main.middleware.OptionalURLPrefixPath',
'crum.CurrentRequestUserMiddleware',
'awx.main.middleware.URLModificationMiddleware',
'awx.main.middleware.SessionTimeoutMiddleware',
@@ -977,6 +929,9 @@
# How often websocket process will generate stats
BROADCAST_WEBSOCKET_STATS_POLL_RATE_SECONDS = 5
+# How often should web instances advertise themselves?
+BROADCAST_WEBSOCKET_BEACON_FROM_WEB_RATE_SECONDS = 15
+
DJANGO_GUID = {'GUID_HEADER_NAME': 'X-API-Request-Id'}
# Name of the default task queue
@@ -1003,3 +958,184 @@
# This is overridden downstream via /etc/tower/conf.d/cluster_host_id.py
CLUSTER_HOST_ID = socket.gethostname()
+
+# License compliance for total host count. Possible values:
+# - '': No model - Subscription not counted from Host Metrics
+# - 'unique_managed_hosts': Compliant = automated - deleted hosts (using /api/v2/host_metrics/)
+SUBSCRIPTION_USAGE_MODEL = ''
+
+# Default URL and query params for obtaining valid AAP subscriptions
+SUBSCRIPTIONS_RHSM_URL = 'https://console.redhat.com/api/rhsm/v2/products?include=providedProducts&oids=480&status=Active'
+
+# Host metrics cleanup - last time of the task/command run
+CLEANUP_HOST_METRICS_LAST_TS = None
+# Host metrics cleanup - minimal interval between two cleanups in days
+CLEANUP_HOST_METRICS_INTERVAL = 30 # days
+# Host metrics cleanup - soft-delete HostMetric records with last_automation < [threshold] (in months)
+CLEANUP_HOST_METRICS_SOFT_THRESHOLD = 12 # months
+# Host metrics cleanup
+# - delete HostMetric record with deleted=True and last_deleted < [threshold]
+# - also threshold for computing HostMetricSummaryMonthly (command/scheduled task)
+CLEANUP_HOST_METRICS_HARD_THRESHOLD = 36 # months
+
+# Host metric summary monthly task - last time of run
+HOST_METRIC_SUMMARY_TASK_LAST_TS = None
+HOST_METRIC_SUMMARY_TASK_INTERVAL = 7 # days
+
+
+# TODO: cmeyers, replace with with register pattern
+# The register pattern is particularly nice for this because we need
+# to know the process to start the thread that will be the server.
+# The registration location should be the same location as we would
+# call MetricsServer.start()
+# Note: if we don't get to this TODO, then at least create constants
+# for the services strings below.
+# TODO: cmeyers, break this out into a separate django app so other
+# projects can take advantage.
+
+METRICS_SERVICE_CALLBACK_RECEIVER = 'callback_receiver'
+METRICS_SERVICE_DISPATCHER = 'dispatcherd'
+METRICS_SERVICE_WEBSOCKETS = 'websockets'
+
+METRICS_SUBSYSTEM_CONFIG = {
+ 'server': {
+ METRICS_SERVICE_CALLBACK_RECEIVER: {
+ 'port': 8014,
+ },
+ METRICS_SERVICE_DISPATCHER: {
+ 'port': 8015,
+ },
+ METRICS_SERVICE_WEBSOCKETS: {
+ 'port': 8016,
+ },
+ }
+}
+
+# django-ansible-base
+ANSIBLE_BASE_TEAM_MODEL = 'main.Team'
+ANSIBLE_BASE_ORGANIZATION_MODEL = 'main.Organization'
+ANSIBLE_BASE_RESOURCE_CONFIG_MODULE = 'awx.resource_api'
+
+# Defaults to be overridden by DAB
+SPECTACULAR_SETTINGS = {
+ 'TITLE': 'AWX API',
+ 'DESCRIPTION': 'AWX API Documentation',
+ 'VERSION': 'v2',
+ 'OAS_VERSION': '3.0.3', # Set OpenAPI Specification version to 3.0.3
+ 'SERVE_INCLUDE_SCHEMA': False,
+ 'SCHEMA_PATH_PREFIX': r'/api/v[0-9]',
+ 'DEFAULT_GENERATOR_CLASS': 'drf_spectacular.generators.SchemaGenerator',
+ 'SCHEMA_COERCE_PATH_PK_SUFFIX': True,
+ 'CONTACT': {'email': 'ansible-community@redhat.com'},
+ 'LICENSE': {'name': 'Apache License'},
+ 'TERMS_OF_SERVICE': 'https://www.google.com/policies/terms/',
+ # Use our custom schema class that handles swagger_topic and deprecated views
+ 'DEFAULT_SCHEMA_CLASS': 'awx.api.schema.CustomAutoSchema',
+ 'COMPONENT_SPLIT_REQUEST': True,
+ # Postprocessing hook to filter CredentialType enum values
+ 'POSTPROCESSING_HOOKS': ['awx.api.schema.filter_credential_type_schema'],
+ 'SWAGGER_UI_SETTINGS': {
+ 'deepLinking': True,
+ 'persistAuthorization': True,
+ 'displayOperationId': True,
+ },
+ # Resolve enum naming collisions with meaningful names
+ 'ENUM_NAME_OVERRIDES': {
+ # Status field collisions
+ 'Status4e1Enum': 'UnifiedJobStatusEnum',
+ 'Status876Enum': 'JobStatusEnum',
+ # Job type field collisions
+ 'JobType8b8Enum': 'JobTemplateJobTypeEnum',
+ 'JobType95bEnum': 'AdHocCommandJobTypeEnum',
+ 'JobType963Enum': 'ProjectUpdateJobTypeEnum',
+ # Verbosity field collisions
+ 'Verbosity481Enum': 'JobVerbosityEnum',
+ 'Verbosity8cfEnum': 'InventoryUpdateVerbosityEnum',
+ # Event field collision
+ 'Event4d3Enum': 'JobEventEnum',
+ # Kind field collision
+ 'Kind362Enum': 'InventoryKindEnum',
+ },
+}
+OAUTH2_PROVIDER = {}
+
+# Add a postfix to the API URL patterns
+# example if set to '' API pattern will be /api
+# example if set to 'controller' API pattern will be /api AND /api/controller
+OPTIONAL_API_URLPATTERN_PREFIX = ''
+
+# Add a postfix to the UI URL patterns for UI URL generated by the API
+# example if set to '' UI URL generated by the API for jobs would be $TOWER_URL/jobs
+# example if set to 'execution' UI URL generated by the API for jobs would be $TOWER_URL/execution/jobs
+OPTIONAL_UI_URL_PREFIX = ''
+
+# Use AWX base view, to give 401 on unauthenticated requests
+ANSIBLE_BASE_CUSTOM_VIEW_PARENT = 'awx.api.generics.APIView'
+
+# If we have a resource server defined, apply local changes to that server
+RESOURCE_SERVER_SYNC_ENABLED = True
+
+# Settings for the ansible_base RBAC system
+
+# This has been moved to data migration code
+ANSIBLE_BASE_ROLE_PRECREATE = {}
+
+# Name for auto-created roles that give users permissions to what they create
+ANSIBLE_BASE_ROLE_CREATOR_NAME = '{cls.__name__} Creator'
+
+# Use the new Gateway RBAC system for evaluations? You should. We will remove the old system soon.
+ANSIBLE_BASE_ROLE_SYSTEM_ACTIVATED = True
+
+# Permissions a user will get when creating a new item
+ANSIBLE_BASE_CREATOR_DEFAULTS = ['change', 'delete', 'execute', 'use', 'adhoc', 'approve', 'update', 'view']
+
+# Temporary, for old roles API compatibility, save child permissions at organization level
+ANSIBLE_BASE_CACHE_PARENT_PERMISSIONS = True
+
+# Currently features are enabled to keep compatibility with old system, except custom roles
+ANSIBLE_BASE_ALLOW_TEAM_ORG_ADMIN = False
+# ANSIBLE_BASE_ALLOW_CUSTOM_ROLES = True
+ANSIBLE_BASE_ALLOW_TEAM_PARENTS = False
+ANSIBLE_BASE_ALLOW_CUSTOM_TEAM_ROLES = False
+ANSIBLE_BASE_ALLOW_SINGLETON_USER_ROLES = True
+ANSIBLE_BASE_ALLOW_SINGLETON_TEAM_ROLES = False # System auditor has always been restricted to users
+ANSIBLE_BASE_ALLOW_SINGLETON_ROLES_API = False # Do not allow creating user-defined system-wide roles
+
+# system username for django-ansible-base
+SYSTEM_USERNAME = None
+
+# For indirect host query processing
+# if a job is not immediently confirmed to have all events processed
+# it will be eligable for processing after this number of minutes
+INDIRECT_HOST_QUERY_FALLBACK_MINUTES = 60
+
+# If an error happens in event collection, give up after this time
+INDIRECT_HOST_QUERY_FALLBACK_GIVEUP_DAYS = 3
+
+# Maximum age for indirect host audit records
+# Older records will be cleaned up
+INDIRECT_HOST_AUDIT_RECORD_MAX_AGE_DAYS = 7
+
+# setting for Policy as Code feature
+FEATURE_POLICY_AS_CODE_ENABLED = False
+
+OPA_HOST = '' # The hostname used to connect to the OPA server. If empty, policy enforcement will be disabled.
+OPA_PORT = 8181 # The port used to connect to the OPA server. Defaults to 8181.
+OPA_SSL = False # Enable or disable the use of SSL to connect to the OPA server. Defaults to false.
+
+OPA_AUTH_TYPE = 'None' # The authentication type that will be used to connect to the OPA server: "None", "Token", or "Certificate".
+OPA_AUTH_TOKEN = '' # The token for authentication to the OPA server. Required when OPA_AUTH_TYPE is "Token". If an authorization header is defined in OPA_AUTH_CUSTOM_HEADERS, it will be overridden by OPA_AUTH_TOKEN.
+OPA_AUTH_CLIENT_CERT = '' # The content of the client certificate file for mTLS authentication to the OPA server. Required when OPA_AUTH_TYPE is "Certificate".
+OPA_AUTH_CLIENT_KEY = '' # The content of the client key for mTLS authentication to the OPA server. Required when OPA_AUTH_TYPE is "Certificate".
+OPA_AUTH_CA_CERT = '' # The content of the CA certificate for mTLS authentication to the OPA server. Required when OPA_AUTH_TYPE is "Certificate".
+OPA_AUTH_CUSTOM_HEADERS = {} # Optional custom headers included in requests to the OPA server. Defaults to empty dictionary ({}).
+OPA_REQUEST_TIMEOUT = 1.5 # The number of seconds after which the connection to the OPA server will time out. Defaults to 1.5 seconds.
+OPA_REQUEST_RETRIES = 2 # The number of retry attempts for connecting to the OPA server. Default is 2.
+
+# feature flags
+FEATURE_INDIRECT_NODE_COUNTING_ENABLED = False
+
+# Dispatcher worker lifetime. If set to None, workers will never be retired
+# based on age. Note workers will finish their last task before retiring if
+# they are busy when they reach retirement age.
+WORKER_MAX_LIFETIME_SECONDS = 14400 # seconds
diff --git a/awx/settings/development.py b/awx/settings/development.py
index 1be4b7295619..5b630c49841a 100644
--- a/awx/settings/development.py
+++ b/awx/settings/development.py
@@ -1,122 +1,13 @@
-# Copyright (c) 2015 Ansible, Inc.
-# All Rights Reserved.
-
-# Development settings for AWX project.
-
-# Python
+# This file exists for backwards compatibility only
+# the current way of running AWX is to point settings to
+# awx/settings/__init__.py as the entry point for the settings
+# that is done by exporting: export DJANGO_SETTINGS_MODULE=awx.settings
import os
-import socket
-import copy
-import sys
-import traceback
-import uuid
-
-# Centos-7 doesn't include the svg mime type
-# /usr/lib64/python/mimetypes.py
-import mimetypes
-
-# Django Split Settings
-from split_settings.tools import optional, include
-
-# Load default settings.
-from .defaults import * # NOQA
-
-# awx-manage shell_plus --notebook
-NOTEBOOK_ARGUMENTS = ['--NotebookApp.token=', '--ip', '0.0.0.0', '--port', '8888', '--allow-root', '--no-browser']
-
-# print SQL queries in shell_plus
-SHELL_PLUS_PRINT_SQL = False
-
-# show colored logs in the dev environment
-# to disable this, set `COLOR_LOGS = False` in awx/settings/local_settings.py
-LOGGING['handlers']['console']['()'] = 'awx.main.utils.handlers.ColorHandler' # noqa
-# task system does not propagate to AWX, so color log these too
-LOGGING['handlers']['task_system'] = LOGGING['handlers']['console'].copy() # noqa
-COLOR_LOGS = True
-
-ALLOWED_HOSTS = ['*']
-
-mimetypes.add_type("image/svg+xml", ".svg", True)
-mimetypes.add_type("image/svg+xml", ".svgz", True)
-
-# Disallow sending session cookies over insecure connections
-SESSION_COOKIE_SECURE = False
-
-# Disallow sending csrf cookies over insecure connections
-CSRF_COOKIE_SECURE = False
-
-# Disable Pendo on the UI for development/test.
-# Note: This setting may be overridden by database settings.
-PENDO_TRACKING_STATE = "off"
-INSIGHTS_TRACKING_STATE = False
-
-# debug toolbar and swagger assume that requirements/requirements_dev.txt are installed
-
-INSTALLED_APPS += ['rest_framework_swagger', 'debug_toolbar'] # NOQA
-
-MIDDLEWARE = ['debug_toolbar.middleware.DebugToolbarMiddleware'] + MIDDLEWARE # NOQA
-
-DEBUG_TOOLBAR_CONFIG = {'ENABLE_STACKTRACES': True}
-
-# Configure a default UUID for development only.
-SYSTEM_UUID = '00000000-0000-0000-0000-000000000000'
-INSTALL_UUID = '00000000-0000-0000-0000-000000000000'
-
-# Store a snapshot of default settings at this point before loading any
-# customizable config files.
-DEFAULTS_SNAPSHOT = {}
-this_module = sys.modules[__name__]
-for setting in dir(this_module):
- if setting == setting.upper():
- DEFAULTS_SNAPSHOT[setting] = copy.deepcopy(getattr(this_module, setting))
-
-# If there is an `/etc/tower/settings.py`, include it.
-# If there is a `/etc/tower/conf.d/*.py`, include them.
-include(optional('/etc/tower/settings.py'), scope=locals())
-include(optional('/etc/tower/conf.d/*.py'), scope=locals())
-
-BASE_VENV_PATH = "/var/lib/awx/venv/"
-AWX_VENV_PATH = os.path.join(BASE_VENV_PATH, "awx")
-
-# Use SQLite for unit tests instead of PostgreSQL. If the lines below are
-# commented out, Django will create the test_awx-dev database in PostgreSQL to
-# run unit tests.
-if "pytest" in sys.modules:
- CACHES = {'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'LOCATION': 'unique-{}'.format(str(uuid.uuid4()))}}
- DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': os.path.join(BASE_DIR, 'awx.sqlite3'), # noqa
- 'TEST': {
- # Test database cannot be :memory: for inventory tests.
- 'NAME': os.path.join(BASE_DIR, 'awx_test.sqlite3') # noqa
- },
- }
- }
-
-CLUSTER_HOST_ID = socket.gethostname()
-
-AWX_CALLBACK_PROFILE = True
-# ======================!!!!!!! FOR DEVELOPMENT ONLY !!!!!!!=================================
-# Disable normal scheduled/triggered task managers (DependencyManager, TaskManager, WorkflowManager).
-# Allows user to trigger task managers directly for debugging and profiling purposes.
-# Only works in combination with settings.SETTINGS_MODULE == 'awx.settings.development'
-AWX_DISABLE_TASK_MANAGERS = False
-# ======================!!!!!!! FOR DEVELOPMENT ONLY !!!!!!!=================================
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "awx.settings")
+os.environ.setdefault("AWX_MODE", "development")
-if 'sqlite3' not in DATABASES['default']['ENGINE']: # noqa
- DATABASES['default'].setdefault('OPTIONS', dict()).setdefault('application_name', f'{CLUSTER_HOST_ID}-{os.getpid()}-{" ".join(sys.argv)}'[:63]) # noqa
+from ansible_base.lib.dynamic_config import export
+from . import DYNACONF # noqa
-# If any local_*.py files are present in awx/settings/, use them to override
-# default settings for development. If not present, we can still run using
-# only the defaults.
-# this needs to stay at the bottom of this file
-try:
- if os.getenv('AWX_KUBE_DEVEL', False):
- include(optional('development_kube.py'), scope=locals())
- else:
- include(optional('local_*.py'), scope=locals())
-except ImportError:
- traceback.print_exc()
- sys.exit(1)
+export(__name__, DYNACONF)
diff --git a/awx/settings/development_defaults.py b/awx/settings/development_defaults.py
new file mode 100644
index 000000000000..49cb1a68b6bc
--- /dev/null
+++ b/awx/settings/development_defaults.py
@@ -0,0 +1,71 @@
+# Copyright (c) 2015 Ansible, Inc.
+# All Rights Reserved.
+
+# Development settings for AWX project.
+
+# Python
+import os
+import socket
+
+# Centos-7 doesn't include the svg mime type
+# /usr/lib64/python/mimetypes.py
+import mimetypes
+
+# awx-manage shell_plus --notebook
+NOTEBOOK_ARGUMENTS = ['--NotebookApp.token=', '--ip', '0.0.0.0', '--port', '9888', '--allow-root', '--no-browser']
+
+# print SQL queries in shell_plus
+SHELL_PLUS_PRINT_SQL = False
+
+# show colored logs in the dev environment
+# to disable this, set `COLOR_LOGS = False` in awx/settings/local_settings.py
+COLOR_LOGS = True
+LOGGING__handlers__console = '@merge {"()": "awx.main.utils.handlers.ColorHandler"}'
+
+ALLOWED_HOSTS = ['*']
+
+mimetypes.add_type("image/svg+xml", ".svg", True)
+mimetypes.add_type("image/svg+xml", ".svgz", True)
+
+# Disallow sending session cookies over insecure connections
+SESSION_COOKIE_SECURE = False
+
+# Disallow sending csrf cookies over insecure connections
+CSRF_COOKIE_SECURE = False
+
+# Disable Pendo on the UI for development/test.
+# Note: This setting may be overridden by database settings.
+PENDO_TRACKING_STATE = "off"
+INSIGHTS_TRACKING_STATE = False
+
+# debug toolbar and swagger assume that requirements/requirements_dev.txt are installed
+INSTALLED_APPS = "@merge drf_spectacular,debug_toolbar"
+MIDDLEWARE = "@insert 0 debug_toolbar.middleware.DebugToolbarMiddleware"
+
+DEBUG_TOOLBAR_CONFIG = {'ENABLE_STACKTRACES': True}
+
+# drf-spectacular settings for API schema generation
+# SPECTACULAR_SETTINGS moved to defaults.py so it's available in all environments
+
+# Configure a default UUID for development only.
+SYSTEM_UUID = '00000000-0000-0000-0000-000000000000'
+INSTALL_UUID = '00000000-0000-0000-0000-000000000000'
+
+# Ansible base virtualenv paths and enablement
+# only used for deprecated fields and management commands for them
+BASE_VENV_PATH = os.path.realpath("/var/lib/awx/venv")
+
+CLUSTER_HOST_ID = socket.gethostname()
+
+AWX_CALLBACK_PROFILE = True
+
+# ======================!!!!!!! FOR DEVELOPMENT ONLY !!!!!!!=================================
+# Disable normal scheduled/triggered task managers (DependencyManager, TaskManager, WorkflowManager).
+# Allows user to trigger task managers directly for debugging and profiling purposes.
+# Only works in combination with settings.SETTINGS_MODULE == 'awx.settings.development'
+AWX_DISABLE_TASK_MANAGERS = False
+
+# Needed for launching runserver in debug mode
+# ======================!!!!!!! FOR DEVELOPMENT ONLY !!!!!!!=================================
+
+FEATURE_INDIRECT_NODE_COUNTING_ENABLED = True
diff --git a/awx/settings/development_kube.py b/awx/settings/development_kube.py
index c30a7fe025fe..e6ba6170c6d7 100644
--- a/awx/settings/development_kube.py
+++ b/awx/settings/development_kube.py
@@ -1,4 +1,13 @@
-BROADCAST_WEBSOCKET_SECRET = '🤖starscream🤖'
-BROADCAST_WEBSOCKET_PORT = 8052
-BROADCAST_WEBSOCKET_VERIFY_CERT = False
-BROADCAST_WEBSOCKET_PROTOCOL = 'http'
+# This file exists for backwards compatibility only
+# the current way of running AWX is to point settings to
+# awx/settings/__init__.py as the entry point for the settings
+# that is done by exporting: export DJANGO_SETTINGS_MODULE=awx.settings
+import os
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "awx.settings")
+os.environ.setdefault("AWX_MODE", "development,kube")
+
+from ansible_base.lib.dynamic_config import export
+from . import DYNACONF # noqa
+
+export(__name__, DYNACONF)
diff --git a/awx/settings/development_quiet.py b/awx/settings/development_quiet.py
index c47e78b69d86..5fea2756e908 100644
--- a/awx/settings/development_quiet.py
+++ b/awx/settings/development_quiet.py
@@ -1,15 +1,13 @@
-# Copyright (c) 2015 Ansible, Inc.
-# All Rights Reserved.
+# This file exists for backwards compatibility only
+# the current way of running AWX is to point settings to
+# awx/settings/__init__.py as the entry point for the settings
+# that is done by exporting: export DJANGO_SETTINGS_MODULE=awx.settings
+import os
-# Development settings for AWX project, but with DEBUG disabled
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "awx.settings")
+os.environ.setdefault("AWX_MODE", "development,quiet")
-# Load development settings.
-from defaults import * # NOQA
+from ansible_base.lib.dynamic_config import export
+from . import DYNACONF # noqa
-# Load development settings.
-from development import * # NOQA
-
-# Disable capturing DEBUG
-DEBUG = False
-TEMPLATE_DEBUG = DEBUG
-SQL_DEBUG = DEBUG
+export(__name__, DYNACONF)
diff --git a/awx/settings/functions.py b/awx/settings/functions.py
new file mode 100644
index 000000000000..70be9befdbae
--- /dev/null
+++ b/awx/settings/functions.py
@@ -0,0 +1,86 @@
+import os
+from ansible_base.lib.dynamic_config import load_python_file_with_injected_context
+from dynaconf import Dynaconf
+from .application_name import get_application_name
+
+
+def merge_application_name(settings):
+ """Return a dynaconf merge dict to set the application name for the connection."""
+ data = {}
+ if "sqlite3" not in settings.get("DATABASES__default__ENGINE", ""):
+ data["DATABASES__default__OPTIONS__application_name"] = get_application_name(settings.get("CLUSTER_HOST_ID"))
+ return data
+
+
+def add_backwards_compatibility():
+ """Add backwards compatibility for AWX_MODE.
+
+ Before dynaconf integration the usage of AWX settings was supported to be just
+ DJANGO_SETTINGS_MODULE=awx.settings.production or DJANGO_SETTINGS_MODULE=awx.settings.development
+ (development_quiet and development_kube were also supported).
+
+ With dynaconf the DJANGO_SETTINGS_MODULE should be set always to "awx.settings" as the only entry point
+ for settings and then "AWX_MODE" can be set to any of production,development,quiet,kube
+ or a combination of them separated by comma.
+
+ E.g:
+
+ export DJANGO_SETTINGS_MODULE=awx.settings
+ export AWX_MODE=production
+ awx-manage [command]
+ dynaconf [command]
+
+ If pointing `DJANGO_SETTINGS_MODULE` to `awx.settings.production` or `awx.settings.development` then
+ this function will set `AWX_MODE` to the correct value.
+ """
+ django_settings_module = os.getenv("DJANGO_SETTINGS_MODULE", "awx.settings")
+ if django_settings_module == "awx.settings":
+ return
+
+ current_mode = os.getenv("AWX_MODE", "")
+ for _module_name in ["development", "production", "development_quiet", "development_kube"]:
+ if django_settings_module == f"awx.settings.{_module_name}":
+ _mode = current_mode.split(",")
+ if "development_" in _module_name and "development" not in current_mode:
+ _mode.append("development")
+ _mode_fragment = _module_name.replace("development_", "")
+ if _mode_fragment not in _mode:
+ _mode.append(_mode_fragment)
+ os.environ["AWX_MODE"] = ",".join(_mode)
+
+
+def load_extra_development_files(settings: Dynaconf):
+ """Load optional development only settings files."""
+ if not settings.is_development_mode:
+ return
+
+ if settings.get_environ("AWX_KUBE_DEVEL"):
+ load_python_file_with_injected_context("kube_defaults.py", settings=settings)
+ else:
+ load_python_file_with_injected_context("local_*.py", settings=settings)
+
+
+def assert_production_settings(settings: Dynaconf, settings_dir: str, settings_file_path: str): # pragma: no cover
+ """Ensure at least one setting file has been loaded in production mode.
+ Current systems will require /etc/tower/settings.py and
+ new systems will require /etc/ansible-automation-platform/*.yaml
+ """
+ if "production" not in settings.current_env.lower():
+ return
+
+ required_settings_paths = [
+ os.path.dirname(settings_file_path),
+ "/etc/ansible-automation-platform/",
+ settings_dir,
+ ]
+
+ for path in required_settings_paths:
+ if any([path in os.path.dirname(f) for f in settings._loaded_files]):
+ break
+ else:
+ from django.core.exceptions import ImproperlyConfigured # noqa
+
+ msg = 'No AWX configuration found at %s.' % required_settings_paths
+ msg += '\nDefine the AWX_SETTINGS_FILE environment variable to '
+ msg += 'specify an alternate path.'
+ raise ImproperlyConfigured(msg)
diff --git a/awx/settings/kube_defaults.py b/awx/settings/kube_defaults.py
new file mode 100644
index 000000000000..c30a7fe025fe
--- /dev/null
+++ b/awx/settings/kube_defaults.py
@@ -0,0 +1,4 @@
+BROADCAST_WEBSOCKET_SECRET = '🤖starscream🤖'
+BROADCAST_WEBSOCKET_PORT = 8052
+BROADCAST_WEBSOCKET_VERIFY_CERT = False
+BROADCAST_WEBSOCKET_PROTOCOL = 'http'
diff --git a/awx/settings/production.py b/awx/settings/production.py
index 3dce95deb08f..bcf483b118cf 100644
--- a/awx/settings/production.py
+++ b/awx/settings/production.py
@@ -1,105 +1,13 @@
-# Copyright (c) 2015 Ansible, Inc.
-# All Rights Reserved.
-
-# Production settings for AWX project.
-
-# Python
+# This file exists for backwards compatibility only
+# the current way of running AWX is to point settings to
+# awx/settings/__init__.py as the entry point for the settings
+# that is done by exporting: export DJANGO_SETTINGS_MODULE=awx.settings
import os
-import copy
-import errno
-import sys
-import traceback
-
-# Django Split Settings
-from split_settings.tools import optional, include
-
-# Load default settings.
-from .defaults import * # NOQA
-
-DEBUG = False
-TEMPLATE_DEBUG = DEBUG
-SQL_DEBUG = DEBUG
-
-# Clear database settings to force production environment to define them.
-DATABASES = {}
-
-# Clear the secret key to force production environment to define it.
-SECRET_KEY = None
-
-# Hosts/domain names that are valid for this site; required if DEBUG is False
-# See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
-ALLOWED_HOSTS = []
-
-# The heartbeat file for the scheduler
-SCHEDULE_METADATA_LOCATION = '/var/lib/awx/.tower_cycle'
-
-# Ansible base virtualenv paths and enablement
-BASE_VENV_PATH = os.path.realpath("/var/lib/awx/venv")
-
-# Base virtualenv paths and enablement
-AWX_VENV_PATH = os.path.join(BASE_VENV_PATH, "awx")
-
-# Very important that this is editable (not read_only) in the API
-AWX_ISOLATION_SHOW_PATHS = [
- '/etc/pki/ca-trust:/etc/pki/ca-trust:O',
- '/usr/share/pki:/usr/share/pki:O',
-]
-
-# Store a snapshot of default settings at this point before loading any
-# customizable config files.
-#
-###############################################################################################
-#
-# Any settings defined after this point will be marked as as a read_only database setting
-#
-################################################################################################
-DEFAULTS_SNAPSHOT = {}
-this_module = sys.modules[__name__]
-for setting in dir(this_module):
- if setting == setting.upper():
- DEFAULTS_SNAPSHOT[setting] = copy.deepcopy(getattr(this_module, setting))
-
-# Load settings from any .py files in the global conf.d directory specified in
-# the environment, defaulting to /etc/tower/conf.d/.
-settings_dir = os.environ.get('AWX_SETTINGS_DIR', '/etc/tower/conf.d/')
-settings_files = os.path.join(settings_dir, '*.py')
-
-# Load remaining settings from the global settings file specified in the
-# environment, defaulting to /etc/tower/settings.py.
-settings_file = os.environ.get('AWX_SETTINGS_FILE', '/etc/tower/settings.py')
-
-# Attempt to load settings from /etc/tower/settings.py first, followed by
-# /etc/tower/conf.d/*.py.
-try:
- include(settings_file, optional(settings_files), scope=locals())
-except ImportError:
- traceback.print_exc()
- sys.exit(1)
-except IOError:
- from django.core.exceptions import ImproperlyConfigured
- included_file = locals().get('__included_file__', '')
- if not included_file or included_file == settings_file:
- # The import doesn't always give permission denied, so try to open the
- # settings file directly.
- try:
- e = None
- open(settings_file)
- except IOError:
- pass
- if e and e.errno == errno.EACCES:
- SECRET_KEY = 'permission-denied'
- LOGGING = {}
- else:
- msg = 'No AWX configuration found at %s.' % settings_file
- msg += '\nDefine the AWX_SETTINGS_FILE environment variable to '
- msg += 'specify an alternate path.'
- raise ImproperlyConfigured(msg)
- else:
- raise
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "awx.settings")
+os.environ.setdefault("AWX_MODE", "production")
-# The below runs AFTER all of the custom settings are imported.
+from ansible_base.lib.dynamic_config import export
+from . import DYNACONF # noqa
-DATABASES.setdefault('default', dict()).setdefault('OPTIONS', dict()).setdefault(
- 'application_name', f'{CLUSTER_HOST_ID}-{os.getpid()}-{" ".join(sys.argv)}'[:63] # NOQA
-) # noqa
+export(__name__, DYNACONF)
diff --git a/awx/settings/production_defaults.py b/awx/settings/production_defaults.py
new file mode 100644
index 000000000000..190779ef286e
--- /dev/null
+++ b/awx/settings/production_defaults.py
@@ -0,0 +1,32 @@
+# Copyright (c) 2015 Ansible, Inc.
+# All Rights Reserved.
+
+# Production settings for AWX project.
+
+import os
+
+DEBUG = False
+TEMPLATE_DEBUG = DEBUG
+SQL_DEBUG = DEBUG
+
+# Clear database settings to force production environment to define them.
+DATABASES = {}
+
+# Clear the secret key to force production environment to define it.
+SECRET_KEY = None
+
+# Hosts/domain names that are valid for this site; required if DEBUG is False
+# See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
+ALLOWED_HOSTS = []
+
+# Ansible base virtualenv paths and enablement
+# only used for deprecated fields and management commands for them
+BASE_VENV_PATH = os.path.realpath("/var/lib/awx/venv")
+
+# Very important that this is editable (not read_only) in the API
+AWX_ISOLATION_SHOW_PATHS = [
+ '/etc/pki/ca-trust:/etc/pki/ca-trust:O',
+ '/usr/share/pki:/usr/share/pki:O',
+]
+
+del os
diff --git a/awx/settings/quiet_defaults.py b/awx/settings/quiet_defaults.py
new file mode 100644
index 000000000000..1cb21720f7dd
--- /dev/null
+++ b/awx/settings/quiet_defaults.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2015 Ansible, Inc.
+# All Rights Reserved.
+# Development settings for AWX project, but with DEBUG disabled
+
+# Disable capturing DEBUG
+DEBUG = False
+TEMPLATE_DEBUG = DEBUG
+SQL_DEBUG = DEBUG
diff --git a/awx/sso/__init__.py b/awx/sso/__init__.py
deleted file mode 100644
index e484e62be15d..000000000000
--- a/awx/sso/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# Copyright (c) 2015 Ansible, Inc.
-# All Rights Reserved.
diff --git a/awx/sso/apps.py b/awx/sso/apps.py
deleted file mode 100644
index 6203ca6d6a11..000000000000
--- a/awx/sso/apps.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Django
-from django.apps import AppConfig
-from django.utils.translation import gettext_lazy as _
-
-
-class SSOConfig(AppConfig):
- name = 'awx.sso'
- verbose_name = _('Single Sign-On')
diff --git a/awx/sso/backends.py b/awx/sso/backends.py
deleted file mode 100644
index c55f24e7de3d..000000000000
--- a/awx/sso/backends.py
+++ /dev/null
@@ -1,446 +0,0 @@
-# Copyright (c) 2015 Ansible, Inc.
-# All Rights Reserved.
-
-# Python
-from collections import OrderedDict
-import logging
-import uuid
-
-import ldap
-
-# Django
-from django.dispatch import receiver
-from django.contrib.auth.models import User
-from django.conf import settings as django_settings
-from django.core.signals import setting_changed
-from django.utils.encoding import force_str
-
-# django-auth-ldap
-from django_auth_ldap.backend import LDAPSettings as BaseLDAPSettings
-from django_auth_ldap.backend import LDAPBackend as BaseLDAPBackend
-from django_auth_ldap.backend import populate_user
-from django.core.exceptions import ImproperlyConfigured
-
-# radiusauth
-from radiusauth.backends import RADIUSBackend as BaseRADIUSBackend
-
-# tacacs+ auth
-import tacacs_plus
-
-# social
-from social_core.backends.saml import OID_USERID
-from social_core.backends.saml import SAMLAuth as BaseSAMLAuth
-from social_core.backends.saml import SAMLIdentityProvider as BaseSAMLIdentityProvider
-
-# Ansible Tower
-from awx.sso.models import UserEnterpriseAuth
-from awx.sso.common import create_org_and_teams, reconcile_users_org_team_mappings
-
-logger = logging.getLogger('awx.sso.backends')
-
-
-class LDAPSettings(BaseLDAPSettings):
- defaults = dict(list(BaseLDAPSettings.defaults.items()) + list({'ORGANIZATION_MAP': {}, 'TEAM_MAP': {}, 'GROUP_TYPE_PARAMS': {}}.items()))
-
- def __init__(self, prefix='AUTH_LDAP_', defaults={}):
- super(LDAPSettings, self).__init__(prefix, defaults)
-
- # If a DB-backed setting is specified that wipes out the
- # OPT_NETWORK_TIMEOUT, fall back to a sane default
- if ldap.OPT_NETWORK_TIMEOUT not in getattr(self, 'CONNECTION_OPTIONS', {}):
- options = getattr(self, 'CONNECTION_OPTIONS', {})
- options[ldap.OPT_NETWORK_TIMEOUT] = 30
- self.CONNECTION_OPTIONS = options
-
- # when specifying `.set_option()` calls for TLS in python-ldap, the
- # *order* in which you invoke them *matters*, particularly in Python3,
- # where dictionary insertion order is persisted
- #
- # specifically, it is *critical* that `ldap.OPT_X_TLS_NEWCTX` be set *last*
- # this manual sorting puts `OPT_X_TLS_NEWCTX` *after* other TLS-related
- # options
- #
- # see: https://github.com/python-ldap/python-ldap/issues/55
- newctx_option = self.CONNECTION_OPTIONS.pop(ldap.OPT_X_TLS_NEWCTX, None)
- self.CONNECTION_OPTIONS = OrderedDict(self.CONNECTION_OPTIONS)
- if newctx_option is not None:
- self.CONNECTION_OPTIONS[ldap.OPT_X_TLS_NEWCTX] = newctx_option
-
-
-class LDAPBackend(BaseLDAPBackend):
-
- """
- Custom LDAP backend for AWX.
- """
-
- settings_prefix = 'AUTH_LDAP_'
-
- def __init__(self, *args, **kwargs):
- self._dispatch_uid = uuid.uuid4()
- super(LDAPBackend, self).__init__(*args, **kwargs)
- setting_changed.connect(self._on_setting_changed, dispatch_uid=self._dispatch_uid)
-
- def _on_setting_changed(self, sender, **kwargs):
- # If any AUTH_LDAP_* setting changes, force settings to be reloaded for
- # this backend instance.
- if kwargs.get('setting', '').startswith(self.settings_prefix):
- self._settings = None
-
- def _get_settings(self):
- if self._settings is None:
- self._settings = LDAPSettings(self.settings_prefix)
- return self._settings
-
- def _set_settings(self, settings):
- self._settings = settings
-
- settings = property(_get_settings, _set_settings)
-
- def authenticate(self, request, username, password):
- if self.settings.START_TLS and ldap.OPT_X_TLS_REQUIRE_CERT in self.settings.CONNECTION_OPTIONS:
- # with python-ldap, if you want to set connection-specific TLS
- # parameters, you must also specify OPT_X_TLS_NEWCTX = 0
- # see: https://stackoverflow.com/a/29722445
- # see: https://stackoverflow.com/a/38136255
- self.settings.CONNECTION_OPTIONS[ldap.OPT_X_TLS_NEWCTX] = 0
-
- if not self.settings.SERVER_URI:
- return None
- try:
- user = User.objects.get(username=username)
- if user and (not user.profile or not user.profile.ldap_dn):
- return None
- except User.DoesNotExist:
- pass
-
- try:
- for setting_name, type_ in [('GROUP_SEARCH', 'LDAPSearch'), ('GROUP_TYPE', 'LDAPGroupType')]:
- if getattr(self.settings, setting_name) is None:
- raise ImproperlyConfigured("{} must be an {} instance.".format(setting_name, type_))
- ldap_user = super(LDAPBackend, self).authenticate(request, username, password)
- # If we have an LDAP user and that user we found has an ldap_user internal object and that object has a bound connection
- # Then we can try and force an unbind to close the sticky connection
- if ldap_user and ldap_user.ldap_user and ldap_user.ldap_user._connection_bound:
- logger.debug("Forcing LDAP connection to close")
- try:
- ldap_user.ldap_user._connection.unbind_s()
- ldap_user.ldap_user._connection_bound = False
- except Exception:
- logger.exception(f"Got unexpected LDAP exception when forcing LDAP disconnect for user {ldap_user}, login will still proceed")
- return ldap_user
- except Exception:
- logger.exception("Encountered an error authenticating to LDAP")
- return None
-
- def get_user(self, user_id):
- if not self.settings.SERVER_URI:
- return None
- return super(LDAPBackend, self).get_user(user_id)
-
- # Disable any LDAP based authorization / permissions checking.
-
- def has_perm(self, user, perm, obj=None):
- return False
-
- def has_module_perms(self, user, app_label):
- return False
-
- def get_all_permissions(self, user, obj=None):
- return set()
-
- def get_group_permissions(self, user, obj=None):
- return set()
-
-
-class LDAPBackend1(LDAPBackend):
- settings_prefix = 'AUTH_LDAP_1_'
-
-
-class LDAPBackend2(LDAPBackend):
- settings_prefix = 'AUTH_LDAP_2_'
-
-
-class LDAPBackend3(LDAPBackend):
- settings_prefix = 'AUTH_LDAP_3_'
-
-
-class LDAPBackend4(LDAPBackend):
- settings_prefix = 'AUTH_LDAP_4_'
-
-
-class LDAPBackend5(LDAPBackend):
- settings_prefix = 'AUTH_LDAP_5_'
-
-
-def _decorate_enterprise_user(user, provider):
- user.set_unusable_password()
- user.save()
- enterprise_auth, _ = UserEnterpriseAuth.objects.get_or_create(user=user, provider=provider)
- return enterprise_auth
-
-
-def _get_or_set_enterprise_user(username, password, provider):
- created = False
- try:
- user = User.objects.prefetch_related('enterprise_auth').get(username=username)
- except User.DoesNotExist:
- user = User(username=username)
- enterprise_auth = _decorate_enterprise_user(user, provider)
- logger.debug("Created enterprise user %s via %s backend." % (username, enterprise_auth.get_provider_display()))
- created = True
- if created or user.is_in_enterprise_category(provider):
- return user
- logger.warning("Enterprise user %s already defined in Tower." % username)
-
-
-class RADIUSBackend(BaseRADIUSBackend):
- """
- Custom Radius backend to verify license status
- """
-
- def authenticate(self, request, username, password):
- if not django_settings.RADIUS_SERVER:
- return None
- return super(RADIUSBackend, self).authenticate(request, username, password)
-
- def get_user(self, user_id):
- if not django_settings.RADIUS_SERVER:
- return None
- user = super(RADIUSBackend, self).get_user(user_id)
- if not user.has_usable_password():
- return user
-
- def get_django_user(self, username, password=None, groups=[], is_staff=False, is_superuser=False):
- return _get_or_set_enterprise_user(force_str(username), force_str(password), 'radius')
-
-
-class TACACSPlusBackend(object):
- """
- Custom TACACS+ auth backend for AWX
- """
-
- def authenticate(self, request, username, password):
- if not django_settings.TACACSPLUS_HOST:
- return None
- try:
- # Upstream TACACS+ client does not accept non-string, so convert if needed.
- auth = tacacs_plus.TACACSClient(
- django_settings.TACACSPLUS_HOST,
- django_settings.TACACSPLUS_PORT,
- django_settings.TACACSPLUS_SECRET,
- timeout=django_settings.TACACSPLUS_SESSION_TIMEOUT,
- ).authenticate(username, password, authen_type=tacacs_plus.TAC_PLUS_AUTHEN_TYPES[django_settings.TACACSPLUS_AUTH_PROTOCOL])
- except Exception as e:
- logger.exception("TACACS+ Authentication Error: %s" % str(e))
- return None
- if auth.valid:
- return _get_or_set_enterprise_user(username, password, 'tacacs+')
-
- def get_user(self, user_id):
- if not django_settings.TACACSPLUS_HOST:
- return None
- try:
- return User.objects.get(pk=user_id)
- except User.DoesNotExist:
- return None
-
-
-class TowerSAMLIdentityProvider(BaseSAMLIdentityProvider):
- """
- Custom Identity Provider to make attributes to what we expect.
- """
-
- def get_user_permanent_id(self, attributes):
- uid = attributes[self.conf.get('attr_user_permanent_id', OID_USERID)]
- if isinstance(uid, str):
- return uid
- return uid[0]
-
- def get_attr(self, attributes, conf_key, default_attribute):
- """
- Get the attribute 'default_attribute' out of the attributes,
- unless self.conf[conf_key] overrides the default by specifying
- another attribute to use.
- """
- key = self.conf.get(conf_key, default_attribute)
- value = attributes[key] if key in attributes else None
- # In certain implementations (like https://pagure.io/ipsilon) this value is a string, not a list
- if isinstance(value, (list, tuple)):
- value = value[0]
- if conf_key in ('attr_first_name', 'attr_last_name', 'attr_username', 'attr_email') and value is None:
- logger.warning(
- "Could not map user detail '%s' from SAML attribute '%s'; " "update SOCIAL_AUTH_SAML_ENABLED_IDPS['%s']['%s'] with the correct SAML attribute.",
- conf_key[5:],
- key,
- self.name,
- conf_key,
- )
- return str(value) if value is not None else value
-
-
-class SAMLAuth(BaseSAMLAuth):
- """
- Custom SAMLAuth backend to verify license status
- """
-
- def get_idp(self, idp_name):
- idp_config = self.setting('ENABLED_IDPS')[idp_name]
- return TowerSAMLIdentityProvider(idp_name, **idp_config)
-
- def authenticate(self, request, *args, **kwargs):
- if not all(
- [
- django_settings.SOCIAL_AUTH_SAML_SP_ENTITY_ID,
- django_settings.SOCIAL_AUTH_SAML_SP_PUBLIC_CERT,
- django_settings.SOCIAL_AUTH_SAML_SP_PRIVATE_KEY,
- django_settings.SOCIAL_AUTH_SAML_ORG_INFO,
- django_settings.SOCIAL_AUTH_SAML_TECHNICAL_CONTACT,
- django_settings.SOCIAL_AUTH_SAML_SUPPORT_CONTACT,
- django_settings.SOCIAL_AUTH_SAML_ENABLED_IDPS,
- ]
- ):
- return None
- user = super(SAMLAuth, self).authenticate(request, *args, **kwargs)
- # Comes from https://github.com/omab/python-social-auth/blob/v0.2.21/social/backends/base.py#L91
- if getattr(user, 'is_new', False):
- enterprise_auth = _decorate_enterprise_user(user, 'saml')
- logger.debug("Created enterprise user %s from %s backend." % (user.username, enterprise_auth.get_provider_display()))
- elif user and not user.is_in_enterprise_category('saml'):
- return None
- if user:
- logger.debug("Enterprise user %s already created in Tower." % user.username)
- return user
-
- def get_user(self, user_id):
- if not all(
- [
- django_settings.SOCIAL_AUTH_SAML_SP_ENTITY_ID,
- django_settings.SOCIAL_AUTH_SAML_SP_PUBLIC_CERT,
- django_settings.SOCIAL_AUTH_SAML_SP_PRIVATE_KEY,
- django_settings.SOCIAL_AUTH_SAML_ORG_INFO,
- django_settings.SOCIAL_AUTH_SAML_TECHNICAL_CONTACT,
- django_settings.SOCIAL_AUTH_SAML_SUPPORT_CONTACT,
- django_settings.SOCIAL_AUTH_SAML_ENABLED_IDPS,
- ]
- ):
- return None
- return super(SAMLAuth, self).get_user(user_id)
-
-
-def _update_m2m_from_groups(ldap_user, opts, remove=True):
- """
- Hepler function to evaluate the LDAP team/org options to determine if LDAP user should
- be a member of the team/org based on their ldap group dns.
-
- Returns:
- True - User should be added
- False - User should be removed
- None - Users membership should not be changed
- """
- if opts is None:
- return None
- elif not opts:
- pass
- elif isinstance(opts, bool) and opts is True:
- return True
- else:
- if isinstance(opts, str):
- opts = [opts]
- # If any of the users groups matches any of the list options
- for group_dn in opts:
- if not isinstance(group_dn, str):
- continue
- if ldap_user._get_groups().is_member_of(group_dn):
- return True
- if remove:
- return False
- return None
-
-
-@receiver(populate_user, dispatch_uid='populate-ldap-user')
-def on_populate_user(sender, **kwargs):
- """
- Handle signal from LDAP backend to populate the user object. Update user
- organization/team memberships according to their LDAP groups.
- """
- user = kwargs['user']
- ldap_user = kwargs['ldap_user']
- backend = ldap_user.backend
-
- # Boolean to determine if we should force an user update
- # to avoid duplicate SQL update statements
- force_user_update = False
-
- # Prefetch user's groups to prevent LDAP queries for each org/team when
- # checking membership.
- ldap_user._get_groups().get_group_dns()
-
- # If the LDAP user has a first or last name > $maxlen chars, truncate it
- for field in ('first_name', 'last_name'):
- max_len = User._meta.get_field(field).max_length
- field_len = len(getattr(user, field))
- if field_len > max_len:
- setattr(user, field, getattr(user, field)[:max_len])
- force_user_update = True
- logger.warning('LDAP user {} has {} > max {} characters'.format(user.username, field, max_len))
-
- org_map = getattr(backend.settings, 'ORGANIZATION_MAP', {})
- team_map = getattr(backend.settings, 'TEAM_MAP', {})
- orgs_list = list(org_map.keys())
- team_map = {}
- for team_name, team_opts in team_map.items():
- if not team_opts.get('organization', None):
- # You can't save the LDAP config in the UI w/o an org (or '' or null as the org) so if we somehow got this condition its an error
- logger.error("Team named {} in LDAP team map settings is invalid due to missing organization".format(team_name))
- continue
- team_map[team_name] = team_opts['organization']
-
- create_org_and_teams(orgs_list, team_map, 'LDAP')
-
- # Compute in memory what the state is of the different LDAP orgs
- org_roles_and_ldap_attributes = {'admin_role': 'admins', 'auditor_role': 'auditors', 'member_role': 'users'}
- desired_org_states = {}
- for org_name, org_opts in org_map.items():
- remove = bool(org_opts.get('remove', True))
- desired_org_states[org_name] = {}
- for org_role_name in org_roles_and_ldap_attributes.keys():
- ldap_name = org_roles_and_ldap_attributes[org_role_name]
- opts = org_opts.get(ldap_name, None)
- remove = bool(org_opts.get('remove_{}'.format(ldap_name), remove))
- desired_org_states[org_name][org_role_name] = _update_m2m_from_groups(ldap_user, opts, remove)
-
- # If everything returned None (because there was no configuration) we can remove this org from our map
- # This will prevent us from loading the org in the next query
- if all(desired_org_states[org_name][org_role_name] is None for org_role_name in org_roles_and_ldap_attributes.keys()):
- del desired_org_states[org_name]
-
- # Compute in memory what the state is of the different LDAP teams
- desired_team_states = {}
- for team_name, team_opts in team_map.items():
- if 'organization' not in team_opts:
- continue
- users_opts = team_opts.get('users', None)
- remove = bool(team_opts.get('remove', True))
- state = _update_m2m_from_groups(ldap_user, users_opts, remove)
- if state is not None:
- organization = team_opts['organization']
- if organization not in desired_team_states:
- desired_team_states[organization] = {}
- desired_team_states[organization][team_name] = {'member_role': state}
-
- # Check if user.profile is available, otherwise force user.save()
- try:
- _ = user.profile
- except ValueError:
- force_user_update = True
- finally:
- if force_user_update:
- user.save()
-
- # Update user profile to store LDAP DN.
- profile = user.profile
- if profile.ldap_dn != ldap_user.dn:
- profile.ldap_dn = ldap_user.dn
- profile.save()
-
- reconcile_users_org_team_mappings(user, desired_org_states, desired_team_states, 'LDAP')
diff --git a/awx/sso/common.py b/awx/sso/common.py
deleted file mode 100644
index 4d601bb22e43..000000000000
--- a/awx/sso/common.py
+++ /dev/null
@@ -1,171 +0,0 @@
-# Copyright (c) 2022 Ansible, Inc.
-# All Rights Reserved.
-
-import logging
-
-from django.contrib.contenttypes.models import ContentType
-from django.db.utils import IntegrityError
-from awx.main.models import Organization, Team
-
-logger = logging.getLogger('awx.sso.common')
-
-
-def get_orgs_by_ids():
- existing_orgs = {}
- for org_id, org_name in Organization.objects.all().values_list('id', 'name'):
- existing_orgs[org_name] = org_id
- return existing_orgs
-
-
-def reconcile_users_org_team_mappings(user, desired_org_states, desired_team_states, source):
- #
- # Arguments:
- # user - a user object
- # desired_org_states: { '': { '': or None } }
- # desired_team_states: { '': { '': { '': or None } } }
- # source - a text label indicating the "authentication adapter" for debug messages
- #
- # This function will load the users existing roles and then based on the desired states modify the users roles
- # True indicates the user needs to be a member of the role
- # False indicates the user should not be a member of the role
- # None means this function should not change the users membership of a role
- #
-
- content_types = []
- reconcile_items = []
- if desired_org_states:
- content_types.append(ContentType.objects.get_for_model(Organization))
- reconcile_items.append(('organization', desired_org_states))
- if desired_team_states:
- content_types.append(ContentType.objects.get_for_model(Team))
- reconcile_items.append(('team', desired_team_states))
-
- if not content_types:
- # If both desired states were empty we can simply return because there is nothing to reconcile
- return
-
- # users_roles is a flat set of IDs
- users_roles = set(user.roles.filter(content_type__in=content_types).values_list('pk', flat=True))
-
- for object_type, desired_states in reconcile_items:
- roles = []
- # Get a set of named tuples for the org/team name plus all of the roles we got above
- if object_type == 'organization':
- for sub_dict in desired_states.values():
- for role_name in sub_dict:
- if sub_dict[role_name] is None:
- continue
- if role_name not in roles:
- roles.append(role_name)
- model_roles = Organization.objects.filter(name__in=desired_states.keys()).values_list('name', *roles, named=True)
- else:
- team_names = []
- for teams_dict in desired_states.values():
- team_names.extend(teams_dict.keys())
- for sub_dict in teams_dict.values():
- for role_name in sub_dict:
- if sub_dict[role_name] is None:
- continue
- if role_name not in roles:
- roles.append(role_name)
- model_roles = Team.objects.filter(name__in=team_names).values_list('name', 'organization__name', *roles, named=True)
-
- for row in model_roles:
- for role_name in roles:
- if object_type == 'organization':
- desired_state = desired_states.get(row.name, {})
- else:
- desired_state = desired_states.get(row.organization__name, {}).get(row.name, {})
-
- if desired_state.get(role_name, None) is None:
- # The mapping was not defined for this [org/team]/role so we can just pass
- continue
-
- # If somehow the auth adapter knows about an items role but that role is not defined in the DB we are going to print a pretty error
- # This is your classic safety net that we should never hit; but here you are reading this comment... good luck and Godspeed.
- role_id = getattr(row, role_name, None)
- if role_id is None:
- logger.error("{} adapter wanted to manage role {} of {} {} but that role is not defined".format(source, role_name, object_type, row.name))
- continue
-
- if desired_state[role_name]:
- # The desired state was the user mapped into the object_type, if the user was not mapped in map them in
- if role_id not in users_roles:
- logger.debug("{} adapter adding user {} to {} {} as {}".format(source, user.username, object_type, row.name, role_name))
- user.roles.add(role_id)
- else:
- # The desired state was the user was not mapped into the org, if the user has the permission remove it
- if role_id in users_roles:
- logger.debug("{} adapter removing user {} permission of {} from {} {}".format(source, user.username, role_name, object_type, row.name))
- user.roles.remove(role_id)
-
-
-def create_org_and_teams(org_list, team_map, adapter, can_create=True):
- #
- # org_list is a set of organization names
- # team_map is a dict of {: }
- #
- # Move this junk into save of the settings for performance later, there is no need to do that here
- # with maybe the exception of someone defining this in settings before the server is started?
- # ==============================================================================================================
-
- if not can_create:
- logger.debug(f"Adapter {adapter} is not allowed to create orgs/teams")
- return
-
- # Get all of the IDs and names of orgs in the DB and create any new org defined in LDAP that does not exist in the DB
- existing_orgs = get_orgs_by_ids()
-
- # Parse through orgs and teams provided and create a list of unique items we care about creating
- all_orgs = list(set(org_list))
- all_teams = []
- for team_name in team_map:
- org_name = team_map[team_name]
- if org_name:
- if org_name not in all_orgs:
- all_orgs.append(org_name)
- # We don't have to test if this is in all_teams because team_map is already a hash
- all_teams.append(team_name)
- else:
- # The UI should prevent this condition so this is just a double check to prevent a stack trace....
- # although the rest of the login process might stack later on
- logger.error("{} adapter is attempting to create a team {} but it does not have an org".format(adapter, team_name))
-
- for org_name in all_orgs:
- if org_name and org_name not in existing_orgs:
- logger.info("{} adapter is creating org {}".format(adapter, org_name))
- try:
- new_org = get_or_create_org_with_default_galaxy_cred(name=org_name)
- except IntegrityError:
- # Another thread must have created this org before we did so now we need to get it
- new_org = get_or_create_org_with_default_galaxy_cred(name=org_name)
- # Add the org name to the existing orgs since we created it and we may need it to build the teams below
- existing_orgs[org_name] = new_org.id
-
- # Do the same for teams
- existing_team_names = list(Team.objects.all().values_list('name', flat=True))
- for team_name in all_teams:
- if team_name not in existing_team_names:
- logger.info("{} adapter is creating team {} in org {}".format(adapter, team_name, team_map[team_name]))
- try:
- Team.objects.create(name=team_name, organization_id=existing_orgs[team_map[team_name]])
- except IntegrityError:
- # If another process got here before us that is ok because we don't need the ID from this team or anything
- pass
- # End move some day
- # ==============================================================================================================
-
-
-def get_or_create_org_with_default_galaxy_cred(**kwargs):
- from awx.main.models import Organization, Credential
-
- (org, org_created) = Organization.objects.get_or_create(**kwargs)
- if org_created:
- logger.debug("Created org {} (id {}) from {}".format(org.name, org.id, kwargs))
- public_galaxy_credential = Credential.objects.filter(managed=True, name='Ansible Galaxy').first()
- if public_galaxy_credential is not None:
- org.galaxy_credentials.add(public_galaxy_credential)
- logger.debug("Added default Ansible Galaxy credential to org")
- else:
- logger.debug("Could not find default Ansible Galaxy credential to add to org")
- return org
diff --git a/awx/sso/conf.py b/awx/sso/conf.py
deleted file mode 100644
index a835399a3dc7..000000000000
--- a/awx/sso/conf.py
+++ /dev/null
@@ -1,1614 +0,0 @@
-# Python
-import collections
-import urllib.parse as urlparse
-
-# Django
-from django.conf import settings
-from django.urls import reverse
-from django.utils.translation import gettext_lazy as _
-
-# Django REST Framework
-from rest_framework import serializers
-
-# AWX
-from awx.conf import register, register_validate, fields
-from awx.sso.fields import (
- AuthenticationBackendsField,
- LDAPConnectionOptionsField,
- LDAPDNField,
- LDAPDNWithUserField,
- LDAPGroupTypeField,
- LDAPGroupTypeParamsField,
- LDAPOrganizationMapField,
- LDAPSearchField,
- LDAPSearchUnionField,
- LDAPServerURIField,
- LDAPTeamMapField,
- LDAPUserAttrMapField,
- LDAPUserFlagsField,
- SAMLContactField,
- SAMLEnabledIdPsField,
- SAMLOrgAttrField,
- SAMLOrgInfoField,
- SAMLSecurityField,
- SAMLTeamAttrField,
- SAMLUserFlagsAttrField,
- SocialOrganizationMapField,
- SocialTeamMapField,
-)
-from awx.main.validators import validate_private_key, validate_certificate
-from awx.sso.validators import validate_ldap_bind_dn, validate_tacacsplus_disallow_nonascii # noqa
-
-
-class SocialAuthCallbackURL(object):
- def __init__(self, provider):
- self.provider = provider
-
- def __call__(self):
- path = reverse('social:complete', args=(self.provider,))
- return urlparse.urljoin(settings.TOWER_URL_BASE, path)
-
-
-SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT = _(
- '''\
-Mapping to organization admins/users from social auth accounts. This setting
-controls which users are placed into which organizations based on their
-username and email address. Configuration details are available in the
-documentation.\
-'''
-)
-
-# FIXME: /regex/gim (flags)
-
-SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER = collections.OrderedDict(
- [
- ('Default', collections.OrderedDict([('users', True)])),
- ('Test Org', collections.OrderedDict([('admins', ['admin@example.com']), ('auditors', ['auditor@example.com']), ('users', True)])),
- (
- 'Test Org 2',
- collections.OrderedDict(
- [
- ('admins', ['admin@example.com', r'/^tower-[^@]+*?@.*$/']),
- ('remove_admins', True),
- ('users', r'/^[^@].*?@example\.com$/i'),
- ('remove_users', True),
- ]
- ),
- ),
- ]
-)
-
-SOCIAL_AUTH_TEAM_MAP_HELP_TEXT = _(
- '''\
-Mapping of team members (users) from social auth accounts. Configuration
-details are available in the documentation.\
-'''
-)
-
-SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER = collections.OrderedDict(
- [
- ('My Team', collections.OrderedDict([('organization', 'Test Org'), ('users', [r'/^[^@]+?@test\.example\.com$/']), ('remove', True)])),
- ('Other Team', collections.OrderedDict([('organization', 'Test Org 2'), ('users', r'/^[^@]+?@test2\.example\.com$/i'), ('remove', False)])),
- ]
-)
-
-###############################################################################
-# AUTHENTICATION BACKENDS DYNAMIC SETTING
-###############################################################################
-
-register(
- 'AUTHENTICATION_BACKENDS',
- field_class=AuthenticationBackendsField,
- label=_('Authentication Backends'),
- help_text=_('List of authentication backends that are enabled based on ' 'license features and other authentication settings.'),
- read_only=True,
- depends_on=AuthenticationBackendsField.get_all_required_settings(),
- category=_('Authentication'),
- category_slug='authentication',
-)
-
-register(
- 'SOCIAL_AUTH_ORGANIZATION_MAP',
- field_class=SocialOrganizationMapField,
- allow_null=True,
- default=None,
- label=_('Social Auth Organization Map'),
- help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT,
- category=_('Authentication'),
- category_slug='authentication',
- placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER,
-)
-
-register(
- 'SOCIAL_AUTH_TEAM_MAP',
- field_class=SocialTeamMapField,
- allow_null=True,
- default=None,
- label=_('Social Auth Team Map'),
- help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT,
- category=_('Authentication'),
- category_slug='authentication',
- placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER,
-)
-
-register(
- 'SOCIAL_AUTH_USER_FIELDS',
- field_class=fields.StringListField,
- allow_null=True,
- default=None,
- label=_('Social Auth User Fields'),
- help_text=_(
- 'When set to an empty list `[]`, this setting prevents new user '
- 'accounts from being created. Only users who have previously '
- 'logged in using social auth or have a user account with a '
- 'matching email address will be able to login.'
- ),
- category=_('Authentication'),
- category_slug='authentication',
- placeholder=['username', 'email'],
-)
-
-###############################################################################
-# LDAP AUTHENTICATION SETTINGS
-###############################################################################
-
-
-def _register_ldap(append=None):
- append_str = '_{}'.format(append) if append else ''
-
- register(
- 'AUTH_LDAP{}_SERVER_URI'.format(append_str),
- field_class=LDAPServerURIField,
- allow_blank=True,
- default='',
- label=_('LDAP Server URI'),
- help_text=_(
- 'URI to connect to LDAP server, such as "ldap://ldap.example.com:389" '
- '(non-SSL) or "ldaps://ldap.example.com:636" (SSL). Multiple LDAP '
- 'servers may be specified by separating with spaces or commas. LDAP '
- 'authentication is disabled if this parameter is empty.'
- ),
- category=_('LDAP'),
- category_slug='ldap',
- placeholder='ldaps://ldap.example.com:636',
- )
-
- register(
- 'AUTH_LDAP{}_BIND_DN'.format(append_str),
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- validators=[validate_ldap_bind_dn],
- label=_('LDAP Bind DN'),
- help_text=_(
- 'DN (Distinguished Name) of user to bind for all search queries. This'
- ' is the system user account we will use to login to query LDAP for other'
- ' user information. Refer to the documentation for example syntax.'
- ),
- category=_('LDAP'),
- category_slug='ldap',
- )
-
- register(
- 'AUTH_LDAP{}_BIND_PASSWORD'.format(append_str),
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('LDAP Bind Password'),
- help_text=_('Password used to bind LDAP user account.'),
- category=_('LDAP'),
- category_slug='ldap',
- encrypted=True,
- )
-
- register(
- 'AUTH_LDAP{}_START_TLS'.format(append_str),
- field_class=fields.BooleanField,
- default=False,
- label=_('LDAP Start TLS'),
- help_text=_('Whether to enable TLS when the LDAP connection is not using SSL.'),
- category=_('LDAP'),
- category_slug='ldap',
- )
-
- register(
- 'AUTH_LDAP{}_CONNECTION_OPTIONS'.format(append_str),
- field_class=LDAPConnectionOptionsField,
- default={'OPT_REFERRALS': 0, 'OPT_NETWORK_TIMEOUT': 30},
- label=_('LDAP Connection Options'),
- help_text=_(
- 'Additional options to set for the LDAP connection. LDAP '
- 'referrals are disabled by default (to prevent certain LDAP '
- 'queries from hanging with AD). Option names should be strings '
- '(e.g. "OPT_REFERRALS"). Refer to '
- 'https://www.python-ldap.org/doc/html/ldap.html#options for '
- 'possible options and values that can be set.'
- ),
- category=_('LDAP'),
- category_slug='ldap',
- placeholder=collections.OrderedDict([('OPT_REFERRALS', 0), ('OPT_NETWORK_TIMEOUT', 30)]),
- )
-
- register(
- 'AUTH_LDAP{}_USER_SEARCH'.format(append_str),
- field_class=LDAPSearchUnionField,
- default=[],
- label=_('LDAP User Search'),
- help_text=_(
- 'LDAP search query to find users. Any user that matches the given '
- 'pattern will be able to login to the service. The user should also be '
- 'mapped into an organization (as defined in the '
- 'AUTH_LDAP_ORGANIZATION_MAP setting). If multiple search queries '
- 'need to be supported use of "LDAPUnion" is possible. See '
- 'the documentation for details.'
- ),
- category=_('LDAP'),
- category_slug='ldap',
- placeholder=('OU=Users,DC=example,DC=com', 'SCOPE_SUBTREE', '(sAMAccountName=%(user)s)'),
- )
-
- register(
- 'AUTH_LDAP{}_USER_DN_TEMPLATE'.format(append_str),
- field_class=LDAPDNWithUserField,
- allow_blank=True,
- allow_null=True,
- default=None,
- label=_('LDAP User DN Template'),
- help_text=_(
- 'Alternative to user search, if user DNs are all of the same '
- 'format. This approach is more efficient for user lookups than '
- 'searching if it is usable in your organizational environment. If '
- 'this setting has a value it will be used instead of '
- 'AUTH_LDAP_USER_SEARCH.'
- ),
- category=_('LDAP'),
- category_slug='ldap',
- placeholder='uid=%(user)s,OU=Users,DC=example,DC=com',
- )
-
- register(
- 'AUTH_LDAP{}_USER_ATTR_MAP'.format(append_str),
- field_class=LDAPUserAttrMapField,
- default={},
- label=_('LDAP User Attribute Map'),
- help_text=_(
- 'Mapping of LDAP user schema to API user attributes. The default'
- ' setting is valid for ActiveDirectory but users with other LDAP'
- ' configurations may need to change the values. Refer to the'
- ' documentation for additional details.'
- ),
- category=_('LDAP'),
- category_slug='ldap',
- placeholder=collections.OrderedDict([('first_name', 'givenName'), ('last_name', 'sn'), ('email', 'mail')]),
- )
-
- register(
- 'AUTH_LDAP{}_GROUP_SEARCH'.format(append_str),
- field_class=LDAPSearchField,
- default=[],
- label=_('LDAP Group Search'),
- help_text=_(
- 'Users are mapped to organizations based on their membership in LDAP'
- ' groups. This setting defines the LDAP search query to find groups. '
- 'Unlike the user search, group search does not support LDAPSearchUnion.'
- ),
- category=_('LDAP'),
- category_slug='ldap',
- placeholder=('DC=example,DC=com', 'SCOPE_SUBTREE', '(objectClass=group)'),
- )
-
- register(
- 'AUTH_LDAP{}_GROUP_TYPE'.format(append_str),
- field_class=LDAPGroupTypeField,
- label=_('LDAP Group Type'),
- help_text=_(
- 'The group type may need to be changed based on the type of the '
- 'LDAP server. Values are listed at: '
- 'https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups'
- ),
- category=_('LDAP'),
- category_slug='ldap',
- default='MemberDNGroupType',
- depends_on=['AUTH_LDAP{}_GROUP_TYPE_PARAMS'.format(append_str)],
- )
-
- register(
- 'AUTH_LDAP{}_GROUP_TYPE_PARAMS'.format(append_str),
- field_class=LDAPGroupTypeParamsField,
- label=_('LDAP Group Type Parameters'),
- help_text=_('Key value parameters to send the chosen group type init method.'),
- category=_('LDAP'),
- category_slug='ldap',
- default=collections.OrderedDict([('member_attr', 'member'), ('name_attr', 'cn')]),
- placeholder=collections.OrderedDict([('ldap_group_user_attr', 'legacyuid'), ('member_attr', 'member'), ('name_attr', 'cn')]),
- depends_on=['AUTH_LDAP{}_GROUP_TYPE'.format(append_str)],
- )
-
- register(
- 'AUTH_LDAP{}_REQUIRE_GROUP'.format(append_str),
- field_class=LDAPDNField,
- allow_blank=True,
- allow_null=True,
- default=None,
- label=_('LDAP Require Group'),
- help_text=_(
- 'Group DN required to login. If specified, user must be a member '
- 'of this group to login via LDAP. If not set, everyone in LDAP '
- 'that matches the user search will be able to login to the service. '
- 'Only one require group is supported.'
- ),
- category=_('LDAP'),
- category_slug='ldap',
- placeholder='CN=Service Users,OU=Users,DC=example,DC=com',
- )
-
- register(
- 'AUTH_LDAP{}_DENY_GROUP'.format(append_str),
- field_class=LDAPDNField,
- allow_blank=True,
- allow_null=True,
- default=None,
- label=_('LDAP Deny Group'),
- help_text=_(
- 'Group DN denied from login. If specified, user will not be ' 'allowed to login if a member of this group. Only one deny group ' 'is supported.'
- ),
- category=_('LDAP'),
- category_slug='ldap',
- placeholder='CN=Disabled Users,OU=Users,DC=example,DC=com',
- )
-
- register(
- 'AUTH_LDAP{}_USER_FLAGS_BY_GROUP'.format(append_str),
- field_class=LDAPUserFlagsField,
- default={},
- label=_('LDAP User Flags By Group'),
- help_text=_(
- 'Retrieve users from a given group. At this time, superuser and system'
- ' auditors are the only groups supported. Refer to the'
- ' documentation for more detail.'
- ),
- category=_('LDAP'),
- category_slug='ldap',
- placeholder=collections.OrderedDict(
- [('is_superuser', 'CN=Domain Admins,CN=Users,DC=example,DC=com'), ('is_system_auditor', 'CN=Domain Auditors,CN=Users,DC=example,DC=com')]
- ),
- )
-
- register(
- 'AUTH_LDAP{}_ORGANIZATION_MAP'.format(append_str),
- field_class=LDAPOrganizationMapField,
- default={},
- label=_('LDAP Organization Map'),
- help_text=_(
- 'Mapping between organization admins/users and LDAP groups. This '
- 'controls which users are placed into which organizations '
- 'relative to their LDAP group memberships. Configuration details '
- 'are available in the documentation.'
- ),
- category=_('LDAP'),
- category_slug='ldap',
- placeholder=collections.OrderedDict(
- [
- (
- 'Test Org',
- collections.OrderedDict(
- [
- ('admins', 'CN=Domain Admins,CN=Users,DC=example,DC=com'),
- ('auditors', 'CN=Domain Auditors,CN=Users,DC=example,DC=com'),
- ('users', ['CN=Domain Users,CN=Users,DC=example,DC=com']),
- ('remove_users', True),
- ('remove_admins', True),
- ]
- ),
- ),
- (
- 'Test Org 2',
- collections.OrderedDict(
- [('admins', 'CN=Administrators,CN=Builtin,DC=example,DC=com'), ('users', True), ('remove_users', True), ('remove_admins', True)]
- ),
- ),
- ]
- ),
- )
-
- register(
- 'AUTH_LDAP{}_TEAM_MAP'.format(append_str),
- field_class=LDAPTeamMapField,
- default={},
- label=_('LDAP Team Map'),
- help_text=_('Mapping between team members (users) and LDAP groups. Configuration' ' details are available in the documentation.'),
- category=_('LDAP'),
- category_slug='ldap',
- placeholder=collections.OrderedDict(
- [
- (
- 'My Team',
- collections.OrderedDict([('organization', 'Test Org'), ('users', ['CN=Domain Users,CN=Users,DC=example,DC=com']), ('remove', True)]),
- ),
- (
- 'Other Team',
- collections.OrderedDict([('organization', 'Test Org 2'), ('users', 'CN=Other Users,CN=Users,DC=example,DC=com'), ('remove', False)]),
- ),
- ]
- ),
- )
-
-
-_register_ldap()
-_register_ldap('1')
-_register_ldap('2')
-_register_ldap('3')
-_register_ldap('4')
-_register_ldap('5')
-
-###############################################################################
-# RADIUS AUTHENTICATION SETTINGS
-###############################################################################
-
-register(
- 'RADIUS_SERVER',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('RADIUS Server'),
- help_text=_('Hostname/IP of RADIUS server. RADIUS authentication is ' 'disabled if this setting is empty.'),
- category=_('RADIUS'),
- category_slug='radius',
- placeholder='radius.example.com',
-)
-
-register(
- 'RADIUS_PORT',
- field_class=fields.IntegerField,
- min_value=1,
- max_value=65535,
- default=1812,
- label=_('RADIUS Port'),
- help_text=_('Port of RADIUS server.'),
- category=_('RADIUS'),
- category_slug='radius',
-)
-
-register(
- 'RADIUS_SECRET',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('RADIUS Secret'),
- help_text=_('Shared secret for authenticating to RADIUS server.'),
- category=_('RADIUS'),
- category_slug='radius',
- encrypted=True,
-)
-
-###############################################################################
-# TACACSPLUS AUTHENTICATION SETTINGS
-###############################################################################
-
-register(
- 'TACACSPLUS_HOST',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('TACACS+ Server'),
- help_text=_('Hostname of TACACS+ server.'),
- category=_('TACACS+'),
- category_slug='tacacsplus',
-)
-
-register(
- 'TACACSPLUS_PORT',
- field_class=fields.IntegerField,
- min_value=1,
- max_value=65535,
- default=49,
- label=_('TACACS+ Port'),
- help_text=_('Port number of TACACS+ server.'),
- category=_('TACACS+'),
- category_slug='tacacsplus',
-)
-
-register(
- 'TACACSPLUS_SECRET',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- validators=[validate_tacacsplus_disallow_nonascii],
- label=_('TACACS+ Secret'),
- help_text=_('Shared secret for authenticating to TACACS+ server.'),
- category=_('TACACS+'),
- category_slug='tacacsplus',
- encrypted=True,
-)
-
-register(
- 'TACACSPLUS_SESSION_TIMEOUT',
- field_class=fields.IntegerField,
- min_value=0,
- default=5,
- label=_('TACACS+ Auth Session Timeout'),
- help_text=_('TACACS+ session timeout value in seconds, 0 disables timeout.'),
- category=_('TACACS+'),
- category_slug='tacacsplus',
- unit=_('seconds'),
-)
-
-register(
- 'TACACSPLUS_AUTH_PROTOCOL',
- field_class=fields.ChoiceField,
- choices=['ascii', 'pap'],
- default='ascii',
- label=_('TACACS+ Authentication Protocol'),
- help_text=_('Choose the authentication protocol used by TACACS+ client.'),
- category=_('TACACS+'),
- category_slug='tacacsplus',
-)
-
-###############################################################################
-# GOOGLE OAUTH2 AUTHENTICATION SETTINGS
-###############################################################################
-
-register(
- 'SOCIAL_AUTH_GOOGLE_OAUTH2_CALLBACK_URL',
- field_class=fields.CharField,
- read_only=True,
- default=SocialAuthCallbackURL('google-oauth2'),
- label=_('Google OAuth2 Callback URL'),
- help_text=_(
- 'Provide this URL as the callback URL for your application as part ' 'of your registration process. Refer to the ' 'documentation for more detail.'
- ),
- category=_('Google OAuth2'),
- category_slug='google-oauth2',
- depends_on=['TOWER_URL_BASE'],
-)
-
-register(
- 'SOCIAL_AUTH_GOOGLE_OAUTH2_KEY',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('Google OAuth2 Key'),
- help_text=_('The OAuth2 key from your web application.'),
- category=_('Google OAuth2'),
- category_slug='google-oauth2',
- placeholder='528620852399-gm2dt4hrl2tsj67fqamk09k1e0ad6gd8.apps.googleusercontent.com',
-)
-
-register(
- 'SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('Google OAuth2 Secret'),
- help_text=_('The OAuth2 secret from your web application.'),
- category=_('Google OAuth2'),
- category_slug='google-oauth2',
- placeholder='q2fMVCmEregbg-drvebPp8OW',
- encrypted=True,
-)
-
-register(
- 'SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS',
- field_class=fields.StringListField,
- default=[],
- label=_('Google OAuth2 Allowed Domains'),
- help_text=_('Update this setting to restrict the domains who are allowed to ' 'login using Google OAuth2.'),
- category=_('Google OAuth2'),
- category_slug='google-oauth2',
- placeholder=['example.com'],
-)
-
-register(
- 'SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS',
- field_class=fields.DictField,
- default={},
- label=_('Google OAuth2 Extra Arguments'),
- help_text=_(
- 'Extra arguments for Google OAuth2 login. You can restrict it to'
- ' only allow a single domain to authenticate, even if the user is'
- ' logged in with multple Google accounts. Refer to the'
- ' documentation for more detail.'
- ),
- category=_('Google OAuth2'),
- category_slug='google-oauth2',
- placeholder={'hd': 'example.com'},
-)
-
-register(
- 'SOCIAL_AUTH_GOOGLE_OAUTH2_ORGANIZATION_MAP',
- field_class=SocialOrganizationMapField,
- allow_null=True,
- default=None,
- label=_('Google OAuth2 Organization Map'),
- help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT,
- category=_('Google OAuth2'),
- category_slug='google-oauth2',
- placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER,
-)
-
-register(
- 'SOCIAL_AUTH_GOOGLE_OAUTH2_TEAM_MAP',
- field_class=SocialTeamMapField,
- allow_null=True,
- default=None,
- label=_('Google OAuth2 Team Map'),
- help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT,
- category=_('Google OAuth2'),
- category_slug='google-oauth2',
- placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER,
-)
-
-###############################################################################
-# GITHUB OAUTH2 AUTHENTICATION SETTINGS
-###############################################################################
-
-register(
- 'SOCIAL_AUTH_GITHUB_CALLBACK_URL',
- field_class=fields.CharField,
- read_only=True,
- default=SocialAuthCallbackURL('github'),
- label=_('GitHub OAuth2 Callback URL'),
- help_text=_(
- 'Provide this URL as the callback URL for your application as part ' 'of your registration process. Refer to the ' 'documentation for more detail.'
- ),
- category=_('GitHub OAuth2'),
- category_slug='github',
- depends_on=['TOWER_URL_BASE'],
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_KEY',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('GitHub OAuth2 Key'),
- help_text=_('The OAuth2 key (Client ID) from your GitHub developer application.'),
- category=_('GitHub OAuth2'),
- category_slug='github',
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_SECRET',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('GitHub OAuth2 Secret'),
- help_text=_('The OAuth2 secret (Client Secret) from your GitHub developer application.'),
- category=_('GitHub OAuth2'),
- category_slug='github',
- encrypted=True,
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP',
- field_class=SocialOrganizationMapField,
- allow_null=True,
- default=None,
- label=_('GitHub OAuth2 Organization Map'),
- help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT,
- category=_('GitHub OAuth2'),
- category_slug='github',
- placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER,
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_TEAM_MAP',
- field_class=SocialTeamMapField,
- allow_null=True,
- default=None,
- label=_('GitHub OAuth2 Team Map'),
- help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT,
- category=_('GitHub OAuth2'),
- category_slug='github',
- placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER,
-)
-
-###############################################################################
-# GITHUB ORG OAUTH2 AUTHENTICATION SETTINGS
-###############################################################################
-
-register(
- 'SOCIAL_AUTH_GITHUB_ORG_CALLBACK_URL',
- field_class=fields.CharField,
- read_only=True,
- default=SocialAuthCallbackURL('github-org'),
- label=_('GitHub Organization OAuth2 Callback URL'),
- help_text=_(
- 'Provide this URL as the callback URL for your application as part ' 'of your registration process. Refer to the ' 'documentation for more detail.'
- ),
- category=_('GitHub Organization OAuth2'),
- category_slug='github-org',
- depends_on=['TOWER_URL_BASE'],
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ORG_KEY',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('GitHub Organization OAuth2 Key'),
- help_text=_('The OAuth2 key (Client ID) from your GitHub organization application.'),
- category=_('GitHub Organization OAuth2'),
- category_slug='github-org',
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ORG_SECRET',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('GitHub Organization OAuth2 Secret'),
- help_text=_('The OAuth2 secret (Client Secret) from your GitHub organization application.'),
- category=_('GitHub Organization OAuth2'),
- category_slug='github-org',
- encrypted=True,
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ORG_NAME',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('GitHub Organization Name'),
- help_text=_('The name of your GitHub organization, as used in your ' 'organization\'s URL: https://github.com//.'),
- category=_('GitHub Organization OAuth2'),
- category_slug='github-org',
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ORG_ORGANIZATION_MAP',
- field_class=SocialOrganizationMapField,
- allow_null=True,
- default=None,
- label=_('GitHub Organization OAuth2 Organization Map'),
- help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT,
- category=_('GitHub Organization OAuth2'),
- category_slug='github-org',
- placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER,
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ORG_TEAM_MAP',
- field_class=SocialTeamMapField,
- allow_null=True,
- default=None,
- label=_('GitHub Organization OAuth2 Team Map'),
- help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT,
- category=_('GitHub Organization OAuth2'),
- category_slug='github-org',
- placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER,
-)
-
-###############################################################################
-# GITHUB TEAM OAUTH2 AUTHENTICATION SETTINGS
-###############################################################################
-
-register(
- 'SOCIAL_AUTH_GITHUB_TEAM_CALLBACK_URL',
- field_class=fields.CharField,
- read_only=True,
- default=SocialAuthCallbackURL('github-team'),
- label=_('GitHub Team OAuth2 Callback URL'),
- help_text=_(
- 'Create an organization-owned application at '
- 'https://github.com/organizations//settings/applications '
- 'and obtain an OAuth2 key (Client ID) and secret (Client Secret). '
- 'Provide this URL as the callback URL for your application.'
- ),
- category=_('GitHub Team OAuth2'),
- category_slug='github-team',
- depends_on=['TOWER_URL_BASE'],
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_TEAM_KEY',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('GitHub Team OAuth2 Key'),
- help_text=_('The OAuth2 key (Client ID) from your GitHub organization application.'),
- category=_('GitHub Team OAuth2'),
- category_slug='github-team',
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_TEAM_SECRET',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('GitHub Team OAuth2 Secret'),
- help_text=_('The OAuth2 secret (Client Secret) from your GitHub organization application.'),
- category=_('GitHub Team OAuth2'),
- category_slug='github-team',
- encrypted=True,
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_TEAM_ID',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('GitHub Team ID'),
- help_text=_('Find the numeric team ID using the Github API: ' 'http://fabian-kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/.'),
- category=_('GitHub Team OAuth2'),
- category_slug='github-team',
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_TEAM_ORGANIZATION_MAP',
- field_class=SocialOrganizationMapField,
- allow_null=True,
- default=None,
- label=_('GitHub Team OAuth2 Organization Map'),
- help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT,
- category=_('GitHub Team OAuth2'),
- category_slug='github-team',
- placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER,
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_TEAM_TEAM_MAP',
- field_class=SocialTeamMapField,
- allow_null=True,
- default=None,
- label=_('GitHub Team OAuth2 Team Map'),
- help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT,
- category=_('GitHub Team OAuth2'),
- category_slug='github-team',
- placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER,
-)
-
-###############################################################################
-# GITHUB ENTERPRISE OAUTH2 AUTHENTICATION SETTINGS
-###############################################################################
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_CALLBACK_URL',
- field_class=fields.CharField,
- read_only=True,
- default=SocialAuthCallbackURL('github-enterprise'),
- label=_('GitHub Enterprise OAuth2 Callback URL'),
- help_text=_(
- 'Provide this URL as the callback URL for your application as part ' 'of your registration process. Refer to the ' 'documentation for more detail.'
- ),
- category=_('GitHub Enterprise OAuth2'),
- category_slug='github-enterprise',
- depends_on=['TOWER_URL_BASE'],
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_URL',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('GitHub Enterprise URL'),
- help_text=_('The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise ' 'documentation for more details.'),
- category=_('GitHub Enterprise OAuth2'),
- category_slug='github-enterprise',
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('GitHub Enterprise API URL'),
- help_text=_(
- 'The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github ' 'Enterprise documentation for more details.'
- ),
- category=_('GitHub Enterprise OAuth2'),
- category_slug='github-enterprise',
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('GitHub Enterprise OAuth2 Key'),
- help_text=_('The OAuth2 key (Client ID) from your GitHub Enterprise developer application.'),
- category=_('GitHub Enterprise OAuth2'),
- category_slug='github-enterprise',
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('GitHub Enterprise OAuth2 Secret'),
- help_text=_('The OAuth2 secret (Client Secret) from your GitHub Enterprise developer application.'),
- category=_('GitHub OAuth2'),
- category_slug='github-enterprise',
- encrypted=True,
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORGANIZATION_MAP',
- field_class=SocialOrganizationMapField,
- allow_null=True,
- default=None,
- label=_('GitHub Enterprise OAuth2 Organization Map'),
- help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT,
- category=_('GitHub Enterprise OAuth2'),
- category_slug='github-enterprise',
- placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER,
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_MAP',
- field_class=SocialTeamMapField,
- allow_null=True,
- default=None,
- label=_('GitHub Enterprise OAuth2 Team Map'),
- help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT,
- category=_('GitHub Enterprise OAuth2'),
- category_slug='github-enterprise',
- placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER,
-)
-
-###############################################################################
-# GITHUB ENTERPRISE ORG OAUTH2 AUTHENTICATION SETTINGS
-###############################################################################
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_CALLBACK_URL',
- field_class=fields.CharField,
- read_only=True,
- default=SocialAuthCallbackURL('github-enterprise-org'),
- label=_('GitHub Enterprise Organization OAuth2 Callback URL'),
- help_text=_(
- 'Provide this URL as the callback URL for your application as part ' 'of your registration process. Refer to the ' 'documentation for more detail.'
- ),
- category=_('GitHub Enterprise Organization OAuth2'),
- category_slug='github-enterprise-org',
- depends_on=['TOWER_URL_BASE'],
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('GitHub Enterprise Organization URL'),
- help_text=_('The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise ' 'documentation for more details.'),
- category=_('GitHub Enterprise OAuth2'),
- category_slug='github-enterprise-org',
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('GitHub Enterprise Organization API URL'),
- help_text=_(
- 'The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github ' 'Enterprise documentation for more details.'
- ),
- category=_('GitHub Enterprise OAuth2'),
- category_slug='github-enterprise-org',
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('GitHub Enterprise Organization OAuth2 Key'),
- help_text=_('The OAuth2 key (Client ID) from your GitHub Enterprise organization application.'),
- category=_('GitHub Enterprise Organization OAuth2'),
- category_slug='github-enterprise-org',
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_SECRET',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('GitHub Enterprise Organization OAuth2 Secret'),
- help_text=_('The OAuth2 secret (Client Secret) from your GitHub Enterprise organization application.'),
- category=_('GitHub Enterprise Organization OAuth2'),
- category_slug='github-enterprise-org',
- encrypted=True,
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('GitHub Enterprise Organization Name'),
- help_text=_('The name of your GitHub Enterprise organization, as used in your ' 'organization\'s URL: https://github.com//.'),
- category=_('GitHub Enterprise Organization OAuth2'),
- category_slug='github-enterprise-org',
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_ORGANIZATION_MAP',
- field_class=SocialOrganizationMapField,
- allow_null=True,
- default=None,
- label=_('GitHub Enterprise Organization OAuth2 Organization Map'),
- help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT,
- category=_('GitHub Enterprise Organization OAuth2'),
- category_slug='github-enterprise-org',
- placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER,
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_TEAM_MAP',
- field_class=SocialTeamMapField,
- allow_null=True,
- default=None,
- label=_('GitHub Enterprise Organization OAuth2 Team Map'),
- help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT,
- category=_('GitHub Enterprise Organization OAuth2'),
- category_slug='github-enterprise-org',
- placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER,
-)
-
-###############################################################################
-# GITHUB ENTERPRISE TEAM OAUTH2 AUTHENTICATION SETTINGS
-###############################################################################
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_CALLBACK_URL',
- field_class=fields.CharField,
- read_only=True,
- default=SocialAuthCallbackURL('github-enterprise-team'),
- label=_('GitHub Enterprise Team OAuth2 Callback URL'),
- help_text=_(
- 'Create an organization-owned application at '
- 'https://github.com/organizations//settings/applications '
- 'and obtain an OAuth2 key (Client ID) and secret (Client Secret). '
- 'Provide this URL as the callback URL for your application.'
- ),
- category=_('GitHub Enterprise Team OAuth2'),
- category_slug='github-enterprise-team',
- depends_on=['TOWER_URL_BASE'],
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('GitHub Enterprise Team URL'),
- help_text=_('The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise ' 'documentation for more details.'),
- category=_('GitHub Enterprise OAuth2'),
- category_slug='github-enterprise-team',
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('GitHub Enterprise Team API URL'),
- help_text=_(
- 'The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github ' 'Enterprise documentation for more details.'
- ),
- category=_('GitHub Enterprise OAuth2'),
- category_slug='github-enterprise-team',
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('GitHub Enterprise Team OAuth2 Key'),
- help_text=_('The OAuth2 key (Client ID) from your GitHub Enterprise organization application.'),
- category=_('GitHub Enterprise Team OAuth2'),
- category_slug='github-enterprise-team',
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_SECRET',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('GitHub Enterprise Team OAuth2 Secret'),
- help_text=_('The OAuth2 secret (Client Secret) from your GitHub Enterprise organization application.'),
- category=_('GitHub Enterprise Team OAuth2'),
- category_slug='github-enterprise-team',
- encrypted=True,
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('GitHub Enterprise Team ID'),
- help_text=_('Find the numeric team ID using the Github Enterprise API: ' 'http://fabian-kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/.'),
- category=_('GitHub Enterprise Team OAuth2'),
- category_slug='github-enterprise-team',
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ORGANIZATION_MAP',
- field_class=SocialOrganizationMapField,
- allow_null=True,
- default=None,
- label=_('GitHub Enterprise Team OAuth2 Organization Map'),
- help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT,
- category=_('GitHub Enterprise Team OAuth2'),
- category_slug='github-enterprise-team',
- placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER,
-)
-
-register(
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_TEAM_MAP',
- field_class=SocialTeamMapField,
- allow_null=True,
- default=None,
- label=_('GitHub Enterprise Team OAuth2 Team Map'),
- help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT,
- category=_('GitHub Enterprise Team OAuth2'),
- category_slug='github-enterprise-team',
- placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER,
-)
-
-###############################################################################
-# MICROSOFT AZURE ACTIVE DIRECTORY SETTINGS
-###############################################################################
-
-register(
- 'SOCIAL_AUTH_AZUREAD_OAUTH2_CALLBACK_URL',
- field_class=fields.CharField,
- read_only=True,
- default=SocialAuthCallbackURL('azuread-oauth2'),
- label=_('Azure AD OAuth2 Callback URL'),
- help_text=_(
- 'Provide this URL as the callback URL for your application as part' ' of your registration process. Refer to the' ' documentation for more detail. '
- ),
- category=_('Azure AD OAuth2'),
- category_slug='azuread-oauth2',
- depends_on=['TOWER_URL_BASE'],
-)
-
-register(
- 'SOCIAL_AUTH_AZUREAD_OAUTH2_KEY',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('Azure AD OAuth2 Key'),
- help_text=_('The OAuth2 key (Client ID) from your Azure AD application.'),
- category=_('Azure AD OAuth2'),
- category_slug='azuread-oauth2',
-)
-
-register(
- 'SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('Azure AD OAuth2 Secret'),
- help_text=_('The OAuth2 secret (Client Secret) from your Azure AD application.'),
- category=_('Azure AD OAuth2'),
- category_slug='azuread-oauth2',
- encrypted=True,
-)
-
-register(
- 'SOCIAL_AUTH_AZUREAD_OAUTH2_ORGANIZATION_MAP',
- field_class=SocialOrganizationMapField,
- allow_null=True,
- default=None,
- label=_('Azure AD OAuth2 Organization Map'),
- help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT,
- category=_('Azure AD OAuth2'),
- category_slug='azuread-oauth2',
- placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER,
-)
-
-register(
- 'SOCIAL_AUTH_AZUREAD_OAUTH2_TEAM_MAP',
- field_class=SocialTeamMapField,
- allow_null=True,
- default=None,
- label=_('Azure AD OAuth2 Team Map'),
- help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT,
- category=_('Azure AD OAuth2'),
- category_slug='azuread-oauth2',
- placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER,
-)
-
-###############################################################################
-# Generic OIDC AUTHENTICATION SETTINGS
-###############################################################################
-
-register(
- 'SOCIAL_AUTH_OIDC_KEY',
- field_class=fields.CharField,
- allow_null=False,
- default=None,
- label=_('OIDC Key'),
- help_text='The OIDC key (Client ID) from your IDP.',
- category=_('Generic OIDC'),
- category_slug='oidc',
-)
-
-register(
- 'SOCIAL_AUTH_OIDC_SECRET',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('OIDC Secret'),
- help_text=_('The OIDC secret (Client Secret) from your IDP.'),
- category=_('Generic OIDC'),
- category_slug='oidc',
- encrypted=True,
-)
-
-register(
- 'SOCIAL_AUTH_OIDC_OIDC_ENDPOINT',
- field_class=fields.CharField,
- allow_blank=True,
- default='',
- label=_('OIDC Provider URL'),
- help_text=_('The URL for your OIDC provider including the path up to /.well-known/openid-configuration'),
- category=_('Generic OIDC'),
- category_slug='oidc',
-)
-
-register(
- 'SOCIAL_AUTH_OIDC_VERIFY_SSL',
- field_class=fields.BooleanField,
- default=True,
- label=_('Verify OIDC Provider Certificate'),
- help_text=_('Verify the OIDV provider ssl certificate.'),
- category=_('Generic OIDC'),
- category_slug='oidc',
-)
-
-###############################################################################
-# SAML AUTHENTICATION SETTINGS
-###############################################################################
-
-
-def get_saml_metadata_url():
- return urlparse.urljoin(settings.TOWER_URL_BASE, reverse('sso:saml_metadata'))
-
-
-def get_saml_entity_id():
- return settings.TOWER_URL_BASE
-
-
-register(
- 'SAML_AUTO_CREATE_OBJECTS',
- field_class=fields.BooleanField,
- default=True,
- label=_('Automatically Create Organizations and Teams on SAML Login'),
- help_text=_('When enabled (the default), mapped Organizations and Teams ' 'will be created automatically on successful SAML login.'),
- category=_('SAML'),
- category_slug='saml',
-)
-
-register(
- 'SOCIAL_AUTH_SAML_CALLBACK_URL',
- field_class=fields.CharField,
- read_only=True,
- default=SocialAuthCallbackURL('saml'),
- label=_('SAML Assertion Consumer Service (ACS) URL'),
- help_text=_(
- 'Register the service as a service provider (SP) with each identity '
- 'provider (IdP) you have configured. Provide your SP Entity ID '
- 'and this ACS URL for your application.'
- ),
- category=_('SAML'),
- category_slug='saml',
- depends_on=['TOWER_URL_BASE'],
-)
-
-register(
- 'SOCIAL_AUTH_SAML_METADATA_URL',
- field_class=fields.CharField,
- read_only=True,
- default=get_saml_metadata_url,
- label=_('SAML Service Provider Metadata URL'),
- help_text=_('If your identity provider (IdP) allows uploading an XML ' 'metadata file, you can download one from this URL.'),
- category=_('SAML'),
- category_slug='saml',
-)
-
-register(
- 'SOCIAL_AUTH_SAML_SP_ENTITY_ID',
- field_class=fields.CharField,
- allow_blank=True,
- default=get_saml_entity_id,
- label=_('SAML Service Provider Entity ID'),
- help_text=_(
- 'The application-defined unique identifier used as the '
- 'audience of the SAML service provider (SP) configuration. '
- 'This is usually the URL for the service.'
- ),
- category=_('SAML'),
- category_slug='saml',
- depends_on=['TOWER_URL_BASE'],
-)
-
-register(
- 'SOCIAL_AUTH_SAML_SP_PUBLIC_CERT',
- field_class=fields.CharField,
- allow_blank=True,
- required=True,
- validators=[validate_certificate],
- label=_('SAML Service Provider Public Certificate'),
- help_text=_('Create a keypair to use as a service provider (SP) ' 'and include the certificate content here.'),
- category=_('SAML'),
- category_slug='saml',
-)
-
-register(
- 'SOCIAL_AUTH_SAML_SP_PRIVATE_KEY',
- field_class=fields.CharField,
- allow_blank=True,
- required=True,
- validators=[validate_private_key],
- label=_('SAML Service Provider Private Key'),
- help_text=_('Create a keypair to use as a service provider (SP) ' 'and include the private key content here.'),
- category=_('SAML'),
- category_slug='saml',
- encrypted=True,
-)
-
-register(
- 'SOCIAL_AUTH_SAML_ORG_INFO',
- field_class=SAMLOrgInfoField,
- required=True,
- label=_('SAML Service Provider Organization Info'),
- help_text=_('Provide the URL, display name, and the name of your app. Refer to' ' the documentation for example syntax.'),
- category=_('SAML'),
- category_slug='saml',
- placeholder=collections.OrderedDict(
- [('en-US', collections.OrderedDict([('name', 'example'), ('displayname', 'Example'), ('url', 'http://www.example.com')]))]
- ),
-)
-
-register(
- 'SOCIAL_AUTH_SAML_TECHNICAL_CONTACT',
- field_class=SAMLContactField,
- allow_blank=True,
- required=True,
- label=_('SAML Service Provider Technical Contact'),
- help_text=_('Provide the name and email address of the technical contact for' ' your service provider. Refer to the documentation' ' for example syntax.'),
- category=_('SAML'),
- category_slug='saml',
- placeholder=collections.OrderedDict([('givenName', 'Technical Contact'), ('emailAddress', 'techsup@example.com')]),
-)
-
-register(
- 'SOCIAL_AUTH_SAML_SUPPORT_CONTACT',
- field_class=SAMLContactField,
- allow_blank=True,
- required=True,
- label=_('SAML Service Provider Support Contact'),
- help_text=_('Provide the name and email address of the support contact for your' ' service provider. Refer to the documentation for' ' example syntax.'),
- category=_('SAML'),
- category_slug='saml',
- placeholder=collections.OrderedDict([('givenName', 'Support Contact'), ('emailAddress', 'support@example.com')]),
-)
-
-register(
- 'SOCIAL_AUTH_SAML_ENABLED_IDPS',
- field_class=SAMLEnabledIdPsField,
- default={},
- label=_('SAML Enabled Identity Providers'),
- help_text=_(
- 'Configure the Entity ID, SSO URL and certificate for each identity'
- ' provider (IdP) in use. Multiple SAML IdPs are supported. Some IdPs'
- ' may provide user data using attribute names that differ from the'
- ' default OIDs. Attribute names may be overridden for each IdP. Refer'
- ' to the Ansible documentation for additional details and syntax.'
- ),
- category=_('SAML'),
- category_slug='saml',
- placeholder=collections.OrderedDict(
- [
- (
- 'Okta',
- collections.OrderedDict(
- [
- ('entity_id', 'http://www.okta.com/HHniyLkaxk9e76wD0Thh'),
- ('url', 'https://dev-123456.oktapreview.com/app/ansibletower/HHniyLkaxk9e76wD0Thh/sso/saml'),
- ('x509cert', 'MIIDpDCCAoygAwIBAgIGAVVZ4rPzMA0GCSqGSIb3...'),
- ('attr_user_permanent_id', 'username'),
- ('attr_first_name', 'first_name'),
- ('attr_last_name', 'last_name'),
- ('attr_username', 'username'),
- ('attr_email', 'email'),
- ]
- ),
- ),
- (
- 'OneLogin',
- collections.OrderedDict(
- [
- ('entity_id', 'https://app.onelogin.com/saml/metadata/123456'),
- ('url', 'https://example.onelogin.com/trust/saml2/http-post/sso/123456'),
- ('x509cert', 'MIIEJjCCAw6gAwIBAgIUfuSD54OPSBhndDHh3gZo...'),
- ('attr_user_permanent_id', 'name_id'),
- ('attr_first_name', 'User.FirstName'),
- ('attr_last_name', 'User.LastName'),
- ('attr_username', 'User.email'),
- ('attr_email', 'User.email'),
- ]
- ),
- ),
- ]
- ),
-)
-
-register(
- 'SOCIAL_AUTH_SAML_SECURITY_CONFIG',
- field_class=SAMLSecurityField,
- allow_null=True,
- default={'requestedAuthnContext': False},
- label=_('SAML Security Config'),
- help_text=_(
- 'A dict of key value pairs that are passed to the underlying' ' python-saml security setting' ' https://github.com/onelogin/python-saml#settings'
- ),
- category=_('SAML'),
- category_slug='saml',
- placeholder=collections.OrderedDict(
- [
- ("nameIdEncrypted", False),
- ("authnRequestsSigned", False),
- ("logoutRequestSigned", False),
- ("logoutResponseSigned", False),
- ("signMetadata", False),
- ("wantMessagesSigned", False),
- ("wantAssertionsSigned", False),
- ("wantAssertionsEncrypted", False),
- ("wantNameId", True),
- ("wantNameIdEncrypted", False),
- ("wantAttributeStatement", True),
- ("requestedAuthnContext", True),
- ("requestedAuthnContextComparison", "exact"),
- ("metadataValidUntil", "2015-06-26T20:00:00Z"),
- ("metadataCacheDuration", "PT518400S"),
- ("signatureAlgorithm", "http://www.w3.org/2000/09/xmldsig#rsa-sha1"),
- ("digestAlgorithm", "http://www.w3.org/2000/09/xmldsig#sha1"),
- ]
- ),
-)
-
-register(
- 'SOCIAL_AUTH_SAML_SP_EXTRA',
- field_class=fields.DictField,
- allow_null=True,
- default=None,
- label=_('SAML Service Provider extra configuration data'),
- help_text=_('A dict of key value pairs to be passed to the underlying' ' python-saml Service Provider configuration setting.'),
- category=_('SAML'),
- category_slug='saml',
- placeholder=collections.OrderedDict(),
-)
-
-register(
- 'SOCIAL_AUTH_SAML_EXTRA_DATA',
- field_class=fields.ListTuplesField,
- allow_null=True,
- default=None,
- label=_('SAML IDP to extra_data attribute mapping'),
- help_text=_('A list of tuples that maps IDP attributes to extra_attributes.' ' Each attribute will be a list of values, even if only 1 value.'),
- category=_('SAML'),
- category_slug='saml',
- placeholder=[('attribute_name', 'extra_data_name_for_attribute'), ('department', 'department'), ('manager_full_name', 'manager_full_name')],
-)
-
-register(
- 'SOCIAL_AUTH_SAML_ORGANIZATION_MAP',
- field_class=SocialOrganizationMapField,
- allow_null=True,
- default=None,
- label=_('SAML Organization Map'),
- help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT,
- category=_('SAML'),
- category_slug='saml',
- placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER,
-)
-
-register(
- 'SOCIAL_AUTH_SAML_TEAM_MAP',
- field_class=SocialTeamMapField,
- allow_null=True,
- default=None,
- label=_('SAML Team Map'),
- help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT,
- category=_('SAML'),
- category_slug='saml',
- placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER,
-)
-
-register(
- 'SOCIAL_AUTH_SAML_ORGANIZATION_ATTR',
- field_class=SAMLOrgAttrField,
- allow_null=True,
- default=None,
- label=_('SAML Organization Attribute Mapping'),
- help_text=_('Used to translate user organization membership.'),
- category=_('SAML'),
- category_slug='saml',
- placeholder=collections.OrderedDict(
- [
- ('saml_attr', 'organization'),
- ('saml_admin_attr', 'organization_admin'),
- ('saml_auditor_attr', 'organization_auditor'),
- ('remove', True),
- ('remove_admins', True),
- ('remove_auditors', True),
- ]
- ),
-)
-
-register(
- 'SOCIAL_AUTH_SAML_TEAM_ATTR',
- field_class=SAMLTeamAttrField,
- allow_null=True,
- default=None,
- label=_('SAML Team Attribute Mapping'),
- help_text=_('Used to translate user team membership.'),
- category=_('SAML'),
- category_slug='saml',
- placeholder=collections.OrderedDict(
- [
- ('saml_attr', 'team'),
- ('remove', True),
- (
- 'team_org_map',
- [
- collections.OrderedDict([('team', 'Marketing'), ('organization', 'Red Hat')]),
- collections.OrderedDict([('team', 'Human Resources'), ('organization', 'Red Hat')]),
- collections.OrderedDict([('team', 'Engineering'), ('organization', 'Red Hat')]),
- collections.OrderedDict([('team', 'Engineering'), ('organization', 'Ansible')]),
- collections.OrderedDict([('team', 'Quality Engineering'), ('organization', 'Ansible')]),
- collections.OrderedDict([('team', 'Sales'), ('organization', 'Ansible')]),
- ],
- ),
- ]
- ),
-)
-
-register(
- 'SOCIAL_AUTH_SAML_USER_FLAGS_BY_ATTR',
- field_class=SAMLUserFlagsAttrField,
- allow_null=True,
- default=None,
- label=_('SAML User Flags Attribute Mapping'),
- help_text=_('Used to map super users and system auditors from SAML.'),
- category=_('SAML'),
- category_slug='saml',
- placeholder=[
- ('is_superuser_attr', 'saml_attr'),
- ('is_superuser_value', ['value']),
- ('is_superuser_role', ['saml_role']),
- ('remove_superusers', True),
- ('is_system_auditor_attr', 'saml_attr'),
- ('is_system_auditor_value', ['value']),
- ('is_system_auditor_role', ['saml_role']),
- ('remove_system_auditors', True),
- ],
-)
-
-
-def tacacs_validate(serializer, attrs):
- if not serializer.instance or not hasattr(serializer.instance, 'TACACSPLUS_HOST') or not hasattr(serializer.instance, 'TACACSPLUS_SECRET'):
- return attrs
- errors = []
- host = serializer.instance.TACACSPLUS_HOST
- if 'TACACSPLUS_HOST' in attrs:
- host = attrs['TACACSPLUS_HOST']
- secret = serializer.instance.TACACSPLUS_SECRET
- if 'TACACSPLUS_SECRET' in attrs:
- secret = attrs['TACACSPLUS_SECRET']
- if host and not secret:
- errors.append('TACACSPLUS_SECRET is required when TACACSPLUS_HOST is provided.')
- if errors:
- raise serializers.ValidationError(_('\n'.join(errors)))
- return attrs
-
-
-register_validate('tacacsplus', tacacs_validate)
diff --git a/awx/sso/fields.py b/awx/sso/fields.py
deleted file mode 100644
index 25b7f2c304a6..000000000000
--- a/awx/sso/fields.py
+++ /dev/null
@@ -1,725 +0,0 @@
-import collections
-import copy
-import inspect
-import json
-import re
-
-import six
-
-# Python LDAP
-import ldap
-import awx
-
-# Django
-from django.utils.translation import gettext_lazy as _
-
-# Django Auth LDAP
-import django_auth_ldap.config
-from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion
-
-from rest_framework.exceptions import ValidationError
-from rest_framework.fields import empty, Field, SkipField
-
-# This must be imported so get_subclasses picks it up
-from awx.sso.ldap_group_types import PosixUIDGroupType # noqa
-
-# AWX
-from awx.conf import fields
-from awx.main.validators import validate_certificate
-from awx.sso.validators import ( # noqa
- validate_ldap_dn,
- validate_ldap_bind_dn,
- validate_ldap_dn_with_user,
- validate_ldap_filter,
- validate_ldap_filter_with_user,
- validate_tacacsplus_disallow_nonascii,
-)
-
-
-def get_subclasses(cls):
- for subclass in cls.__subclasses__():
- for subsubclass in get_subclasses(subclass):
- yield subsubclass
- yield subclass
-
-
-def find_class_in_modules(class_name):
- """
- Used to find ldap subclasses by string
- """
- module_search_space = [django_auth_ldap.config, awx.sso.ldap_group_types]
- for m in module_search_space:
- cls = getattr(m, class_name, None)
- if cls:
- return cls
- return None
-
-
-class DependsOnMixin:
- def get_depends_on(self):
- """
- Get the value of the dependent field.
- First try to find the value in the request.
- Then fall back to the raw value from the setting in the DB.
- """
- from django.conf import settings
-
- dependent_key = next(iter(self.depends_on))
-
- if self.context:
- request = self.context.get('request', None)
- if request and request.data and request.data.get(dependent_key, None):
- return request.data.get(dependent_key)
- res = settings._get_local(dependent_key, validate=False)
- return res
-
-
-class _Forbidden(Field):
- default_error_messages = {'invalid': _('Invalid field.')}
-
- def run_validation(self, value):
- self.fail('invalid')
-
-
-class HybridDictField(fields.DictField):
- """A DictField, but with defined fixed Fields for certain keys."""
-
- def __init__(self, *args, **kwargs):
- self.allow_blank = kwargs.pop('allow_blank', False)
-
- fields = [
- sorted(
- ((field_name, obj) for field_name, obj in cls.__dict__.items() if isinstance(obj, Field) and field_name != 'child'),
- key=lambda x: x[1]._creation_counter,
- )
- for cls in reversed(self.__class__.__mro__)
- ]
- self._declared_fields = collections.OrderedDict(f for group in fields for f in group)
-
- super().__init__(*args, **kwargs)
-
- def to_representation(self, value):
- fields = copy.deepcopy(self._declared_fields)
- return {
- key: field.to_representation(val) if val is not None else None
- for key, val, field in ((six.text_type(key), val, fields.get(key, self.child)) for key, val in value.items())
- if not field.write_only
- }
-
- def run_child_validation(self, data):
- result = {}
-
- if not data and self.allow_blank:
- return result
-
- errors = collections.OrderedDict()
- fields = copy.deepcopy(self._declared_fields)
- keys = set(fields.keys()) | set(data.keys())
-
- for key in keys:
- value = data.get(key, empty)
- key = six.text_type(key)
- field = fields.get(key, self.child)
- try:
- if field.read_only:
- continue # Ignore read_only fields, as Serializer seems to do.
- result[key] = field.run_validation(value)
- except ValidationError as e:
- errors[key] = e.detail
- except SkipField:
- pass
-
- if not errors:
- return result
- raise ValidationError(errors)
-
-
-class AuthenticationBackendsField(fields.StringListField):
- # Mapping of settings that must be set in order to enable each
- # authentication backend.
- REQUIRED_BACKEND_SETTINGS = collections.OrderedDict(
- [
- ('awx.sso.backends.LDAPBackend', ['AUTH_LDAP_SERVER_URI']),
- ('awx.sso.backends.LDAPBackend1', ['AUTH_LDAP_1_SERVER_URI']),
- ('awx.sso.backends.LDAPBackend2', ['AUTH_LDAP_2_SERVER_URI']),
- ('awx.sso.backends.LDAPBackend3', ['AUTH_LDAP_3_SERVER_URI']),
- ('awx.sso.backends.LDAPBackend4', ['AUTH_LDAP_4_SERVER_URI']),
- ('awx.sso.backends.LDAPBackend5', ['AUTH_LDAP_5_SERVER_URI']),
- ('awx.sso.backends.RADIUSBackend', ['RADIUS_SERVER']),
- ('social_core.backends.google.GoogleOAuth2', ['SOCIAL_AUTH_GOOGLE_OAUTH2_KEY', 'SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET']),
- ('social_core.backends.github.GithubOAuth2', ['SOCIAL_AUTH_GITHUB_KEY', 'SOCIAL_AUTH_GITHUB_SECRET']),
- ('social_core.backends.open_id_connect.OpenIdConnectAuth', ['SOCIAL_AUTH_OIDC_KEY', 'SOCIAL_AUTH_OIDC_SECRET', 'SOCIAL_AUTH_OIDC_OIDC_ENDPOINT']),
- (
- 'social_core.backends.github.GithubOrganizationOAuth2',
- ['SOCIAL_AUTH_GITHUB_ORG_KEY', 'SOCIAL_AUTH_GITHUB_ORG_SECRET', 'SOCIAL_AUTH_GITHUB_ORG_NAME'],
- ),
- ('social_core.backends.github.GithubTeamOAuth2', ['SOCIAL_AUTH_GITHUB_TEAM_KEY', 'SOCIAL_AUTH_GITHUB_TEAM_SECRET', 'SOCIAL_AUTH_GITHUB_TEAM_ID']),
- (
- 'social_core.backends.github_enterprise.GithubEnterpriseOAuth2',
- [
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_URL',
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL',
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY',
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET',
- ],
- ),
- (
- 'social_core.backends.github_enterprise.GithubEnterpriseOrganizationOAuth2',
- [
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL',
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL',
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY',
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_SECRET',
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME',
- ],
- ),
- (
- 'social_core.backends.github_enterprise.GithubEnterpriseTeamOAuth2',
- [
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL',
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL',
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY',
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_SECRET',
- 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID',
- ],
- ),
- ('social_core.backends.azuread.AzureADOAuth2', ['SOCIAL_AUTH_AZUREAD_OAUTH2_KEY', 'SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET']),
- (
- 'awx.sso.backends.SAMLAuth',
- [
- 'SOCIAL_AUTH_SAML_SP_ENTITY_ID',
- 'SOCIAL_AUTH_SAML_SP_PUBLIC_CERT',
- 'SOCIAL_AUTH_SAML_SP_PRIVATE_KEY',
- 'SOCIAL_AUTH_SAML_ORG_INFO',
- 'SOCIAL_AUTH_SAML_TECHNICAL_CONTACT',
- 'SOCIAL_AUTH_SAML_SUPPORT_CONTACT',
- 'SOCIAL_AUTH_SAML_ENABLED_IDPS',
- ],
- ),
- ('django.contrib.auth.backends.ModelBackend', []),
- ('awx.main.backends.AWXModelBackend', []),
- ]
- )
-
- @classmethod
- def get_all_required_settings(cls):
- all_required_settings = set(['LICENSE'])
- for required_settings in cls.REQUIRED_BACKEND_SETTINGS.values():
- all_required_settings.update(required_settings)
- return all_required_settings
-
- def __init__(self, *args, **kwargs):
- kwargs.setdefault('default', self._default_from_required_settings)
- super(AuthenticationBackendsField, self).__init__(*args, **kwargs)
-
- def _default_from_required_settings(self):
- from django.conf import settings
-
- try:
- backends = settings._awx_conf_settings._get_default('AUTHENTICATION_BACKENDS')
- except AttributeError:
- backends = self.REQUIRED_BACKEND_SETTINGS.keys()
- # Filter which authentication backends are enabled based on their
- # required settings being defined and non-empty.
- for backend, required_settings in self.REQUIRED_BACKEND_SETTINGS.items():
- if backend not in backends:
- continue
- if all([getattr(settings, rs, None) for rs in required_settings]):
- continue
- backends = [x for x in backends if x != backend]
- return backends
-
-
-class LDAPServerURIField(fields.URLField):
- def __init__(self, **kwargs):
- kwargs.setdefault('schemes', ('ldap', 'ldaps'))
- kwargs.setdefault('allow_plain_hostname', True)
- super(LDAPServerURIField, self).__init__(**kwargs)
-
- def run_validators(self, value):
- for url in filter(None, re.split(r'[, ]', (value or ''))):
- super(LDAPServerURIField, self).run_validators(url)
- return value
-
-
-class LDAPConnectionOptionsField(fields.DictField):
- default_error_messages = {'invalid_options': _('Invalid connection option(s): {invalid_options}.')}
-
- def to_representation(self, value):
- value = value or {}
- opt_names = ldap.OPT_NAMES_DICT
- # Convert integer options to their named constants.
- repr_value = {}
- for opt, opt_value in value.items():
- if opt in opt_names:
- repr_value[opt_names[opt]] = opt_value
- return repr_value
-
- def to_internal_value(self, data):
- data = super(LDAPConnectionOptionsField, self).to_internal_value(data)
- valid_options = dict([(v, k) for k, v in ldap.OPT_NAMES_DICT.items()])
- invalid_options = set(data.keys()) - set(valid_options.keys())
- if invalid_options:
- invalid_options = sorted(list(invalid_options))
- options_display = json.dumps(invalid_options).lstrip('[').rstrip(']')
- self.fail('invalid_options', invalid_options=options_display)
- # Convert named options to their integer constants.
- internal_data = {}
- for opt_name, opt_value in data.items():
- internal_data[valid_options[opt_name]] = opt_value
- return internal_data
-
-
-class LDAPDNField(fields.CharField):
- def __init__(self, **kwargs):
- super(LDAPDNField, self).__init__(**kwargs)
- self.validators.append(validate_ldap_dn)
-
- def run_validation(self, data=empty):
- value = super(LDAPDNField, self).run_validation(data)
- # django-auth-ldap expects DN fields (like AUTH_LDAP_REQUIRE_GROUP)
- # to be either a valid string or ``None`` (not an empty string)
- return None if value == '' else value
-
-
-class LDAPDNListField(fields.StringListField):
- def __init__(self, **kwargs):
- super(LDAPDNListField, self).__init__(**kwargs)
- self.validators.append(lambda dn: list(map(validate_ldap_dn, dn)))
-
- def run_validation(self, data=empty):
- if not isinstance(data, (list, tuple)):
- data = [data]
- return super(LDAPDNListField, self).run_validation(data)
-
-
-class LDAPDNWithUserField(fields.CharField):
- def __init__(self, **kwargs):
- super(LDAPDNWithUserField, self).__init__(**kwargs)
- self.validators.append(validate_ldap_dn_with_user)
-
- def run_validation(self, data=empty):
- value = super(LDAPDNWithUserField, self).run_validation(data)
- # django-auth-ldap expects DN fields (like AUTH_LDAP_USER_DN_TEMPLATE)
- # to be either a valid string or ``None`` (not an empty string)
- return None if value == '' else value
-
-
-class LDAPFilterField(fields.CharField):
- def __init__(self, **kwargs):
- super(LDAPFilterField, self).__init__(**kwargs)
- self.validators.append(validate_ldap_filter)
-
-
-class LDAPFilterWithUserField(fields.CharField):
- def __init__(self, **kwargs):
- super(LDAPFilterWithUserField, self).__init__(**kwargs)
- self.validators.append(validate_ldap_filter_with_user)
-
-
-class LDAPScopeField(fields.ChoiceField):
- def __init__(self, choices=None, **kwargs):
- choices = choices or [('SCOPE_BASE', _('Base')), ('SCOPE_ONELEVEL', _('One Level')), ('SCOPE_SUBTREE', _('Subtree'))]
- super(LDAPScopeField, self).__init__(choices, **kwargs)
-
- def to_representation(self, value):
- for choice in self.choices.keys():
- if value == getattr(ldap, choice):
- return choice
- return super(LDAPScopeField, self).to_representation(value)
-
- def to_internal_value(self, data):
- value = super(LDAPScopeField, self).to_internal_value(data)
- return getattr(ldap, value)
-
-
-class LDAPSearchField(fields.ListField):
- default_error_messages = {
- 'invalid_length': _('Expected a list of three items but got {length} instead.'),
- 'type_error': _('Expected an instance of LDAPSearch but got {input_type} instead.'),
- }
- ldap_filter_field_class = LDAPFilterField
-
- def to_representation(self, value):
- if not value:
- return []
- if not isinstance(value, LDAPSearch):
- self.fail('type_error', input_type=type(value))
- return [
- LDAPDNField().to_representation(value.base_dn),
- LDAPScopeField().to_representation(value.scope),
- self.ldap_filter_field_class().to_representation(value.filterstr),
- ]
-
- def to_internal_value(self, data):
- data = super(LDAPSearchField, self).to_internal_value(data)
- if len(data) == 0:
- return None
- if len(data) != 3:
- self.fail('invalid_length', length=len(data))
- return LDAPSearch(
- LDAPDNField().run_validation(data[0]), LDAPScopeField().run_validation(data[1]), self.ldap_filter_field_class().run_validation(data[2])
- )
-
-
-class LDAPSearchWithUserField(LDAPSearchField):
- ldap_filter_field_class = LDAPFilterWithUserField
-
-
-class LDAPSearchUnionField(fields.ListField):
- default_error_messages = {'type_error': _('Expected an instance of LDAPSearch or LDAPSearchUnion but got {input_type} instead.')}
- ldap_search_field_class = LDAPSearchWithUserField
-
- def to_representation(self, value):
- if not value:
- return []
- elif isinstance(value, LDAPSearchUnion):
- return [self.ldap_search_field_class().to_representation(s) for s in value.searches]
- elif isinstance(value, LDAPSearch):
- return self.ldap_search_field_class().to_representation(value)
- else:
- self.fail('type_error', input_type=type(value))
-
- def to_internal_value(self, data):
- data = super(LDAPSearchUnionField, self).to_internal_value(data)
- if len(data) == 0:
- return None
- if len(data) == 3 and isinstance(data[0], str):
- return self.ldap_search_field_class().run_validation(data)
- else:
- search_args = []
- for i in range(len(data)):
- if not isinstance(data[i], list):
- raise ValidationError('In order to ultilize LDAP Union, input element No. %d' ' should be a search query array.' % (i + 1))
- try:
- search_args.append(self.ldap_search_field_class().run_validation(data[i]))
- except Exception as e:
- if hasattr(e, 'detail') and isinstance(e.detail, list):
- e.detail.insert(0, "Error parsing LDAP Union element No. %d:" % (i + 1))
- raise e
- return LDAPSearchUnion(*search_args)
-
-
-class LDAPUserAttrMapField(fields.DictField):
- default_error_messages = {'invalid_attrs': _('Invalid user attribute(s): {invalid_attrs}.')}
- valid_user_attrs = {'first_name', 'last_name', 'email'}
- child = fields.CharField()
-
- def to_internal_value(self, data):
- data = super(LDAPUserAttrMapField, self).to_internal_value(data)
- invalid_attrs = set(data.keys()) - self.valid_user_attrs
- if invalid_attrs:
- invalid_attrs = sorted(list(invalid_attrs))
- attrs_display = json.dumps(invalid_attrs).lstrip('[').rstrip(']')
- self.fail('invalid_attrs', invalid_attrs=attrs_display)
- return data
-
-
-class LDAPGroupTypeField(fields.ChoiceField, DependsOnMixin):
- default_error_messages = {
- 'type_error': _('Expected an instance of LDAPGroupType but got {input_type} instead.'),
- 'missing_parameters': _('Missing required parameters in {dependency}.'),
- 'invalid_parameters': _('Invalid group_type parameters. Expected instance of dict but got {parameters_type} instead.'),
- }
-
- def __init__(self, choices=None, **kwargs):
- group_types = get_subclasses(django_auth_ldap.config.LDAPGroupType)
- choices = choices or [(x.__name__, x.__name__) for x in group_types]
- super(LDAPGroupTypeField, self).__init__(choices, **kwargs)
-
- def to_representation(self, value):
- if not value:
- return 'MemberDNGroupType'
- if not isinstance(value, django_auth_ldap.config.LDAPGroupType):
- self.fail('type_error', input_type=type(value))
- return value.__class__.__name__
-
- def to_internal_value(self, data):
- data = super(LDAPGroupTypeField, self).to_internal_value(data)
- if not data:
- return None
-
- cls = find_class_in_modules(data)
- if not cls:
- return None
-
- # Per-group type parameter validation and handling here
-
- # Backwords compatability. Before AUTH_LDAP_GROUP_TYPE_PARAMS existed
- # MemberDNGroupType was the only group type, of the underlying lib, that
- # took a parameter.
- params = self.get_depends_on() or {}
- params_sanitized = dict()
-
- cls_args = inspect.getfullargspec(cls.__init__).args[1:]
-
- if cls_args:
- if not isinstance(params, dict):
- self.fail('invalid_parameters', parameters_type=type(params))
-
- for attr in cls_args:
- if attr in params:
- params_sanitized[attr] = params[attr]
-
- try:
- return cls(**params_sanitized)
- except TypeError:
- self.fail('missing_parameters', dependency=list(self.depends_on)[0])
-
-
-class LDAPGroupTypeParamsField(fields.DictField, DependsOnMixin):
- default_error_messages = {'invalid_keys': _('Invalid key(s): {invalid_keys}.')}
-
- def to_internal_value(self, value):
- value = super(LDAPGroupTypeParamsField, self).to_internal_value(value)
- if not value:
- return value
- group_type_str = self.get_depends_on()
- group_type_str = group_type_str or ''
-
- group_type_cls = find_class_in_modules(group_type_str)
- if not group_type_cls:
- # Fail safe
- return {}
-
- invalid_keys = set(value.keys()) - set(inspect.getfullargspec(group_type_cls.__init__).args[1:])
- if invalid_keys:
- invalid_keys = sorted(list(invalid_keys))
- keys_display = json.dumps(invalid_keys).lstrip('[').rstrip(']')
- self.fail('invalid_keys', invalid_keys=keys_display)
- return value
-
-
-class LDAPUserFlagsField(fields.DictField):
- default_error_messages = {'invalid_flag': _('Invalid user flag: "{invalid_flag}".')}
- valid_user_flags = {'is_superuser', 'is_system_auditor'}
- child = LDAPDNListField()
-
- def to_internal_value(self, data):
- data = super(LDAPUserFlagsField, self).to_internal_value(data)
- invalid_flags = set(data.keys()) - self.valid_user_flags
- if invalid_flags:
- self.fail('invalid_flag', invalid_flag=list(invalid_flags)[0])
- return data
-
-
-class LDAPDNMapField(fields.StringListBooleanField):
- child = LDAPDNField()
-
-
-class LDAPSingleOrganizationMapField(HybridDictField):
- admins = LDAPDNMapField(allow_null=True, required=False)
- users = LDAPDNMapField(allow_null=True, required=False)
- auditors = LDAPDNMapField(allow_null=True, required=False)
- remove_admins = fields.BooleanField(required=False)
- remove_users = fields.BooleanField(required=False)
- remove_auditors = fields.BooleanField(required=False)
-
- child = _Forbidden()
-
-
-class LDAPOrganizationMapField(fields.DictField):
- child = LDAPSingleOrganizationMapField()
-
-
-class LDAPSingleTeamMapField(HybridDictField):
- organization = fields.CharField()
- users = LDAPDNMapField(allow_null=True, required=False)
- remove = fields.BooleanField(required=False)
-
- child = _Forbidden()
-
-
-class LDAPTeamMapField(fields.DictField):
- child = LDAPSingleTeamMapField()
-
-
-class SocialMapStringRegexField(fields.CharField):
- def to_representation(self, value):
- if isinstance(value, type(re.compile(''))):
- flags = []
- if value.flags & re.I:
- flags.append('i')
- if value.flags & re.M:
- flags.append('m')
- return '/{}/{}'.format(value.pattern, ''.join(flags))
- else:
- return super(SocialMapStringRegexField, self).to_representation(value)
-
- def to_internal_value(self, data):
- data = super(SocialMapStringRegexField, self).to_internal_value(data)
- match = re.match(r'^/(?P.*)/(?P[im]+)?$', data)
- if match:
- flags = 0
- if match.group('flags'):
- if 'i' in match.group('flags'):
- flags |= re.I
- if 'm' in match.group('flags'):
- flags |= re.M
- try:
- return re.compile(match.group('pattern'), flags)
- except re.error as e:
- raise ValidationError('{}: {}'.format(e, data))
- return data
-
-
-class SocialMapField(fields.ListField):
- default_error_messages = {'type_error': _('Expected None, True, False, a string or list of strings but got {input_type} instead.')}
- child = SocialMapStringRegexField()
-
- def to_representation(self, value):
- if isinstance(value, (list, tuple)):
- return super(SocialMapField, self).to_representation(value)
- elif value in fields.BooleanField.TRUE_VALUES:
- return True
- elif value in fields.BooleanField.FALSE_VALUES:
- return False
- elif value in fields.BooleanField.NULL_VALUES:
- return None
- elif isinstance(value, (str, type(re.compile('')))):
- return self.child.to_representation(value)
- else:
- self.fail('type_error', input_type=type(value))
-
- def to_internal_value(self, data):
- if isinstance(data, (list, tuple)):
- return super(SocialMapField, self).to_internal_value(data)
- elif data in fields.BooleanField.TRUE_VALUES:
- return True
- elif data in fields.BooleanField.FALSE_VALUES:
- return False
- elif data in fields.BooleanField.NULL_VALUES:
- return None
- elif isinstance(data, str):
- return self.child.run_validation(data)
- else:
- self.fail('type_error', input_type=type(data))
-
-
-class SocialSingleOrganizationMapField(HybridDictField):
- admins = SocialMapField(allow_null=True, required=False)
- users = SocialMapField(allow_null=True, required=False)
- remove_admins = fields.BooleanField(required=False)
- remove_users = fields.BooleanField(required=False)
- organization_alias = SocialMapField(allow_null=True, required=False)
-
- child = _Forbidden()
-
-
-class SocialOrganizationMapField(fields.DictField):
- child = SocialSingleOrganizationMapField()
-
-
-class SocialSingleTeamMapField(HybridDictField):
- organization = fields.CharField()
- users = SocialMapField(allow_null=True, required=False)
- remove = fields.BooleanField(required=False)
-
- child = _Forbidden()
-
-
-class SocialTeamMapField(fields.DictField):
- child = SocialSingleTeamMapField()
-
-
-class SAMLOrgInfoValueField(HybridDictField):
- name = fields.CharField()
- displayname = fields.CharField()
- url = fields.URLField()
-
-
-class SAMLOrgInfoField(fields.DictField):
- default_error_messages = {'invalid_lang_code': _('Invalid language code(s) for org info: {invalid_lang_codes}.')}
- child = SAMLOrgInfoValueField()
-
- def to_internal_value(self, data):
- data = super(SAMLOrgInfoField, self).to_internal_value(data)
- invalid_keys = set()
- for key in data.keys():
- if not re.match(r'^[a-z]{2}(?:-[a-z]{2})??$', key, re.I):
- invalid_keys.add(key)
- if invalid_keys:
- invalid_keys = sorted(list(invalid_keys))
- keys_display = json.dumps(invalid_keys).lstrip('[').rstrip(']')
- self.fail('invalid_lang_code', invalid_lang_codes=keys_display)
- return data
-
-
-class SAMLContactField(HybridDictField):
- givenName = fields.CharField()
- emailAddress = fields.EmailField()
-
-
-class SAMLIdPField(HybridDictField):
- entity_id = fields.CharField()
- url = fields.URLField()
- x509cert = fields.CharField(validators=[validate_certificate])
- attr_user_permanent_id = fields.CharField(required=False)
- attr_first_name = fields.CharField(required=False)
- attr_last_name = fields.CharField(required=False)
- attr_username = fields.CharField(required=False)
- attr_email = fields.CharField(required=False)
-
-
-class SAMLEnabledIdPsField(fields.DictField):
- child = SAMLIdPField()
-
-
-class SAMLSecurityField(HybridDictField):
- nameIdEncrypted = fields.BooleanField(required=False)
- authnRequestsSigned = fields.BooleanField(required=False)
- logoutRequestSigned = fields.BooleanField(required=False)
- logoutResponseSigned = fields.BooleanField(required=False)
- signMetadata = fields.BooleanField(required=False)
- wantMessagesSigned = fields.BooleanField(required=False)
- wantAssertionsSigned = fields.BooleanField(required=False)
- wantAssertionsEncrypted = fields.BooleanField(required=False)
- wantNameId = fields.BooleanField(required=False)
- wantNameIdEncrypted = fields.BooleanField(required=False)
- wantAttributeStatement = fields.BooleanField(required=False)
- requestedAuthnContext = fields.StringListBooleanField(required=False)
- requestedAuthnContextComparison = fields.CharField(required=False)
- metadataValidUntil = fields.CharField(allow_null=True, required=False)
- metadataCacheDuration = fields.CharField(allow_null=True, required=False)
- signatureAlgorithm = fields.CharField(allow_null=True, required=False)
- digestAlgorithm = fields.CharField(allow_null=True, required=False)
-
-
-class SAMLOrgAttrField(HybridDictField):
- remove = fields.BooleanField(required=False)
- saml_attr = fields.CharField(required=False, allow_null=True)
- remove_admins = fields.BooleanField(required=False)
- saml_admin_attr = fields.CharField(required=False, allow_null=True)
- remove_auditors = fields.BooleanField(required=False)
- saml_auditor_attr = fields.CharField(required=False, allow_null=True)
-
- child = _Forbidden()
-
-
-class SAMLTeamAttrTeamOrgMapField(HybridDictField):
- team = fields.CharField(required=True, allow_null=False)
- team_alias = fields.CharField(required=False, allow_null=True)
- organization = fields.CharField(required=True, allow_null=False)
-
- child = _Forbidden()
-
-
-class SAMLTeamAttrField(HybridDictField):
- team_org_map = fields.ListField(required=False, child=SAMLTeamAttrTeamOrgMapField(), allow_null=True)
- remove = fields.BooleanField(required=False)
- saml_attr = fields.CharField(required=False, allow_null=True)
-
- child = _Forbidden()
-
-
-class SAMLUserFlagsAttrField(HybridDictField):
- is_superuser_attr = fields.CharField(required=False, allow_null=True)
- is_superuser_value = fields.StringListField(required=False, allow_null=True)
- is_superuser_role = fields.StringListField(required=False, allow_null=True)
- remove_superusers = fields.BooleanField(required=False, allow_null=True)
- is_system_auditor_attr = fields.CharField(required=False, allow_null=True)
- is_system_auditor_value = fields.StringListField(required=False, allow_null=True)
- is_system_auditor_role = fields.StringListField(required=False, allow_null=True)
- remove_system_auditors = fields.BooleanField(required=False, allow_null=True)
-
- child = _Forbidden()
diff --git a/awx/sso/ldap_group_types.py b/awx/sso/ldap_group_types.py
deleted file mode 100644
index 2a5434c15440..000000000000
--- a/awx/sso/ldap_group_types.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# Copyright (c) 2018 Ansible by Red Hat
-# All Rights Reserved.
-
-# Python
-import ldap
-
-# Django
-from django.utils.encoding import force_str
-
-# 3rd party
-from django_auth_ldap.config import LDAPGroupType
-
-
-class PosixUIDGroupType(LDAPGroupType):
- def __init__(self, name_attr='cn', ldap_group_user_attr='uid'):
- self.ldap_group_user_attr = ldap_group_user_attr
- super(PosixUIDGroupType, self).__init__(name_attr)
-
- """
- An LDAPGroupType subclass that handles non-standard DS.
- """
-
- def user_groups(self, ldap_user, group_search):
- """
- Searches for any group that is either the user's primary or contains the
- user as a member.
- """
- groups = []
-
- try:
- user_uid = ldap_user.attrs[self.ldap_group_user_attr][0]
-
- if 'gidNumber' in ldap_user.attrs:
- user_gid = ldap_user.attrs['gidNumber'][0]
- filterstr = u'(|(gidNumber=%s)(memberUid=%s))' % (
- self.ldap.filter.escape_filter_chars(user_gid),
- self.ldap.filter.escape_filter_chars(user_uid),
- )
- else:
- filterstr = u'(memberUid=%s)' % (self.ldap.filter.escape_filter_chars(user_uid),)
-
- search = group_search.search_with_additional_term_string(filterstr)
- search.attrlist = [str(self.name_attr)]
- groups = search.execute(ldap_user.connection)
- except (KeyError, IndexError):
- pass
-
- return groups
-
- def is_member(self, ldap_user, group_dn):
- """
- Returns True if the group is the user's primary group or if the user is
- listed in the group's memberUid attribute.
- """
- is_member = False
- try:
- user_uid = ldap_user.attrs[self.ldap_group_user_attr][0]
-
- try:
- is_member = ldap_user.connection.compare_s(force_str(group_dn), 'memberUid', force_str(user_uid))
- except (ldap.UNDEFINED_TYPE, ldap.NO_SUCH_ATTRIBUTE):
- is_member = False
-
- if not is_member:
- try:
- user_gid = ldap_user.attrs['gidNumber'][0]
- is_member = ldap_user.connection.compare_s(force_str(group_dn), 'gidNumber', force_str(user_gid))
- except (ldap.UNDEFINED_TYPE, ldap.NO_SUCH_ATTRIBUTE):
- is_member = False
- except (KeyError, IndexError):
- is_member = False
-
- return is_member
diff --git a/awx/sso/middleware.py b/awx/sso/middleware.py
deleted file mode 100644
index f8b2b7974167..000000000000
--- a/awx/sso/middleware.py
+++ /dev/null
@@ -1,80 +0,0 @@
-# Copyright (c) 2015 Ansible, Inc.
-# All Rights Reserved.
-
-# Python
-import urllib.parse
-
-# Django
-from django.conf import settings
-from django.utils.functional import LazyObject
-from django.shortcuts import redirect
-
-# Python Social Auth
-from social_core.exceptions import SocialAuthBaseException
-from social_core.utils import social_logger
-from social_django import utils
-from social_django.middleware import SocialAuthExceptionMiddleware
-
-
-class SocialAuthMiddleware(SocialAuthExceptionMiddleware):
- def process_request(self, request):
- if request.path.startswith('/sso'):
- # See upgrade blocker note in requirements/README.md
- utils.BACKENDS = settings.AUTHENTICATION_BACKENDS
- token_key = request.COOKIES.get('token', '')
- token_key = urllib.parse.quote(urllib.parse.unquote(token_key).strip('"'))
-
- if not hasattr(request, 'successful_authenticator'):
- request.successful_authenticator = None
-
- if not request.path.startswith('/sso/') and 'migrations_notran' not in request.path:
- if request.user and request.user.is_authenticated:
- # The rest of the code base rely hevily on type/inheritance checks,
- # LazyObject sent from Django auth middleware can be buggy if not
- # converted back to its original object.
- if isinstance(request.user, LazyObject) and request.user._wrapped:
- request.user = request.user._wrapped
- request.session.pop('social_auth_error', None)
- request.session.pop('social_auth_last_backend', None)
- return self.get_response(request)
-
- def process_view(self, request, callback, callback_args, callback_kwargs):
- if request.path.startswith('/sso/login/'):
- request.session['social_auth_last_backend'] = callback_kwargs['backend']
-
- def process_exception(self, request, exception):
- strategy = getattr(request, 'social_strategy', None)
- if strategy is None or self.raise_exception(request, exception):
- return
-
- if isinstance(exception, SocialAuthBaseException) or request.path.startswith('/sso/'):
- backend = getattr(request, 'backend', None)
- backend_name = getattr(backend, 'name', 'unknown-backend')
-
- message = self.get_message(request, exception)
- if request.session.get('social_auth_last_backend') != backend_name:
- backend_name = request.session.get('social_auth_last_backend')
- message = request.GET.get('error_description', message)
-
- full_backend_name = backend_name
- try:
- idp_name = strategy.request_data()['RelayState']
- full_backend_name = '%s:%s' % (backend_name, idp_name)
- except KeyError:
- pass
-
- social_logger.error(message)
-
- url = self.get_redirect_uri(request, exception)
- request.session['social_auth_error'] = (full_backend_name, message)
- return redirect(url)
-
- def get_message(self, request, exception):
- msg = str(exception)
- if msg and msg[-1] not in '.?!':
- msg = msg + '.'
- return msg
-
- def get_redirect_uri(self, request, exception):
- strategy = getattr(request, 'social_strategy', None)
- return strategy.session_get('next', '') or strategy.setting('LOGIN_ERROR_URL')
diff --git a/awx/sso/migrations/0001_initial.py b/awx/sso/migrations/0001_initial.py
deleted file mode 100644
index d759e22437b5..000000000000
--- a/awx/sso/migrations/0001_initial.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-from django.conf import settings
-
-
-class Migration(migrations.Migration):
- dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
-
- operations = [
- migrations.CreateModel(
- name='UserEnterpriseAuth',
- fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
- ('provider', models.CharField(max_length=32, choices=[(b'radius', 'RADIUS'), (b'tacacs+', 'TACACS+')])),
- ('user', models.ForeignKey(related_name='enterprise_auth', on_delete=models.CASCADE, to=settings.AUTH_USER_MODEL)),
- ],
- ),
- migrations.AlterUniqueTogether(name='userenterpriseauth', unique_together=set([('user', 'provider')])),
- ]
diff --git a/awx/sso/migrations/0002_expand_provider_options.py b/awx/sso/migrations/0002_expand_provider_options.py
deleted file mode 100644
index 68f877717f2f..000000000000
--- a/awx/sso/migrations/0002_expand_provider_options.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
- dependencies = [('sso', '0001_initial')]
-
- operations = [
- migrations.AlterField(
- model_name='userenterpriseauth',
- name='provider',
- field=models.CharField(max_length=32, choices=[('radius', 'RADIUS'), ('tacacs+', 'TACACS+'), ('saml', 'SAML')]),
- )
- ]
diff --git a/awx/sso/migrations/0003_convert_saml_string_to_list.py b/awx/sso/migrations/0003_convert_saml_string_to_list.py
deleted file mode 100644
index bacc25e3c00f..000000000000
--- a/awx/sso/migrations/0003_convert_saml_string_to_list.py
+++ /dev/null
@@ -1,58 +0,0 @@
-from django.db import migrations, connection
-import json
-
-_values_to_change = ['is_superuser_value', 'is_superuser_role', 'is_system_auditor_value', 'is_system_auditor_role']
-
-
-def _get_setting():
- with connection.cursor() as cursor:
- cursor.execute('SELECT value FROM conf_setting WHERE key= %s', ['SOCIAL_AUTH_SAML_USER_FLAGS_BY_ATTR'])
- row = cursor.fetchone()
- if row == None:
- return {}
- existing_setting = row[0]
-
- try:
- existing_json = json.loads(existing_setting)
- except json.decoder.JSONDecodeError as e:
- print("Failed to decode existing json setting:")
- print(existing_setting)
- raise e
-
- return existing_json
-
-
-def _set_setting(value):
- with connection.cursor() as cursor:
- cursor.execute('UPDATE conf_setting SET value = %s WHERE key = %s', [json.dumps(value), 'SOCIAL_AUTH_SAML_USER_FLAGS_BY_ATTR'])
-
-
-def forwards(app, schema_editor):
- # The Operation should use schema_editor to apply any changes it
- # wants to make to the database.
- existing_json = _get_setting()
- for key in _values_to_change:
- if existing_json.get(key, None) and isinstance(existing_json.get(key), str):
- existing_json[key] = [existing_json.get(key)]
- _set_setting(existing_json)
-
-
-def backwards(app, schema_editor):
- existing_json = _get_setting()
- for key in _values_to_change:
- if existing_json.get(key, None) and not isinstance(existing_json.get(key), str):
- try:
- existing_json[key] = existing_json.get(key).pop()
- except IndexError:
- existing_json[key] = ""
- _set_setting(existing_json)
-
-
-class Migration(migrations.Migration):
- dependencies = [
- ('sso', '0002_expand_provider_options'),
- ]
-
- operations = [
- migrations.RunPython(forwards, backwards),
- ]
diff --git a/awx/sso/models.py b/awx/sso/models.py
deleted file mode 100644
index 28eb23857f4b..000000000000
--- a/awx/sso/models.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# Copyright (c) 2015 Ansible, Inc.
-# All Rights Reserved.
-
-# Django
-from django.db import models
-from django.contrib.auth.models import User
-from django.utils.translation import gettext_lazy as _
-
-
-class UserEnterpriseAuth(models.Model):
- """Enterprise Auth association model"""
-
- PROVIDER_CHOICES = (('radius', _('RADIUS')), ('tacacs+', _('TACACS+')), ('saml', _('SAML')))
-
- class Meta:
- unique_together = ('user', 'provider')
-
- user = models.ForeignKey(User, related_name='enterprise_auth', on_delete=models.CASCADE)
- provider = models.CharField(max_length=32, choices=PROVIDER_CHOICES)
diff --git a/awx/sso/saml_pipeline.py b/awx/sso/saml_pipeline.py
deleted file mode 100644
index e8c4f9ff6d8c..000000000000
--- a/awx/sso/saml_pipeline.py
+++ /dev/null
@@ -1,291 +0,0 @@
-# Copyright (c) 2015 Ansible, Inc.
-# All Rights Reserved.
-
-# Python
-import re
-import logging
-
-# Django
-from django.conf import settings
-
-from awx.main.models import Team
-from awx.sso.common import create_org_and_teams, reconcile_users_org_team_mappings, get_orgs_by_ids
-
-logger = logging.getLogger('awx.sso.saml_pipeline')
-
-
-def populate_user(backend, details, user=None, *args, **kwargs):
- if not user:
- return
-
- # Build the in-memory settings for how this user should be modeled
- desired_org_state = {}
- desired_team_state = {}
- orgs_to_create = []
- teams_to_create = {}
- _update_user_orgs_by_saml_attr(backend, desired_org_state, orgs_to_create, **kwargs)
- _update_user_teams_by_saml_attr(desired_team_state, teams_to_create, **kwargs)
- _update_user_orgs(backend, desired_org_state, orgs_to_create, user)
- _update_user_teams(backend, desired_team_state, teams_to_create, user)
-
- # If the SAML adapter is allowed to create objects, lets do that first
- create_org_and_teams(orgs_to_create, teams_to_create, 'SAML', settings.SAML_AUTO_CREATE_OBJECTS)
-
- # Finally reconcile the user
- reconcile_users_org_team_mappings(user, desired_org_state, desired_team_state, 'SAML')
-
-
-def _update_m2m_from_expression(user, expr, remove=True):
- """
- Helper function to update m2m relationship based on user matching one or
- more expressions.
- """
- should_add = False
- if expr is None or not expr:
- pass
- elif expr is True:
- should_add = True
- else:
- if isinstance(expr, (str, type(re.compile('')))):
- expr = [expr]
- for ex in expr:
- if isinstance(ex, str):
- if user.username == ex or user.email == ex:
- should_add = True
- elif isinstance(ex, type(re.compile(''))):
- if ex.match(user.username) or ex.match(user.email):
- should_add = True
- if should_add:
- return True
- elif remove:
- return False
- else:
- return None
-
-
-def _update_user_orgs(backend, desired_org_state, orgs_to_create, user=None):
- """
- Update organization memberships for the given user based on mapping rules
- defined in settings.
- """
- org_map = backend.setting('ORGANIZATION_MAP') or {}
- for org_name, org_opts in org_map.items():
- organization_alias = org_opts.get('organization_alias')
- if organization_alias:
- organization_name = organization_alias
- else:
- organization_name = org_name
- if organization_name not in orgs_to_create:
- orgs_to_create.append(organization_name)
-
- remove = bool(org_opts.get('remove', True))
-
- if organization_name not in desired_org_state:
- desired_org_state[organization_name] = {}
-
- for role_name, user_type in (('admin_role', 'admins'), ('member_role', 'users'), ('auditor_role', 'auditors')):
- is_member_expression = org_opts.get(user_type, None)
- remove_members = bool(org_opts.get('remove_{}'.format(user_type), remove))
- has_role = _update_m2m_from_expression(user, is_member_expression, remove_members)
- desired_org_state[organization_name][role_name] = has_role
-
-
-def _update_user_teams(backend, desired_team_state, teams_to_create, user=None):
- """
- Update team memberships for the given user based on mapping rules defined
- in settings.
- """
-
- team_map = backend.setting('TEAM_MAP') or {}
- for team_name, team_opts in team_map.items():
- # Get or create the org to update.
- if 'organization' not in team_opts:
- continue
- teams_to_create[team_name] = team_opts['organization']
- users_expr = team_opts.get('users', None)
- remove = bool(team_opts.get('remove', True))
- add_or_remove = _update_m2m_from_expression(user, users_expr, remove)
- if add_or_remove is not None:
- org_name = team_opts['organization']
- if org_name not in desired_team_state:
- desired_team_state[org_name] = {}
- desired_team_state[org_name][team_name] = {'member_role': add_or_remove}
-
-
-def _update_user_orgs_by_saml_attr(backend, desired_org_state, orgs_to_create, **kwargs):
- org_map = settings.SOCIAL_AUTH_SAML_ORGANIZATION_ATTR
- roles_and_flags = (
- ('member_role', 'remove', 'saml_attr'),
- ('admin_role', 'remove_admins', 'saml_admin_attr'),
- ('auditor_role', 'remove_auditors', 'saml_auditor_attr'),
- )
-
- # If the remove_flag was present we need to load all of the orgs and remove the user from the role
- all_orgs = None
- for role, remove_flag, _ in roles_and_flags:
- remove = bool(org_map.get(remove_flag, True))
- if remove:
- # Only get the all orgs once, and only if needed
- if all_orgs is None:
- all_orgs = get_orgs_by_ids()
- for org_name in all_orgs.keys():
- if org_name not in desired_org_state:
- desired_org_state[org_name] = {}
- desired_org_state[org_name][role] = False
-
- # Now we can add the user as a member/admin/auditor for any orgs they have specified
- for role, _, attr_flag in roles_and_flags:
- if org_map.get(attr_flag) is None:
- continue
- saml_attr_values = kwargs.get('response', {}).get('attributes', {}).get(org_map.get(attr_flag), [])
- for org_name in saml_attr_values:
- try:
- organization_alias = backend.setting('ORGANIZATION_MAP').get(org_name).get('organization_alias')
- if organization_alias is not None:
- organization_name = organization_alias
- else:
- organization_name = org_name
- except Exception:
- organization_name = org_name
- if organization_name not in orgs_to_create:
- orgs_to_create.append(organization_name)
- if organization_name not in desired_org_state:
- desired_org_state[organization_name] = {}
- desired_org_state[organization_name][role] = True
-
-
-def _update_user_teams_by_saml_attr(desired_team_state, teams_to_create, **kwargs):
- #
- # Map users into organizations based on SOCIAL_AUTH_SAML_TEAM_ATTR setting
- #
- team_map = settings.SOCIAL_AUTH_SAML_TEAM_ATTR
- if team_map.get('saml_attr') is None:
- return
-
- all_teams = None
- # The role and flag is hard coded here but intended to be flexible in case we ever wanted to add another team type
- for role, remove_flag in [('member_role', 'remove')]:
- remove = bool(team_map.get(remove_flag, True))
- if remove:
- # Only get the all orgs once, and only if needed
- if all_teams is None:
- all_teams = Team.objects.all().values_list('name', 'organization__name')
- for team_name, organization_name in all_teams:
- if organization_name not in desired_team_state:
- desired_team_state[organization_name] = {}
- desired_team_state[organization_name][team_name] = {role: False}
-
- saml_team_names = set(kwargs.get('response', {}).get('attributes', {}).get(team_map['saml_attr'], []))
-
- for team_name_map in team_map.get('team_org_map', []):
- team_name = team_name_map.get('team', None)
- team_alias = team_name_map.get('team_alias', None)
- organization_name = team_name_map.get('organization', None)
- if team_name in saml_team_names:
- if not organization_name:
- # Settings field validation should prevent this.
- logger.error("organization name invalid for team {}".format(team_name))
- continue
-
- if team_alias:
- team_name = team_alias
-
- teams_to_create[team_name] = organization_name
- user_is_member_of_team = True
- else:
- user_is_member_of_team = False
-
- if organization_name not in desired_team_state:
- desired_team_state[organization_name] = {}
- desired_team_state[organization_name][team_name] = {'member_role': user_is_member_of_team}
-
-
-def _get_matches(list1, list2):
- # Because we are just doing an intersection here we don't really care which list is in which parameter
-
- # A SAML provider could return either a string or a list of items so we need to coerce the SAML value into a list (if needed)
- if not isinstance(list1, (list, tuple)):
- list1 = [list1]
-
- # In addition, we used to allow strings in the SAML config instead of Lists. The migration should take case of that but just in case, we will convert our list too
- if not isinstance(list2, (list, tuple)):
- list2 = [list2]
-
- return set(list1).intersection(set(list2))
-
-
-def _check_flag(user, flag, attributes, user_flags_settings):
- '''
- Helper function to set the is_superuser is_system_auditor flags for the SAML adapter
- Returns the new flag and whether or not it changed the flag
- '''
- new_flag = False
- is_role_key = "is_%s_role" % (flag)
- is_attr_key = "is_%s_attr" % (flag)
- is_value_key = "is_%s_value" % (flag)
- remove_setting = "remove_%ss" % (flag)
-
- # Check to see if we are respecting a role and, if so, does our user have that role?
- required_roles = user_flags_settings.get(is_role_key, None)
- if required_roles:
- matching_roles = _get_matches(required_roles, attributes.get('Role', []))
-
- # We do a 2 layer check here so that we don't spit out the else message if there is no role defined
- if matching_roles:
- logger.debug("User %s has %s role(s) %s" % (user.username, flag, ', '.join(matching_roles)))
- new_flag = True
- else:
- logger.debug("User %s is missing the %s role(s) %s" % (user.username, flag, ', '.join(required_roles)))
-
- # Next, check to see if we are respecting an attribute; this will take priority over the role if its defined
- attr_setting = user_flags_settings.get(is_attr_key, None)
- if attr_setting and attributes.get(attr_setting, None):
- # Do we have a required value for the attribute
- required_value = user_flags_settings.get(is_value_key, None)
- if required_value:
- # If so, check and see if the value of the attr matches the required value
- saml_user_attribute_value = attributes.get(attr_setting, None)
- matching_values = _get_matches(required_value, saml_user_attribute_value)
-
- if matching_values:
- logger.debug("Giving %s %s from attribute %s with matching values %s" % (user.username, flag, attr_setting, ', '.join(matching_values)))
- new_flag = True
- # if they don't match make sure that new_flag is false
- else:
- logger.debug(
- "Refusing %s for %s because attr %s (%s) did not match value(s) %s"
- % (flag, user.username, attr_setting, ", ".join(saml_user_attribute_value), ', '.join(required_value))
- )
- new_flag = False
- # If there was no required value then we can just allow them in because of the attribute
- else:
- logger.debug("Giving %s %s from attribute %s" % (user.username, flag, attr_setting))
- new_flag = True
-
- # Get the users old flag
- old_value = getattr(user, "is_%s" % (flag))
-
- # If we are not removing the flag and they were a system admin and now we don't want them to be just return
- remove_flag = user_flags_settings.get(remove_setting, True)
- if not remove_flag and (old_value and not new_flag):
- logger.debug("Remove flag %s preventing removal of %s for %s" % (remove_flag, flag, user.username))
- return old_value, False
-
- # If the user was flagged and we are going to make them not flagged make sure there is a message
- if old_value and not new_flag:
- logger.debug("Revoking %s from %s" % (flag, user.username))
-
- return new_flag, old_value != new_flag
-
-
-def update_user_flags(backend, details, user=None, *args, **kwargs):
- user_flags_settings = settings.SOCIAL_AUTH_SAML_USER_FLAGS_BY_ATTR
-
- attributes = kwargs.get('response', {}).get('attributes', {})
- logger.debug("User attributes for %s: %s" % (user.username, attributes))
-
- user.is_superuser, superuser_changed = _check_flag(user, 'superuser', attributes, user_flags_settings)
- user.is_system_auditor, auditor_changed = _check_flag(user, 'system_auditor', attributes, user_flags_settings)
-
- if superuser_changed or auditor_changed:
- user.save()
diff --git a/awx/sso/social_base_pipeline.py b/awx/sso/social_base_pipeline.py
deleted file mode 100644
index ccdaf1d20079..000000000000
--- a/awx/sso/social_base_pipeline.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright (c) 2015 Ansible, Inc.
-# All Rights Reserved.
-
-# Python Social Auth
-from social_core.exceptions import AuthException
-
-# Django
-from django.utils.translation import gettext_lazy as _
-
-
-class AuthNotFound(AuthException):
- def __init__(self, backend, email_or_uid, *args, **kwargs):
- self.email_or_uid = email_or_uid
- super(AuthNotFound, self).__init__(backend, *args, **kwargs)
-
- def __str__(self):
- return _('An account cannot be found for {0}').format(self.email_or_uid)
-
-
-class AuthInactive(AuthException):
- def __str__(self):
- return _('Your account is inactive')
-
-
-def check_user_found_or_created(backend, details, user=None, *args, **kwargs):
- if not user:
- email_or_uid = details.get('email') or kwargs.get('email') or kwargs.get('uid') or '???'
- raise AuthNotFound(backend, email_or_uid)
-
-
-def set_is_active_for_new_user(strategy, details, user=None, *args, **kwargs):
- if kwargs.get('is_new', False):
- details['is_active'] = True
- return {'details': details}
-
-
-def prevent_inactive_login(backend, details, user=None, *args, **kwargs):
- if user and not user.is_active:
- raise AuthInactive(backend)
diff --git a/awx/sso/social_pipeline.py b/awx/sso/social_pipeline.py
deleted file mode 100644
index b4fb4c1fe323..000000000000
--- a/awx/sso/social_pipeline.py
+++ /dev/null
@@ -1,90 +0,0 @@
-# Copyright (c) 2015 Ansible, Inc.
-# All Rights Reserved.
-
-# Python
-import re
-import logging
-
-from awx.sso.common import get_or_create_org_with_default_galaxy_cred
-
-logger = logging.getLogger('awx.sso.social_pipeline')
-
-
-def _update_m2m_from_expression(user, related, expr, remove=True):
- """
- Helper function to update m2m relationship based on user matching one or
- more expressions.
- """
- should_add = False
- if expr is None:
- return
- elif not expr:
- pass
- elif expr is True:
- should_add = True
- else:
- if isinstance(expr, (str, type(re.compile('')))):
- expr = [expr]
- for ex in expr:
- if isinstance(ex, str):
- if user.username == ex or user.email == ex:
- should_add = True
- elif isinstance(ex, type(re.compile(''))):
- if ex.match(user.username) or ex.match(user.email):
- should_add = True
- if should_add:
- related.add(user)
- elif remove:
- related.remove(user)
-
-
-def update_user_orgs(backend, details, user=None, *args, **kwargs):
- """
- Update organization memberships for the given user based on mapping rules
- defined in settings.
- """
- if not user:
- return
-
- org_map = backend.setting('ORGANIZATION_MAP') or {}
- for org_name, org_opts in org_map.items():
- organization_alias = org_opts.get('organization_alias')
- if organization_alias:
- organization_name = organization_alias
- else:
- organization_name = org_name
- org = get_or_create_org_with_default_galaxy_cred(name=organization_name)
-
- # Update org admins from expression(s).
- remove = bool(org_opts.get('remove', True))
- admins_expr = org_opts.get('admins', None)
- remove_admins = bool(org_opts.get('remove_admins', remove))
- _update_m2m_from_expression(user, org.admin_role.members, admins_expr, remove_admins)
-
- # Update org users from expression(s).
- users_expr = org_opts.get('users', None)
- remove_users = bool(org_opts.get('remove_users', remove))
- _update_m2m_from_expression(user, org.member_role.members, users_expr, remove_users)
-
-
-def update_user_teams(backend, details, user=None, *args, **kwargs):
- """
- Update team memberships for the given user based on mapping rules defined
- in settings.
- """
- if not user:
- return
- from awx.main.models import Team
-
- team_map = backend.setting('TEAM_MAP') or {}
- for team_name, team_opts in team_map.items():
- # Get or create the org to update.
- if 'organization' not in team_opts:
- continue
- org = get_or_create_org_with_default_galaxy_cred(name=team_opts['organization'])
-
- # Update team members from expression(s).
- team = Team.objects.get_or_create(name=team_name, organization=org)[0]
- users_expr = team_opts.get('users', None)
- remove = bool(team_opts.get('remove', True))
- _update_m2m_from_expression(user, team.member_role.members, users_expr, remove)
diff --git a/awx/sso/tests/__init__.py b/awx/sso/tests/__init__.py
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/awx/sso/tests/conftest.py b/awx/sso/tests/conftest.py
deleted file mode 100644
index f94b1c528f6d..000000000000
--- a/awx/sso/tests/conftest.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import pytest
-
-from django.contrib.auth.models import User
-
-from awx.sso.backends import TACACSPlusBackend
-from awx.sso.models import UserEnterpriseAuth
-
-
-@pytest.fixture
-def tacacsplus_backend():
- return TACACSPlusBackend()
-
-
-@pytest.fixture
-def existing_normal_user():
- try:
- user = User.objects.get(username="alice")
- except User.DoesNotExist:
- user = User(username="alice", password="password")
- user.save()
- return user
-
-
-@pytest.fixture
-def existing_tacacsplus_user():
- try:
- user = User.objects.get(username="foo")
- except User.DoesNotExist:
- user = User(username="foo")
- user.set_unusable_password()
- user.save()
- enterprise_auth = UserEnterpriseAuth(user=user, provider='tacacs+')
- enterprise_auth.save()
- return user
diff --git a/awx/sso/tests/functional/__init__.py b/awx/sso/tests/functional/__init__.py
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/awx/sso/tests/functional/test_backends.py b/awx/sso/tests/functional/test_backends.py
deleted file mode 100644
index a0d2c31da3e2..000000000000
--- a/awx/sso/tests/functional/test_backends.py
+++ /dev/null
@@ -1,115 +0,0 @@
-import pytest
-from awx.sso.backends import _update_m2m_from_groups
-
-
-class MockLDAPGroups(object):
- def is_member_of(self, group_dn):
- return bool(group_dn)
-
-
-class MockLDAPUser(object):
- def _get_groups(self):
- return MockLDAPGroups()
-
-
-@pytest.mark.parametrize(
- "setting, expected_result",
- [
- (True, True),
- ('something', True),
- (False, False),
- ('', False),
- ],
-)
-def test_mock_objects(setting, expected_result):
- ldap_user = MockLDAPUser()
- assert ldap_user._get_groups().is_member_of(setting) == expected_result
-
-
-@pytest.mark.parametrize(
- "opts, remove, expected_result",
- [
- # In these case we will pass no opts so we should get None as a return in all cases
- (
- None,
- False,
- None,
- ),
- (
- None,
- True,
- None,
- ),
- # Next lets test with empty opts ([]) This should return False if remove is True and None otherwise
- (
- [],
- True,
- False,
- ),
- (
- [],
- False,
- None,
- ),
- # Next opts is True, this will always return True
- (
- True,
- True,
- True,
- ),
- (
- True,
- False,
- True,
- ),
- # If we get only a non-string as an option we hit a continue and will either return None or False depending on the remove flag
- (
- [32],
- False,
- None,
- ),
- (
- [32],
- True,
- False,
- ),
- # Finally we need to test whether or not a user should be allowed in or not.
- # We use a mock class for ldap_user that simply returns true/false based on the otps
- (
- ['true'],
- False,
- True,
- ),
- # In this test we are going to pass a string to test the part of the code that coverts strings into array, this should give us True
- (
- 'something',
- True,
- True,
- ),
- (
- [''],
- False,
- None,
- ),
- (
- False,
- True,
- False,
- ),
- # Empty strings are considered opts == None and will result in None or False based on the remove flag
- (
- '',
- True,
- False,
- ),
- (
- '',
- False,
- None,
- ),
- ],
-)
-@pytest.mark.django_db
-def test__update_m2m_from_groups(opts, remove, expected_result):
- ldap_user = MockLDAPUser()
- assert expected_result == _update_m2m_from_groups(ldap_user, opts, remove)
diff --git a/awx/sso/tests/functional/test_common.py b/awx/sso/tests/functional/test_common.py
deleted file mode 100644
index 4fc3edd841bb..000000000000
--- a/awx/sso/tests/functional/test_common.py
+++ /dev/null
@@ -1,280 +0,0 @@
-import pytest
-from collections import Counter
-from django.core.exceptions import FieldError
-from django.utils.timezone import now
-
-from awx.main.models import Credential, CredentialType, Organization, Team, User
-from awx.sso.common import get_orgs_by_ids, reconcile_users_org_team_mappings, create_org_and_teams, get_or_create_org_with_default_galaxy_cred
-
-
-@pytest.mark.django_db
-class TestCommonFunctions:
- @pytest.fixture
- def orgs(self):
- o1 = Organization.objects.create(name='Default1')
- o2 = Organization.objects.create(name='Default2')
- o3 = Organization.objects.create(name='Default3')
- return (o1, o2, o3)
-
- @pytest.fixture
- def galaxy_credential(self):
- galaxy_type = CredentialType.objects.create(kind='galaxy')
- cred = Credential(
- created=now(), modified=now(), name='Ansible Galaxy', managed=True, credential_type=galaxy_type, inputs={'url': 'https://galaxy.ansible.com/'}
- )
- cred.save()
-
- def test_get_orgs_by_ids(self, orgs):
- orgs_and_ids = get_orgs_by_ids()
- o1, o2, o3 = orgs
- assert Counter(orgs_and_ids.keys()) == Counter([o1.name, o2.name, o3.name])
- assert Counter(orgs_and_ids.values()) == Counter([o1.id, o2.id, o3.id])
-
- def test_reconcile_users_org_team_mappings(self):
- # Create objects for us to play with
- user = User.objects.create(username='user1@foo.com', last_name='foo', first_name='bar', email='user1@foo.com', is_active=True)
- org1 = Organization.objects.create(name='Default1')
- org2 = Organization.objects.create(name='Default2')
- team1 = Team.objects.create(name='Team1', organization=org1)
- team2 = Team.objects.create(name='Team1', organization=org2)
-
- # Try adding nothing
- reconcile_users_org_team_mappings(user, {}, {}, 'Nada')
- assert list(user.roles.all()) == []
-
- # Add a user to an org that does not exist (should have no affect)
- reconcile_users_org_team_mappings(
- user,
- {
- 'junk': {'member_role': True},
- },
- {},
- 'Nada',
- )
- assert list(user.roles.all()) == []
-
- # Remove a user to an org that does not exist (should have no affect)
- reconcile_users_org_team_mappings(
- user,
- {
- 'junk': {'member_role': False},
- },
- {},
- 'Nada',
- )
- assert list(user.roles.all()) == []
-
- # Add the user to the orgs
- reconcile_users_org_team_mappings(user, {org1.name: {'member_role': True}, org2.name: {'member_role': True}}, {}, 'Nada')
- assert len(user.roles.all()) == 2
- assert user in org1.member_role
- assert user in org2.member_role
-
- # Remove the user from the orgs
- reconcile_users_org_team_mappings(user, {org1.name: {'member_role': False}, org2.name: {'member_role': False}}, {}, 'Nada')
- assert list(user.roles.all()) == []
- assert user not in org1.member_role
- assert user not in org2.member_role
-
- # Remove the user from the orgs (again, should have no affect)
- reconcile_users_org_team_mappings(user, {org1.name: {'member_role': False}, org2.name: {'member_role': False}}, {}, 'Nada')
- assert list(user.roles.all()) == []
- assert user not in org1.member_role
- assert user not in org2.member_role
-
- # Add a user back to the member role
- reconcile_users_org_team_mappings(
- user,
- {
- org1.name: {
- 'member_role': True,
- },
- },
- {},
- 'Nada',
- )
- users_roles = set(user.roles.values_list('pk', flat=True))
- assert len(users_roles) == 1
- assert user in org1.member_role
-
- # Add the user to additional roles
- reconcile_users_org_team_mappings(
- user,
- {
- org1.name: {'admin_role': True, 'auditor_role': True},
- },
- {},
- 'Nada',
- )
- assert len(user.roles.all()) == 3
- assert user in org1.member_role
- assert user in org1.admin_role
- assert user in org1.auditor_role
-
- # Add a user to a non-existent role (results in FieldError exception)
- with pytest.raises(FieldError):
- reconcile_users_org_team_mappings(
- user,
- {
- org1.name: {
- 'dne_role': True,
- },
- },
- {},
- 'Nada',
- )
-
- # Try adding a user to a role that should not exist on an org (technically this works at this time)
- reconcile_users_org_team_mappings(
- user,
- {
- org1.name: {
- 'read_role_id': True,
- },
- },
- {},
- 'Nada',
- )
- assert len(user.roles.all()) == 4
- assert user in org1.member_role
- assert user in org1.admin_role
- assert user in org1.auditor_role
-
- # Remove all of the org perms to test team perms
- reconcile_users_org_team_mappings(
- user,
- {
- org1.name: {
- 'read_role_id': False,
- 'member_role': False,
- 'admin_role': False,
- 'auditor_role': False,
- },
- },
- {},
- 'Nada',
- )
- assert list(user.roles.all()) == []
-
- # Add the user as a member to one of the teams
- reconcile_users_org_team_mappings(user, {}, {org1.name: {team1.name: {'member_role': True}}}, 'Nada')
- assert len(user.roles.all()) == 1
- assert user in team1.member_role
- # Validate that the user did not become a member of a team with the same name in a different org
- assert user not in team2.member_role
-
- # Remove the user from the team
- reconcile_users_org_team_mappings(user, {}, {org1.name: {team1.name: {'member_role': False}}}, 'Nada')
- assert list(user.roles.all()) == []
- assert user not in team1.member_role
-
- # Remove the user from the team again
- reconcile_users_org_team_mappings(user, {}, {org1.name: {team1.name: {'member_role': False}}}, 'Nada')
- assert list(user.roles.all()) == []
-
- # Add the user to a team that does not exist (should have no affect)
- reconcile_users_org_team_mappings(user, {}, {org1.name: {'junk': {'member_role': True}}}, 'Nada')
- assert list(user.roles.all()) == []
-
- # Remove the user from a team that does not exist (should have no affect)
- reconcile_users_org_team_mappings(user, {}, {org1.name: {'junk': {'member_role': False}}}, 'Nada')
- assert list(user.roles.all()) == []
-
- # Test a None setting
- reconcile_users_org_team_mappings(user, {}, {org1.name: {'junk': {'member_role': None}}}, 'Nada')
- assert list(user.roles.all()) == []
-
- # Add the user multiple teams in different orgs
- reconcile_users_org_team_mappings(user, {}, {org1.name: {team1.name: {'member_role': True}}, org2.name: {team2.name: {'member_role': True}}}, 'Nada')
- assert len(user.roles.all()) == 2
- assert user in team1.member_role
- assert user in team2.member_role
-
- # Remove the user from just one of the teams
- reconcile_users_org_team_mappings(user, {}, {org2.name: {team2.name: {'member_role': False}}}, 'Nada')
- assert len(user.roles.all()) == 1
- assert user in team1.member_role
- assert user not in team2.member_role
-
- @pytest.mark.parametrize(
- "org_list, team_map, can_create, org_count, team_count",
- [
- # In this case we will only pass in organizations
- (
- ["org1", "org2"],
- {},
- True,
- 2,
- 0,
- ),
- # In this case we will only pass in teams but the orgs will be created from the teams
- (
- [],
- {"team1": "org1", "team2": "org2"},
- True,
- 2,
- 2,
- ),
- # In this case we will reuse an org
- (
- ["org1"],
- {"team1": "org1", "team2": "org1"},
- True,
- 1,
- 2,
- ),
- # In this case we have a combination of orgs, orgs reused and an org created by a team
- (
- ["org1", "org2", "org3"],
- {"team1": "org1", "team2": "org4"},
- True,
- 4,
- 2,
- ),
- # In this case we will test a case that the UI should prevent and have a team with no Org
- # This should create org1/2 but only team1
- (
- ["org1"],
- {"team1": "org2", "team2": None},
- True,
- 2,
- 1,
- ),
- # Block any creation with the can_create flag
- (
- ["org1"],
- {"team1": "org2", "team2": None},
- False,
- 0,
- 0,
- ),
- ],
- )
- def test_create_org_and_teams(self, galaxy_credential, org_list, team_map, can_create, org_count, team_count):
- create_org_and_teams(org_list, team_map, 'py.test', can_create=can_create)
- assert Organization.objects.count() == org_count
- assert Team.objects.count() == team_count
-
- def test_get_or_create_org_with_default_galaxy_cred_add_galaxy_cred(self, galaxy_credential):
- # If this method creates the org it should get the default galaxy credential
- num_orgs = 4
- for number in range(1, (num_orgs + 1)):
- get_or_create_org_with_default_galaxy_cred(name=f"Default {number}")
-
- assert Organization.objects.count() == 4
-
- for o in Organization.objects.all():
- assert o.galaxy_credentials.count() == 1
- assert o.galaxy_credentials.first().name == 'Ansible Galaxy'
-
- def test_get_or_create_org_with_default_galaxy_cred_no_galaxy_cred(self, galaxy_credential):
- # If the org is pre-created, we should not add the galaxy_credential
- num_orgs = 4
- for number in range(1, (num_orgs + 1)):
- Organization.objects.create(name=f"Default {number}")
- get_or_create_org_with_default_galaxy_cred(name=f"Default {number}")
-
- assert Organization.objects.count() == 4
-
- for o in Organization.objects.all():
- assert o.galaxy_credentials.count() == 0
diff --git a/awx/sso/tests/functional/test_get_or_set_enterprise_user.py b/awx/sso/tests/functional/test_get_or_set_enterprise_user.py
deleted file mode 100644
index 3f37b41df319..000000000000
--- a/awx/sso/tests/functional/test_get_or_set_enterprise_user.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# Python
-import pytest
-from unittest import mock
-
-# AWX
-from awx.sso.backends import _get_or_set_enterprise_user
-
-
-@pytest.mark.django_db
-def test_fetch_user_if_exist(existing_tacacsplus_user):
- with mock.patch('awx.sso.backends.logger') as mocked_logger:
- new_user = _get_or_set_enterprise_user("foo", "password", "tacacs+")
- mocked_logger.debug.assert_not_called()
- mocked_logger.warning.assert_not_called()
- assert new_user == existing_tacacsplus_user
-
-
-@pytest.mark.django_db
-def test_create_user_if_not_exist(existing_tacacsplus_user):
- with mock.patch('awx.sso.backends.logger') as mocked_logger:
- new_user = _get_or_set_enterprise_user("bar", "password", "tacacs+")
- mocked_logger.debug.assert_called_once_with(u'Created enterprise user bar via TACACS+ backend.')
- assert new_user != existing_tacacsplus_user
-
-
-@pytest.mark.django_db
-def test_created_user_has_no_usable_password():
- new_user = _get_or_set_enterprise_user("bar", "password", "tacacs+")
- assert not new_user.has_usable_password()
-
-
-@pytest.mark.django_db
-def test_non_enterprise_user_does_not_get_pass(existing_normal_user):
- with mock.patch('awx.sso.backends.logger') as mocked_logger:
- new_user = _get_or_set_enterprise_user("alice", "password", "tacacs+")
- mocked_logger.warning.assert_called_once_with(u'Enterprise user alice already defined in Tower.')
- assert new_user is None
diff --git a/awx/sso/tests/functional/test_ldap.py b/awx/sso/tests/functional/test_ldap.py
deleted file mode 100644
index 881ab29e2b4f..000000000000
--- a/awx/sso/tests/functional/test_ldap.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from django.test.utils import override_settings
-import ldap
-import pytest
-
-from awx.sso.backends import LDAPSettings
-
-
-@override_settings(AUTH_LDAP_CONNECTION_OPTIONS={ldap.OPT_NETWORK_TIMEOUT: 60})
-@pytest.mark.django_db
-def test_ldap_with_custom_timeout():
- settings = LDAPSettings()
- assert settings.CONNECTION_OPTIONS == {ldap.OPT_NETWORK_TIMEOUT: 60}
-
-
-@override_settings(AUTH_LDAP_CONNECTION_OPTIONS={ldap.OPT_REFERRALS: 0})
-@pytest.mark.django_db
-def test_ldap_with_missing_timeout():
- settings = LDAPSettings()
- assert settings.CONNECTION_OPTIONS == {ldap.OPT_REFERRALS: 0, ldap.OPT_NETWORK_TIMEOUT: 30}
diff --git a/awx/sso/tests/functional/test_saml_pipeline.py b/awx/sso/tests/functional/test_saml_pipeline.py
deleted file mode 100644
index 628d793d4eec..000000000000
--- a/awx/sso/tests/functional/test_saml_pipeline.py
+++ /dev/null
@@ -1,639 +0,0 @@
-import pytest
-import re
-
-from django.test.utils import override_settings
-from awx.main.models import User, Organization, Team
-from awx.sso.saml_pipeline import (
- _update_m2m_from_expression,
- _update_user_orgs,
- _update_user_teams,
- _update_user_orgs_by_saml_attr,
- _update_user_teams_by_saml_attr,
- _check_flag,
-)
-
-# from unittest import mock
-# from django.utils.timezone import now
-# , Credential, CredentialType
-
-
-@pytest.fixture
-def users():
- u1 = User.objects.create(username='user1@foo.com', last_name='foo', first_name='bar', email='user1@foo.com')
- u2 = User.objects.create(username='user2@foo.com', last_name='foo', first_name='bar', email='user2@foo.com')
- u3 = User.objects.create(username='user3@foo.com', last_name='foo', first_name='bar', email='user3@foo.com')
- return (u1, u2, u3)
-
-
-@pytest.mark.django_db
-class TestSAMLPopulateUser:
- # The main populate_user does not need to be tested since its just a conglomeration of other functions that we test
- # This test is here in case someone alters the code in the future in a way that does require testing
- def test_populate_user(self):
- assert True
-
-
-@pytest.mark.django_db
-class TestSAMLSimpleMaps:
- # This tests __update_user_orgs and __update_user_teams
- @pytest.fixture
- def backend(self):
- class Backend:
- s = {
- 'ORGANIZATION_MAP': {
- 'Default': {
- 'remove': True,
- 'admins': 'foobar',
- 'remove_admins': True,
- 'users': 'foo',
- 'remove_users': True,
- 'organization_alias': '',
- }
- },
- 'TEAM_MAP': {'Blue': {'organization': 'Default', 'remove': True, 'users': ''}, 'Red': {'organization': 'Default', 'remove': True, 'users': ''}},
- }
-
- def setting(self, key):
- return self.s[key]
-
- return Backend()
-
- def test__update_user_orgs(self, backend, users):
- u1, u2, u3 = users
-
- # Test user membership logic with regular expressions
- backend.setting('ORGANIZATION_MAP')['Default']['admins'] = re.compile('.*')
- backend.setting('ORGANIZATION_MAP')['Default']['users'] = re.compile('.*')
-
- desired_org_state = {}
- orgs_to_create = []
- _update_user_orgs(backend, desired_org_state, orgs_to_create, u1)
- _update_user_orgs(backend, desired_org_state, orgs_to_create, u2)
- _update_user_orgs(backend, desired_org_state, orgs_to_create, u3)
-
- assert desired_org_state == {'Default': {'member_role': True, 'admin_role': True, 'auditor_role': False}}
- assert orgs_to_create == ['Default']
-
- # Test remove feature enabled
- backend.setting('ORGANIZATION_MAP')['Default']['admins'] = ''
- backend.setting('ORGANIZATION_MAP')['Default']['users'] = ''
- backend.setting('ORGANIZATION_MAP')['Default']['remove_admins'] = True
- backend.setting('ORGANIZATION_MAP')['Default']['remove_users'] = True
- desired_org_state = {}
- orgs_to_create = []
- _update_user_orgs(backend, desired_org_state, orgs_to_create, u1)
- assert desired_org_state == {'Default': {'member_role': False, 'admin_role': False, 'auditor_role': False}}
- assert orgs_to_create == ['Default']
-
- # Test remove feature disabled
- backend.setting('ORGANIZATION_MAP')['Default']['remove_admins'] = False
- backend.setting('ORGANIZATION_MAP')['Default']['remove_users'] = False
- desired_org_state = {}
- orgs_to_create = []
- _update_user_orgs(backend, desired_org_state, orgs_to_create, u2)
-
- assert desired_org_state == {'Default': {'member_role': None, 'admin_role': None, 'auditor_role': False}}
- assert orgs_to_create == ['Default']
-
- # Test organization alias feature
- backend.setting('ORGANIZATION_MAP')['Default']['organization_alias'] = 'Default_Alias'
- orgs_to_create = []
- _update_user_orgs(backend, {}, orgs_to_create, u1)
- assert orgs_to_create == ['Default_Alias']
-
- def test__update_user_teams(self, backend, users):
- u1, u2, u3 = users
-
- # Test user membership logic with regular expressions
- backend.setting('TEAM_MAP')['Blue']['users'] = re.compile('.*')
- backend.setting('TEAM_MAP')['Red']['users'] = re.compile('.*')
-
- desired_team_state = {}
- teams_to_create = {}
- _update_user_teams(backend, desired_team_state, teams_to_create, u1)
- assert teams_to_create == {'Red': 'Default', 'Blue': 'Default'}
- assert desired_team_state == {'Default': {'Blue': {'member_role': True}, 'Red': {'member_role': True}}}
-
- # Test remove feature enabled
- backend.setting('TEAM_MAP')['Blue']['remove'] = True
- backend.setting('TEAM_MAP')['Red']['remove'] = True
- backend.setting('TEAM_MAP')['Blue']['users'] = ''
- backend.setting('TEAM_MAP')['Red']['users'] = ''
-
- desired_team_state = {}
- teams_to_create = {}
- _update_user_teams(backend, desired_team_state, teams_to_create, u1)
- assert teams_to_create == {'Red': 'Default', 'Blue': 'Default'}
- assert desired_team_state == {'Default': {'Blue': {'member_role': False}, 'Red': {'member_role': False}}}
-
- # Test remove feature disabled
- backend.setting('TEAM_MAP')['Blue']['remove'] = False
- backend.setting('TEAM_MAP')['Red']['remove'] = False
-
- desired_team_state = {}
- teams_to_create = {}
- _update_user_teams(backend, desired_team_state, teams_to_create, u2)
- assert teams_to_create == {'Red': 'Default', 'Blue': 'Default'}
- # If we don't care about team memberships we just don't add them to the hash so this would be an empty hash
- assert desired_team_state == {}
-
-
-@pytest.mark.django_db
-class TestSAMLM2M:
- @pytest.mark.parametrize(
- "expression, remove, expected_return",
- [
- # No expression with no remove
- (None, False, None),
- ("", False, None),
- # No expression with remove
- (None, True, False),
- # True expression with and without remove
- (True, False, True),
- (True, True, True),
- # Single string matching the user name
- ("user1", False, True),
- # Single string matching the user email
- ("user1@foo.com", False, True),
- # Single string not matching username or email, no remove
- ("user27", False, None),
- # Single string not matching username or email, with remove
- ("user27", True, False),
- # Same tests with arrays instead of strings
- (["user1"], False, True),
- (["user1@foo.com"], False, True),
- (["user27"], False, None),
- (["user27"], True, False),
- # Arrays with nothing matching
- (["user27", "user28"], False, None),
- (["user27", "user28"], True, False),
- # Arrays with all matches
- (["user1", "user1@foo.com"], False, True),
- # Arrays with some match, some not
- (["user1", "user28", "user27"], False, True),
- #
- # Note: For RE's, usually settings takes care of the compilation for us, so we have to do it manually for testing.
- # we also need to remove any / or flags for the compile to happen
- #
- # Matching username regex non-array
- (re.compile("^user.*"), False, True),
- (re.compile("^user.*"), True, True),
- # Matching email regex non-array
- (re.compile(".*@foo.com$"), False, True),
- (re.compile(".*@foo.com$"), True, True),
- # Non-array not matching username or email
- (re.compile("^$"), False, None),
- (re.compile("^$"), True, False),
- # All re tests just in array form
- ([re.compile("^user.*")], False, True),
- ([re.compile("^user.*")], True, True),
- ([re.compile(".*@foo.com$")], False, True),
- ([re.compile(".*@foo.com$")], True, True),
- ([re.compile("^$")], False, None),
- ([re.compile("^$")], True, False),
- # An re with username matching but not email
- ([re.compile("^user.*"), re.compile(".*@bar.com$")], False, True),
- # An re with email matching but not username
- ([re.compile("^user27$"), re.compile(".*@foo.com$")], False, True),
- # An re array with no matching
- ([re.compile("^user27$"), re.compile(".*@bar.com$")], False, None),
- ([re.compile("^user27$"), re.compile(".*@bar.com$")], True, False),
- #
- # A mix of re and strings
- #
- # String matches, re does not
- (["user1", re.compile(".*@bar.com$")], False, True),
- # String does not match, re does
- (["user27", re.compile(".*@foo.com$")], False, True),
- # Nothing matches
- (["user27", re.compile(".*@bar.com$")], False, None),
- (["user27", re.compile(".*@bar.com$")], True, False),
- ],
- )
- def test__update_m2m_from_expression(self, expression, remove, expected_return):
- user = User.objects.create(username='user1', last_name='foo', first_name='bar', email='user1@foo.com')
- return_val = _update_m2m_from_expression(user, expression, remove)
- assert return_val == expected_return
-
-
-@pytest.mark.django_db
-class TestSAMLAttrMaps:
- @pytest.fixture
- def backend(self):
- class Backend:
- s = {
- 'ORGANIZATION_MAP': {
- 'Default1': {
- 'remove': True,
- 'admins': 'foobar',
- 'remove_admins': True,
- 'users': 'foo',
- 'remove_users': True,
- 'organization_alias': 'o1_alias',
- }
- }
- }
-
- def setting(self, key):
- return self.s[key]
-
- return Backend()
-
- @pytest.mark.parametrize(
- "setting, expected_state, expected_orgs_to_create, kwargs_member_of_mods",
- [
- (
- # Default test, make sure that our roles get applied and removed as specified (with an alias)
- {
- 'saml_attr': 'memberOf',
- 'saml_admin_attr': 'admins',
- 'saml_auditor_attr': 'auditors',
- 'remove': True,
- 'remove_admins': True,
- },
- {
- 'Default2': {'member_role': True},
- 'Default3': {'admin_role': True},
- 'Default4': {'auditor_role': True},
- 'o1_alias': {'member_role': True},
- 'Rando1': {'admin_role': False, 'auditor_role': False, 'member_role': False},
- },
- [
- 'o1_alias',
- 'Default2',
- 'Default3',
- 'Default4',
- ],
- None,
- ),
- (
- # Similar test, we are just going to override the values "coming from the IdP" to limit the teams
- {
- 'saml_attr': 'memberOf',
- 'saml_admin_attr': 'admins',
- 'saml_auditor_attr': 'auditors',
- 'remove': True,
- 'remove_admins': True,
- },
- {
- 'Default3': {'admin_role': True, 'member_role': True},
- 'Default4': {'auditor_role': True},
- 'Rando1': {'admin_role': False, 'auditor_role': False, 'member_role': False},
- },
- [
- 'Default3',
- 'Default4',
- ],
- ['Default3'],
- ),
- (
- # Test to make sure the remove logic is working
- {
- 'saml_attr': 'memberOf',
- 'saml_admin_attr': 'admins',
- 'saml_auditor_attr': 'auditors',
- 'remove': False,
- 'remove_admins': False,
- 'remove_auditors': False,
- },
- {
- 'Default2': {'member_role': True},
- 'Default3': {'admin_role': True},
- 'Default4': {'auditor_role': True},
- 'o1_alias': {'member_role': True},
- },
- [
- 'o1_alias',
- 'Default2',
- 'Default3',
- 'Default4',
- ],
- ['Default1', 'Default2'],
- ),
- ],
- )
- def test__update_user_orgs_by_saml_attr(self, backend, setting, expected_state, expected_orgs_to_create, kwargs_member_of_mods):
- kwargs = {
- 'username': u'cmeyers@redhat.com',
- 'uid': 'idp:cmeyers@redhat.com',
- 'request': {u'SAMLResponse': [], u'RelayState': [u'idp']},
- 'is_new': False,
- 'response': {
- 'session_index': '_0728f0e0-b766-0135-75fa-02842b07c044',
- 'idp_name': u'idp',
- 'attributes': {
- 'memberOf': ['Default1', 'Default2'],
- 'admins': ['Default3'],
- 'auditors': ['Default4'],
- 'groups': ['Blue', 'Red'],
- 'User.email': ['cmeyers@redhat.com'],
- 'User.LastName': ['Meyers'],
- 'name_id': 'cmeyers@redhat.com',
- 'User.FirstName': ['Chris'],
- 'PersonImmutableID': [],
- },
- },
- 'social': None,
- 'strategy': None,
- 'new_association': False,
- }
- if kwargs_member_of_mods:
- kwargs['response']['attributes']['memberOf'] = kwargs_member_of_mods
-
- # Create a random organization in the database for testing
- Organization.objects.create(name='Rando1')
-
- with override_settings(SOCIAL_AUTH_SAML_ORGANIZATION_ATTR=setting):
- desired_org_state = {}
- orgs_to_create = []
- _update_user_orgs_by_saml_attr(backend, desired_org_state, orgs_to_create, **kwargs)
- assert desired_org_state == expected_state
- assert orgs_to_create == expected_orgs_to_create
-
- @pytest.mark.parametrize(
- "setting, expected_team_state, expected_teams_to_create, kwargs_group_override",
- [
- (
- {
- 'saml_attr': 'groups',
- 'remove': False,
- 'team_org_map': [
- {'team': 'Blue', 'organization': 'Default1'},
- {'team': 'Blue', 'organization': 'Default2'},
- {'team': 'Blue', 'organization': 'Default3'},
- {'team': 'Red', 'organization': 'Default1'},
- {'team': 'Green', 'organization': 'Default1'},
- {'team': 'Green', 'organization': 'Default3'},
- {'team': 'Yellow', 'team_alias': 'Yellow_Alias', 'organization': 'Default4', 'organization_alias': 'Default4_Alias'},
- ],
- },
- {
- 'Default1': {
- 'Blue': {'member_role': True},
- 'Green': {'member_role': False},
- 'Red': {'member_role': True},
- },
- 'Default2': {
- 'Blue': {'member_role': True},
- },
- 'Default3': {
- 'Blue': {'member_role': True},
- 'Green': {'member_role': False},
- },
- 'Default4': {
- 'Yellow': {'member_role': False},
- },
- },
- {
- 'Blue': 'Default3',
- 'Red': 'Default1',
- },
- None,
- ),
- (
- {
- 'saml_attr': 'groups',
- 'remove': False,
- 'team_org_map': [
- {'team': 'Blue', 'organization': 'Default1'},
- {'team': 'Blue', 'organization': 'Default2'},
- {'team': 'Blue', 'organization': 'Default3'},
- {'team': 'Red', 'organization': 'Default1'},
- {'team': 'Green', 'organization': 'Default1'},
- {'team': 'Green', 'organization': 'Default3'},
- {'team': 'Yellow', 'team_alias': 'Yellow_Alias', 'organization': 'Default4', 'organization_alias': 'Default4_Alias'},
- ],
- },
- {
- 'Default1': {
- 'Blue': {'member_role': True},
- 'Green': {'member_role': True},
- 'Red': {'member_role': True},
- },
- 'Default2': {
- 'Blue': {'member_role': True},
- },
- 'Default3': {
- 'Blue': {'member_role': True},
- 'Green': {'member_role': True},
- },
- 'Default4': {
- 'Yellow': {'member_role': False},
- },
- },
- {
- 'Blue': 'Default3',
- 'Red': 'Default1',
- 'Green': 'Default3',
- },
- ['Blue', 'Red', 'Green'],
- ),
- (
- {
- 'saml_attr': 'groups',
- 'remove': True,
- 'team_org_map': [
- {'team': 'Blue', 'organization': 'Default1'},
- {'team': 'Blue', 'organization': 'Default2'},
- {'team': 'Blue', 'organization': 'Default3'},
- {'team': 'Red', 'organization': 'Default1'},
- {'team': 'Green', 'organization': 'Default1'},
- {'team': 'Green', 'organization': 'Default3'},
- {'team': 'Yellow', 'team_alias': 'Yellow_Alias', 'organization': 'Default4', 'organization_alias': 'Default4_Alias'},
- ],
- },
- {
- 'Default1': {
- 'Blue': {'member_role': False},
- 'Green': {'member_role': True},
- 'Red': {'member_role': False},
- },
- 'Default2': {
- 'Blue': {'member_role': False},
- },
- 'Default3': {
- 'Blue': {'member_role': False},
- 'Green': {'member_role': True},
- },
- 'Default4': {
- 'Yellow': {'member_role': False},
- },
- 'Rando1': {
- 'Rando1': {'member_role': False},
- },
- },
- {
- 'Green': 'Default3',
- },
- ['Green'],
- ),
- ],
- )
- def test__update_user_teams_by_saml_attr(self, setting, expected_team_state, expected_teams_to_create, kwargs_group_override):
- kwargs = {
- 'username': u'cmeyers@redhat.com',
- 'uid': 'idp:cmeyers@redhat.com',
- 'request': {u'SAMLResponse': [], u'RelayState': [u'idp']},
- 'is_new': False,
- 'response': {
- 'session_index': '_0728f0e0-b766-0135-75fa-02842b07c044',
- 'idp_name': u'idp',
- 'attributes': {
- 'memberOf': ['Default1', 'Default2'],
- 'admins': ['Default3'],
- 'auditors': ['Default4'],
- 'groups': ['Blue', 'Red'],
- 'User.email': ['cmeyers@redhat.com'],
- 'User.LastName': ['Meyers'],
- 'name_id': 'cmeyers@redhat.com',
- 'User.FirstName': ['Chris'],
- 'PersonImmutableID': [],
- },
- },
- 'social': None,
- 'strategy': None,
- 'new_association': False,
- }
- if kwargs_group_override:
- kwargs['response']['attributes']['groups'] = kwargs_group_override
-
- o = Organization.objects.create(name='Rando1')
- Team.objects.create(name='Rando1', organization_id=o.id)
-
- with override_settings(SOCIAL_AUTH_SAML_TEAM_ATTR=setting):
- desired_team_state = {}
- teams_to_create = {}
- _update_user_teams_by_saml_attr(desired_team_state, teams_to_create, **kwargs)
- assert desired_team_state == expected_team_state
- assert teams_to_create == expected_teams_to_create
-
-
-@pytest.mark.django_db
-class TestSAMLUserFlags:
- @pytest.mark.parametrize(
- "user_flags_settings, expected, is_superuser",
- [
- # In this case we will pass no user flags so new_flag should be false and changed will def be false
- (
- {},
- (False, False),
- False,
- ),
- # NOTE: The first handful of tests test role/value as string instead of lists.
- # This was from the initial implementation of these fields but the code should be able to handle this
- # There are a couple tests at the end of this which will validate arrays in these values.
- #
- # In this case we will give the user a group to make them an admin
- (
- {'is_superuser_role': 'test-role-1'},
- (True, True),
- False,
- ),
- # In this case we will give the user a flag that will make then an admin
- (
- {'is_superuser_attr': 'is_superuser'},
- (True, True),
- False,
- ),
- # In this case we will give the user a flag but the wrong value
- (
- {'is_superuser_attr': 'is_superuser', 'is_superuser_value': 'junk'},
- (False, False),
- False,
- ),
- # In this case we will give the user a flag and the right value
- (
- {'is_superuser_attr': 'is_superuser', 'is_superuser_value': 'true'},
- (True, True),
- False,
- ),
- # In this case we will give the user a proper role and an is_superuser_attr role that they don't have, this should make them an admin
- (
- {'is_superuser_role': 'test-role-1', 'is_superuser_attr': 'gibberish', 'is_superuser_value': 'true'},
- (True, True),
- False,
- ),
- # In this case we will give the user a proper role and an is_superuser_attr role that they have, this should make them an admin
- (
- {'is_superuser_role': 'test-role-1', 'is_superuser_attr': 'test-role-1'},
- (True, True),
- False,
- ),
- # In this case we will give the user a proper role and an is_superuser_attr role that they have but a bad value, this should make them an admin
- (
- {'is_superuser_role': 'test-role-1', 'is_superuser_attr': 'is_superuser', 'is_superuser_value': 'junk'},
- (False, False),
- False,
- ),
- # In this case we will give the user everything
- (
- {'is_superuser_role': 'test-role-1', 'is_superuser_attr': 'is_superuser', 'is_superuser_value': 'true'},
- (True, True),
- False,
- ),
- # In this test case we will validate that a single attribute (instead of a list) still works
- (
- {'is_superuser_attr': 'name_id', 'is_superuser_value': 'test_id'},
- (True, True),
- False,
- ),
- # This will be a negative test for a single attribute
- (
- {'is_superuser_attr': 'name_id', 'is_superuser_value': 'junk'},
- (False, False),
- False,
- ),
- # The user is already a superuser so we should remove them
- (
- {'is_superuser_attr': 'name_id', 'is_superuser_value': 'junk', 'remove_superusers': True},
- (False, True),
- True,
- ),
- # The user is already a superuser but we don't have a remove field
- (
- {'is_superuser_attr': 'name_id', 'is_superuser_value': 'junk', 'remove_superusers': False},
- (True, False),
- True,
- ),
- # Positive test for multiple values for is_superuser_value
- (
- {'is_superuser_attr': 'is_superuser', 'is_superuser_value': ['junk', 'junk2', 'else', 'junk']},
- (True, True),
- False,
- ),
- # Negative test for multiple values for is_superuser_value
- (
- {'is_superuser_attr': 'is_superuser', 'is_superuser_value': ['junk', 'junk2', 'junk']},
- (False, True),
- True,
- ),
- # Positive test for multiple values of is_superuser_role
- (
- {'is_superuser_role': ['junk', 'junk2', 'something', 'junk']},
- (True, True),
- False,
- ),
- # Negative test for multiple values of is_superuser_role
- (
- {'is_superuser_role': ['junk', 'junk2', 'junk']},
- (False, True),
- True,
- ),
- ],
- )
- def test__check_flag(self, user_flags_settings, expected, is_superuser):
- user = User()
- user.username = 'John'
- user.is_superuser = is_superuser
-
- attributes = {
- 'email': ['noone@nowhere.com'],
- 'last_name': ['Westcott'],
- 'is_superuser': ['something', 'else', 'true'],
- 'username': ['test_id'],
- 'first_name': ['John'],
- 'Role': ['test-role-1', 'something', 'different'],
- 'name_id': 'test_id',
- }
-
- assert expected == _check_flag(user, 'superuser', attributes, user_flags_settings)
diff --git a/awx/sso/tests/functional/test_social_base_pipeline.py b/awx/sso/tests/functional/test_social_base_pipeline.py
deleted file mode 100644
index 38a49e15f331..000000000000
--- a/awx/sso/tests/functional/test_social_base_pipeline.py
+++ /dev/null
@@ -1,76 +0,0 @@
-import pytest
-
-from awx.main.models import User
-from awx.sso.social_base_pipeline import AuthNotFound, check_user_found_or_created, set_is_active_for_new_user, prevent_inactive_login, AuthInactive
-
-
-@pytest.mark.django_db
-class TestSocialBasePipeline:
- def test_check_user_found_or_created_no_exception(self):
- # If we have a user (the True param, we should not get an exception)
- try:
- check_user_found_or_created(None, {}, True)
- except AuthNotFound:
- assert False, 'check_user_found_or_created should not have raised an exception with a user'
-
- @pytest.mark.parametrize(
- "details, kwargs, expected_id",
- [
- (
- {},
- {},
- '???',
- ),
- (
- {},
- {'uid': 'kwargs_uid'},
- 'kwargs_uid',
- ),
- (
- {},
- {'uid': 'kwargs_uid', 'email': 'kwargs_email'},
- 'kwargs_email',
- ),
- (
- {'email': 'details_email'},
- {'uid': 'kwargs_uid', 'email': 'kwargs_email'},
- 'details_email',
- ),
- ],
- )
- def test_check_user_found_or_created_exceptions(self, details, expected_id, kwargs):
- with pytest.raises(AuthNotFound) as e:
- check_user_found_or_created(None, details, False, None, **kwargs)
- assert f'An account cannot be found for {expected_id}' == str(e.value)
-
- @pytest.mark.parametrize(
- "kwargs, expected_details, expected_response",
- [
- ({}, {}, None),
- ({'is_new': False}, {}, None),
- ({'is_new': True}, {'is_active': True}, {'details': {'is_active': True}}),
- ],
- )
- def test_set_is_active_for_new_user(self, kwargs, expected_details, expected_response):
- details = {}
- response = set_is_active_for_new_user(None, details, None, None, **kwargs)
- assert details == expected_details
- assert response == expected_response
-
- def test_prevent_inactive_login_no_exception_no_user(self):
- try:
- prevent_inactive_login(None, None, None, None, None)
- except AuthInactive:
- assert False, 'prevent_inactive_login should not have raised an exception with no user'
-
- def test_prevent_inactive_login_no_exception_active_user(self):
- user = User.objects.create(username='user1@foo.com', last_name='foo', first_name='bar', email='user1@foo.com', is_active=True)
- try:
- prevent_inactive_login(None, None, user, None, None)
- except AuthInactive:
- assert False, 'prevent_inactive_login should not have raised an exception with an active user'
-
- def test_prevent_inactive_login_no_exception_inactive_user(self):
- user = User.objects.create(username='user1@foo.com', last_name='foo', first_name='bar', email='user1@foo.com', is_active=False)
- with pytest.raises(AuthInactive):
- prevent_inactive_login(None, None, user, None, None)
diff --git a/awx/sso/tests/functional/test_social_pipeline.py b/awx/sso/tests/functional/test_social_pipeline.py
deleted file mode 100644
index f26886e71944..000000000000
--- a/awx/sso/tests/functional/test_social_pipeline.py
+++ /dev/null
@@ -1,113 +0,0 @@
-import pytest
-import re
-
-from awx.sso.social_pipeline import update_user_orgs, update_user_teams
-from awx.main.models import User, Team, Organization
-
-
-@pytest.fixture
-def users():
- u1 = User.objects.create(username='user1@foo.com', last_name='foo', first_name='bar', email='user1@foo.com')
- u2 = User.objects.create(username='user2@foo.com', last_name='foo', first_name='bar', email='user2@foo.com')
- u3 = User.objects.create(username='user3@foo.com', last_name='foo', first_name='bar', email='user3@foo.com')
- return (u1, u2, u3)
-
-
-@pytest.mark.django_db
-class TestSocialPipeline:
- @pytest.fixture
- def backend(self):
- class Backend:
- s = {
- 'ORGANIZATION_MAP': {
- 'Default': {
- 'remove': True,
- 'admins': 'foobar',
- 'remove_admins': True,
- 'users': 'foo',
- 'remove_users': True,
- 'organization_alias': '',
- }
- },
- 'TEAM_MAP': {'Blue': {'organization': 'Default', 'remove': True, 'users': ''}, 'Red': {'organization': 'Default', 'remove': True, 'users': ''}},
- }
-
- def setting(self, key):
- return self.s[key]
-
- return Backend()
-
- @pytest.fixture
- def org(self):
- return Organization.objects.create(name="Default")
-
- def test_update_user_orgs(self, org, backend, users):
- u1, u2, u3 = users
-
- # Test user membership logic with regular expressions
- backend.setting('ORGANIZATION_MAP')['Default']['admins'] = re.compile('.*')
- backend.setting('ORGANIZATION_MAP')['Default']['users'] = re.compile('.*')
-
- update_user_orgs(backend, None, u1)
- update_user_orgs(backend, None, u2)
- update_user_orgs(backend, None, u3)
-
- assert org.admin_role.members.count() == 3
- assert org.member_role.members.count() == 3
-
- # Test remove feature enabled
- backend.setting('ORGANIZATION_MAP')['Default']['admins'] = ''
- backend.setting('ORGANIZATION_MAP')['Default']['users'] = ''
- backend.setting('ORGANIZATION_MAP')['Default']['remove_admins'] = True
- backend.setting('ORGANIZATION_MAP')['Default']['remove_users'] = True
- update_user_orgs(backend, None, u1)
-
- assert org.admin_role.members.count() == 2
- assert org.member_role.members.count() == 2
-
- # Test remove feature disabled
- backend.setting('ORGANIZATION_MAP')['Default']['remove_admins'] = False
- backend.setting('ORGANIZATION_MAP')['Default']['remove_users'] = False
- update_user_orgs(backend, None, u2)
-
- assert org.admin_role.members.count() == 2
- assert org.member_role.members.count() == 2
-
- # Test organization alias feature
- backend.setting('ORGANIZATION_MAP')['Default']['organization_alias'] = 'Default_Alias'
- update_user_orgs(backend, None, u1)
- assert Organization.objects.get(name="Default_Alias") is not None
-
- def test_update_user_teams(self, backend, users):
- u1, u2, u3 = users
-
- # Test user membership logic with regular expressions
- backend.setting('TEAM_MAP')['Blue']['users'] = re.compile('.*')
- backend.setting('TEAM_MAP')['Red']['users'] = re.compile('.*')
-
- update_user_teams(backend, None, u1)
- update_user_teams(backend, None, u2)
- update_user_teams(backend, None, u3)
-
- assert Team.objects.get(name="Red").member_role.members.count() == 3
- assert Team.objects.get(name="Blue").member_role.members.count() == 3
-
- # Test remove feature enabled
- backend.setting('TEAM_MAP')['Blue']['remove'] = True
- backend.setting('TEAM_MAP')['Red']['remove'] = True
- backend.setting('TEAM_MAP')['Blue']['users'] = ''
- backend.setting('TEAM_MAP')['Red']['users'] = ''
-
- update_user_teams(backend, None, u1)
-
- assert Team.objects.get(name="Red").member_role.members.count() == 2
- assert Team.objects.get(name="Blue").member_role.members.count() == 2
-
- # Test remove feature disabled
- backend.setting('TEAM_MAP')['Blue']['remove'] = False
- backend.setting('TEAM_MAP')['Red']['remove'] = False
-
- update_user_teams(backend, None, u2)
-
- assert Team.objects.get(name="Red").member_role.members.count() == 2
- assert Team.objects.get(name="Blue").member_role.members.count() == 2
diff --git a/awx/sso/tests/test_env.py b/awx/sso/tests/test_env.py
deleted file mode 100644
index b63da8ed8a16..000000000000
--- a/awx/sso/tests/test_env.py
+++ /dev/null
@@ -1,4 +0,0 @@
-# Ensure that our autouse overwrites are working
-def test_cache(settings):
- assert settings.CACHES['default']['BACKEND'] == 'django.core.cache.backends.locmem.LocMemCache'
- assert settings.CACHES['default']['LOCATION'].startswith('unique-')
diff --git a/awx/sso/tests/unit/test_fields.py b/awx/sso/tests/unit/test_fields.py
deleted file mode 100644
index 35ab58d07fab..000000000000
--- a/awx/sso/tests/unit/test_fields.py
+++ /dev/null
@@ -1,235 +0,0 @@
-import pytest
-from unittest import mock
-
-from rest_framework.exceptions import ValidationError
-
-from awx.sso.fields import SAMLOrgAttrField, SAMLTeamAttrField, SAMLUserFlagsAttrField, LDAPGroupTypeParamsField, LDAPServerURIField
-
-
-class TestSAMLOrgAttrField:
- @pytest.mark.parametrize(
- "data, expected",
- [
- ({}, {}),
- ({'remove': True, 'saml_attr': 'foobar'}, {'remove': True, 'saml_attr': 'foobar'}),
- ({'remove': True, 'saml_attr': 1234}, {'remove': True, 'saml_attr': '1234'}),
- ({'remove': True, 'saml_attr': 3.14}, {'remove': True, 'saml_attr': '3.14'}),
- ({'saml_attr': 'foobar'}, {'saml_attr': 'foobar'}),
- ({'remove': True}, {'remove': True}),
- ({'remove': True, 'saml_admin_attr': 'foobar'}, {'remove': True, 'saml_admin_attr': 'foobar'}),
- ({'saml_admin_attr': 'foobar'}, {'saml_admin_attr': 'foobar'}),
- ({'remove_admins': True, 'saml_admin_attr': 'foobar'}, {'remove_admins': True, 'saml_admin_attr': 'foobar'}),
- (
- {'remove': True, 'saml_attr': 'foo', 'remove_admins': True, 'saml_admin_attr': 'bar'},
- {'remove': True, 'saml_attr': 'foo', 'remove_admins': True, 'saml_admin_attr': 'bar'},
- ),
- ],
- )
- def test_internal_value_valid(self, data, expected):
- field = SAMLOrgAttrField()
- res = field.to_internal_value(data)
- assert res == expected
-
- @pytest.mark.parametrize(
- "data, expected",
- [
- ({'remove': 'blah', 'saml_attr': 'foobar'}, {'remove': ['Must be a valid boolean.']}),
- ({'remove': True, 'saml_attr': False}, {'saml_attr': ['Not a valid string.']}),
- (
- {'remove': True, 'saml_attr': False, 'foo': 'bar', 'gig': 'ity'},
- {'saml_attr': ['Not a valid string.'], 'foo': ['Invalid field.'], 'gig': ['Invalid field.']},
- ),
- ({'remove_admins': True, 'saml_admin_attr': False}, {'saml_admin_attr': ['Not a valid string.']}),
- ({'remove_admins': 'blah', 'saml_admin_attr': 'foobar'}, {'remove_admins': ['Must be a valid boolean.']}),
- ],
- )
- def test_internal_value_invalid(self, data, expected):
- field = SAMLOrgAttrField()
- with pytest.raises(ValidationError) as e:
- field.to_internal_value(data)
- assert e.value.detail == expected
-
-
-class TestSAMLTeamAttrField:
- @pytest.mark.parametrize(
- "data",
- [
- {},
- {'remove': True, 'saml_attr': 'foobar', 'team_org_map': []},
- {'remove': True, 'saml_attr': 'foobar', 'team_org_map': [{'team': 'Engineering', 'organization': 'Ansible'}]},
- {
- 'remove': True,
- 'saml_attr': 'foobar',
- 'team_org_map': [
- {'team': 'Engineering', 'organization': 'Ansible'},
- {'team': 'Engineering', 'organization': 'Ansible2'},
- {'team': 'Engineering2', 'organization': 'Ansible'},
- ],
- },
- {
- 'remove': True,
- 'saml_attr': 'foobar',
- 'team_org_map': [
- {'team': 'Engineering', 'organization': 'Ansible'},
- {'team': 'Engineering', 'organization': 'Ansible2'},
- {'team': 'Engineering2', 'organization': 'Ansible'},
- ],
- },
- {
- 'remove': True,
- 'saml_attr': 'foobar',
- 'team_org_map': [
- {'team': 'Engineering', 'team_alias': 'Engineering Team', 'organization': 'Ansible'},
- {'team': 'Engineering', 'organization': 'Ansible2'},
- {'team': 'Engineering2', 'organization': 'Ansible'},
- ],
- },
- ],
- )
- def test_internal_value_valid(self, data):
- field = SAMLTeamAttrField()
- res = field.to_internal_value(data)
- assert res == data
-
- @pytest.mark.parametrize(
- "data, expected",
- [
- (
- {'remove': True, 'saml_attr': 'foobar', 'team_org_map': [{'team': 'foobar', 'not_a_valid_key': 'blah', 'organization': 'Ansible'}]},
- {'team_org_map': {0: {'not_a_valid_key': ['Invalid field.']}}},
- ),
- (
- {'remove': False, 'saml_attr': 'foobar', 'team_org_map': [{'organization': 'Ansible'}]},
- {'team_org_map': {0: {'team': ['This field is required.']}}},
- ),
- (
- {'remove': False, 'saml_attr': 'foobar', 'team_org_map': [{}]},
- {'team_org_map': {0: {'organization': ['This field is required.'], 'team': ['This field is required.']}}},
- ),
- ],
- )
- def test_internal_value_invalid(self, data, expected):
- field = SAMLTeamAttrField()
- with pytest.raises(ValidationError) as e:
- field.to_internal_value(data)
- assert e.value.detail == expected
-
-
-class TestSAMLUserFlagsAttrField:
- @pytest.mark.parametrize(
- "data",
- [
- {},
- {'is_superuser_attr': 'something'},
- {'is_superuser_value': ['value']},
- {'is_superuser_role': ['my_peeps']},
- {'remove_superusers': False},
- {'is_system_auditor_attr': 'something_else'},
- {'is_system_auditor_value': ['value2']},
- {'is_system_auditor_role': ['other_peeps']},
- {'remove_system_auditors': False},
- ],
- )
- def test_internal_value_valid(self, data):
- field = SAMLUserFlagsAttrField()
- res = field.to_internal_value(data)
- assert res == data
-
- @pytest.mark.parametrize(
- "data, expected",
- [
- (
- {
- 'junk': 'something',
- 'is_superuser_value': 'value',
- 'is_superuser_role': 'my_peeps',
- 'is_system_auditor_attr': 'else',
- 'is_system_auditor_value': 'value2',
- 'is_system_auditor_role': 'other_peeps',
- },
- {
- 'junk': ['Invalid field.'],
- 'is_superuser_role': ['Expected a list of items but got type "str".'],
- 'is_superuser_value': ['Expected a list of items but got type "str".'],
- 'is_system_auditor_role': ['Expected a list of items but got type "str".'],
- 'is_system_auditor_value': ['Expected a list of items but got type "str".'],
- },
- ),
- (
- {
- 'junk': 'something',
- },
- {
- 'junk': ['Invalid field.'],
- },
- ),
- (
- {
- 'junk': 'something',
- 'junk2': 'else',
- },
- {
- 'junk': ['Invalid field.'],
- 'junk2': ['Invalid field.'],
- },
- ),
- # make sure we can't pass a string to the boolean fields
- (
- {
- 'remove_superusers': 'test',
- 'remove_system_auditors': 'test',
- },
- {
- "remove_superusers": ["Must be a valid boolean."],
- "remove_system_auditors": ["Must be a valid boolean."],
- },
- ),
- ],
- )
- def test_internal_value_invalid(self, data, expected):
- field = SAMLUserFlagsAttrField()
- with pytest.raises(ValidationError) as e:
- field.to_internal_value(data)
- print(e.value.detail)
- assert e.value.detail == expected
-
-
-class TestLDAPGroupTypeParamsField:
- @pytest.mark.parametrize(
- "group_type, data, expected",
- [
- ('LDAPGroupType', {'name_attr': 'user', 'bob': ['a', 'b'], 'scooter': 'hello'}, ['Invalid key(s): "bob", "scooter".']),
- ('MemberDNGroupType', {'name_attr': 'user', 'member_attr': 'west', 'bob': ['a', 'b'], 'scooter': 'hello'}, ['Invalid key(s): "bob", "scooter".']),
- (
- 'PosixUIDGroupType',
- {'name_attr': 'user', 'member_attr': 'west', 'ldap_group_user_attr': 'legacyThing', 'bob': ['a', 'b'], 'scooter': 'hello'},
- ['Invalid key(s): "bob", "member_attr", "scooter".'],
- ),
- ],
- )
- def test_internal_value_invalid(self, group_type, data, expected):
- field = LDAPGroupTypeParamsField()
- field.get_depends_on = mock.MagicMock(return_value=group_type)
-
- with pytest.raises(ValidationError) as e:
- field.to_internal_value(data)
- assert e.value.detail == expected
-
-
-class TestLDAPServerURIField:
- @pytest.mark.parametrize(
- "ldap_uri, exception, expected",
- [
- (r'ldap://servername.com:444', None, r'ldap://servername.com:444'),
- (r'ldap://servername.so3:444', None, r'ldap://servername.so3:444'),
- (r'ldaps://servername3.s300:344', None, r'ldaps://servername3.s300:344'),
- (r'ldap://servername.-so3:444', ValidationError, None),
- ],
- )
- def test_run_validators_valid(self, ldap_uri, exception, expected):
- field = LDAPServerURIField()
- if exception is None:
- assert field.run_validators(ldap_uri) == expected
- else:
- with pytest.raises(exception):
- field.run_validators(ldap_uri)
diff --git a/awx/sso/tests/unit/test_ldap.py b/awx/sso/tests/unit/test_ldap.py
deleted file mode 100644
index 300102934f79..000000000000
--- a/awx/sso/tests/unit/test_ldap.py
+++ /dev/null
@@ -1,25 +0,0 @@
-import ldap
-
-from awx.sso.backends import LDAPSettings
-from awx.sso.validators import validate_ldap_filter
-from django.core.cache import cache
-
-
-def test_ldap_default_settings(mocker):
- from_db = mocker.Mock(**{'order_by.return_value': []})
- with mocker.patch('awx.conf.models.Setting.objects.filter', return_value=from_db):
- settings = LDAPSettings()
- assert settings.ORGANIZATION_MAP == {}
- assert settings.TEAM_MAP == {}
-
-
-def test_ldap_default_network_timeout(mocker):
- cache.clear() # clearing cache avoids picking up stray default for OPT_REFERRALS
- from_db = mocker.Mock(**{'order_by.return_value': []})
- with mocker.patch('awx.conf.models.Setting.objects.filter', return_value=from_db):
- settings = LDAPSettings()
- assert settings.CONNECTION_OPTIONS[ldap.OPT_NETWORK_TIMEOUT] == 30
-
-
-def test_ldap_filter_validator():
- validate_ldap_filter('(test-uid=%(user)s)', with_user=True)
diff --git a/awx/sso/tests/unit/test_pipelines.py b/awx/sso/tests/unit/test_pipelines.py
deleted file mode 100644
index 94a1111187b8..000000000000
--- a/awx/sso/tests/unit/test_pipelines.py
+++ /dev/null
@@ -1,12 +0,0 @@
-import pytest
-
-
-@pytest.mark.parametrize(
- "lib",
- [
- ("saml_pipeline"),
- ("social_pipeline"),
- ],
-)
-def test_module_loads(lib):
- module = __import__("awx.sso." + lib) # noqa
diff --git a/awx/sso/tests/unit/test_tacacsplus.py b/awx/sso/tests/unit/test_tacacsplus.py
deleted file mode 100644
index 60ed0c47995d..000000000000
--- a/awx/sso/tests/unit/test_tacacsplus.py
+++ /dev/null
@@ -1,49 +0,0 @@
-from unittest import mock
-
-
-def test_empty_host_fails_auth(tacacsplus_backend):
- with mock.patch('awx.sso.backends.django_settings') as settings:
- settings.TACACSPLUS_HOST = ''
- ret_user = tacacsplus_backend.authenticate(None, u"user", u"pass")
- assert ret_user is None
-
-
-def test_client_raises_exception(tacacsplus_backend):
- client = mock.MagicMock()
- client.authenticate.side_effect = Exception("foo")
- with mock.patch('awx.sso.backends.django_settings') as settings, mock.patch('awx.sso.backends.logger') as logger, mock.patch(
- 'tacacs_plus.TACACSClient', return_value=client
- ):
- settings.TACACSPLUS_HOST = 'localhost'
- settings.TACACSPLUS_AUTH_PROTOCOL = 'ascii'
- ret_user = tacacsplus_backend.authenticate(None, u"user", u"pass")
- assert ret_user is None
- logger.exception.assert_called_once_with("TACACS+ Authentication Error: foo")
-
-
-def test_client_return_invalid_fails_auth(tacacsplus_backend):
- auth = mock.MagicMock()
- auth.valid = False
- client = mock.MagicMock()
- client.authenticate.return_value = auth
- with mock.patch('awx.sso.backends.django_settings') as settings, mock.patch('tacacs_plus.TACACSClient', return_value=client):
- settings.TACACSPLUS_HOST = 'localhost'
- settings.TACACSPLUS_AUTH_PROTOCOL = 'ascii'
- ret_user = tacacsplus_backend.authenticate(None, u"user", u"pass")
- assert ret_user is None
-
-
-def test_client_return_valid_passes_auth(tacacsplus_backend):
- auth = mock.MagicMock()
- auth.valid = True
- client = mock.MagicMock()
- client.authenticate.return_value = auth
- user = mock.MagicMock()
- user.has_usable_password = mock.MagicMock(return_value=False)
- with mock.patch('awx.sso.backends.django_settings') as settings, mock.patch('tacacs_plus.TACACSClient', return_value=client), mock.patch(
- 'awx.sso.backends._get_or_set_enterprise_user', return_value=user
- ):
- settings.TACACSPLUS_HOST = 'localhost'
- settings.TACACSPLUS_AUTH_PROTOCOL = 'ascii'
- ret_user = tacacsplus_backend.authenticate(None, u"user", u"pass")
- assert ret_user == user
diff --git a/awx/sso/urls.py b/awx/sso/urls.py
deleted file mode 100644
index 93da0996c970..000000000000
--- a/awx/sso/urls.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (c) 2015 Ansible, Inc.
-# All Rights Reserved.
-
-from django.urls import re_path
-
-from awx.sso.views import sso_complete, sso_error, sso_inactive, saml_metadata
-
-
-app_name = 'sso'
-urlpatterns = [
- re_path(r'^complete/$', sso_complete, name='sso_complete'),
- re_path(r'^error/$', sso_error, name='sso_error'),
- re_path(r'^inactive/$', sso_inactive, name='sso_inactive'),
- re_path(r'^metadata/saml/$', saml_metadata, name='saml_metadata'),
-]
diff --git a/awx/sso/validators.py b/awx/sso/validators.py
deleted file mode 100644
index 478b86b36fc9..000000000000
--- a/awx/sso/validators.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# Python
-import re
-
-# Python-LDAP
-import ldap
-
-# Django
-from django.core.exceptions import ValidationError
-from django.utils.translation import gettext_lazy as _
-
-__all__ = [
- 'validate_ldap_dn',
- 'validate_ldap_dn_with_user',
- 'validate_ldap_bind_dn',
- 'validate_ldap_filter',
- 'validate_ldap_filter_with_user',
- 'validate_tacacsplus_disallow_nonascii',
-]
-
-
-def validate_ldap_dn(value, with_user=False):
- if with_user:
- if '%(user)s' not in value:
- raise ValidationError(_('DN must include "%%(user)s" placeholder for username: %s') % value)
- dn_value = value.replace('%(user)s', 'USER')
- else:
- dn_value = value
- try:
- ldap.dn.str2dn(dn_value.encode('utf-8'))
- except ldap.DECODING_ERROR:
- raise ValidationError(_('Invalid DN: %s') % value)
-
-
-def validate_ldap_dn_with_user(value):
- validate_ldap_dn(value, with_user=True)
-
-
-def validate_ldap_bind_dn(value):
- if not re.match(r'^[A-Za-z][A-Za-z0-9._-]*?\\[A-Za-z0-9 ._-]+?$', value.strip()) and not re.match(
- r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$', value.strip()
- ):
- validate_ldap_dn(value)
-
-
-def validate_ldap_filter(value, with_user=False):
- value = value.strip()
- if not value:
- return
- if with_user:
- if '%(user)s' not in value:
- raise ValidationError(_('DN must include "%%(user)s" placeholder for username: %s') % value)
- dn_value = value.replace('%(user)s', 'USER')
- else:
- dn_value = value
- if re.match(r'^\([A-Za-z0-9-]+?=[^()]+?\)$', dn_value):
- return
- elif re.match(r'^\([&|!]\(.*?\)\)$', dn_value):
- try:
- map(validate_ldap_filter, ['(%s)' % x for x in dn_value[3:-2].split(')(')])
- return
- except ValidationError:
- pass
- raise ValidationError(_('Invalid filter: %s') % value)
-
-
-def validate_ldap_filter_with_user(value):
- validate_ldap_filter(value, with_user=True)
-
-
-def validate_tacacsplus_disallow_nonascii(value):
- try:
- value.encode('ascii')
- except (UnicodeEncodeError, UnicodeDecodeError):
- raise ValidationError(_('TACACS+ secret does not allow non-ascii characters'))
diff --git a/awx/sso/views.py b/awx/sso/views.py
deleted file mode 100644
index c4ecdc763239..000000000000
--- a/awx/sso/views.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# Copyright (c) 2015 Ansible, Inc.
-# All Rights Reserved.
-
-# Python
-import urllib.parse
-import logging
-
-# Django
-from django.urls import reverse
-from django.http import HttpResponse
-from django.views.generic import View
-from django.views.generic.base import RedirectView
-from django.utils.encoding import smart_str
-from django.conf import settings
-
-logger = logging.getLogger('awx.sso.views')
-
-
-class BaseRedirectView(RedirectView):
- permanent = True
-
- def get_redirect_url(self, *args, **kwargs):
- last_path = self.request.COOKIES.get('lastPath', '')
- last_path = urllib.parse.quote(urllib.parse.unquote(last_path).strip('"'))
- url = reverse('ui:index')
- if last_path:
- return '%s#%s' % (url, last_path)
- else:
- return url
-
-
-sso_error = BaseRedirectView.as_view()
-sso_inactive = BaseRedirectView.as_view()
-
-
-class CompleteView(BaseRedirectView):
- def dispatch(self, request, *args, **kwargs):
- response = super(CompleteView, self).dispatch(request, *args, **kwargs)
- if self.request.user and self.request.user.is_authenticated:
- logger.info(smart_str(u"User {} logged in".format(self.request.user.username)))
- response.set_cookie('userLoggedIn', 'true')
- response.setdefault('X-API-Session-Cookie-Name', getattr(settings, 'SESSION_COOKIE_NAME', 'awx_sessionid'))
- return response
-
-
-sso_complete = CompleteView.as_view()
-
-
-class MetadataView(View):
- def get(self, request, *args, **kwargs):
- from social_django.utils import load_backend, load_strategy
-
- complete_url = reverse('social:complete', args=('saml',))
- try:
- saml_backend = load_backend(load_strategy(request), 'saml', redirect_uri=complete_url)
- metadata, errors = saml_backend.generate_metadata_xml()
- except Exception as e:
- logger.exception('unable to generate SAML metadata')
- errors = e
- if not errors:
- return HttpResponse(content=metadata, content_type='text/xml')
- else:
- return HttpResponse(content=str(errors), content_type='text/plain')
-
-
-saml_metadata = MetadataView.as_view()
diff --git a/awx/static/RedHatDisplay-Medium.ttf b/awx/static/RedHatDisplay-Medium.ttf
new file mode 100644
index 000000000000..a3adad30a5fd
Binary files /dev/null and b/awx/static/RedHatDisplay-Medium.ttf differ
diff --git a/awx/static/RedHatDisplay-Regular.ttf b/awx/static/RedHatDisplay-Regular.ttf
new file mode 100644
index 000000000000..546554484b68
Binary files /dev/null and b/awx/static/RedHatDisplay-Regular.ttf differ
diff --git a/awx/static/api/api.js b/awx/static/api/api.js
index 67053ae2f626..98d803ad9f0c 100644
--- a/awx/static/api/api.js
+++ b/awx/static/api/api.js
@@ -14,7 +14,7 @@ $(function() {
$('span.str').each(function() {
var s = $(this).html();
if (s.match(/^\"\/.+\/\"$/) || s.match(/^\"\/.+\/\?.*\"$/)) {
- $(this).html('"' + s.replace(/\"/g, '') + '"');
+ $(this).html('"' + s.replaceAll('"', '') + '"');
}
});
@@ -27,7 +27,7 @@ $(function() {
}).each(function() {
$(this).nextUntil('span.pun:contains("]")').filter('span.str').each(function() {
if ($(this).text().match(/^\".+\"$/)) {
- var s = $(this).text().replace(/\"/g, '');
+ var s = $(this).text().replaceAll('"', '');
$(this).html('"' + s + '"');
}
else if ($(this).text() !== '"') {
diff --git a/awx/static/awx-spud-reading.svg b/awx/static/awx-spud-reading.svg
new file mode 100644
index 000000000000..9e1a02dffff8
--- /dev/null
+++ b/awx/static/awx-spud-reading.svg
@@ -0,0 +1,383 @@
+
+
+
diff --git a/awx/static/custom_404.html b/awx/static/custom_404.html
new file mode 100644
index 000000000000..9e22cc0767d3
--- /dev/null
+++ b/awx/static/custom_404.html
@@ -0,0 +1,8 @@
+
+
+
+ Redirecting
+
+
+ Redirecting
+
diff --git a/awx/static/custom_502.html b/awx/static/custom_502.html
new file mode 100644
index 000000000000..5fc541245a3c
--- /dev/null
+++ b/awx/static/custom_502.html
@@ -0,0 +1,21 @@
+
+
+
+ On Break...
+
+
+
+
+
+
+

+
502
+
+
+
HTTP Response: 502
+
The spud is taking a much needed break...
+
Please check back later.
+
+
+
+
diff --git a/awx/static/custom_504.html b/awx/static/custom_504.html
new file mode 100644
index 000000000000..5f39b7f28d6b
--- /dev/null
+++ b/awx/static/custom_504.html
@@ -0,0 +1,21 @@
+
+
+
+ On Break...
+
+
+
+
+
+
+

+
504
+
+
+
HTTP Response: 504
+
The spud is taking a much needed break...
+
Please check back later.
+
+
+
+
diff --git a/awx/static/custom_error.css b/awx/static/custom_error.css
new file mode 100644
index 000000000000..94401bb3937b
--- /dev/null
+++ b/awx/static/custom_error.css
@@ -0,0 +1,81 @@
+@font-face {
+ font-family: redhat-display-medium;
+ src: url('./RedHatDisplay-Medium.ttf');
+}
+
+@font-face {
+ font-family: redhat-display-regular;
+ src: url('./RedHatDisplay-Regular.ttf');
+}
+
+html, body {
+ height:100%;
+ width:100%;
+}
+
+body {
+ background-color: #F0F0F0;
+}
+
+.container {
+ position: absolute;
+ top: 24px;
+ right: 24px;
+ bottom: 24px;
+ left: 24px;
+}
+
+.upper_div {
+ background-color: #F8EBA7;
+ justify-content: center;
+ text-align: center;
+ height: 50%;
+ align-items: flex-end;
+ display: flex;
+ min-height: 450px;
+ width: 100%;
+}
+
+.main_image {
+ height: 450px;
+ width:auto;
+}
+
+.error_number {
+ position: absolute;
+ top: 23px;
+ right: 90px;
+ font-size:200px;
+ color: #FDBA48;
+ font-family: Impact, Haettenschweiler, "Franklin Gothic Bold", Charcoal, "Helvetica Inserat", "Bitstream Vera Sans Bold", "Arial Black", sans-serif;
+}
+
+.message_div {
+ background-color: #FFFFFF;
+ border: 1px solid #D2D2D2;
+ text-align: center;
+ height: 50%;
+ vertical-align: top;
+}
+
+.m1,.m2,.m3 {
+ color: #151515;
+ width: 100%;
+ font-family: redhat-display-medium, sans-serif;
+}
+
+.m1 {
+ font-size: 24px;
+ padding-top: 24px;
+}
+
+.m2 {
+ font-size: 20px;
+ padding-top: 20px;
+}
+
+.m3 {
+ font-size: 16px;
+ padding-top: 20px;
+ font-family: redhat-display-regular, sans-serif;
+}
diff --git a/awx/templates/error.html b/awx/templates/error.html
index 815235ebfc85..8f6dcc754aeb 100644
--- a/awx/templates/error.html
+++ b/awx/templates/error.html
@@ -18,7 +18,7 @@
@@ -74,5 +81,14 @@
{{ block.super }}
+
+{% is_proxied_request as proxied %}
+{% if proxied %}
+
+{% else %}
+{% endif %}
{% endblock %}
diff --git a/awx/templates/rest_framework/login.html b/awx/templates/rest_framework/login.html
index 343d8a6ce0bb..00031dcc8ff8 100644
--- a/awx/templates/rest_framework/login.html
+++ b/awx/templates/rest_framework/login.html
@@ -9,7 +9,7 @@
}
- />
-
- );
- });
- wrapper.update();
-
- expect(wrapper.find('div#test-warning-message').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/components/LaunchPrompt/steps/OtherPromptsStep.js b/awx/ui/src/components/LaunchPrompt/steps/OtherPromptsStep.js
deleted file mode 100644
index b4c97312fda3..000000000000
--- a/awx/ui/src/components/LaunchPrompt/steps/OtherPromptsStep.js
+++ /dev/null
@@ -1,238 +0,0 @@
-import React from 'react';
-import { t } from '@lingui/macro';
-import { useField } from 'formik';
-import { Form, FormGroup, Switch } from '@patternfly/react-core';
-import styled from 'styled-components';
-import LabelSelect from '../../LabelSelect';
-import FormField from '../../FormField';
-import { TagMultiSelect } from '../../MultiSelect';
-import AnsibleSelect from '../../AnsibleSelect';
-import { VariablesField } from '../../CodeEditor';
-import Popover from '../../Popover';
-import { VerbositySelectField } from '../../VerbositySelectField';
-import jobHelpText from '../../../screens/Job/Job.helptext';
-import workflowHelpText from '../../../screens/Template/shared/WorkflowJobTemplate.helptext';
-
-const FieldHeader = styled.div`
- display: flex;
- justify-content: space-between;
- padding-bottom: var(--pf-c-form__label--PaddingBottom);
-
- label {
- --pf-c-form__label--PaddingBottom: 0px;
- }
-`;
-
-function OtherPromptsStep({ launchConfig, variablesMode, onVarModeChange }) {
- const helpTextSource = launchConfig.job_template_data
- ? jobHelpText
- : workflowHelpText;
- return (
-
-
-
- );
-}
-
-function JobTypeField({ helpTextSource }) {
- const [field, meta, helpers] = useField('job_type');
- const options = [
- {
- value: '',
- key: '',
- label: t`Choose a job type`,
- isDisabled: true,
- },
- { value: 'run', key: 'run', label: t`Run`, isDisabled: false },
- {
- value: 'check',
- key: 'check',
- label: t`Check`,
- isDisabled: false,
- },
- ];
- const isValid = !(meta.touched && meta.error);
- return (
-
}
- isRequired
- validated={isValid ? 'default' : 'error'}
- >
-
helpers.setValue(value)}
- />
-
- );
-}
-
-function VerbosityField({ helpTextSource }) {
- const [, meta] = useField('verbosity');
- const isValid = !(meta.touched && meta.error);
-
- return (
-
- );
-}
-
-function ShowChangesToggle() {
- const [field, , helpers] = useField('diff_mode');
- return (
-
-
- {' '}
-
-
-
-
- );
-}
-
-function TagField({ id, name, label, tooltip }) {
- const [field, , helpers] = useField(name);
- return (
- }
- >
-
-
- );
-}
-
-function LabelsField({ helpTextSource }) {
- const [field, meta, helpers] = useField('labels');
-
- return (
- }
- validated={!meta.touched || !meta.error ? 'default' : 'error'}
- helperTextInvalid={meta.error}
- >
- helpers.setValue(labels)}
- createText={t`Create`}
- onError={(err) => helpers.setError(err)}
- />
-
- );
-}
-
-export default OtherPromptsStep;
diff --git a/awx/ui/src/components/LaunchPrompt/steps/OtherPromptsStep.test.js b/awx/ui/src/components/LaunchPrompt/steps/OtherPromptsStep.test.js
deleted file mode 100644
index cdaeb41995f7..000000000000
--- a/awx/ui/src/components/LaunchPrompt/steps/OtherPromptsStep.test.js
+++ /dev/null
@@ -1,240 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import OtherPromptsStep from './OtherPromptsStep';
-
-describe('OtherPromptsStep', () => {
- test('should render job type field', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
-
- expect(wrapper.find('JobTypeField')).toHaveLength(1);
- expect(
- wrapper.find('JobTypeField AnsibleSelect').prop('data')
- ).toHaveLength(3);
- expect(wrapper.find('JobTypeField AnsibleSelect').prop('value')).toEqual(
- 'run'
- );
- });
-
- test('should render limit field', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
-
- expect(wrapper.find('FormField#prompt-limit')).toHaveLength(1);
- expect(wrapper.find('FormField#prompt-limit input').prop('name')).toEqual(
- 'limit'
- );
- });
-
- test('should render timeout field', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
-
- expect(wrapper.find('FormField#prompt-timeout')).toHaveLength(1);
- expect(wrapper.find('FormField#prompt-timeout input').prop('name')).toEqual(
- 'timeout'
- );
- });
-
- test('should render forks field', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
-
- expect(wrapper.find('FormField#prompt-forks')).toHaveLength(1);
- expect(wrapper.find('FormField#prompt-forks input').prop('name')).toEqual(
- 'forks'
- );
- });
-
- test('should render job slicing field', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
-
- expect(wrapper.find('FormField#prompt-job-slicing')).toHaveLength(1);
- expect(
- wrapper.find('FormField#prompt-job-slicing input').prop('name')
- ).toEqual('job_slice_count');
- });
-
- test('should render source control branch field', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
-
- expect(wrapper.find('FormField#prompt-scm-branch')).toHaveLength(1);
- expect(
- wrapper.find('FormField#prompt-scm-branch input').prop('name')
- ).toEqual('scm_branch');
- });
-
- test('should render verbosity field', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
-
- expect(wrapper.find('VerbosityField')).toHaveLength(1);
- expect(
- wrapper.find('VerbosityField AnsibleSelect').prop('data')
- ).toHaveLength(6);
- });
-
- test('should render show changes toggle', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
-
- expect(wrapper.find('ShowChangesToggle')).toHaveLength(1);
- expect(wrapper.find('ShowChangesToggle Switch').prop('isChecked')).toEqual(
- true
- );
- });
-
- test('should pass mode and onModeChange to VariablesField', async () => {
- let wrapper;
- const onModeChange = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
-
- expect(wrapper.find('VariablesField').prop('initialMode')).toEqual(
- 'javascript'
- );
- expect(wrapper.find('VariablesField').prop('onModeChange')).toEqual(
- onModeChange
- );
- });
-});
diff --git a/awx/ui/src/components/LaunchPrompt/steps/PreviewStep.js b/awx/ui/src/components/LaunchPrompt/steps/PreviewStep.js
deleted file mode 100644
index 753133b10968..000000000000
--- a/awx/ui/src/components/LaunchPrompt/steps/PreviewStep.js
+++ /dev/null
@@ -1,77 +0,0 @@
-import React from 'react';
-import styled from 'styled-components';
-import { ExclamationCircleIcon as PFExclamationCircleIcon } from '@patternfly/react-icons';
-import { Tooltip } from '@patternfly/react-core';
-import { t } from '@lingui/macro';
-import { useFormikContext } from 'formik';
-
-import yaml from 'js-yaml';
-import mergeExtraVars, { maskPasswords } from 'util/prompt/mergeExtraVars';
-import getSurveyValues from 'util/prompt/getSurveyValues';
-import PromptDetail from '../../PromptDetail';
-
-const ExclamationCircleIcon = styled(PFExclamationCircleIcon)`
- margin-left: 10px;
- margin-top: -2px;
-`;
-
-const ErrorMessageWrapper = styled.div`
- align-items: center;
- color: var(--pf-global--danger-color--200);
- display: flex;
- font-weight: var(--pf-global--FontWeight--bold);
- margin-bottom: 10px;
-`;
-
-function PreviewStep({ resource, launchConfig, surveyConfig, formErrors }) {
- const { values } = useFormikContext();
- const surveyValues = getSurveyValues(values);
-
- const overrides = {
- ...values,
- };
-
- if (launchConfig.ask_variables_on_launch || launchConfig.survey_enabled) {
- try {
- const initialExtraVars =
- launchConfig.ask_variables_on_launch && (overrides.extra_vars || '---');
- if (surveyConfig?.spec) {
- const passwordFields = surveyConfig.spec
- .filter((q) => q.type === 'password')
- .map((q) => q.variable);
- const masked = maskPasswords(surveyValues, passwordFields);
- overrides.extra_vars = yaml.dump(
- mergeExtraVars(initialExtraVars, masked)
- );
- } else {
- overrides.extra_vars = yaml.dump(mergeExtraVars(initialExtraVars, {}));
- }
- } catch (e) {
- //
- }
- }
-
- return (
-
- {formErrors && (
-
- {t`Some of the previous step(s) have errors`}
-
-
-
-
- )}
-
-
- );
-}
-
-export default PreviewStep;
diff --git a/awx/ui/src/components/LaunchPrompt/steps/PreviewStep.test.js b/awx/ui/src/components/LaunchPrompt/steps/PreviewStep.test.js
deleted file mode 100644
index 322deb8bd639..000000000000
--- a/awx/ui/src/components/LaunchPrompt/steps/PreviewStep.test.js
+++ /dev/null
@@ -1,158 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import PreviewStep from './PreviewStep';
-
-const resource = {
- id: 1,
- type: 'job_template',
- summary_fields: {
- inventory: { id: 12 },
- recent_jobs: [],
- },
- related: {},
-};
-
-const survey = {
- name: '',
- spec: [
- {
- variable: 'foo',
- type: 'text',
- },
- ],
-};
-
-const formErrors = {
- inventory: 'An inventory must be selected',
-};
-
-describe('PreviewStep', () => {
- test('should render PromptDetail', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
-
- const detail = wrapper.find('PromptDetail');
- expect(detail).toHaveLength(1);
- expect(detail.prop('resource')).toEqual(resource);
- expect(detail.prop('overrides')).toEqual({
- extra_vars: 'foo: abc\n',
- limit: '4',
- survey_foo: 'abc',
- });
- });
-
- test('should render PromptDetail without survey', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
-
- const detail = wrapper.find('PromptDetail');
- expect(detail).toHaveLength(1);
- expect(detail.prop('resource')).toEqual(resource);
- expect(detail.prop('overrides')).toEqual({
- limit: '4',
- });
- });
- test('should handle extra vars with survey', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
-
- const detail = wrapper.find('PromptDetail');
- expect(detail).toHaveLength(1);
- expect(detail.prop('resource')).toEqual(resource);
- expect(detail.prop('overrides')).toEqual({
- extra_vars: 'one: 1\nfoo: abc\n',
- survey_foo: 'abc',
- });
- });
- test('should handle extra vars without survey', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
-
- const detail = wrapper.find('PromptDetail');
- expect(detail).toHaveLength(1);
- expect(detail.prop('resource')).toEqual(resource);
- expect(detail.prop('overrides')).toEqual({
- extra_vars: 'one: 1\n',
- });
- });
- test('should remove survey with empty array value', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
-
- const detail = wrapper.find('PromptDetail');
- expect(detail).toHaveLength(1);
- expect(detail.prop('resource')).toEqual(resource);
- expect(detail.prop('overrides')).toEqual({
- extra_vars: 'one: 1\n',
- });
- });
-});
diff --git a/awx/ui/src/components/LaunchPrompt/steps/StepName.js b/awx/ui/src/components/LaunchPrompt/steps/StepName.js
deleted file mode 100644
index 705e6ff4f852..000000000000
--- a/awx/ui/src/components/LaunchPrompt/steps/StepName.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import React from 'react';
-import styled from 'styled-components';
-
-import { t } from '@lingui/macro';
-import { Tooltip } from '@patternfly/react-core';
-import { ExclamationCircleIcon as PFExclamationCircleIcon } from '@patternfly/react-icons';
-
-const AlertText = styled.div`
- color: var(--pf-global--danger-color--200);
- font-weight: var(--pf-global--FontWeight--bold);
-`;
-
-const ExclamationCircleIcon = styled(PFExclamationCircleIcon)`
- margin-left: 10px;
-`;
-
-function StepName({ hasErrors, children, id }) {
- if (!hasErrors) {
- return {children}
;
- }
- return (
-
- {children}
-
-
-
-
- );
-}
-
-export default StepName;
diff --git a/awx/ui/src/components/LaunchPrompt/steps/SurveyStep.js b/awx/ui/src/components/LaunchPrompt/steps/SurveyStep.js
deleted file mode 100644
index 770983f05e9b..000000000000
--- a/awx/ui/src/components/LaunchPrompt/steps/SurveyStep.js
+++ /dev/null
@@ -1,205 +0,0 @@
-import React, { useState } from 'react';
-import { t } from '@lingui/macro';
-import { useField } from 'formik';
-import {
- Form,
- FormGroup,
- Select,
- SelectOption,
- SelectVariant,
-} from '@patternfly/react-core';
-import {
- required,
- minMaxValue,
- maxLength,
- minLength,
- integer,
- combine,
-} from 'util/validators';
-import { Survey } from 'types';
-import FormField from '../../FormField';
-import Popover from '../../Popover';
-
-function SurveyStep({ surveyConfig }) {
- const fieldTypes = {
- text: TextField,
- textarea: TextField,
- password: TextField,
- multiplechoice: MultipleChoiceField,
- multiselect: MultiSelectField,
- integer: NumberField,
- float: NumberField,
- };
- return (
-
-
-
- );
-}
-SurveyStep.propTypes = {
- surveyConfig: Survey.isRequired,
-};
-
-function TextField({ question }) {
- const validators = [
- question.required ? required(null) : null,
- question.required && question.min ? minLength(question.min) : null,
- question.required && question.max ? maxLength(question.max) : null,
- ];
- return (
-
- );
-}
-
-function NumberField({ question }) {
- const validators = [
- question.required ? required(null) : null,
- minMaxValue(question.min, question.max),
- question.type === 'integer' ? integer() : null,
- ];
- return (
-
- );
-}
-
-function MultipleChoiceField({ question }) {
- const [field, meta, helpers] = useField({
- name: `survey_${question.variable}`,
- validate: question.required ? required(null) : null,
- });
- const [isOpen, setIsOpen] = useState(false);
- const id = `survey-question-${question.variable}`;
- const isValid = !(meta.touched && meta.error);
-
- let options = [];
-
- if (typeof question.choices === 'string') {
- options = question.choices.split('\n');
- } else if (Array.isArray(question.choices)) {
- options = [...question.choices];
- }
-
- return (
- }
- >
-
-
- );
-}
-
-function MultiSelectField({ question }) {
- const [isOpen, setIsOpen] = useState(false);
- const [field, meta, helpers] = useField({
- name: `survey_${question.variable}`,
- validate: question.required ? required(null) : null,
- });
- const id = `survey-question-${question.variable}`;
- const hasActualValue = !question.required || meta.value?.length > 0;
- const isValid = !meta.touched || (!meta.error && hasActualValue);
-
- let options = [];
-
- if (typeof question.choices === 'string') {
- options = question.choices.split('\n');
- } else if (Array.isArray(question.choices)) {
- options = [...question.choices];
- }
-
- return (
- }
- >
-
-
- );
-}
-
-export default SurveyStep;
diff --git a/awx/ui/src/components/LaunchPrompt/steps/SurveyStep.test.js b/awx/ui/src/components/LaunchPrompt/steps/SurveyStep.test.js
deleted file mode 100644
index 8d27a055964f..000000000000
--- a/awx/ui/src/components/LaunchPrompt/steps/SurveyStep.test.js
+++ /dev/null
@@ -1,88 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import SurveyStep from './SurveyStep';
-
-describe('SurveyStep', () => {
- test('should handle choices as a string', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
-
- await act(async () => {
- wrapper.find('SelectToggle').simulate('click');
- });
- wrapper.update();
- const selectOptions = wrapper.find('SelectOption');
- expect(selectOptions.at(0).prop('value')).toEqual('1');
- expect(selectOptions.at(1).prop('value')).toEqual('2');
- expect(selectOptions.at(2).prop('value')).toEqual('3');
- expect(selectOptions.at(3).prop('value')).toEqual('4');
- expect(selectOptions.at(4).prop('value')).toEqual('5');
- expect(selectOptions.at(5).prop('value')).toEqual('6');
- });
- test('should handle choices as an array', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
-
- await act(async () => {
- wrapper.find('SelectToggle').simulate('click');
- });
- wrapper.update();
- const selectOptions = wrapper.find('SelectOption');
- expect(selectOptions.at(0).prop('value')).toEqual('1');
- expect(selectOptions.at(1).prop('value')).toEqual('2');
- expect(selectOptions.at(2).prop('value')).toEqual('3');
- expect(selectOptions.at(3).prop('value')).toEqual('4');
- expect(selectOptions.at(4).prop('value')).toEqual('5');
- expect(selectOptions.at(5).prop('value')).toEqual('6');
- });
-});
diff --git a/awx/ui/src/components/LaunchPrompt/steps/credentialsValidator.js b/awx/ui/src/components/LaunchPrompt/steps/credentialsValidator.js
deleted file mode 100644
index c0eaac98758a..000000000000
--- a/awx/ui/src/components/LaunchPrompt/steps/credentialsValidator.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import { t } from '@lingui/macro';
-
-const credentialPromptsForPassword = (credential) =>
- credential?.inputs?.password === 'ASK' ||
- credential?.inputs?.ssh_key_unlock === 'ASK' ||
- credential?.inputs?.become_password === 'ASK' ||
- credential?.inputs?.vault_password === 'ASK';
-
-export default function credentialsValidator(
- allowCredentialsWithPasswords,
- selectedCredentials,
- defaultCredentials = []
-) {
- if (defaultCredentials.length > 0 && selectedCredentials) {
- const missingCredentialTypes = [];
- defaultCredentials.forEach((defaultCredential) => {
- if (
- !selectedCredentials.find(
- (selectedCredential) =>
- (selectedCredential?.credential_type ===
- defaultCredential?.credential_type &&
- !selectedCredential.inputs?.vault_id &&
- !defaultCredential.inputs?.vault_id) ||
- (defaultCredential.inputs?.vault_id &&
- selectedCredential.inputs?.vault_id ===
- defaultCredential.inputs?.vault_id)
- )
- ) {
- missingCredentialTypes.push(
- defaultCredential.inputs?.vault_id
- ? `${defaultCredential.summary_fields.credential_type.name} | ${defaultCredential.inputs.vault_id}`
- : defaultCredential.summary_fields.credential_type.name
- );
- }
- });
-
- if (missingCredentialTypes.length > 0) {
- return t`Job Template default credentials must be replaced with one of the same type. Please select a credential for the following types in order to proceed: ${missingCredentialTypes.join(
- ', '
- )}`;
- }
- }
-
- if (!allowCredentialsWithPasswords && selectedCredentials) {
- const credentialsThatPrompt = [];
- selectedCredentials.forEach((selectedCredential) => {
- if (credentialPromptsForPassword(selectedCredential)) {
- credentialsThatPrompt.push(selectedCredential.name);
- }
- });
- if (credentialsThatPrompt.length > 0) {
- return t`Credentials that require passwords on launch are not permitted. Please remove or replace the following credentials with a credential of the same type in order to proceed: ${credentialsThatPrompt.join(
- ', '
- )}`;
- }
- }
-
- return undefined;
-}
diff --git a/awx/ui/src/components/LaunchPrompt/steps/useCredentialPasswordsStep.js b/awx/ui/src/components/LaunchPrompt/steps/useCredentialPasswordsStep.js
deleted file mode 100644
index 174f72d64c11..000000000000
--- a/awx/ui/src/components/LaunchPrompt/steps/useCredentialPasswordsStep.js
+++ /dev/null
@@ -1,251 +0,0 @@
-import React from 'react';
-import { t } from '@lingui/macro';
-import { useFormikContext } from 'formik';
-import CredentialPasswordsStep from './CredentialPasswordsStep';
-import StepName from './StepName';
-
-const STEP_ID = 'credentialPasswords';
-
-const isValueMissing = (val) => !val || val === '';
-
-export default function useCredentialPasswordsStep(
- launchConfig,
-
- showStep,
- visitedSteps
-) {
- const { values, setFieldError } = useFormikContext();
- const hasError =
- Object.keys(visitedSteps).includes(STEP_ID) &&
- checkForError(launchConfig, values);
-
- return {
- step: showStep
- ? {
- id: STEP_ID,
- name: (
-
- {t`Credential passwords`}
-
- ),
- component: ,
- enableNext: true,
- }
- : null,
- initialValues: getInitialValues(launchConfig, values.credentials),
- isReady: true,
- contentError: null,
- hasError,
- setTouched: (setFieldTouched) => {
- Object.keys(values.credential_passwords).forEach((credentialValueKey) =>
- setFieldTouched(
- `credential_passwords['${credentialValueKey}']`,
- true,
- false
- )
- );
- },
- validate: () => {
- const setPasswordFieldError = (fieldName) => {
- setFieldError(fieldName, t`This field may not be blank`);
- };
-
- if (
- !launchConfig.ask_credential_on_launch &&
- launchConfig.passwords_needed_to_start
- ) {
- launchConfig.passwords_needed_to_start.forEach((password) => {
- if (isValueMissing(values.credential_passwords[password])) {
- setPasswordFieldError(`credential_passwords['${password}']`);
- }
- });
- } else if (values.credentials) {
- values.credentials.forEach((credential) => {
- if (!credential.inputs) {
- const launchConfigCredential =
- launchConfig.defaults.credentials.find(
- (defaultCred) => defaultCred.id === credential.id
- );
-
- if (launchConfigCredential?.passwords_needed.length > 0) {
- launchConfigCredential.passwords_needed.forEach((password) => {
- if (isValueMissing(values.credential_passwords[password])) {
- setPasswordFieldError(`credential_passwords['${password}']`);
- }
- });
- }
- } else {
- if (
- credential?.inputs?.password === 'ASK' &&
- isValueMissing(values.credential_passwords.ssh_password)
- ) {
- setPasswordFieldError('credential_passwords.ssh_password');
- }
-
- if (
- credential?.inputs?.become_password === 'ASK' &&
- isValueMissing(values.credential_passwords.become_password)
- ) {
- setPasswordFieldError('credential_passwords.become_password');
- }
-
- if (
- credential?.inputs?.ssh_key_unlock === 'ASK' &&
- isValueMissing(values.credential_passwords.ssh_key_unlock)
- ) {
- setPasswordFieldError('credential_passwords.ssh_key_unlock');
- }
-
- if (
- credential?.inputs?.vault_password === 'ASK' &&
- isValueMissing(
- values.credential_passwords[
- `vault_password${
- credential.inputs.vault_id !== ''
- ? `.${credential.inputs.vault_id}`
- : ''
- }`
- ]
- )
- ) {
- setPasswordFieldError(
- `credential_passwords['vault_password${
- credential.inputs.vault_id !== ''
- ? `.${credential.inputs.vault_id}`
- : ''
- }']`
- );
- }
- }
- });
- }
- },
- };
-}
-
-function getInitialValues(launchConfig, selectedCredentials = []) {
- const initialValues = {
- credential_passwords: {},
- };
-
- if (!launchConfig) {
- return initialValues;
- }
-
- if (
- !launchConfig.ask_credential_on_launch &&
- launchConfig.passwords_needed_to_start
- ) {
- launchConfig.passwords_needed_to_start.forEach((password) => {
- initialValues.credential_passwords[password] = '';
- });
- return initialValues;
- }
-
- selectedCredentials.forEach((credential) => {
- if (!credential.inputs) {
- const launchConfigCredential = launchConfig.defaults.credentials.find(
- (defaultCred) => defaultCred.id === credential.id
- );
-
- if (launchConfigCredential?.passwords_needed.length > 0) {
- launchConfigCredential.passwords_needed.forEach((password) => {
- initialValues.credential_passwords[password] = '';
- });
- }
- } else {
- if (credential?.inputs?.password === 'ASK') {
- initialValues.credential_passwords.ssh_password = '';
- }
-
- if (credential?.inputs?.become_password === 'ASK') {
- initialValues.credential_passwords.become_password = '';
- }
-
- if (credential?.inputs?.ssh_key_unlock === 'ASK') {
- initialValues.credential_passwords.ssh_key_unlock = '';
- }
-
- if (credential?.inputs?.vault_password === 'ASK') {
- if (!credential.inputs.vault_id || credential.inputs.vault_id === '') {
- initialValues.credential_passwords.vault_password = '';
- } else {
- initialValues.credential_passwords[
- `vault_password.${credential.inputs.vault_id}`
- ] = '';
- }
- }
- }
- });
-
- return initialValues;
-}
-
-function checkForError(launchConfig, values) {
- let hasError = false;
-
- if (
- !launchConfig.ask_credential_on_launch &&
- launchConfig.passwords_needed_to_start
- ) {
- launchConfig.passwords_needed_to_start.forEach((password) => {
- if (isValueMissing(values.credential_passwords[password])) {
- hasError = true;
- }
- });
- } else if (values.credentials) {
- values.credentials.forEach((credential) => {
- if (!credential.inputs) {
- const launchConfigCredential = launchConfig.defaults.credentials.find(
- (defaultCred) => defaultCred.id === credential.id
- );
-
- if (launchConfigCredential?.passwords_needed.length > 0) {
- launchConfigCredential.passwords_needed.forEach((password) => {
- if (isValueMissing(values.credential_passwords[password])) {
- hasError = true;
- }
- });
- }
- } else {
- if (
- credential?.inputs?.password === 'ASK' &&
- isValueMissing(values.credential_passwords.ssh_password)
- ) {
- hasError = true;
- }
-
- if (
- credential?.inputs?.become_password === 'ASK' &&
- isValueMissing(values.credential_passwords.become_password)
- ) {
- hasError = true;
- }
-
- if (
- credential?.inputs?.ssh_key_unlock === 'ASK' &&
- isValueMissing(values.credential_passwords.ssh_key_unlock)
- ) {
- hasError = true;
- }
-
- if (
- credential?.inputs?.vault_password === 'ASK' &&
- isValueMissing(
- values.credential_passwords[
- `vault_password${
- credential.inputs.vault_id !== ''
- ? `.${credential.inputs.vault_id}`
- : ''
- }`
- ]
- )
- ) {
- hasError = true;
- }
- }
- });
- }
-
- return hasError;
-}
diff --git a/awx/ui/src/components/LaunchPrompt/steps/useCredentialsStep.js b/awx/ui/src/components/LaunchPrompt/steps/useCredentialsStep.js
deleted file mode 100644
index 735ae21a4d38..000000000000
--- a/awx/ui/src/components/LaunchPrompt/steps/useCredentialsStep.js
+++ /dev/null
@@ -1,83 +0,0 @@
-import React from 'react';
-import { t } from '@lingui/macro';
-import { useField } from 'formik';
-import CredentialsStep from './CredentialsStep';
-import StepName from './StepName';
-import credentialsValidator from './credentialsValidator';
-
-const STEP_ID = 'credentials';
-
-export default function useCredentialsStep(
- launchConfig,
- resource,
- resourceDefaultCredentials = [],
- allowCredentialsWithPasswords = false
-) {
- const [field, meta, helpers] = useField('credentials');
- const formError =
- !resource || resource?.type === 'workflow_job_template'
- ? false
- : meta.error;
- return {
- step: getStep(
- launchConfig,
- allowCredentialsWithPasswords,
- formError,
- resourceDefaultCredentials
- ),
- initialValues: getInitialValues(launchConfig, resourceDefaultCredentials),
- isReady: true,
- contentError: null,
- hasError: launchConfig.ask_credential_on_launch && formError,
- setTouched: (setFieldTouched) => {
- setFieldTouched('credentials', true, false);
- },
- validate: () => {
- helpers.setError(
- credentialsValidator(
- allowCredentialsWithPasswords,
- field.value,
- resourceDefaultCredentials
- )
- );
- },
- };
-}
-
-function getStep(
- launchConfig,
-
- allowCredentialsWithPasswords,
- formError,
- resourceDefaultCredentials
-) {
- if (!launchConfig.ask_credential_on_launch) {
- return null;
- }
- return {
- id: STEP_ID,
- key: 4,
- name: (
-
- {t`Credentials`}
-
- ),
- component: (
-
- ),
- enableNext: true,
- };
-}
-
-function getInitialValues(launchConfig, resourceDefaultCredentials) {
- if (!launchConfig.ask_credential_on_launch) {
- return {};
- }
-
- return {
- credentials: resourceDefaultCredentials,
- };
-}
diff --git a/awx/ui/src/components/LaunchPrompt/steps/useExecutionEnvironmentStep.js b/awx/ui/src/components/LaunchPrompt/steps/useExecutionEnvironmentStep.js
deleted file mode 100644
index 611330ad55c6..000000000000
--- a/awx/ui/src/components/LaunchPrompt/steps/useExecutionEnvironmentStep.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import React from 'react';
-import { t } from '@lingui/macro';
-import ExecutionEnvironmentStep from './ExecutionEnvironmentStep';
-import StepName from './StepName';
-
-const STEP_ID = 'executionEnvironment';
-
-export default function useExecutionEnvironmentStep(launchConfig, resource) {
- return {
- step: getStep(launchConfig, resource),
- initialValues: getInitialValues(launchConfig, resource),
- isReady: true,
- contentError: null,
- hasError: false,
- setTouched: (setFieldTouched) => {
- setFieldTouched('execution_environment', true, false);
- },
- validate: () => {},
- };
-}
-function getStep(launchConfig) {
- if (!launchConfig.ask_execution_environment_on_launch) {
- return null;
- }
- return {
- id: STEP_ID,
- name: (
-
- {t`Execution Environment`}
-
- ),
- component: ,
- enableNext: true,
- };
-}
-
-function getInitialValues(launchConfig, resource) {
- if (!launchConfig.ask_execution_environment_on_launch) {
- return {};
- }
-
- return {
- execution_environment:
- resource?.summary_fields?.execution_environment || null,
- };
-}
diff --git a/awx/ui/src/components/LaunchPrompt/steps/useInstanceGroupsStep.js b/awx/ui/src/components/LaunchPrompt/steps/useInstanceGroupsStep.js
deleted file mode 100644
index a15b868b69d1..000000000000
--- a/awx/ui/src/components/LaunchPrompt/steps/useInstanceGroupsStep.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import React from 'react';
-import { t } from '@lingui/macro';
-import InstanceGroupsStep from './InstanceGroupsStep';
-import StepName from './StepName';
-
-const STEP_ID = 'instanceGroups';
-
-export default function useInstanceGroupsStep(
- launchConfig,
- resource,
- instanceGroups
-) {
- return {
- step: getStep(launchConfig, resource),
- initialValues: getInitialValues(launchConfig, instanceGroups),
- isReady: true,
- contentError: null,
- hasError: false,
- setTouched: (setFieldTouched) => {
- setFieldTouched('instance_groups', true, false);
- },
- validate: () => {},
- };
-}
-function getStep(launchConfig) {
- if (!launchConfig.ask_instance_groups_on_launch) {
- return null;
- }
- return {
- id: STEP_ID,
- name: {t`Instance Groups`},
- component: ,
- enableNext: true,
- };
-}
-
-function getInitialValues(launchConfig, instanceGroups) {
- if (!launchConfig.ask_instance_groups_on_launch) {
- return {};
- }
-
- return {
- instance_groups: instanceGroups || [],
- };
-}
diff --git a/awx/ui/src/components/LaunchPrompt/steps/useInventoryStep.js b/awx/ui/src/components/LaunchPrompt/steps/useInventoryStep.js
deleted file mode 100644
index 9d952de60c15..000000000000
--- a/awx/ui/src/components/LaunchPrompt/steps/useInventoryStep.js
+++ /dev/null
@@ -1,77 +0,0 @@
-import React from 'react';
-import { t } from '@lingui/macro';
-import { useField } from 'formik';
-import styled from 'styled-components';
-import { Alert } from '@patternfly/react-core';
-import InventoryStep from './InventoryStep';
-import StepName from './StepName';
-
-const InventoryAlert = styled(Alert)`
- margin-bottom: 16px;
-`;
-
-const STEP_ID = 'inventory';
-
-export default function useInventoryStep(launchConfig, resource, visitedSteps) {
- const [, meta, helpers] = useField('inventory');
- const formError =
- !resource || resource?.type === 'workflow_job_template'
- ? false
- : Object.keys(visitedSteps).includes(STEP_ID) &&
- meta.touched &&
- !meta.value;
-
- return {
- step: getStep(launchConfig, formError, resource),
- initialValues: getInitialValues(launchConfig, resource),
- isReady: true,
- contentError: null,
- hasError: launchConfig.ask_inventory_on_launch && formError,
- setTouched: (setFieldTouched) => {
- setFieldTouched('inventory', true, false);
- },
- validate: () => {
- if (meta.touched && !meta.value && resource.type === 'job_template') {
- helpers.setError(t`An inventory must be selected`);
- }
- },
- };
-}
-function getStep(launchConfig, formError, resource) {
- if (!launchConfig.ask_inventory_on_launch) {
- return null;
- }
- return {
- id: STEP_ID,
- name: (
-
- {t`Inventory`}
-
- ),
- component: (
-
- ) : null
- }
- />
- ),
- enableNext: true,
- };
-}
-
-function getInitialValues(launchConfig, resource) {
- if (!launchConfig.ask_inventory_on_launch) {
- return {};
- }
-
- return {
- inventory: resource?.summary_fields?.inventory || null,
- };
-}
diff --git a/awx/ui/src/components/LaunchPrompt/steps/useOtherPromptsStep.js b/awx/ui/src/components/LaunchPrompt/steps/useOtherPromptsStep.js
deleted file mode 100644
index 620fe8337c88..000000000000
--- a/awx/ui/src/components/LaunchPrompt/steps/useOtherPromptsStep.js
+++ /dev/null
@@ -1,165 +0,0 @@
-import React, { useState } from 'react';
-import { t } from '@lingui/macro';
-import { useField } from 'formik';
-import { jsonToYaml, yamlToJson } from 'util/yaml';
-import OtherPromptsStep from './OtherPromptsStep';
-import StepName from './StepName';
-
-const STEP_ID = 'other';
-export const YAML_MODE = 'yaml';
-export const JSON_MODE = 'javascript';
-
-const getVariablesData = (resource) => {
- if (resource?.extra_data) {
- return jsonToYaml(JSON.stringify(resource.extra_data));
- }
- if (resource?.extra_vars && resource?.extra_vars !== '---') {
- return resource.extra_vars;
- }
- return '---';
-};
-
-const FIELD_NAMES = [
- 'job_type',
- 'limit',
- 'verbosity',
- 'diff_mode',
- 'job_tags',
- 'skip_tags',
- 'extra_vars',
- 'labels',
- 'timeout',
- 'job_slice_count',
- 'forks',
- 'labels',
-];
-
-export default function useOtherPromptsStep(launchConfig, resource, labels) {
- const [variablesField] = useField('extra_vars');
- const [variablesMode, setVariablesMode] = useState(null);
- const [isTouched, setIsTouched] = useState(false);
-
- const handleModeChange = (mode) => {
- setVariablesMode(mode);
- };
-
- const validateVariables = () => {
- if (!isTouched) {
- return false;
- }
- try {
- if (variablesMode === JSON_MODE) {
- JSON.parse(variablesField.value);
- } else {
- yamlToJson(variablesField.value);
- }
- } catch (error) {
- return true;
- }
- return false;
- };
- const hasError = launchConfig.ask_variables_on_launch
- ? validateVariables()
- : false;
-
- return {
- step: getStep(launchConfig, hasError, variablesMode, handleModeChange),
- initialValues: getInitialValues(launchConfig, resource, labels),
- isReady: true,
- contentError: null,
- hasError,
- setTouched: (setFieldTouched) => {
- setIsTouched(true);
- FIELD_NAMES.forEach((fieldName) =>
- setFieldTouched(fieldName, true, false)
- );
- },
- validate: () => {},
- };
-}
-
-function getStep(launchConfig, hasError, variablesMode, handleModeChange) {
- if (!shouldShowPrompt(launchConfig)) {
- return null;
- }
- return {
- id: STEP_ID,
- key: 5,
- name: (
-
- {t`Other prompts`}
-
- ),
- component: (
-
- ),
- enableNext: true,
- };
-}
-
-function shouldShowPrompt(launchConfig) {
- return (
- launchConfig.ask_job_type_on_launch ||
- launchConfig.ask_limit_on_launch ||
- launchConfig.ask_verbosity_on_launch ||
- launchConfig.ask_tags_on_launch ||
- launchConfig.ask_skip_tags_on_launch ||
- launchConfig.ask_variables_on_launch ||
- launchConfig.ask_scm_branch_on_launch ||
- launchConfig.ask_diff_mode_on_launch ||
- launchConfig.ask_labels_on_launch ||
- launchConfig.ask_forks_on_launch ||
- launchConfig.ask_job_slice_count_on_launch ||
- launchConfig.ask_timeout_on_launch
- );
-}
-
-function getInitialValues(launchConfig, resource, labels) {
- const initialValues = {};
-
- if (!launchConfig) {
- return initialValues;
- }
-
- if (launchConfig.ask_job_type_on_launch) {
- initialValues.job_type = resource?.job_type || '';
- }
- if (launchConfig.ask_limit_on_launch) {
- initialValues.limit = resource?.limit || null;
- }
- if (launchConfig.ask_verbosity_on_launch) {
- initialValues.verbosity = resource?.verbosity || 0;
- }
- if (launchConfig.ask_tags_on_launch) {
- initialValues.job_tags = resource?.job_tags || '';
- }
- if (launchConfig.ask_skip_tags_on_launch) {
- initialValues.skip_tags = resource?.skip_tags || '';
- }
- if (launchConfig.ask_variables_on_launch) {
- initialValues.extra_vars = getVariablesData(resource);
- }
- if (launchConfig.ask_scm_branch_on_launch) {
- initialValues.scm_branch = resource?.scm_branch || '';
- }
- if (launchConfig.ask_diff_mode_on_launch) {
- initialValues.diff_mode = resource?.diff_mode || false;
- }
- if (launchConfig.ask_forks_on_launch) {
- initialValues.forks = resource?.forks || 0;
- }
- if (launchConfig.ask_job_slice_count_on_launch) {
- initialValues.job_slice_count = resource?.job_slice_count || 1;
- }
- if (launchConfig.ask_timeout_on_launch) {
- initialValues.timeout = resource?.timeout || 0;
- }
- if (launchConfig.ask_labels_on_launch) {
- initialValues.labels = labels || [];
- }
- return initialValues;
-}
diff --git a/awx/ui/src/components/LaunchPrompt/steps/usePreviewStep.js b/awx/ui/src/components/LaunchPrompt/steps/usePreviewStep.js
deleted file mode 100644
index bc6e1c9e7226..000000000000
--- a/awx/ui/src/components/LaunchPrompt/steps/usePreviewStep.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import React from 'react';
-import { t } from '@lingui/macro';
-import PreviewStep from './PreviewStep';
-import StepName from './StepName';
-
-const STEP_ID = 'preview';
-
-export default function usePreviewStep(
- launchConfig,
-
- resource,
- surveyConfig,
- hasErrors,
- showStep,
- nextButtonText
-) {
- return {
- step: showStep
- ? {
- id: STEP_ID,
- name: (
-
- {t`Preview`}
-
- ),
- component: (
-
- ),
- enableNext: !hasErrors,
- nextButtonText: nextButtonText || t`Launch`,
- }
- : null,
- initialValues: {},
- isReady: true,
- error: null,
- setTouched: () => {},
- validate: () => {},
- };
-}
diff --git a/awx/ui/src/components/LaunchPrompt/steps/useSurveyStep.js b/awx/ui/src/components/LaunchPrompt/steps/useSurveyStep.js
deleted file mode 100644
index a19bc46a57b8..000000000000
--- a/awx/ui/src/components/LaunchPrompt/steps/useSurveyStep.js
+++ /dev/null
@@ -1,146 +0,0 @@
-import React from 'react';
-import { t } from '@lingui/macro';
-import { useFormikContext } from 'formik';
-import SurveyStep from './SurveyStep';
-import StepName from './StepName';
-
-const STEP_ID = 'survey';
-
-export default function useSurveyStep(
- launchConfig,
- surveyConfig,
- resource,
- visitedSteps
-) {
- const { setFieldError, values } = useFormikContext();
- const hasError =
- Object.keys(visitedSteps).includes(STEP_ID) &&
- checkForError(launchConfig, surveyConfig, values);
-
- return {
- step: launchConfig.survey_enabled
- ? {
- id: STEP_ID,
- name: (
-
- {t`Survey`}
-
- ),
- component: ,
- enableNext: true,
- }
- : null,
- initialValues: getInitialValues(launchConfig, surveyConfig, resource),
- surveyConfig,
- isReady: true,
- contentError: null,
- hasError,
- setTouched: (setFieldTouched) => {
- if (!surveyConfig?.spec) {
- return;
- }
- surveyConfig.spec.forEach((question) => {
- setFieldTouched(`survey_${question.variable}`, true, false);
- });
- },
- validate: () => {
- if (launchConfig.survey_enabled && surveyConfig.spec) {
- surveyConfig.spec.forEach((question) => {
- const errMessage = validateSurveyField(
- question,
- values[`survey_${question.variable}`]
- );
- if (errMessage) {
- setFieldError(`survey_${question.variable}`, errMessage);
- }
- });
- }
- },
- };
-}
-
-function getInitialValues(launchConfig, surveyConfig, resource) {
- if (!launchConfig.survey_enabled || !surveyConfig) {
- return {};
- }
-
- const values = {};
- if (surveyConfig?.spec) {
- surveyConfig.spec.forEach((question) => {
- if (question.type === 'multiselect') {
- values[`survey_${question.variable}`] = question.default
- ? question.default.split('\n')
- : [];
- } else {
- values[`survey_${question.variable}`] = question.default ?? '';
- }
- if (resource?.extra_data) {
- Object.entries(resource.extra_data).forEach(([key, value]) => {
- if (key === question.variable) {
- if (question.type === 'multiselect') {
- values[`survey_${question.variable}`] = value;
- } else {
- values[`survey_${question.variable}`] = value;
- }
- }
- });
- }
- });
- }
-
- return values;
-}
-
-function validateSurveyField(question, value) {
- const isTextField = ['text', 'textarea'].includes(question.type);
- const isNumeric = ['integer', 'float'].includes(question.type);
- if (isTextField && (value || value === 0)) {
- if (question.min && value.length < question.min) {
- return t`This field must be at least ${question.min} characters`;
- }
- if (question.max && value.length > question.max) {
- return t`This field must not exceed ${question.max} characters`;
- }
- }
- if (isNumeric && (value || value === 0)) {
- if (value < question.min || value > question.max) {
- return t`This field must be a number and have a value between ${question.min} and ${question.max}`;
- }
- }
- if (question.required && !value && value !== 0) {
- return t`This field must not be blank`;
- }
- return null;
-}
-
-function checkForError(launchConfig, surveyConfig, values) {
- let hasError = false;
- if (launchConfig.survey_enabled && surveyConfig.spec) {
- surveyConfig.spec.forEach((question) => {
- const value = values[`survey_${question.variable}`];
- const isTextField = ['text', 'textarea'].includes(question.type);
- const isNumeric = ['integer', 'float'].includes(question.type);
- if (isTextField && (value || value === 0)) {
- if (
- (question.min && value.length < question.min) ||
- (question.max && value.length > question.max)
- ) {
- hasError = true;
- }
- }
- if (isNumeric) {
- if (
- (value < question.min || value > question.max || value === '') &&
- question.required
- ) {
- hasError = true;
- }
- }
- if (question.required && (!value || value.length === 0) && !isNumeric) {
- hasError = true;
- }
- });
- }
-
- return hasError;
-}
diff --git a/awx/ui/src/components/LaunchPrompt/useLaunchSteps.js b/awx/ui/src/components/LaunchPrompt/useLaunchSteps.js
deleted file mode 100644
index 7cbba9be8a46..000000000000
--- a/awx/ui/src/components/LaunchPrompt/useLaunchSteps.js
+++ /dev/null
@@ -1,166 +0,0 @@
-import { useState, useEffect } from 'react';
-import { useFormikContext } from 'formik';
-import useInventoryStep from './steps/useInventoryStep';
-import useCredentialsStep from './steps/useCredentialsStep';
-import useCredentialPasswordsStep from './steps/useCredentialPasswordsStep';
-import useExecutionEnvironmentStep from './steps/useExecutionEnvironmentStep';
-import useOtherPromptsStep from './steps/useOtherPromptsStep';
-import useSurveyStep from './steps/useSurveyStep';
-import usePreviewStep from './steps/usePreviewStep';
-import useInstanceGroupsStep from './steps/useInstanceGroupsStep';
-
-function showCredentialPasswordsStep(launchConfig, credentials = []) {
- if (
- !launchConfig?.ask_credential_on_launch &&
- launchConfig?.passwords_needed_to_start
- ) {
- return launchConfig.passwords_needed_to_start.length > 0;
- }
-
- let credentialPasswordStepRequired = false;
-
- credentials.forEach((credential) => {
- if (!credential.inputs) {
- const launchConfigCredential = launchConfig.defaults.credentials.find(
- (defaultCred) => defaultCred.id === credential.id
- );
-
- if (launchConfigCredential?.passwords_needed.length > 0) {
- credentialPasswordStepRequired = true;
- }
- } else if (
- credential?.inputs?.password === 'ASK' ||
- credential?.inputs?.become_password === 'ASK' ||
- credential?.inputs?.ssh_key_unlock === 'ASK' ||
- credential?.inputs?.vault_password === 'ASK'
- ) {
- credentialPasswordStepRequired = true;
- }
- });
-
- return credentialPasswordStepRequired;
-}
-
-export default function useLaunchSteps(
- launchConfig,
- surveyConfig,
- resource,
- labels,
- instanceGroups
-) {
- const [visited, setVisited] = useState({});
- const [isReady, setIsReady] = useState(false);
- const { touched, values: formikValues } = useFormikContext();
- const steps = [
- useInventoryStep(launchConfig, resource, visited),
- useCredentialsStep(
- launchConfig,
- resource,
- resource.summary_fields.credentials || [],
- true
- ),
- useCredentialPasswordsStep(
- launchConfig,
- showCredentialPasswordsStep(launchConfig, formikValues.credentials),
- visited
- ),
- useExecutionEnvironmentStep(launchConfig, resource),
- useInstanceGroupsStep(launchConfig, resource, instanceGroups),
- useOtherPromptsStep(launchConfig, resource, labels),
- useSurveyStep(launchConfig, surveyConfig, resource, visited),
- ];
- const { resetForm } = useFormikContext();
- const hasErrors = steps.some((step) => step.hasError);
-
- steps.push(
- usePreviewStep(launchConfig, resource, surveyConfig, hasErrors, true)
- );
-
- const pfSteps = steps.map((s) => s.step).filter((s) => s != null);
- const stepsAreReady = !steps.some((s) => !s.isReady);
-
- useEffect(() => {
- if (!stepsAreReady) {
- return;
- }
-
- const initialValues = steps.reduce(
- (acc, cur) => ({
- ...acc,
- ...cur.initialValues,
- }),
- {}
- );
-
- const newFormValues = { ...initialValues };
-
- Object.keys(formikValues).forEach((formikValueKey) => {
- if (
- formikValueKey === 'credential_passwords' &&
- Object.prototype.hasOwnProperty.call(
- newFormValues,
- 'credential_passwords'
- )
- ) {
- const formikCredentialPasswords = formikValues.credential_passwords;
- Object.keys(formikCredentialPasswords).forEach(
- (credentialPasswordValueKey) => {
- if (
- Object.prototype.hasOwnProperty.call(
- newFormValues.credential_passwords,
- credentialPasswordValueKey
- )
- ) {
- newFormValues.credential_passwords[credentialPasswordValueKey] =
- formikCredentialPasswords[credentialPasswordValueKey];
- }
- }
- );
- } else if (
- Object.prototype.hasOwnProperty.call(newFormValues, formikValueKey)
- ) {
- newFormValues[formikValueKey] = formikValues[formikValueKey];
- }
- });
-
- resetForm({
- values: newFormValues,
- touched,
- });
-
- setIsReady(true);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [formikValues.credentials, stepsAreReady]);
-
- const stepWithError = steps.find((s) => s.contentError);
- const contentError = stepWithError ? stepWithError.contentError : null;
-
- return {
- steps: pfSteps,
- isReady,
- validateStep: (stepId) => {
- steps.find((s) => s?.step?.id === stepId).validate();
- },
- visitStep: (prevStepId, setFieldTouched) => {
- setVisited({
- ...visited,
- [prevStepId]: true,
- });
- steps.find((s) => s?.step?.id === prevStepId).setTouched(setFieldTouched);
- },
- visitAllSteps: (setFieldTouched) => {
- setVisited({
- inventory: true,
- credentials: true,
- credentialPasswords: true,
- executionEnvironment: true,
- instanceGroups: true,
- other: true,
- survey: true,
- preview: true,
- });
- steps.forEach((s) => s.setTouched(setFieldTouched));
- },
- contentError,
- };
-}
diff --git a/awx/ui/src/components/ListHeader/ListHeader.js b/awx/ui/src/components/ListHeader/ListHeader.js
deleted file mode 100644
index 8fc3e300b6dc..000000000000
--- a/awx/ui/src/components/ListHeader/ListHeader.js
+++ /dev/null
@@ -1,150 +0,0 @@
-/* eslint-disable react/jsx-no-useless-fragment */
-import React, { useState } from 'react';
-import PropTypes from 'prop-types';
-import { useHistory, useLocation } from 'react-router-dom';
-import styled from 'styled-components';
-import { Toolbar, ToolbarContent } from '@patternfly/react-core';
-
-import {
- parseQueryString,
- mergeParams,
- removeParams,
- updateQueryString,
-} from 'util/qs';
-import { QSConfig, SearchColumns, SortColumns, SearchableKeys } from 'types';
-import DataListToolbar from '../DataListToolbar';
-
-const EmptyStateControlsWrapper = styled.div`
- display: flex;
- margin-top: 20px;
- margin-right: 20px;
- margin-bottom: 20px;
- justify-content: flex-end;
-
- & > :not(:first-child) {
- margin-left: 20px;
- }
-`;
-function ListHeader(props) {
- const { search, pathname } = useLocation();
- const [isFilterCleared, setIsFilterCleared] = useState(false);
- const history = useHistory();
- const {
- emptyStateControls,
- itemCount,
- pagination,
- qsConfig,
- relatedSearchableKeys,
- renderToolbar,
- searchColumns,
- searchableKeys,
- sortColumns,
- } = props;
-
- const handleSearch = (key, value) => {
- const params = parseQueryString(qsConfig, search);
- const qs = updateQueryString(qsConfig, search, {
- ...mergeParams(params, { [key]: value }),
- page: 1,
- });
- pushHistoryState(qs);
- };
-
- const handleReplaceSearch = (key, value) => {
- const qs = updateQueryString(qsConfig, search, {
- [key]: value,
- });
- pushHistoryState(qs);
- };
-
- const handleRemove = (key, value) => {
- const oldParams = parseQueryString(qsConfig, search);
- const updatedParams = removeParams(qsConfig, oldParams, {
- [key]: value,
- });
- const qs = updateQueryString(qsConfig, search, updatedParams);
- pushHistoryState(qs);
- };
-
- const handleRemoveAll = () => {
- const oldParams = parseQueryString(qsConfig, search);
- Object.keys(oldParams).forEach((key) => {
- oldParams[key] = null;
- });
- delete oldParams.page_size;
- delete oldParams.order_by;
- const qs = updateQueryString(qsConfig, search, oldParams);
- setIsFilterCleared(true);
- pushHistoryState(qs);
- };
-
- const handleSort = (key, order) => {
- const qs = updateQueryString(qsConfig, search, {
- order_by: order === 'ascending' ? key : `-${key}`,
- page: null,
- });
- pushHistoryState(qs);
- };
-
- const pushHistoryState = (queryString) => {
- history.push(queryString ? `${pathname}?${queryString}` : pathname);
- };
-
- const params = parseQueryString(qsConfig, search);
- const isEmpty = itemCount === 0 && Object.keys(params).length === 0;
- return (
- <>
- {isEmpty ? (
-
-
-
- {emptyStateControls}
-
-
-
- ) : (
- <>
- {renderToolbar({
- itemCount,
- searchColumns,
- sortColumns,
- searchableKeys,
- relatedSearchableKeys,
- onSearch: handleSearch,
- onReplaceSearch: handleReplaceSearch,
- onSort: handleSort,
- onRemove: handleRemove,
- clearAllFilters: handleRemoveAll,
- qsConfig,
- pagination,
- isFilterCleared,
- })}
- >
- )}
- >
- );
-}
-
-ListHeader.propTypes = {
- itemCount: PropTypes.number.isRequired,
- qsConfig: QSConfig.isRequired,
- searchColumns: SearchColumns.isRequired,
- searchableKeys: SearchableKeys,
- relatedSearchableKeys: PropTypes.arrayOf(PropTypes.string),
- sortColumns: SortColumns,
- renderToolbar: PropTypes.func,
-};
-
-ListHeader.defaultProps = {
- renderToolbar: (props) => ,
- searchableKeys: [],
- sortColumns: null,
- relatedSearchableKeys: [],
-};
-
-export default ListHeader;
diff --git a/awx/ui/src/components/ListHeader/ListHeader.test.js b/awx/ui/src/components/ListHeader/ListHeader.test.js
deleted file mode 100644
index c4fa88000a72..000000000000
--- a/awx/ui/src/components/ListHeader/ListHeader.test.js
+++ /dev/null
@@ -1,131 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import ListHeader from './ListHeader';
-
-describe('ListHeader', () => {
- const qsConfig = {
- namespace: 'item',
- defaultParams: { page: 1, page_size: 5, order_by: 'foo' },
- integerFields: ['id', 'page', 'page_size'],
- dateFields: ['modified', 'created'],
- };
- const renderToolbarFn = jest.fn();
-
- test('initially renders without crashing', () => {
- const history = createMemoryHistory({
- initialEntries: ['/organizations/1/teams'],
- });
- const wrapper = mountWithContexts(
- ,
- { context: { router: { history } } }
- );
- expect(wrapper.length).toBe(1);
- });
-
- test('should navigate when DataListToolbar calls onSort prop', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/organizations/1/teams'],
- });
- const wrapper = mountWithContexts(
- ,
- { context: { router: { history } } }
- );
-
- const toolbar = wrapper.find('DataListToolbar');
- toolbar.prop('onSort')('foo', 'descending');
- expect(history.location.search).toEqual('?item.order_by=-foo');
- toolbar.prop('onSort')('foo', 'ascending');
- // since order_by = name is the default, that should be strip out of the search
- expect(history.location.search).toEqual('');
- });
-
- test('should clear all', () => {
- const query = '?item.page_size=5&item.name=foo';
- const history = createMemoryHistory({
- initialEntries: [`/organizations/1/teams${query}`],
- });
- const wrapper = mountWithContexts(
- ,
- { context: { router: { history } } }
- );
-
- expect(history.location.search).toEqual(query);
- const toolbar = wrapper.find('DataListToolbar');
- act(() => {
- toolbar.prop('clearAllFilters')();
- });
- expect(history.location.search).toEqual('?item.page_size=5');
- });
-
- test('should test handle search', () => {
- const query = '?item.page_size=10';
- const history = createMemoryHistory({
- initialEntries: [`/organizations/1/teams${query}`],
- });
- const wrapper = mountWithContexts(
- ,
- { context: { router: { history } } }
- );
-
- expect(history.location.search).toEqual(query);
- const toolbar = wrapper.find('DataListToolbar');
- toolbar.prop('onSearch')('name__icontains', 'foo');
- expect(history.location.search).toEqual(
- '?item.name__icontains=foo&item.page_size=10'
- );
- });
-
- test('should test handle remove', () => {
- const query = '?item.name__icontains=foo&item.page_size=10';
- const history = createMemoryHistory({
- initialEntries: [`/organizations/1/teams${query}`],
- });
- const wrapper = mountWithContexts(
- ,
- { context: { router: { history } } }
- );
-
- expect(history.location.search).toEqual(query);
- const toolbar = wrapper.find('DataListToolbar');
- toolbar.prop('onRemove')('name__icontains', 'foo');
- expect(history.location.search).toEqual('?item.page_size=10');
- });
-});
diff --git a/awx/ui/src/components/ListHeader/index.js b/awx/ui/src/components/ListHeader/index.js
deleted file mode 100644
index 64af4f118ac5..000000000000
--- a/awx/ui/src/components/ListHeader/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ListHeader';
diff --git a/awx/ui/src/components/LoadingSpinner/LoadingSpinner.js b/awx/ui/src/components/LoadingSpinner/LoadingSpinner.js
deleted file mode 100644
index 36ed8adf5ea1..000000000000
--- a/awx/ui/src/components/LoadingSpinner/LoadingSpinner.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import React from 'react';
-
-import { Spinner } from '@patternfly/react-core';
-import styled from 'styled-components';
-
-const UpdatingContent = styled.div`
- position: fixed;
- top: 50%;
- left: 50%;
- z-index: 300;
- width: 100%;
- height: 100%;
- & + * {
- opacity: 0.5;
- }
-`;
-
-const LoadingSpinner = () => (
-
-
-
-);
-export default LoadingSpinner;
diff --git a/awx/ui/src/components/LoadingSpinner/index.js b/awx/ui/src/components/LoadingSpinner/index.js
deleted file mode 100644
index 6513c5cb53a5..000000000000
--- a/awx/ui/src/components/LoadingSpinner/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './LoadingSpinner';
diff --git a/awx/ui/src/components/Lookup/ApplicationLookup.js b/awx/ui/src/components/Lookup/ApplicationLookup.js
deleted file mode 100644
index 2a5e43cbc981..000000000000
--- a/awx/ui/src/components/Lookup/ApplicationLookup.js
+++ /dev/null
@@ -1,154 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { func, node, string } from 'prop-types';
-import { useLocation } from 'react-router-dom';
-import { t } from '@lingui/macro';
-import { FormGroup } from '@patternfly/react-core';
-import { ApplicationsAPI } from 'api';
-import { Application } from 'types';
-import { getSearchableKeys } from 'components/PaginatedTable';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import useRequest from 'hooks/useRequest';
-import Lookup from './Lookup';
-import OptionsList from '../OptionsList';
-import LookupErrorMessage from './shared/LookupErrorMessage';
-
-const QS_CONFIG = getQSConfig('applications', {
- page: 1,
- page_size: 5,
- order_by: 'name',
-});
-
-function ApplicationLookup({ onChange, value, label, fieldName, validate }) {
- const location = useLocation();
- const {
- error,
- result: { applications, itemCount, relatedSearchableKeys, searchableKeys },
- request: fetchApplications,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
-
- const [
- {
- data: { results, count },
- },
- actionsResponse,
- ] = await Promise.all([
- ApplicationsAPI.read(params),
- ApplicationsAPI.readOptions,
- ]);
- return {
- applications: results,
- itemCount: count,
- relatedSearchableKeys: (
- actionsResponse?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(actionsResponse?.data?.actions?.GET),
- };
- }, [location]),
- {
- applications: [],
- itemCount: 0,
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- const checkApplicationName = useCallback(
- async (name) => {
- if (!name) {
- onChange(null);
- return;
- }
-
- try {
- const {
- data: { results: nameMatchResults, count: nameMatchCount },
- } = await ApplicationsAPI.read({ name });
- onChange(nameMatchCount ? nameMatchResults[0] : null);
- } catch {
- onChange(null);
- }
- },
- [onChange]
- );
-
- useEffect(() => {
- fetchApplications();
- }, [fetchApplications]);
- return (
-
- (
- dispatch({ type: 'SELECT_ITEM', item })}
- deselectItem={(item) => dispatch({ type: 'DESELECT_ITEM', item })}
- />
- )}
- />
-
-
- );
-}
-ApplicationLookup.propTypes = {
- label: node.isRequired,
- onChange: func.isRequired,
- value: Application,
- validate: func,
- fieldName: string,
-};
-
-ApplicationLookup.defaultProps = {
- value: null,
- validate: () => undefined,
- fieldName: 'application',
-};
-
-export default ApplicationLookup;
diff --git a/awx/ui/src/components/Lookup/ApplicationLookup.test.js b/awx/ui/src/components/Lookup/ApplicationLookup.test.js
deleted file mode 100644
index 9c9cb6b48124..000000000000
--- a/awx/ui/src/components/Lookup/ApplicationLookup.test.js
+++ /dev/null
@@ -1,86 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { ApplicationsAPI } from 'api';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import ApplicationLookup from './ApplicationLookup';
-
-jest.mock('../../api');
-const application = {
- id: 1,
- name: 'app',
- description: '',
-};
-
-const fetchedApplications = {
- count: 2,
- results: [
- {
- id: 1,
- name: 'app',
- description: '',
- },
- {
- id: 4,
- name: 'application that should not crach',
- description: '',
- },
- ],
-};
-describe('ApplicationLookup', () => {
- let wrapper;
-
- beforeEach(() => {
- ApplicationsAPI.read.mockResolvedValueOnce(fetchedApplications);
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should render successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- />
-
- );
- });
- expect(wrapper.find('ApplicationLookup')).toHaveLength(1);
- });
-
- test('should fetch applications', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- />
-
- );
- });
- expect(ApplicationsAPI.read).toHaveBeenCalledTimes(1);
- });
-
- test('should display label', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- />
-
- );
- });
- const title = wrapper.find('FormGroup .pf-c-form__label-text');
- expect(title.text()).toEqual('Application');
- });
-});
diff --git a/awx/ui/src/components/Lookup/CredentialLookup.js b/awx/ui/src/components/Lookup/CredentialLookup.js
deleted file mode 100644
index 5256c20e6b44..000000000000
--- a/awx/ui/src/components/Lookup/CredentialLookup.js
+++ /dev/null
@@ -1,280 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { useHistory } from 'react-router-dom';
-import {
- arrayOf,
- bool,
- func,
- node,
- number,
- string,
- oneOfType,
-} from 'prop-types';
-
-import { t } from '@lingui/macro';
-import { FormGroup } from '@patternfly/react-core';
-import { CredentialsAPI } from 'api';
-import { Credential } from 'types';
-import { getSearchableKeys } from 'components/PaginatedTable';
-import { getQSConfig, parseQueryString, mergeParams } from 'util/qs';
-import useAutoPopulateLookup from 'hooks/useAutoPopulateLookup';
-import useRequest from 'hooks/useRequest';
-import Popover from '../Popover';
-import Lookup from './Lookup';
-import OptionsList from '../OptionsList';
-import LookupErrorMessage from './shared/LookupErrorMessage';
-
-const QS_CONFIG = getQSConfig('credentials', {
- page: 1,
- page_size: 5,
- order_by: 'name',
-});
-
-function CredentialLookup({
- autoPopulate,
- credentialTypeId,
- credentialTypeKind,
- credentialTypeNamespace,
- fieldName,
- helperTextInvalid,
- isDisabled,
- isSelectedDraggable,
- isValid,
- label,
- modalDescription,
- multiple,
- onBlur,
- onChange,
- required,
- tooltip,
- validate,
- value,
-}) {
- const history = useHistory();
- const autoPopulateLookup = useAutoPopulateLookup(onChange);
- const {
- result: { count, credentials, relatedSearchableKeys, searchableKeys },
- error,
- request: fetchCredentials,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, history.location.search);
- const typeIdParams = credentialTypeId
- ? { credential_type: credentialTypeId }
- : {};
- const typeKindParams = credentialTypeKind
- ? { credential_type__kind: credentialTypeKind }
- : {};
- const typeNamespaceParams = credentialTypeNamespace
- ? { credential_type__namespace: credentialTypeNamespace }
- : {};
-
- const [{ data }, actionsResponse] = await Promise.all([
- CredentialsAPI.read(
- mergeParams(params, {
- ...typeIdParams,
- ...typeKindParams,
- ...typeNamespaceParams,
- })
- ),
- CredentialsAPI.readOptions(),
- ]);
-
- if (autoPopulate) {
- autoPopulateLookup(data.results);
- }
-
- const searchKeys = getSearchableKeys(actionsResponse.data.actions?.GET);
- const item = searchKeys.find((k) => k.key === 'type');
- if (item) {
- item.key = 'credential_type__kind';
- }
-
- return {
- count: data.count,
- credentials: data.results,
- relatedSearchableKeys: (
- actionsResponse?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: searchKeys,
- };
- }, [
- autoPopulate,
- autoPopulateLookup,
- credentialTypeId,
- credentialTypeKind,
- credentialTypeNamespace,
- history.location.search,
- ]),
- {
- count: 0,
- credentials: [],
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- const checkCredentialName = useCallback(
- async (name) => {
- if (!name) {
- onChange(null);
- return;
- }
-
- try {
- const typeIdParams = credentialTypeId
- ? { credential_type: credentialTypeId }
- : {};
- const typeKindParams = credentialTypeKind
- ? { credential_type__kind: credentialTypeKind }
- : {};
- const typeNamespaceParams = credentialTypeNamespace
- ? { credential_type__namespace: credentialTypeNamespace }
- : {};
-
- const {
- data: { results: nameMatchResults, count: nameMatchCount },
- } = await CredentialsAPI.read({
- name,
- ...typeIdParams,
- ...typeKindParams,
- ...typeNamespaceParams,
- });
- onChange(nameMatchCount ? nameMatchResults[0] : null);
- } catch {
- onChange(null);
- }
- },
- [onChange, credentialTypeId, credentialTypeKind, credentialTypeNamespace]
- );
-
- useEffect(() => {
- fetchCredentials();
- }, [fetchCredentials]);
-
- // TODO: replace credential type search with REST-based grabbing of cred types
-
- return (
- }
- helperTextInvalid={helperTextInvalid}
- >
- (
- dispatch({ type: 'SELECT_ITEM', item })}
- deselectItem={(item) => dispatch({ type: 'DESELECT_ITEM', item })}
- sortSelectedItems={(selectedItems) =>
- dispatch({ type: 'SET_SELECTED_ITEMS', selectedItems })
- }
- multiple={multiple}
- isSelectedDraggable={isSelectedDraggable}
- />
- )}
- />
-
-
- );
-}
-
-function idOrKind(props, propName, componentName) {
- let error;
- if (
- !Object.prototype.hasOwnProperty.call(props, 'credentialTypeId') &&
- !Object.prototype.hasOwnProperty.call(props, 'credentialTypeKind')
- )
- error = new Error(
- `Either "credentialTypeId" or "credentialTypeKind" is required`
- );
- if (
- !Object.prototype.hasOwnProperty.call(props, 'credentialTypeId') &&
- typeof props[propName] !== 'string'
- ) {
- error = new Error(
- `Invalid prop '${propName}' '${props[propName]}' supplied to '${componentName}'.`
- );
- }
- return error;
-}
-
-CredentialLookup.propTypes = {
- credentialTypeId: oneOfType([number, string]),
- credentialTypeKind: idOrKind,
- helperTextInvalid: node,
- isValid: bool,
- label: string.isRequired,
- multiple: bool,
- onBlur: func,
- onChange: func.isRequired,
- required: bool,
- value: oneOfType([Credential, arrayOf(Credential)]),
- isDisabled: bool,
- autoPopulate: bool,
- validate: func,
- fieldName: string,
-};
-
-CredentialLookup.defaultProps = {
- credentialTypeId: '',
- credentialTypeKind: '',
- helperTextInvalid: '',
- isValid: true,
- multiple: false,
- onBlur: () => {},
- required: false,
- value: null,
- isDisabled: false,
- autoPopulate: false,
- validate: () => undefined,
- fieldName: 'credential',
-};
-
-export { CredentialLookup as _CredentialLookup };
-export default CredentialLookup;
diff --git a/awx/ui/src/components/Lookup/CredentialLookup.test.js b/awx/ui/src/components/Lookup/CredentialLookup.test.js
deleted file mode 100644
index 3a5c8bdaee50..000000000000
--- a/awx/ui/src/components/Lookup/CredentialLookup.test.js
+++ /dev/null
@@ -1,169 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { CredentialsAPI } from 'api';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import CredentialLookup, { _CredentialLookup } from './CredentialLookup';
-
-jest.mock('../../api');
-
-describe('CredentialLookup', () => {
- let wrapper;
-
- beforeEach(() => {
- CredentialsAPI.read.mockResolvedValueOnce({
- data: {
- results: [
- { id: 1, kind: 'cloud', name: 'Cred 1', url: 'www.google.com' },
- { id: 2, kind: 'ssh', name: 'Cred 2', url: 'www.google.com' },
- { id: 3, kind: 'Ansible', name: 'Cred 3', url: 'www.google.com' },
- { id: 4, kind: 'Machine', name: 'Cred 4', url: 'www.google.com' },
- { id: 5, kind: 'Machine', name: 'Cred 5', url: 'www.google.com' },
- ],
- count: 5,
- },
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should render successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- />
-
- );
- });
- expect(wrapper.find('CredentialLookup')).toHaveLength(1);
- });
-
- test('should fetch credentials', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- />
-
- );
- });
- expect(CredentialsAPI.read).toHaveBeenCalledTimes(1);
- expect(CredentialsAPI.read).toHaveBeenCalledWith({
- credential_type: 1,
- order_by: 'name',
- page: 1,
- page_size: 5,
- });
- });
-
- test('should display label', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- />
-
- );
- });
- const title = wrapper.find('FormGroup .pf-c-form__label-text');
- expect(title.text()).toEqual('Foo');
- });
-
- test('should define default value for function props', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- />
-
- );
- });
- expect(_CredentialLookup.defaultProps.onBlur).toBeInstanceOf(Function);
- expect(_CredentialLookup.defaultProps.onBlur).not.toThrow();
- });
-
- test('should not auto-select credential when autoPopulate prop is false', async () => {
- CredentialsAPI.read.mockReturnValue({
- data: {
- results: [{ id: 1 }],
- count: 1,
- },
- });
- const onChange = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- expect(onChange).not.toHaveBeenCalled();
- });
-
- test('should not auto-select credential when multiple available', async () => {
- CredentialsAPI.read.mockReturnValue({
- data: {
- results: [{ id: 1 }, { id: 2 }],
- count: 2,
- },
- });
- const onChange = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- expect(onChange).not.toHaveBeenCalled();
- });
-});
-
-describe('CredentialLookup auto select', () => {
- test('should auto-select credential when only one available and autoPopulate prop is true', async () => {
- CredentialsAPI.read.mockResolvedValue({
- data: {
- results: [{ id: 1 }],
- count: 1,
- },
- });
- const onChange = jest.fn();
- await act(async () => {
- mountWithContexts(
-
-
-
- );
- });
- expect(onChange).toHaveBeenCalledWith({ id: 1 });
- });
-});
diff --git a/awx/ui/src/components/Lookup/ExecutionEnvironmentLookup.js b/awx/ui/src/components/Lookup/ExecutionEnvironmentLookup.js
deleted file mode 100644
index 42767dcd858b..000000000000
--- a/awx/ui/src/components/Lookup/ExecutionEnvironmentLookup.js
+++ /dev/null
@@ -1,267 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { string, func, bool, oneOfType, number } from 'prop-types';
-import { useLocation } from 'react-router-dom';
-import { t } from '@lingui/macro';
-import { FormGroup, Tooltip } from '@patternfly/react-core';
-import { ExecutionEnvironmentsAPI, ProjectsAPI } from 'api';
-import { getSearchableKeys } from 'components/PaginatedTable';
-import { ExecutionEnvironment } from 'types';
-import { getQSConfig, parseQueryString, mergeParams } from 'util/qs';
-import useRequest from 'hooks/useRequest';
-import Popover from '../Popover';
-import OptionsList from '../OptionsList';
-import Lookup from './Lookup';
-import LookupErrorMessage from './shared/LookupErrorMessage';
-import FieldWithPrompt from '../FieldWithPrompt';
-
-const QS_CONFIG = getQSConfig('execution_environments', {
- page: 1,
- page_size: 5,
- order_by: 'name',
-});
-
-function ExecutionEnvironmentLookup({
- id,
- globallyAvailable,
- helperTextInvalid,
- isDisabled,
- isValid,
- onBlur,
- onChange,
- organizationId,
- popoverContent,
- projectId,
- tooltip,
- validate,
- value,
- fieldName,
- overrideLabel,
- isPromptableField,
- promptId,
- promptName,
-}) {
- const location = useLocation();
- const {
- request: fetchProject,
- error: fetchProjectError,
- isLoading: isProjectLoading,
- result: project,
- } = useRequest(
- useCallback(async () => {
- if (!projectId) {
- return {};
- }
- const { data } = await ProjectsAPI.readDetail(projectId);
- return data;
- }, [projectId]),
- {
- project: null,
- isLoading: true,
- }
- );
-
- useEffect(() => {
- fetchProject();
- }, [fetchProject]);
-
- const {
- result: {
- executionEnvironments,
- count,
- relatedSearchableKeys,
- searchableKeys,
- },
- request: fetchExecutionEnvironments,
- error,
- isLoading,
- } = useRequest(
- useCallback(async () => {
- if (isProjectLoading) {
- return {
- executionEnvironments: [],
- count: 0,
- };
- }
- const params = parseQueryString(QS_CONFIG, location.search);
- const globallyAvailableParams = globallyAvailable
- ? { or__organization__isnull: 'True' }
- : {};
- const organizationIdParams = organizationId
- ? { or__organization__id: organizationId }
- : {};
- const projectIdParams =
- projectId && project?.organization
- ? {
- or__organization__id: project.organization,
- }
- : {};
- const [{ data }, actionsResponse] = await Promise.all([
- ExecutionEnvironmentsAPI.read(
- mergeParams(params, {
- ...globallyAvailableParams,
- ...organizationIdParams,
- ...projectIdParams,
- })
- ),
- ExecutionEnvironmentsAPI.readOptions(),
- ]);
- return {
- executionEnvironments: data.results,
- count: data.count,
- relatedSearchableKeys: (
- actionsResponse?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
- };
- }, [
- location,
- globallyAvailable,
- organizationId,
- projectId,
- project,
- isProjectLoading,
- ]),
- {
- executionEnvironments: [],
- count: 0,
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- const checkExecutionEnvironmentName = useCallback(
- async (name) => {
- if (!name) {
- onChange(null);
- return;
- }
-
- try {
- const {
- data: { results: nameMatchResults, count: nameMatchCount },
- } = await ExecutionEnvironmentsAPI.read({ name });
- onChange(nameMatchCount ? nameMatchResults[0] : null);
- } catch {
- onChange(null);
- }
- },
- [onChange]
- );
-
- useEffect(() => {
- fetchExecutionEnvironments();
- }, [fetchExecutionEnvironments]);
-
- const renderLookup = () => (
- <>
- (
- dispatch({ type: 'SELECT_ITEM', item })}
- deselectItem={(item) => dispatch({ type: 'DESELECT_ITEM', item })}
- />
- )}
- />
-
- >
- );
-
- const renderLabel = () => {
- if (overrideLabel) {
- return null;
- }
- return t`Execution Environment`;
- };
-
- return isPromptableField ? (
-
- {tooltip && isDisabled ? (
- {renderLookup()}
- ) : (
- renderLookup()
- )}
-
- ) : (
- }
- helperTextInvalid={helperTextInvalid}
- validated={isValid ? 'default' : 'error'}
- >
- {tooltip && isDisabled ? (
- {renderLookup()}
- ) : (
- renderLookup()
- )}
-
-
-
- );
-}
-
-ExecutionEnvironmentLookup.propTypes = {
- id: string,
- value: ExecutionEnvironment,
- popoverContent: string,
- onChange: func.isRequired,
- projectId: oneOfType([number, string]),
- organizationId: oneOfType([number, string]),
- validate: func,
- fieldName: string,
- overrideLabel: bool,
-};
-
-ExecutionEnvironmentLookup.defaultProps = {
- id: 'execution-environments',
- popoverContent: '',
- value: null,
- projectId: null,
- organizationId: null,
- validate: () => undefined,
- fieldName: 'execution_environment',
- overrideLabel: false,
-};
-
-export default ExecutionEnvironmentLookup;
diff --git a/awx/ui/src/components/Lookup/ExecutionEnvironmentLookup.test.js b/awx/ui/src/components/Lookup/ExecutionEnvironmentLookup.test.js
deleted file mode 100644
index e1f020deffbb..000000000000
--- a/awx/ui/src/components/Lookup/ExecutionEnvironmentLookup.test.js
+++ /dev/null
@@ -1,159 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { ExecutionEnvironmentsAPI, ProjectsAPI } from 'api';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import ExecutionEnvironmentLookup from './ExecutionEnvironmentLookup';
-
-jest.mock('../../api');
-
-const mockedExecutionEnvironments = {
- count: 1,
- results: [
- {
- id: 2,
- name: 'Foo',
- image: 'quay.io/ansible/awx-ee',
- pull: 'missing',
- },
- ],
-};
-
-const executionEnvironment = {
- id: 42,
- name: 'Bar',
- image: 'quay.io/ansible/bar',
- pull: 'missing',
-};
-
-describe('ExecutionEnvironmentLookup', () => {
- let wrapper;
-
- beforeEach(() => {
- ExecutionEnvironmentsAPI.read.mockResolvedValue({
- data: mockedExecutionEnvironments,
- });
- ProjectsAPI.readDetail.mockResolvedValue({ data: { organization: 39 } });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should render successfully', async () => {
- ExecutionEnvironmentsAPI.readOptions.mockReturnValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- related_search_fields: [],
- },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- />
-
- );
- });
- wrapper.update();
- expect(ExecutionEnvironmentsAPI.read).toHaveBeenCalledTimes(1);
- expect(wrapper.find('ExecutionEnvironmentLookup')).toHaveLength(1);
- expect(
- wrapper.find('FormGroup[label="Execution Environment"]').length
- ).toBe(1);
- expect(wrapper.find('Checkbox[aria-label="Prompt on launch"]').length).toBe(
- 0
- );
- });
-
- test('should fetch execution environments', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- />
-
- );
- });
- expect(ExecutionEnvironmentsAPI.read).toHaveBeenCalledTimes(1);
- expect(
- wrapper.find('FormGroup[label="Default Execution Environment"]').length
- ).toBe(0);
- expect(
- wrapper.find('FormGroup[label="Execution Environment"]').length
- ).toBe(1);
- });
-
- test('should call api with organization id', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- organizationId={1}
- globallyAvailable
- />
-
- );
- });
- expect(ExecutionEnvironmentsAPI.read).toHaveBeenCalledWith({
- or__organization__id: 1,
- or__organization__isnull: 'True',
- order_by: 'name',
- page: 1,
- page_size: 5,
- });
- });
-
- test('should call api with organization id from the related project', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- projectId={12}
- globallyAvailable
- />
-
- );
- });
- expect(ProjectsAPI.readDetail).toHaveBeenCalledWith(12);
- expect(ExecutionEnvironmentsAPI.read).toHaveBeenCalledWith({
- or__organization__id: 39,
- or__organization__isnull: 'True',
- order_by: 'name',
- page: 1,
- page_size: 5,
- });
- });
-
- test('should render prompt on launch checkbox when necessary', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- projectId={12}
- globallyAvailable
- isPromptableField
- promptId="ee-prompt"
- promptName="ask_execution_environment_on_launch"
- />
-
- );
- });
- expect(wrapper.find('Checkbox[aria-label="Prompt on launch"]').length).toBe(
- 1
- );
- });
-});
diff --git a/awx/ui/src/components/Lookup/HostFilterLookup.js b/awx/ui/src/components/Lookup/HostFilterLookup.js
deleted file mode 100644
index 46e0a3dd1f3e..000000000000
--- a/awx/ui/src/components/Lookup/HostFilterLookup.js
+++ /dev/null
@@ -1,467 +0,0 @@
-import React, { useState, useEffect, useCallback } from 'react';
-import { useHistory, useLocation } from 'react-router-dom';
-import { number, func, bool, string } from 'prop-types';
-
-import styled from 'styled-components';
-import { t } from '@lingui/macro';
-import { SearchIcon } from '@patternfly/react-icons';
-import {
- Alert as PFAlert,
- Button,
- ButtonVariant,
- Chip,
- FormGroup,
- InputGroup,
- Modal,
- Tooltip,
-} from '@patternfly/react-core';
-import { HostsAPI } from 'api';
-import { getQSConfig, mergeParams, parseQueryString } from 'util/qs';
-import getDocsBaseUrl from 'util/getDocsBaseUrl';
-import { useConfig } from 'contexts/Config';
-import useRequest, { useDismissableError } from 'hooks/useRequest';
-import ChipGroup from '../ChipGroup';
-import Popover from '../Popover';
-import DataListToolbar from '../DataListToolbar';
-import LookupErrorMessage from './shared/LookupErrorMessage';
-import PaginatedTable, {
- HeaderCell,
- HeaderRow,
- getSearchableKeys,
-} from '../PaginatedTable';
-import HostListItem from './HostListItem';
-import {
- removeDefaultParams,
- removeNamespacedKeys,
- toHostFilter,
- toQueryString,
- toSearchParams,
- modifyHostFilter,
-} from './shared/HostFilterUtils';
-
-const Alert = styled(PFAlert)`
- && {
- margin-bottom: 8px;
- }
-`;
-
-const ChipHolder = styled.div`
- && {
- --pf-c-form-control--Height: auto;
- }
- .pf-c-chip-group {
- margin-right: 8px;
- }
-`;
-
-const ModalList = styled.div`
- .pf-c-toolbar__content {
- padding: 0 !important;
- }
-`;
-
-const useModal = () => {
- const [isModalOpen, setIsModalOpen] = useState(false);
-
- function toggleModal() {
- setIsModalOpen(!isModalOpen);
- }
-
- function closeModal() {
- setIsModalOpen(false);
- }
-
- return {
- isModalOpen,
- toggleModal,
- closeModal,
- };
-};
-
-const QS_CONFIG = getQSConfig(
- 'smart_hosts',
- {
- page: 1,
- page_size: 5,
- order_by: 'name',
- },
- ['id', 'page', 'page_size', 'inventory']
-);
-
-const buildSearchColumns = () => [
- {
- name: t`Name`,
- key: 'name__icontains',
- isDefault: true,
- },
- {
- name: t`ID`,
- key: 'id',
- },
- {
- name: t`Group`,
- key: 'groups__name__icontains',
- },
- {
- name: t`Inventory ID`,
- key: 'inventory',
- },
- {
- name: t`Enabled`,
- key: 'enabled',
- isBoolean: true,
- },
- {
- name: t`Instance ID`,
- key: 'instance_id',
- },
- {
- name: t`Last job`,
- key: 'last_job',
- },
- {
- name: t`Insights system ID`,
- key: 'insights_system_id',
- },
-];
-
-function HostFilterLookup({
- helperTextInvalid,
- isValid,
- isDisabled,
- onBlur,
- onChange,
- organizationId,
- value,
- enableNegativeFiltering,
- enableRelatedFuzzyFiltering,
-}) {
- const history = useHistory();
- const location = useLocation();
- const [chips, setChips] = useState({});
- const [queryString, setQueryString] = useState('');
- const { isModalOpen, toggleModal, closeModal } = useModal();
- const [isAnsibleFactsSelected, setIsAnsibleFactsSelected] = useState(false);
-
- const searchColumns = buildSearchColumns();
- const config = useConfig();
-
- const parseRelatedSearchFields = (searchFields) => {
- if (searchFields.indexOf('__search') !== -1) {
- return searchFields.slice(0, -8);
- }
- return searchFields;
- };
-
- const {
- result: { count, hosts, relatedSearchableKeys, searchableKeys },
- error: contentError,
- request: fetchHosts,
- isLoading,
- } = useRequest(
- useCallback(
- async (orgId) => {
- const params = parseQueryString(QS_CONFIG, location.search);
- const [{ data }, { data: actions }] = await Promise.all([
- HostsAPI.read(
- mergeParams(params, { inventory__organization: orgId })
- ),
- HostsAPI.readOptions(),
- ]);
- return {
- count: data.count,
- hosts: data.results,
- relatedSearchableKeys: (actions?.related_search_fields || []).map(
- parseRelatedSearchFields
- ),
- searchableKeys: getSearchableKeys(actions?.actions.GET),
- };
- },
- [location.search]
- ),
- {
- count: 0,
- hosts: [],
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- const { error, dismissError } = useDismissableError(contentError);
-
- useEffect(() => {
- if (isModalOpen && organizationId) {
- dismissError();
- fetchHosts(organizationId);
- }
- }, [fetchHosts, organizationId, isModalOpen]); // eslint-disable-line react-hooks/exhaustive-deps
-
- useEffect(() => {
- const filters = toSearchParams(value);
- let modifiedFilters = modifyHostFilter(value, filters);
- setQueryString(toQueryString(QS_CONFIG, modifiedFilters));
- modifiedFilters = removeHostFilter(modifiedFilters);
- setChips(buildChips(modifiedFilters));
- }, [value]);
-
- function qsToHostFilter(qs) {
- const searchParams = toSearchParams(qs);
- const withoutNamespace = removeNamespacedKeys(QS_CONFIG, searchParams);
- const withoutDefaultParams = removeDefaultParams(
- QS_CONFIG,
- withoutNamespace
- );
- return toHostFilter(withoutDefaultParams);
- }
-
- const save = () => {
- const hostFilterString = qsToHostFilter(location.search);
- onChange(hostFilterString);
- closeModal();
- history.replace({
- pathname: `${location.pathname}`,
- search: '',
- });
- };
-
- const removeHostFilter = (filter) => {
- if ('host_filter' in filter) {
- filter.ansible_facts = filter.host_filter.substring(
- 'ansible_facts__'.length
- );
- delete filter.host_filter;
- }
-
- return filter;
- };
-
- function buildChips(filter = {}) {
- const inputGroupChips = Object.keys(filter).reduce((obj, param) => {
- const parsedKey = param.replace('or__', '');
- const chipsArray = [];
-
- if (Array.isArray(filter[param])) {
- filter[param].forEach((val) =>
- chipsArray.push({
- key: `${param}:${val}`,
- node: `${val}`,
- })
- );
- } else {
- chipsArray.push({
- key: `${param}:${filter[param]}`,
- node: `${filter[param]}`,
- });
- }
-
- obj[parsedKey] = {
- key: parsedKey,
- label: filter[param],
- chips: [...chipsArray],
- };
-
- return obj;
- }, {});
-
- return inputGroupChips;
- }
-
- const handleOpenModal = () => {
- history.replace({
- pathname: `${location.pathname}`,
- search: queryString,
- });
- fetchHosts(organizationId);
- toggleModal();
- };
-
- const handleClose = () => {
- closeModal();
- history.replace({
- pathname: `${location.pathname}`,
- search: '',
- });
- };
-
- const renderLookup = () => (
-
-
-
- {searchColumns.map(({ name, key }) => (
-
- {chips[key]?.chips?.map((chip) => (
-
- {chip.node}
-
- ))}
-
- ))}
- {/* Parse advanced search chips */}
- {Object.keys(chips).length > 0 &&
- Object.keys(chips)
- .filter((val) => chips[val].chips.length > 0)
- .filter(
- (val) => searchColumns.map((val2) => val2.key).indexOf(val) === -1
- )
- .map((leftoverKey) => (
-
- {chips[leftoverKey]?.chips?.map((chip) => (
-
- {chip.node}
-
- ))}
-
- ))}
-
-
- );
-
- return (
-
- }
- >
- {isDisabled ? (
-
- {renderLookup()}
-
- ) : (
- renderLookup()
- )}
-
- {t`Select`}
- ,
- ,
- ]}
- >
-
- {isAnsibleFactsSelected && (
-
- {t`Searching by ansible_facts requires special syntax. Refer to the`}{' '}
-
- {t`documentation`}
- {' '}
- {t`for more info.`}
- >
- }
- />
- )}
-
- {t`Name`}
- {t`Description`}
- {t`Inventory`}
-
- }
- renderRow={(item) => }
- renderToolbar={(props) => (
-
- )}
- toolbarSearchColumns={searchColumns}
- toolbarSearchableKeys={searchableKeys}
- toolbarRelatedSearchableKeys={relatedSearchableKeys}
- />
-
-
-
-
- );
-}
-
-HostFilterLookup.propTypes = {
- isValid: bool,
- onBlur: func,
- onChange: func,
- organizationId: number,
- value: string,
- enableNegativeFiltering: bool,
- enableRelatedFuzzyFiltering: bool,
-};
-HostFilterLookup.defaultProps = {
- isValid: true,
- onBlur: () => {},
- onChange: () => {},
- organizationId: null,
- value: '',
- enableNegativeFiltering: true,
- enableRelatedFuzzyFiltering: true,
-};
-
-export default HostFilterLookup;
diff --git a/awx/ui/src/components/Lookup/HostListItem.js b/awx/ui/src/components/Lookup/HostListItem.js
deleted file mode 100644
index 11d9fe641fa9..000000000000
--- a/awx/ui/src/components/Lookup/HostListItem.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import React from 'react';
-import { t } from '@lingui/macro';
-import { Td, Tr } from '@patternfly/react-table';
-
-function HostListItem({ item }) {
- return (
-
- | {item.name} |
- {item.description} |
- {item.summary_fields.inventory.name} |
-
- );
-}
-
-export default HostListItem;
diff --git a/awx/ui/src/components/Lookup/HostListItem.test.js b/awx/ui/src/components/Lookup/HostListItem.test.js
deleted file mode 100644
index 56768f33e507..000000000000
--- a/awx/ui/src/components/Lookup/HostListItem.test.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import HostListItem from './HostListItem';
-
-describe('HostListItem', () => {
- let wrapper;
- const mockInventory = {
- id: 1,
- type: 'inventory',
- name: 'Foo',
- description: 'Buzz',
- summary_fields: {
- inventory: {
- name: 'Bar',
- },
- },
- };
- test('initially renders successfully', () => {
- wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('HostListItem').length).toBe(1);
- expect(wrapper.find('Td').at(0).text()).toBe('Foo');
- expect(wrapper.find('Td').at(1).text()).toBe('Buzz');
- expect(wrapper.find('Td').at(2).text()).toBe('Bar');
- });
-});
diff --git a/awx/ui/src/components/Lookup/InstanceGroupsLookup.js b/awx/ui/src/components/Lookup/InstanceGroupsLookup.js
deleted file mode 100644
index 848d54bcfd92..000000000000
--- a/awx/ui/src/components/Lookup/InstanceGroupsLookup.js
+++ /dev/null
@@ -1,180 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { arrayOf, string, func, bool } from 'prop-types';
-import { withRouter } from 'react-router-dom';
-import { t, Trans } from '@lingui/macro';
-import { FormGroup } from '@patternfly/react-core';
-import { InstanceGroupsAPI } from 'api';
-import { InstanceGroup } from 'types';
-import { getSearchableKeys } from 'components/PaginatedTable';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import useRequest from 'hooks/useRequest';
-import Popover from '../Popover';
-import OptionsList from '../OptionsList';
-import Lookup from './Lookup';
-import LookupErrorMessage from './shared/LookupErrorMessage';
-import FieldWithPrompt from '../FieldWithPrompt';
-
-const QS_CONFIG = getQSConfig('instance-groups', {
- page: 1,
- page_size: 5,
- order_by: 'name',
-});
-
-function InstanceGroupsLookup({
- id,
- value,
- onChange,
- tooltip,
- className,
- required,
- history,
- fieldName,
- validate,
- isPromptableField,
- promptId,
- promptName,
-}) {
- const {
- result: { instanceGroups, count, relatedSearchableKeys, searchableKeys },
- request: fetchInstanceGroups,
- error,
- isLoading,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, history.location.search);
- const [{ data }, actionsResponse] = await Promise.all([
- InstanceGroupsAPI.read(params),
- InstanceGroupsAPI.readOptions(),
- ]);
- return {
- instanceGroups: data.results,
- count: data.count,
- relatedSearchableKeys: (
- actionsResponse?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
- };
- }, [history.location]),
- {
- instanceGroups: [],
- count: 0,
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- useEffect(() => {
- fetchInstanceGroups();
- }, [fetchInstanceGroups]);
-
- const renderLookup = () => (
- <>
-
-
- Selected
-
-
-
- Note: The order in which these are selected sets the execution
- precedence. Select more than one to enable drag.
-
- >
- }
- renderOptionsList={({ state, dispatch, canDelete }) => (
- dispatch({ type: 'SELECT_ITEM', item })}
- deselectItem={(item) => dispatch({ type: 'DESELECT_ITEM', item })}
- sortSelectedItems={(selectedItems) =>
- dispatch({ type: 'SET_SELECTED_ITEMS', selectedItems })
- }
- isSelectedDraggable
- />
- )}
- />
-
- >
- );
-
- return isPromptableField ? (
-
- {renderLookup()}
-
- ) : (
- }
- fieldId={id}
- >
- {renderLookup()}
-
- );
-}
-
-InstanceGroupsLookup.propTypes = {
- id: string,
- value: arrayOf(InstanceGroup).isRequired,
- tooltip: string,
- onChange: func.isRequired,
- className: string,
- required: bool,
- validate: func,
- fieldName: string,
-};
-
-InstanceGroupsLookup.defaultProps = {
- id: 'org-instance-groups',
- tooltip: '',
- className: '',
- required: false,
- validate: () => undefined,
- fieldName: 'instance_groups',
-};
-
-export default withRouter(InstanceGroupsLookup);
diff --git a/awx/ui/src/components/Lookup/InstanceGroupsLookup.test.js b/awx/ui/src/components/Lookup/InstanceGroupsLookup.test.js
deleted file mode 100644
index b6acdb4ed91a..000000000000
--- a/awx/ui/src/components/Lookup/InstanceGroupsLookup.test.js
+++ /dev/null
@@ -1,111 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { InstanceGroupsAPI } from 'api';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import InstanceGroupsLookup from './InstanceGroupsLookup';
-
-jest.mock('../../api');
-
-const mockedInstanceGroups = {
- count: 1,
- results: [
- {
- id: 2,
- name: 'Foo',
- image: 'quay.io/ansible/awx-ee',
- pull: 'missing',
- },
- ],
-};
-
-const instanceGroups = [
- {
- id: 1,
- type: 'instance_group',
- url: '/api/v2/instance_groups/1/',
- related: {
- jobs: '/api/v2/instance_groups/1/jobs/',
- instances: '/api/v2/instance_groups/1/instances/',
- },
- name: 'controlplane',
- created: '2022-09-13T15:44:54.870579Z',
- modified: '2022-09-13T15:44:54.886047Z',
- capacity: 59,
- consumed_capacity: 0,
- percent_capacity_remaining: 100.0,
- jobs_running: 0,
- jobs_total: 40,
- instances: 1,
- is_container_group: false,
- credential: null,
- policy_instance_percentage: 100,
- policy_instance_minimum: 0,
- policy_instance_list: [],
- pod_spec_override: '',
- summary_fields: {
- user_capabilities: {
- edit: true,
- delete: false,
- },
- },
- },
-];
-
-describe('InstanceGroupsLookup', () => {
- let wrapper;
-
- beforeEach(() => {
- InstanceGroupsAPI.read.mockResolvedValue({
- data: mockedInstanceGroups,
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should render successfully', async () => {
- InstanceGroupsAPI.readOptions.mockReturnValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- related_search_fields: [],
- },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}} />
-
- );
- });
- wrapper.update();
- expect(InstanceGroupsAPI.read).toHaveBeenCalledTimes(1);
- expect(wrapper.find('InstanceGroupsLookup')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Instance Groups"]').length).toBe(1);
- expect(wrapper.find('Checkbox[aria-label="Prompt on launch"]').length).toBe(
- 0
- );
- });
- test('should render prompt on launch checkbox when necessary', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- isPromptableField
- promptId="ig-prompt"
- promptName="ask_instance_groups_on_launch"
- />
-
- );
- });
- expect(wrapper.find('Checkbox[aria-label="Prompt on launch"]').length).toBe(
- 1
- );
- });
-});
diff --git a/awx/ui/src/components/Lookup/InventoryLookup.js b/awx/ui/src/components/Lookup/InventoryLookup.js
deleted file mode 100644
index e37805451df6..000000000000
--- a/awx/ui/src/components/Lookup/InventoryLookup.js
+++ /dev/null
@@ -1,261 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { func, bool, string } from 'prop-types';
-import { withRouter } from 'react-router-dom';
-import { t } from '@lingui/macro';
-import { InventoriesAPI } from 'api';
-import { Inventory } from 'types';
-import useRequest from 'hooks/useRequest';
-import useAutoPopulateLookup from 'hooks/useAutoPopulateLookup';
-import { getQSConfig, parseQueryString, mergeParams } from 'util/qs';
-import Lookup from './Lookup';
-import OptionsList from '../OptionsList';
-import LookupErrorMessage from './shared/LookupErrorMessage';
-import FieldWithPrompt from '../FieldWithPrompt';
-
-const QS_CONFIG = getQSConfig('inventory', {
- page: 1,
- page_size: 5,
- order_by: 'name',
- role_level: 'use_role',
-});
-
-function InventoryLookup({
- autoPopulate,
- fieldId,
- fieldName,
- hideSmartInventories,
- history,
- isDisabled,
- isPromptableField,
- onBlur,
- onChange,
- promptId,
- promptName,
- required,
- validate,
- value,
-}) {
- const autoPopulateLookup = useAutoPopulateLookup(onChange);
-
- const {
- result: { inventories, count, relatedSearchableKeys, searchableKeys },
- request: fetchInventories,
- error,
- isLoading,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, history.location.search);
- const inventoryKindParams = hideSmartInventories
- ? { not__kind: 'smart' }
- : {};
- const [{ data }, actionsResponse] = await Promise.all([
- InventoriesAPI.read(
- mergeParams(params, {
- ...inventoryKindParams,
- })
- ),
- InventoriesAPI.readOptions(),
- ]);
-
- if (autoPopulate) {
- autoPopulateLookup(data.results);
- }
-
- return {
- inventories: data.results,
- count: data.count,
- relatedSearchableKeys: (
- actionsResponse?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: Object.keys(actionsResponse.data.actions?.GET || {})
- .filter((key) => {
- if (['kind', 'host_filter'].includes(key) && hideSmartInventories) {
- return false;
- }
- return actionsResponse.data.actions?.GET[key].filterable;
- })
- .map((key) => ({
- key,
- type: actionsResponse.data.actions?.GET[key].type,
- })),
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [autoPopulate, autoPopulateLookup, history.location]),
- {
- inventories: [],
- count: 0,
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- const checkInventoryName = useCallback(
- async (name) => {
- if (!name) {
- onChange(null);
- return;
- }
-
- try {
- const {
- data: { results: nameMatchResults, count: nameMatchCount },
- } = await InventoriesAPI.read({ name });
- onChange(nameMatchCount ? nameMatchResults[0] : null);
- } catch {
- onChange(null);
- }
- },
- [onChange]
- );
-
- useEffect(() => {
- fetchInventories();
- }, [fetchInventories]);
-
- return isPromptableField ? (
-
- (
- dispatch({ type: 'SELECT_ITEM', item })}
- deselectItem={(item) => dispatch({ type: 'DESELECT_ITEM', item })}
- />
- )}
- />
-
-
- ) : (
- <>
- (
- dispatch({ type: 'SELECT_ITEM', item })}
- deselectItem={(item) => dispatch({ type: 'DESELECT_ITEM', item })}
- />
- )}
- />
-
- >
- );
-}
-
-InventoryLookup.propTypes = {
- autoPopulate: bool,
- fieldId: string,
- fieldName: string,
- hideSmartInventories: bool,
- isDisabled: bool,
- onChange: func.isRequired,
- required: bool,
- validate: func,
- value: Inventory,
-};
-
-InventoryLookup.defaultProps = {
- autoPopulate: false,
- fieldId: 'inventory',
- fieldName: 'inventory',
- hideSmartInventories: false,
- isDisabled: false,
- required: false,
- validate: () => {},
- value: null,
-};
-
-export default withRouter(InventoryLookup);
diff --git a/awx/ui/src/components/Lookup/InventoryLookup.test.js b/awx/ui/src/components/Lookup/InventoryLookup.test.js
deleted file mode 100644
index 120b4927e92d..000000000000
--- a/awx/ui/src/components/Lookup/InventoryLookup.test.js
+++ /dev/null
@@ -1,133 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { InventoriesAPI } from 'api';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import InventoryLookup from './InventoryLookup';
-
-jest.mock('../../api');
-
-const mockedInventories = {
- data: {
- count: 2,
- results: [
- { id: 2, name: 'Bar' },
- { id: 3, name: 'Baz' },
- ],
- },
-};
-
-describe('InventoryLookup', () => {
- let wrapper;
-
- beforeEach(() => {
- InventoriesAPI.read.mockResolvedValue(mockedInventories);
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should render successfully and fetch data', async () => {
- InventoriesAPI.readOptions.mockReturnValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- related_search_fields: [],
- },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}} />
-
- );
- });
- wrapper.update();
- expect(InventoriesAPI.read).toHaveBeenCalledTimes(1);
- expect(InventoriesAPI.read).toHaveBeenCalledWith({
- order_by: 'name',
- page: 1,
- page_size: 5,
- role_level: 'use_role',
- });
- expect(wrapper.find('InventoryLookup')).toHaveLength(1);
- expect(wrapper.find('Lookup').prop('isDisabled')).toBe(false);
- });
-
- test('should fetch only regular inventories when hideSmartInventories is true', async () => {
- InventoriesAPI.readOptions.mockReturnValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- related_search_fields: [],
- },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}} hideSmartInventories />
-
- );
- });
- wrapper.update();
- expect(InventoriesAPI.read).toHaveBeenCalledTimes(1);
- expect(InventoriesAPI.read).toHaveBeenCalledWith({
- not__kind: 'smart',
- order_by: 'name',
- page: 1,
- page_size: 5,
- role_level: 'use_role',
- });
- expect(wrapper.find('InventoryLookup')).toHaveLength(1);
- expect(wrapper.find('Lookup').prop('isDisabled')).toBe(false);
- });
-
- test('inventory lookup should be enabled', async () => {
- InventoriesAPI.readOptions.mockReturnValue({
- data: {
- actions: {
- GET: {},
- },
- related_search_fields: [],
- },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}} />
-
- );
- });
- wrapper.update();
- expect(InventoriesAPI.read).toHaveBeenCalledTimes(1);
- expect(wrapper.find('InventoryLookup')).toHaveLength(1);
- expect(wrapper.find('Lookup').prop('isDisabled')).toBe(false);
- });
-
- test('inventory lookup should be disabled', async () => {
- InventoriesAPI.readOptions.mockReturnValue({
- data: {
- actions: {
- GET: {},
- },
- related_search_fields: [],
- },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}} />
-
- );
- });
- wrapper.update();
- expect(InventoriesAPI.read).toHaveBeenCalledTimes(1);
- expect(wrapper.find('InventoryLookup')).toHaveLength(1);
- expect(wrapper.find('Lookup').prop('isDisabled')).toBe(true);
- });
-});
diff --git a/awx/ui/src/components/Lookup/Lookup.js b/awx/ui/src/components/Lookup/Lookup.js
deleted file mode 100644
index fdcb98cb5560..000000000000
--- a/awx/ui/src/components/Lookup/Lookup.js
+++ /dev/null
@@ -1,270 +0,0 @@
-import React, { useReducer, useEffect, useState } from 'react';
-import {
- string,
- bool,
- arrayOf,
- func,
- number,
- oneOfType,
- shape,
- node,
- object,
-} from 'prop-types';
-import { withRouter } from 'react-router-dom';
-import { useField } from 'formik';
-import { SearchIcon } from '@patternfly/react-icons';
-import {
- Button,
- ButtonVariant,
- Chip,
- InputGroup,
- Modal,
- TextInput,
-} from '@patternfly/react-core';
-import { t } from '@lingui/macro';
-import styled from 'styled-components';
-import useDebounce from 'hooks/useDebounce';
-import { QSConfig } from 'types';
-import ChipGroup from '../ChipGroup';
-import reducer, { initReducer } from './shared/reducer';
-
-const ChipHolder = styled.div`
- --pf-c-form-control--Height: auto;
- background-color: ${(props) =>
- props.isDisabled ? 'var(--pf-global--disabled-color--300)' : null};
-`;
-function Lookup(props) {
- const {
- id,
- header,
- onChange,
- onBlur,
- isLoading,
- value,
- multiple,
- required,
- qsConfig,
- renderItemChip,
- renderOptionsList,
- history,
- isDisabled,
- onDebounce,
- fieldName,
- validate,
- modalDescription,
- onUpdate,
- } = props;
- const [typedText, setTypedText] = useState('');
- const debounceRequest = useDebounce(onDebounce, 1000);
- useField({
- name: fieldName,
- validate: (val) => {
- if (!multiple && !val && typedText && typedText !== '') {
- return t`That value was not found. Please enter or select a valid value.`;
- }
- return validate(val);
- },
- });
-
- const [state, dispatch] = useReducer(
- reducer,
- { value, multiple, required },
- initReducer
- );
-
- useEffect(() => {
- dispatch({ type: 'SET_MULTIPLE', value: multiple });
- }, [multiple]);
-
- useEffect(() => {
- dispatch({ type: 'SET_VALUE', value });
- if (value?.name) {
- setTypedText(value.name);
- } else {
- setTypedText('');
- }
- }, [value, multiple]);
-
- useEffect(() => {
- if (!multiple) {
- setTypedText(state.selectedItems[0] ? state.selectedItems[0].name : '');
- }
- }, [state.selectedItems, multiple]);
-
- const clearQSParams = () => {
- if (!history.location.search) {
- // This prevents "Warning: Hash history cannot PUSH the same path;
- // a new entry will not be added to the history stack" from appearing in the console.
- return;
- }
- const parts = history.location.search.replace(/^\?/, '').split('&');
- const ns = qsConfig.namespace;
- const otherParts = parts.filter((param) => !param.startsWith(`${ns}.`));
- history.push(`${history.location.pathname}?${otherParts.join('&')}`);
- };
-
- const save = () => {
- const { selectedItems } = state;
- if (multiple) {
- onChange(selectedItems);
- } else {
- onChange(selectedItems[0] || null);
- }
- clearQSParams();
- dispatch({ type: 'CLOSE_MODAL' });
- };
-
- const removeItem = (item) => onChange(value.filter((i) => i.id !== item.id));
-
- const closeModal = () => {
- clearQSParams();
- dispatch({ type: 'CLOSE_MODAL' });
- };
-
- const onClick = () => {
- onUpdate();
- dispatch({ type: 'TOGGLE_MODAL' });
- };
-
- const { isModalOpen, selectedItems } = state;
- const canDelete =
- (!required || (multiple && value.length > 1)) && !isDisabled;
- let items = [];
- if (multiple) {
- items = value;
- } else if (value) {
- items.push(value);
- }
-
- return (
- <>
-
-
- {multiple ? (
-
-
- {items.map((item) =>
- renderItemChip({
- item,
- removeItem,
- canDelete,
- })
- )}
-
-
- ) : (
- {
- setTypedText(inputValue);
- if (value?.name !== inputValue) {
- debounceRequest(inputValue);
- }
- }}
- isDisabled={isLoading || isDisabled}
- />
- )}
-
-
- 0 && modalDescription}
- ouiaId={`${id}-modal`}
- actions={[
- ,
- ,
- ]}
- >
- {renderOptionsList({
- state,
- dispatch,
- canDelete,
- })}
-
- >
- );
-}
-
-const Item = shape({
- id: number.isRequired,
-});
-
-Lookup.propTypes = {
- id: string,
- header: string,
- modalDescription: oneOfType([string, node]),
- onChange: func.isRequired,
- onUpdate: func,
- value: oneOfType([Item, arrayOf(Item), object]),
- multiple: bool,
- required: bool,
- onBlur: func,
- qsConfig: QSConfig.isRequired,
- renderItemChip: func,
- renderOptionsList: func.isRequired,
- fieldName: string.isRequired,
- validate: func,
- onDebounce: func,
- isDisabled: bool,
-};
-
-Lookup.defaultProps = {
- id: 'lookup-search',
- header: null,
- value: null,
- multiple: false,
- required: false,
- modalDescription: '',
- onBlur: () => {},
- renderItemChip: ({ item, removeItem, canDelete }) => (
- removeItem(item)}
- isReadOnly={!canDelete}
- >
- {item.name}
-
- ),
- validate: () => undefined,
- onDebounce: () => undefined,
- onUpdate: () => {},
- isDisabled: false,
-};
-
-export { Lookup as _Lookup };
-export default withRouter(Lookup);
diff --git a/awx/ui/src/components/Lookup/Lookup.test.js b/awx/ui/src/components/Lookup/Lookup.test.js
deleted file mode 100644
index ebd27cf0e6aa..000000000000
--- a/awx/ui/src/components/Lookup/Lookup.test.js
+++ /dev/null
@@ -1,198 +0,0 @@
-/* eslint-disable react/jsx-pascal-case */
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { getQSConfig } from 'util/qs';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../testUtils/enzymeHelpers';
-import Lookup from './Lookup';
-
-/**
- * Check that an element is present on the document body
- * @param {selector} query selector
- */
-function checkRootElementPresent(selector) {
- const queryResult = global.document.querySelector(selector);
- expect(queryResult).not.toEqual(null);
-}
-
-/**
- * Check that an element isn't present on the document body
- * @param {selector} query selector
- */
-function checkRootElementNotPresent(selector) {
- const queryResult = global.document.querySelector(selector);
- expect(queryResult).toEqual(null);
-}
-
-/**
- * Check lookup input group tags for expected values
- * @param {wrapper} enzyme wrapper instance
- * @param {expected} array of expected tag values
- */
-async function checkInputTagValues(wrapper, expected) {
- checkRootElementNotPresent('body div[role="dialog"]');
- // check input group chip values
- const chips = await waitForElement(
- wrapper,
- 'Lookup InputGroup Chip span',
- (el) => el.length === expected.length
- );
- expect(chips).toHaveLength(expected.length);
- chips.forEach((el, index) => {
- expect(el.text()).toEqual(expected[index]);
- });
-}
-
-const QS_CONFIG = getQSConfig('test', {});
-const TestList = () => ;
-
-describe('', () => {
- let wrapper;
- let onChange;
-
- async function mountWrapper() {
- const mockSelected = [{ name: 'foo', id: 1, url: '/api/v2/item/1' }];
- await act(async () => {
- wrapper = mountWithContexts(
-
- (
-
- )}
- fieldName="foo"
- />
-
- );
- });
- return wrapper;
- }
-
- beforeEach(() => {
- onChange = jest.fn();
- document.body.innerHTML = '';
- });
-
- afterEach(() => {
- jest.restoreAllMocks();
- });
-
- test('should render successfully', async () => {
- wrapper = await mountWrapper();
- expect(wrapper.find('Lookup')).toHaveLength(1);
- });
-
- test('should show selected items', async () => {
- wrapper = await mountWrapper();
- expect(wrapper.find('Lookup')).toHaveLength(1);
- await checkInputTagValues(wrapper, ['foo']);
- });
-
- test('should open and close modal', async () => {
- wrapper = await mountWrapper();
- checkRootElementNotPresent('body div[role="dialog"]');
- wrapper.find('button[aria-label="Search"]').simulate('click');
- checkRootElementPresent('body div[role="dialog"]');
- const list = wrapper.find('TestList');
- expect(list).toHaveLength(1);
- expect(list.prop('state')).toEqual({
- selectedItems: [{ id: 1, name: 'foo', url: '/api/v2/item/1' }],
- value: [{ id: 1, name: 'foo', url: '/api/v2/item/1' }],
- multiple: true,
- isModalOpen: true,
- required: false,
- });
- expect(list.prop('dispatch')).toBeTruthy();
- expect(list.prop('canDelete')).toEqual(true);
- wrapper
- .find('Modal button')
- .findWhere((e) => e.text() === 'Cancel')
- .first()
- .simulate('click');
- checkRootElementNotPresent('body div[role="dialog"]');
- });
-
- test('should remove item when X button clicked', async () => {
- wrapper = await mountWrapper();
- await checkInputTagValues(wrapper, ['foo']);
- wrapper
- .find('Lookup InputGroup Chip')
- .findWhere((el) => el.text() === 'foo')
- .first()
- .invoke('onClick')();
- expect(onChange).toHaveBeenCalledTimes(1);
- expect(onChange).toHaveBeenCalledWith([]);
- });
-
- test('should pass canDelete false if required single select', async () => {
- await act(async () => {
- const mockSelected = { name: 'foo', id: 1, url: '/api/v2/item/1' };
- wrapper = mountWithContexts(
-
- (
-
- )}
- fieldName="foo"
- />
-
- );
- });
- wrapper.find('button[aria-label="Search"]').simulate('click');
- const list = wrapper.find('TestList');
- expect(list.prop('canDelete')).toEqual(false);
- });
-
- test('should be disabled while isLoading is true', async () => {
- const mockSelected = [{ name: 'foo', id: 1, url: '/api/v2/item/1' }];
- wrapper = mountWithContexts(
-
- (
-
- )}
- fieldName="foo"
- />
-
- );
- checkRootElementNotPresent('body div[role="dialog"]');
- const button = wrapper.find('button[aria-label="Search"]');
- expect(button.prop('disabled')).toEqual(true);
- });
-});
diff --git a/awx/ui/src/components/Lookup/MultiCredentialsLookup.js b/awx/ui/src/components/Lookup/MultiCredentialsLookup.js
deleted file mode 100644
index ef0f3ef745c5..000000000000
--- a/awx/ui/src/components/Lookup/MultiCredentialsLookup.js
+++ /dev/null
@@ -1,267 +0,0 @@
-import 'styled-components/macro';
-import React, { useState, useCallback, useEffect } from 'react';
-import { withRouter } from 'react-router-dom';
-import PropTypes from 'prop-types';
-import { t } from '@lingui/macro';
-import { ToolbarItem, Alert } from '@patternfly/react-core';
-import { CredentialsAPI, CredentialTypesAPI } from 'api';
-import { getSearchableKeys } from 'components/PaginatedTable';
-import useRequest from 'hooks/useRequest';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import useIsMounted from 'hooks/useIsMounted';
-import AnsibleSelect from '../AnsibleSelect';
-import CredentialChip from '../CredentialChip';
-import OptionsList from '../OptionsList';
-import Lookup from './Lookup';
-
-const QS_CONFIG = getQSConfig('credentials', {
- page: 1,
- page_size: 5,
- order_by: 'name',
-});
-
-async function loadCredentials(params, selectedCredentialTypeId) {
- params.credential_type = selectedCredentialTypeId || 1;
- const { data } = await CredentialsAPI.read(params);
- return data;
-}
-
-function MultiCredentialsLookup({
- value,
- onChange,
- onError,
- history,
- fieldName,
- validate,
-}) {
- const [selectedType, setSelectedType] = useState(null);
- const isMounted = useIsMounted();
-
- const {
- result: credentialTypes,
- request: fetchTypes,
- error: typesError,
- isLoading: isTypesLoading,
- } = useRequest(
- useCallback(async () => {
- const types = await CredentialTypesAPI.loadAllTypes();
- const match = types.find((type) => type.kind === 'ssh') || types[0];
- if (isMounted.current) {
- setSelectedType(match);
- }
- return types;
- /* eslint-disable-next-line react-hooks/exhaustive-deps */
- }, []),
- []
- );
-
- useEffect(() => {
- fetchTypes();
- }, [fetchTypes]);
-
- const {
- result: {
- credentials,
- credentialsCount,
- relatedSearchableKeys,
- searchableKeys,
- },
- request: fetchCredentials,
- error: credentialsError,
- isLoading: isCredentialsLoading,
- } = useRequest(
- useCallback(async () => {
- if (!selectedType) {
- return {
- credentials: [],
- credentialsCount: 0,
- relatedSearchableKeys: [],
- searchableKeys: [],
- };
- }
-
- const params = parseQueryString(QS_CONFIG, history.location.search);
- const [{ results, count }, actionsResponse] = await Promise.all([
- loadCredentials(params, selectedType.id),
- CredentialsAPI.readOptions(),
- ]);
-
- results.map((result) => {
- if (result.kind === 'vault' && result.inputs?.vault_id) {
- result.label = `${result.name} | ${result.inputs.vault_id}`;
- return result;
- }
- result.label = `${result.name}`;
- return result;
- });
-
- return {
- credentials: results,
- credentialsCount: count,
- relatedSearchableKeys: (
- actionsResponse?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
- };
- }, [selectedType, history.location]),
- {
- credentials: [],
- credentialsCount: 0,
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- useEffect(() => {
- fetchCredentials();
- }, [fetchCredentials]);
-
- useEffect(() => {
- if (typesError || credentialsError) {
- onError(typesError || credentialsError);
- }
- }, [typesError, credentialsError, onError]);
-
- const renderChip = ({ item, removeItem, canDelete }) => (
- removeItem(item)}
- isReadOnly={!canDelete}
- credential={item}
- />
- );
- const isVault = selectedType?.kind === 'vault';
-
- return (
- (
- <>
- {isVault && (
-
- )}
- {credentialTypes && credentialTypes.length > 0 && (
-
-
- {t`Selected Category`}
-
- ({
- key: type.id,
- value: type.id,
- label: type.name,
- isDisabled: false,
- }))}
- value={selectedType && selectedType.id}
- onChange={(e, id) => {
- // Reset query params when the category of credentials is changed
- history.replace({
- search: '',
- });
- setSelectedType(
- credentialTypes.find((o) => o.id === parseInt(id, 10))
- );
- }}
- />
-
- )}
- {
- const hasSameVaultID = (val) =>
- val?.inputs?.vault_id !== undefined &&
- val?.inputs?.vault_id === item?.inputs?.vault_id;
- const hasSameCredentialType = (val) =>
- val.credential_type === item.credential_type;
- const selectedItems = state.selectedItems.filter((i) =>
- isVault ? !hasSameVaultID(i) : !hasSameCredentialType(i)
- );
- selectedItems.push(item);
- return dispatch({
- type: 'SET_SELECTED_ITEMS',
- selectedItems,
- });
- }}
- deselectItem={(item) => dispatch({ type: 'DESELECT_ITEM', item })}
- renderItemChip={renderChip}
- />
- >
- )}
- />
- );
-}
-
-MultiCredentialsLookup.propTypes = {
- value: PropTypes.arrayOf(
- PropTypes.shape({
- id: PropTypes.number,
- name: PropTypes.string,
- description: PropTypes.string,
- kind: PropTypes.string,
- clound: PropTypes.bool,
- })
- ),
- onChange: PropTypes.func.isRequired,
- onError: PropTypes.func.isRequired,
- validate: PropTypes.func,
- fieldName: PropTypes.string,
-};
-
-MultiCredentialsLookup.defaultProps = {
- value: [],
- validate: () => undefined,
- fieldName: 'credentials',
-};
-
-export { MultiCredentialsLookup as _MultiCredentialsLookup };
-export default withRouter(MultiCredentialsLookup);
diff --git a/awx/ui/src/components/Lookup/MultiCredentialsLookup.test.js b/awx/ui/src/components/Lookup/MultiCredentialsLookup.test.js
deleted file mode 100644
index 7efa248bf8e1..000000000000
--- a/awx/ui/src/components/Lookup/MultiCredentialsLookup.test.js
+++ /dev/null
@@ -1,594 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { CredentialsAPI, CredentialTypesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../testUtils/enzymeHelpers';
-import { createMemoryHistory } from 'history';
-import MultiCredentialsLookup from './MultiCredentialsLookup';
-
-jest.mock('../../api');
-
-describe('', () => {
- let wrapper;
-
- const credentials = [
- {
- id: 1,
- credential_type: 1,
- kind: 'gce',
- name: 'Foo',
- url: 'www.google.com',
- },
- {
- id: 2,
- credential_type: 2,
- kind: 'ssh',
- name: 'Alex',
- url: 'www.google.com',
- },
- {
- id: 21,
- credential_type: 3,
- kind: 'vault',
- inputs: { vault_id: '1' },
- name: 'Gatsby',
- },
- { id: 23, credential_type: 3, kind: 'vault', name: 'Gatsby 2' },
- { id: 8, credential_type: 4, kind: 'Machine', name: 'Gatsby' },
- ];
-
- beforeEach(() => {
- CredentialTypesAPI.loadAllTypes.mockResolvedValueOnce([
- {
- id: 400,
- kind: 'ssh',
- namespace: 'biz',
- name: 'Amazon Web Services',
- },
- { id: 500, kind: 'vault', namespace: 'buzz', name: 'Vault' },
- { id: 600, kind: 'machine', namespace: 'fuzz', name: 'Machine' },
- ]);
- CredentialsAPI.read.mockResolvedValueOnce({
- data: {
- results: [
- {
- id: 1,
- credential_type: 1,
- kind: 'gc2',
- name: 'Cred 1',
- url: 'www.google.com',
- },
- {
- id: 2,
- credential_type: 2,
- kind: 'ssh',
- name: 'Cred 2',
- url: 'www.google.com',
- },
- {
- id: 3,
- credential_type: 5,
- kind: 'Ansible',
- name: 'Cred 3',
- url: 'www.google.com',
- },
- {
- id: 4,
- credential_type: 4,
- kind: 'Machine',
- name: 'Cred 4',
- url: 'www.google.com',
- },
- {
- id: 5,
- credential_type: 4,
- kind: 'Machine',
- name: 'Cred 5',
- url: 'www.google.com',
- },
-
- {
- id: 6,
- credential_type: 5,
- kind: 'vault',
- name: 'Cred 6',
- url: 'www.google.com',
- inputs: { vault_id: 'vault ID' },
- },
- {
- id: 7,
- credential_type: 5,
- kind: 'vault',
- name: 'Cred 7',
- url: 'www.google.com',
- inputs: {},
- },
- ],
- count: 3,
- },
- });
- CredentialsAPI.readOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- related_search_fields: [],
- },
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should load credential types', async () => {
- const onChange = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- />
-
- );
- });
- wrapper.update();
- expect(wrapper.find('MultiCredentialsLookup')).toHaveLength(1);
- expect(CredentialTypesAPI.loadAllTypes).toHaveBeenCalled();
- });
-
- test('onChange is called when you click to remove a credential from input', async () => {
- const onChange = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- />
-
- );
- });
- const chip = wrapper.find('CredentialChip');
- expect(chip).toHaveLength(5);
- const button = chip.at(1).find('Chip Button');
- await act(async () => {
- button.invoke('onClick')();
- });
- expect(onChange).toBeCalledWith([
- {
- id: 1,
- credential_type: 1,
- kind: 'gce',
- name: 'Foo',
- url: 'www.google.com',
- },
- {
- id: 21,
- credential_type: 3,
- kind: 'vault',
- inputs: { vault_id: '1' },
- name: 'Gatsby',
- },
- { id: 23, credential_type: 3, kind: 'vault', name: 'Gatsby 2' },
- { id: 8, credential_type: 4, kind: 'Machine', name: 'Gatsby' },
- ]);
- });
-
- test('should change credential types', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- onError={() => {}}
- />
-
- );
- });
- const searchButton = await waitForElement(
- wrapper,
- 'Button[aria-label="Search"]'
- );
- await act(async () => {
- searchButton.invoke('onClick')();
- });
- expect(CredentialsAPI.read).toHaveBeenCalledTimes(2);
- const select = await waitForElement(wrapper, 'AnsibleSelect');
- CredentialsAPI.read.mockResolvedValueOnce({
- data: {
- results: [
- { id: 1, kind: 'cloud', name: 'New Cred', url: 'www.google.com' },
- ],
- count: 1,
- },
- });
- await act(async () => {
- select.invoke('onChange')({}, 500);
- });
- wrapper.update();
- expect(wrapper.find('OptionsList').prop('options')).toEqual([
- {
- id: 1,
- kind: 'cloud',
- name: 'New Cred',
- url: 'www.google.com',
- label: 'New Cred',
- },
- ]);
- });
-
- test('should reset query params (credentials.page) when selected credential type is changed', async () => {
- const history = createMemoryHistory({
- initialEntries: ['?credentials.page=2'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- onError={() => {}}
- />
- ,
- {
- context: { router: { history } },
- }
- );
- });
- const searchButton = await waitForElement(
- wrapper,
- 'Button[aria-label="Search"]'
- );
- await act(async () => {
- searchButton.invoke('onClick')();
- });
- expect(CredentialsAPI.read).toHaveBeenCalledWith({
- credential_type: 400,
- order_by: 'name',
- page: 2,
- page_size: 5,
- });
-
- const select = await waitForElement(wrapper, 'AnsibleSelect');
- await act(async () => {
- select.invoke('onChange')({}, 500);
- });
- wrapper.update();
-
- expect(CredentialsAPI.read).toHaveBeenCalledWith({
- credential_type: 500,
- order_by: 'name',
- page: 1,
- page_size: 5,
- });
- });
-
- test('should only add 1 credential per credential type except vault(see below)', async () => {
- const onChange = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- />
-
- );
- });
- const searchButton = await waitForElement(
- wrapper,
- 'Button[aria-label="Search"]'
- );
- await act(async () => {
- searchButton.invoke('onClick')();
- });
- wrapper.update();
- const optionsList = wrapper.find('OptionsList');
- expect(optionsList.prop('multiple')).toEqual(false);
- act(() => {
- optionsList.invoke('selectItem')({
- id: 5,
- credential_type: 4,
- kind: 'Machine',
- name: 'Cred 5',
- url: 'www.google.com',
- });
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('Button[variant="primary"]').invoke('onClick')();
- });
- expect(onChange).toBeCalledWith([
- {
- id: 1,
- credential_type: 1,
- kind: 'gce',
- name: 'Foo',
- url: 'www.google.com',
- },
- {
- id: 2,
- credential_type: 2,
- kind: 'ssh',
- name: 'Alex',
- url: 'www.google.com',
- },
- {
- id: 21,
- credential_type: 3,
- kind: 'vault',
- inputs: { vault_id: '1' },
- name: 'Gatsby',
- },
- { id: 23, credential_type: 3, kind: 'vault', name: 'Gatsby 2' },
- {
- id: 5,
- credential_type: 4,
- kind: 'Machine',
- name: 'Cred 5',
- url: 'www.google.com',
- },
- ]);
- });
-
- test('should properly render vault credential labels', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- onError={() => {}}
- />
-
- );
- });
- const searchButton = await waitForElement(
- wrapper,
- 'Button[aria-label="Search"]'
- );
- await act(async () => {
- searchButton.invoke('onClick')();
- });
- wrapper.update();
- const typeSelect = wrapper.find('AnsibleSelect');
- await act(async () => {
- typeSelect.invoke('onChange')({}, 500);
- });
- wrapper.update();
- const optionsList = wrapper.find('OptionsList');
- expect(optionsList.prop('multiple')).toEqual(true);
- expect(wrapper.find('CheckboxListItem[label="Cred 6 | vault ID"]'));
- expect(wrapper.find('CheckboxListItem[label="Cred 7"]'));
- });
-
- test('should allow multiple vault credentials with no vault id', async () => {
- const onChange = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- />
-
- );
- });
- const searchButton = await waitForElement(
- wrapper,
- 'Button[aria-label="Search"]'
- );
- await act(async () => {
- searchButton.invoke('onClick')();
- });
- wrapper.update();
- const typeSelect = wrapper.find('AnsibleSelect');
- act(() => {
- typeSelect.invoke('onChange')({}, 500);
- });
- wrapper.update();
- const optionsList = wrapper.find('OptionsList');
- expect(optionsList.prop('multiple')).toEqual(true);
- act(() => {
- optionsList.invoke('selectItem')({
- id: 11,
- credential_type: 3,
- kind: 'vault',
- name: 'Vault',
- });
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('Button[variant="primary"]').invoke('onClick')();
- });
- expect(onChange).toBeCalledWith([
- {
- id: 1,
- credential_type: 1,
- kind: 'gce',
- name: 'Foo',
- url: 'www.google.com',
- },
- {
- id: 2,
- credential_type: 2,
- kind: 'ssh',
- name: 'Alex',
- url: 'www.google.com',
- },
- {
- id: 21,
- credential_type: 3,
- kind: 'vault',
- inputs: { vault_id: '1' },
- name: 'Gatsby',
- },
- { id: 23, credential_type: 3, kind: 'vault', name: 'Gatsby 2' },
- { id: 8, credential_type: 4, kind: 'Machine', name: 'Gatsby' },
- { id: 11, credential_type: 3, kind: 'vault', name: 'Vault' },
- ]);
- });
-
- test('should allow multiple vault credentials with different vault ids', async () => {
- const onChange = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- />
-
- );
- });
- const searchButton = await waitForElement(
- wrapper,
- 'Button[aria-label="Search"]'
- );
- await act(async () => {
- searchButton.invoke('onClick')();
- });
- wrapper.update();
- const typeSelect = wrapper.find('AnsibleSelect');
- act(() => {
- typeSelect.invoke('onChange')({}, 500);
- });
- wrapper.update();
- const optionsList = wrapper.find('OptionsList');
- expect(optionsList.prop('multiple')).toEqual(true);
- act(() => {
- optionsList.invoke('selectItem')({
- id: 12,
- credential_type: 3,
- kind: 'vault',
- name: 'Other Vault',
- vault_id: '2',
- });
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('Button[variant="primary"]').invoke('onClick')();
- });
- expect(onChange).toBeCalledWith([
- {
- id: 1,
- credential_type: 1,
- kind: 'gce',
- name: 'Foo',
- url: 'www.google.com',
- },
- {
- id: 2,
- credential_type: 2,
- kind: 'ssh',
- name: 'Alex',
- url: 'www.google.com',
- },
- {
- id: 21,
- credential_type: 3,
- kind: 'vault',
- inputs: { vault_id: '1' },
- name: 'Gatsby',
- },
- { id: 23, credential_type: 3, kind: 'vault', name: 'Gatsby 2' },
- { id: 8, credential_type: 4, kind: 'Machine', name: 'Gatsby' },
- {
- id: 12,
- credential_type: 3,
- kind: 'vault',
- name: 'Other Vault',
- vault_id: '2',
- },
- ]);
- });
-
- test('should not select multiple vault credentials with same vault id', async () => {
- const onChange = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- />
-
- );
- });
- const searchButton = await waitForElement(
- wrapper,
- 'Button[aria-label="Search"]'
- );
- await act(async () => {
- searchButton.invoke('onClick')();
- });
- wrapper.update();
- const typeSelect = wrapper.find('AnsibleSelect');
- act(() => {
- typeSelect.invoke('onChange')({}, 500);
- });
- wrapper.update();
- const optionsList = wrapper.find('OptionsList');
- expect(optionsList.prop('multiple')).toEqual(true);
- act(() => {
- optionsList.invoke('selectItem')({
- id: 13,
- credential_type: 3,
- kind: 'vault',
- name: 'Vault Cred with Same Vault Id',
- inputs: { vault_id: '1' },
- });
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('Button[variant="primary"]').invoke('onClick')();
- });
- expect(onChange).toBeCalledWith([
- {
- id: 1,
- credential_type: 1,
- kind: 'gce',
- name: 'Foo',
- url: 'www.google.com',
- },
- {
- id: 2,
- credential_type: 2,
- kind: 'ssh',
- name: 'Alex',
- url: 'www.google.com',
- },
- { id: 23, credential_type: 3, kind: 'vault', name: 'Gatsby 2' },
- { id: 8, credential_type: 4, kind: 'Machine', name: 'Gatsby' },
- {
- id: 13,
- credential_type: 3,
- kind: 'vault',
- name: 'Vault Cred with Same Vault Id',
- inputs: { vault_id: '1' },
- },
- ]);
- });
-});
diff --git a/awx/ui/src/components/Lookup/OrganizationLookup.js b/awx/ui/src/components/Lookup/OrganizationLookup.js
deleted file mode 100644
index f3d7c1f1f4f6..000000000000
--- a/awx/ui/src/components/Lookup/OrganizationLookup.js
+++ /dev/null
@@ -1,189 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { node, func, bool, string } from 'prop-types';
-import { withRouter } from 'react-router-dom';
-import { t } from '@lingui/macro';
-import { FormGroup } from '@patternfly/react-core';
-import { OrganizationsAPI } from 'api';
-import { Organization } from 'types';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import { getSearchableKeys } from 'components/PaginatedTable';
-import useRequest from 'hooks/useRequest';
-import useAutoPopulateLookup from 'hooks/useAutoPopulateLookup';
-import OptionsList from '../OptionsList';
-import Lookup from './Lookup';
-import LookupErrorMessage from './shared/LookupErrorMessage';
-
-const QS_CONFIG = getQSConfig('organizations', {
- page: 1,
- page_size: 5,
- order_by: 'name',
-});
-
-function OrganizationLookup({
- id,
- helperTextInvalid,
- isValid,
- onBlur,
- onChange,
- required,
- value,
- history,
- autoPopulate,
- isDisabled,
- helperText,
- validate,
- fieldName,
-}) {
- const autoPopulateLookup = useAutoPopulateLookup(onChange);
-
- const {
- result: { itemCount, organizations, relatedSearchableKeys, searchableKeys },
- error: contentError,
- request: fetchOrganizations,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, history.location.search);
- const [response, actionsResponse] = await Promise.all([
- OrganizationsAPI.read(params),
- OrganizationsAPI.readOptions(),
- ]);
-
- if (autoPopulate) {
- autoPopulateLookup(response.data.results);
- }
-
- return {
- organizations: response.data.results,
- itemCount: response.data.count,
- relatedSearchableKeys: (
- actionsResponse?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
- };
- }, [autoPopulate, autoPopulateLookup, history.location.search]),
- {
- organizations: [],
- itemCount: 0,
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- const checkOrganizationName = useCallback(
- async (name) => {
- if (!name) {
- onChange(null);
- return;
- }
-
- try {
- const {
- data: { results: nameMatchResults, count: nameMatchCount },
- } = await OrganizationsAPI.read({ name });
- onChange(nameMatchCount ? nameMatchResults[0] : null);
- } catch {
- onChange(null);
- }
- },
- [onChange]
- );
-
- useEffect(() => {
- fetchOrganizations();
- }, [fetchOrganizations]);
-
- return (
-
- (
- dispatch({ type: 'SELECT_ITEM', item })}
- deselectItem={(item) => dispatch({ type: 'DESELECT_ITEM', item })}
- />
- )}
- />
-
-
- );
-}
-
-OrganizationLookup.propTypes = {
- id: string,
- helperTextInvalid: node,
- isValid: bool,
- onBlur: func,
- onChange: func.isRequired,
- required: bool,
- value: Organization,
- autoPopulate: bool,
- isDisabled: bool,
- validate: func,
- fieldName: string,
-};
-
-OrganizationLookup.defaultProps = {
- id: 'organization',
- helperTextInvalid: '',
- isValid: true,
- onBlur: () => {},
- required: false,
- value: null,
- autoPopulate: false,
- isDisabled: false,
- validate: () => undefined,
- fieldName: 'organization',
-};
-
-export { OrganizationLookup as _OrganizationLookup };
-export default withRouter(OrganizationLookup);
diff --git a/awx/ui/src/components/Lookup/OrganizationLookup.test.js b/awx/ui/src/components/Lookup/OrganizationLookup.test.js
deleted file mode 100644
index 76309446fe40..000000000000
--- a/awx/ui/src/components/Lookup/OrganizationLookup.test.js
+++ /dev/null
@@ -1,121 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { OrganizationsAPI } from 'api';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import OrganizationLookup, { _OrganizationLookup } from './OrganizationLookup';
-
-jest.mock('../../api');
-
-describe('OrganizationLookup', () => {
- let wrapper;
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should render successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}} />
-
- );
- });
- expect(wrapper).toHaveLength(1);
- });
-
- test('should fetch organizations', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}} />
-
- );
- });
- expect(OrganizationsAPI.read).toHaveBeenCalledTimes(1);
- expect(OrganizationsAPI.read).toHaveBeenCalledWith({
- order_by: 'name',
- page: 1,
- page_size: 5,
- });
- });
-
- test('should display "Organization" label', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}} />
-
- );
- });
- const title = wrapper.find('FormGroup .pf-c-form__label-text');
- expect(title.text()).toEqual('Organization');
- });
-
- test('should define default value for function props', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}} />
-
- );
- });
- expect(_OrganizationLookup.defaultProps.onBlur).toBeInstanceOf(Function);
- expect(_OrganizationLookup.defaultProps.onBlur).not.toThrow();
- });
-
- test('should auto-select organization when only one available and autoPopulate prop is true', async () => {
- OrganizationsAPI.read.mockReturnValue({
- data: {
- results: [{ id: 1 }],
- count: 1,
- },
- });
- const onChange = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- expect(onChange).toHaveBeenCalledWith({ id: 1 });
- });
-
- test('should not auto-select organization when autoPopulate prop is false', async () => {
- OrganizationsAPI.read.mockReturnValue({
- data: {
- results: [{ id: 1 }],
- count: 1,
- },
- });
- const onChange = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- expect(onChange).not.toHaveBeenCalled();
- });
-
- test('should not auto-select organization when multiple available', async () => {
- OrganizationsAPI.read.mockReturnValue({
- data: {
- results: [{ id: 1 }, { id: 2 }],
- count: 2,
- },
- });
- const onChange = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- expect(onChange).not.toHaveBeenCalled();
- });
-});
diff --git a/awx/ui/src/components/Lookup/ProjectLookup.js b/awx/ui/src/components/Lookup/ProjectLookup.js
deleted file mode 100644
index 7d3bc588a098..000000000000
--- a/awx/ui/src/components/Lookup/ProjectLookup.js
+++ /dev/null
@@ -1,208 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { node, string, func, bool, object, oneOfType } from 'prop-types';
-import { withRouter } from 'react-router-dom';
-import { t } from '@lingui/macro';
-import { FormGroup } from '@patternfly/react-core';
-import { ProjectsAPI } from 'api';
-import { Project } from 'types';
-import useAutoPopulateLookup from 'hooks/useAutoPopulateLookup';
-import useRequest from 'hooks/useRequest';
-import { getSearchableKeys } from 'components/PaginatedTable';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import OptionsList from '../OptionsList';
-import Popover from '../Popover';
-import Lookup from './Lookup';
-import LookupErrorMessage from './shared/LookupErrorMessage';
-
-const QS_CONFIG = getQSConfig('project', {
- page: 1,
- page_size: 5,
- order_by: 'name',
- role_level: 'use_role',
-});
-
-function ProjectLookup({
- helperTextInvalid,
- autoPopulate,
- isValid,
- onChange,
- required,
- tooltip,
- value,
- onBlur,
- history,
- isOverrideDisabled,
- validate,
- fieldName,
-}) {
- const autoPopulateLookup = useAutoPopulateLookup(onChange);
- const {
- result: { projects, count, relatedSearchableKeys, searchableKeys, canEdit },
- request: fetchProjects,
- error,
- isLoading,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, history.location.search);
- const [{ data }, actionsResponse] = await Promise.all([
- ProjectsAPI.read(params),
- ProjectsAPI.readOptions(),
- ]);
- if (autoPopulate) {
- autoPopulateLookup(data.results);
- }
- return {
- count: data.count,
- projects: data.results,
- relatedSearchableKeys: (
- actionsResponse?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
- canEdit:
- Boolean(actionsResponse.data.actions.POST) || isOverrideDisabled,
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [autoPopulate, autoPopulateLookup, history.location.search]),
- {
- count: 0,
- projects: [],
- relatedSearchableKeys: [],
- searchableKeys: [],
- canEdit: false,
- }
- );
-
- const checkProjectName = useCallback(
- async (name) => {
- if (!name) {
- onChange(null);
- return;
- }
-
- try {
- const {
- data: { results: nameMatchResults, count: nameMatchCount },
- } = await ProjectsAPI.read({ name });
- onChange(nameMatchCount ? nameMatchResults[0] : null);
- } catch {
- onChange(null);
- }
- },
- [onChange]
- );
-
- useEffect(() => {
- fetchProjects();
- }, [fetchProjects]);
-
- return (
- }
- >
- (
- dispatch({ type: 'SELECT_ITEM', item })}
- deselectItem={(item) => dispatch({ type: 'DESELECT_ITEM', item })}
- />
- )}
- />
-
-
- );
-}
-
-ProjectLookup.propTypes = {
- autoPopulate: bool,
- helperTextInvalid: node,
- isValid: bool,
- onBlur: func,
- onChange: func.isRequired,
- required: bool,
- tooltip: string,
- value: oneOfType([Project, object]),
- isOverrideDisabled: bool,
- validate: func,
- fieldName: string,
-};
-
-ProjectLookup.defaultProps = {
- autoPopulate: false,
- helperTextInvalid: '',
- isValid: true,
- onBlur: () => {},
- required: false,
- tooltip: '',
- value: null,
- isOverrideDisabled: false,
- validate: () => undefined,
- fieldName: 'project',
-};
-
-export { ProjectLookup as _ProjectLookup };
-export default withRouter(ProjectLookup);
diff --git a/awx/ui/src/components/Lookup/ProjectLookup.test.js b/awx/ui/src/components/Lookup/ProjectLookup.test.js
deleted file mode 100644
index a714ef4c5c8d..000000000000
--- a/awx/ui/src/components/Lookup/ProjectLookup.test.js
+++ /dev/null
@@ -1,180 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { ProjectsAPI } from 'api';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import ProjectLookup from './ProjectLookup';
-
-jest.mock('../../api');
-
-describe('', () => {
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should auto-select project when only one available and autoPopulate prop is true', async () => {
- ProjectsAPI.read.mockReturnValue({
- data: {
- results: [{ id: 1, name: 'Test' }],
- count: 1,
- },
- });
- const onChange = jest.fn();
- await act(async () => {
- mountWithContexts(
-
-
-
- );
- });
- expect(onChange).toHaveBeenCalledWith({ id: 1, name: 'Test' });
- });
-
- test('should not auto-select project when autoPopulate prop is false', async () => {
- ProjectsAPI.read.mockReturnValue({
- data: {
- results: [{ id: 1, name: 'Test' }],
- count: 1,
- },
- });
- const onChange = jest.fn();
- await act(async () => {
- mountWithContexts(
-
-
-
- );
- });
- expect(onChange).not.toHaveBeenCalled();
- });
-
- test('should not auto-select project when multiple available', async () => {
- ProjectsAPI.read.mockReturnValue({
- data: {
- results: [
- { id: 1, name: 'Test' },
- { id: 2, name: 'Test 2' },
- ],
- count: 2,
- },
- });
- const onChange = jest.fn();
- await act(async () => {
- mountWithContexts(
-
-
-
- );
- });
- expect(onChange).not.toHaveBeenCalled();
- });
-
- test('project lookup should be enabled', async () => {
- let wrapper;
- ProjectsAPI.read.mockReturnValue({
- data: {
- results: [{ id: 1, name: 'Test' }],
- count: 1,
- },
- });
- ProjectsAPI.readOptions.mockReturnValue({
- data: {
- actions: {
- GET: {},
- },
- related_search_fields: [],
- },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}} />
-
- );
- });
- wrapper.update();
- expect(ProjectsAPI.read).toHaveBeenCalledTimes(1);
- expect(wrapper.find('ProjectLookup')).toHaveLength(1);
- expect(wrapper.find('Lookup').prop('isDisabled')).toBe(false);
- });
-
- test('project lookup should be disabled', async () => {
- let wrapper;
-
- ProjectsAPI.readOptions.mockReturnValue({
- data: {
- actions: {
- GET: {},
- },
- related_search_fields: [],
- },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}} />
-
- );
- });
- wrapper.update();
- expect(ProjectsAPI.read).toHaveBeenCalledTimes(1);
- expect(wrapper.find('ProjectLookup')).toHaveLength(1);
- expect(wrapper.find('Lookup').prop('isDisabled')).toBe(true);
- });
-
- test('should not show helper text', async () => {
- let wrapper;
-
- ProjectsAPI.readOptions.mockReturnValue({
- data: {
- actions: {
- GET: {},
- },
- related_search_fields: [],
- },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- />
-
- );
- });
- wrapper.update();
-
- expect(wrapper.find('div#project-helper').length).toBe(0);
- });
-
- test('should not show helper text', async () => {
- let wrapper;
-
- ProjectsAPI.readOptions.mockReturnValue({
- data: {
- actions: {
- GET: {},
- },
- related_search_fields: [],
- },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}}
- />
-
- );
- });
- wrapper.update();
-
- expect(wrapper.find('div#project-helper').text('helperTextInvalid')).toBe(
- 'select value'
- );
- });
-});
diff --git a/awx/ui/src/components/Lookup/README.md b/awx/ui/src/components/Lookup/README.md
deleted file mode 100644
index 4d5dc69674dc..000000000000
--- a/awx/ui/src/components/Lookup/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Lookup
-
-required single select lookups should not include a close X on the tag... you would have to select something else to change it
-
-optional single select lookups should include a close X to remove it on the spot
diff --git a/awx/ui/src/components/Lookup/index.js b/awx/ui/src/components/Lookup/index.js
deleted file mode 100644
index 7c8b6845b1e8..000000000000
--- a/awx/ui/src/components/Lookup/index.js
+++ /dev/null
@@ -1,10 +0,0 @@
-export { default } from './Lookup';
-export { default as InstanceGroupsLookup } from './InstanceGroupsLookup';
-export { default as InventoryLookup } from './InventoryLookup';
-export { default as ProjectLookup } from './ProjectLookup';
-export { default as MultiCredentialsLookup } from './MultiCredentialsLookup';
-export { default as CredentialLookup } from './CredentialLookup';
-export { default as ApplicationLookup } from './ApplicationLookup';
-export { default as HostFilterLookup } from './HostFilterLookup';
-export { default as OrganizationLookup } from './OrganizationLookup';
-export { default as ExecutionEnvironmentLookup } from './ExecutionEnvironmentLookup';
diff --git a/awx/ui/src/components/Lookup/shared/HostFilterUtils.js b/awx/ui/src/components/Lookup/shared/HostFilterUtils.js
deleted file mode 100644
index 8d6f9fd04966..000000000000
--- a/awx/ui/src/components/Lookup/shared/HostFilterUtils.js
+++ /dev/null
@@ -1,194 +0,0 @@
-/**
- * Convert host filter string to params object
- * @param {string} string host filter string
- * @return {object} A string or array of strings keyed by query param key
- */
-export function toSearchParams(string = '') {
- if (string === '') {
- return {};
- }
-
- const readableParamsStr = string.replace(/^\?/, '').replace(/&/g, ' and ');
- const orArr = readableParamsStr.split(/ or /);
-
- if (orArr.length > 1) {
- orArr.forEach((str, index) => {
- orArr[index] = `or__${str}`;
- });
- }
-
- const unescapeString = (v) =>
- // This is necessary when editing a string that was initially
- // escaped to allow white space
- v ? v.replace(/"/g, '') : '';
-
- return orArr
- .join(' and ')
- .split(/ and | or /)
- .map((s) => s.split('='))
- .reduce((searchParams, [k, v]) => {
- const key = decodeURIComponent(k);
- const value = decodeURIComponent(unescapeString(v));
- if (searchParams[key] === undefined) {
- searchParams[key] = value;
- } else if (Array.isArray(searchParams[key])) {
- searchParams[key] = [...searchParams[key], value];
- } else {
- searchParams[key] = [searchParams[key], value];
- }
- return searchParams;
- }, {});
-}
-
-/**
- * Convert params object to an encoded namespaced url query string
- * Used to put into url bar when modal opens
- * @param {object} config Config object for namespacing params
- * @param {object} searchParams A string or array of strings keyed by query param key
- * @return {string} URL query string
- */
-export function toQueryString(config, searchParams = {}) {
- if (Object.keys(searchParams).length === 0) return '';
- return Object.keys(searchParams)
- .flatMap((key) => {
- if (Array.isArray(searchParams[key])) {
- return searchParams[key].map(
- (val) =>
- `${config.namespace}.${encodeURIComponent(
- key
- )}=${encodeURIComponent(val)}`
- );
- }
- return `${config.namespace}.${encodeURIComponent(
- key
- )}=${encodeURIComponent(searchParams[key])}`;
- })
- .join('&');
-}
-
-/**
- * Escape a string with double quote in case there was a white space
- * @param {string} key The key of the value to be parsed
- * @param {string} value A string to be parsed
- * @return {string} string
- */
-const escapeString = (key, value) => {
- if (verifySpace(value) || key.includes('regex')) {
- return `"${value}"`;
- }
- return value;
-};
-
-/**
- * Verify whether a string has white spaces
- * @param {string} value A string to be parsed
- * @return {bool} true if a string has white spaces
- */
-const verifySpace = (value) => value.trim().indexOf(' ') >= 0;
-
-/**
- * Convert params object to host filter string
- * @param {object} searchParams A string or array of strings keyed by query param key
- * @return {string} Host filter string
- */
-export function toHostFilter(searchParams = {}) {
- const flattenSearchParams = Object.keys(searchParams)
- .sort()
- .flatMap((key) => {
- if (Array.isArray(searchParams[key])) {
- return searchParams[key].map(
- (val) => `${key}=${escapeString(key, val)}`
- );
- }
- return `${key}=${escapeString(key, searchParams[key])}`;
- });
-
- const filteredSearchParams = flattenSearchParams.filter(
- (el) => el.indexOf('or__') === -1
- );
-
- const conditionalSearchParams = flattenSearchParams.filter(
- (el) => !filteredSearchParams.includes(el)
- );
-
- const conditionalQuery = conditionalSearchParams
- .map((el) => el.replace('or__', 'or '))
- .join(' ')
- .trim();
-
- if (filteredSearchParams.length === 0 && conditionalQuery) {
- // when there are just or operators the first one should be removed from the query
- // `name=foo or name__contains=bar or name__iexact=foo` instead of
- // `or name=foo or name__contains=bar or name__iexact=foo` that is the reason of the slice(3)
- return conditionalQuery.slice(3);
- }
-
- if (conditionalQuery) {
- return filteredSearchParams.join(' and ').concat(' ', conditionalQuery);
- }
-
- return filteredSearchParams.join(' and ').trim();
-}
-
-/**
- * Helper function to remove namespace from params object
- * @param {object} config Config object with namespace param
- * @param {object} obj A string or array of strings keyed by query param key
- * @return {object} Params object without namespaced keys
- */
-export function removeNamespacedKeys(config, obj = {}) {
- const clonedObj = { ...obj };
- const newObj = {};
- Object.keys(clonedObj).forEach((nsKey) => {
- let key = nsKey;
- if (nsKey.startsWith(config.namespace)) {
- key = nsKey.substr(config.namespace.length + 1);
- }
- newObj[key] = clonedObj[nsKey];
- });
- return newObj;
-}
-
-/**
- * Helper function to remove default params from params object
- * @param {object} config Config object with default params
- * @param {object} obj A string or array of strings keyed by query param key
- * @return {string} Params object without default params
- */
-export function removeDefaultParams(config, obj = {}) {
- const clonedObj = { ...obj };
- const defaultKeys = Object.keys(config.defaultParams);
- defaultKeys.forEach((keyToOmit) => {
- delete clonedObj[keyToOmit];
- });
- return clonedObj;
-}
-
-/**
- * Helper function to update host_filter value
- * @param {string} value A string with host_filter value from querystring
- * @param {object} obj An object returned by toSearchParams - in which the
- * host_filter value was partially removed.
- * @return {object} An object with the value of host_filter modified
- */
-export function modifyHostFilter(value, obj) {
- if (!value.includes('host_filter=')) return obj;
- const clonedObj = { ...obj };
- const host_filter = {};
- value.split(' ').forEach((item) => {
- if (item.includes('host_filter')) {
- host_filter.host_filter = item.slice('host_filter='.length);
- }
- });
-
- Object.keys(clonedObj).forEach((key) => {
- if (key.indexOf('host_filter') !== -1) {
- delete clonedObj[key];
- }
- });
-
- return {
- ...clonedObj,
- ...host_filter,
- };
-}
diff --git a/awx/ui/src/components/Lookup/shared/HostFilterUtils.test.js b/awx/ui/src/components/Lookup/shared/HostFilterUtils.test.js
deleted file mode 100644
index 79475fe61490..000000000000
--- a/awx/ui/src/components/Lookup/shared/HostFilterUtils.test.js
+++ /dev/null
@@ -1,216 +0,0 @@
-import {
- removeDefaultParams,
- removeNamespacedKeys,
- toHostFilter,
- toQueryString,
- toSearchParams,
- modifyHostFilter,
-} from './HostFilterUtils';
-
-const QS_CONFIG = {
- namespace: 'mock',
- defaultParams: { page: 1, page_size: 5, order_by: 'name' },
- integerFields: ['page', 'page_size', 'id', 'inventory'],
-};
-
-describe('toSearchParams', () => {
- let string;
- let paramsObject;
-
- test('should return an empty object', () => {
- expect(toSearchParams(undefined)).toEqual({});
- expect(toSearchParams('')).toEqual({});
- });
- test('should take a query string and return search params object', () => {
- string = '?foo=bar';
- paramsObject = { foo: 'bar' };
- expect(toSearchParams(string)).toEqual(paramsObject);
- });
- test('should take a host filter string and return search params object', () => {
- string = 'foo=bar and foo=baz and foo=qux and isa=sampu';
- paramsObject = {
- foo: ['bar', 'baz', 'qux'],
- isa: 'sampu',
- };
- expect(toSearchParams(string)).toEqual(paramsObject);
- });
- test('should take a host filter string separated by or and return search params object with or', () => {
- string = 'foo=bar or foo=baz';
- paramsObject = {
- or__foo: ['bar', 'baz'],
- };
- expect(toSearchParams(string)).toEqual(paramsObject);
- });
- test('should take a host filter string with or and return search params object with or', () => {
- string = 'or__foo=1&or__foo=2';
- paramsObject = {
- or__foo: ['1', '2'],
- };
- expect(toSearchParams(string)).toEqual(paramsObject);
- });
-});
-
-describe('toQueryString', () => {
- test('should return an empty string', () => {
- expect(toQueryString(QS_CONFIG, undefined)).toEqual('');
- });
- test('should return namespaced query string with a single key-value pair', () => {
- const object = {
- foo: 'bar',
- };
- expect(toQueryString(QS_CONFIG, object)).toEqual('mock.foo=bar');
- });
- test('should return namespaced query string with multiple values per key', () => {
- const object = {
- foo: ['bar', 'baz'],
- };
- expect(toQueryString(QS_CONFIG, object)).toEqual(
- 'mock.foo=bar&mock.foo=baz'
- );
- });
- test('should return namespaced query string with multiple key-value pairs', () => {
- const object = {
- foo: ['bar', 'baz', 'qux'],
- isa: 'sampu',
- };
- expect(toQueryString(QS_CONFIG, object)).toEqual(
- 'mock.foo=bar&mock.foo=baz&mock.foo=qux&mock.isa=sampu'
- );
- });
-});
-
-describe('toHostFilter', () => {
- test('should return an empty string', () => {
- expect(toHostFilter(undefined)).toEqual('');
- });
- test('should return a host filter string', () => {
- const object = {
- isa: '2',
- tatlo: ['foo', 'bar', 'baz'],
- };
- expect(toHostFilter(object)).toEqual(
- 'isa=2 and tatlo=foo and tatlo=bar and tatlo=baz'
- );
- });
- test('should return a host filter with mixed conditionals', () => {
- const object = {
- or__name__contains: 'bar',
- or__name__iexact: 'foo',
- enabled: 'true',
- name__contains: 'x',
- or__name: 'foo',
- };
- expect(toHostFilter(object)).toEqual(
- 'enabled=true and name__contains=x or name=foo or name__contains=bar or name__iexact=foo'
- );
- });
-
- test('should return a host filter with escaped string', () => {
- const object = {
- or__description__contains: 'bar biz',
- enabled: 'true',
- name__contains: 'x',
- or__name: 'foo',
- };
- expect(toHostFilter(object)).toEqual(
- 'enabled=true and name__contains=x or description__contains="bar biz" or name=foo'
- );
- });
-
- test('should return a host filter with and conditional', () => {
- const object = {
- enabled: 'true',
- name__contains: 'x',
- };
- expect(toHostFilter(object)).toEqual('enabled=true and name__contains=x');
- });
-
- test('should return a host filter with or conditional', () => {
- const object = {
- or__name__contains: 'bar',
- or__name__iexact: 'foo',
- or__name: 'foo',
- };
- expect(toHostFilter(object)).toEqual(
- 'name=foo or name__contains=bar or name__iexact=foo'
- );
- });
-
- test('should escape name__regex and name__iregex', () => {
- const object = {
- or__name__regex: '(t|e)st',
- or__name__iregex: '(f|o)',
- or__name: 'foo',
- };
- expect(toHostFilter(object)).toEqual(
- 'name=foo or name__iregex="(f|o)" or name__regex="(t|e)st"'
- );
- });
-
- test('should return a host filter with or conditional when value is array', () => {
- const object = {
- or__groups__id: ['1', '2'],
- };
- expect(toHostFilter(object)).toEqual('groups__id=1 or groups__id=2');
- });
-});
-
-describe('removeNamespacedKeys', () => {
- test('should return an empty object', () => {
- expect(removeNamespacedKeys(QS_CONFIG, undefined)).toEqual({});
- });
- test('should remove namespace from keys', () => {
- expect(removeNamespacedKeys(QS_CONFIG, { 'mock.foo': 'bar' })).toEqual({
- foo: 'bar',
- });
- });
-});
-
-describe('removeDefaultParams', () => {
- test('should return an empty object', () => {
- expect(removeDefaultParams(QS_CONFIG, undefined)).toEqual({});
- });
- test('should remove default params', () => {
- const object = {
- foo: ['bar', 'baz', 'qux'],
- apat: 'lima',
- page: 10,
- order_by: '-name',
- };
- expect(removeDefaultParams(QS_CONFIG, object)).toEqual({
- foo: ['bar', 'baz', 'qux'],
- apat: 'lima',
- });
- });
-});
-
-describe('modifyHostFilter', () => {
- test('should modify host_filter', () => {
- const object = {
- foo: ['bar', 'baz', 'qux'],
- apat: 'lima',
- page: 10,
- order_by: '-name',
- };
- expect(
- modifyHostFilter(
- 'host_filter=ansible_facts__ansible_lo__ipv6[]__scope="host"',
- object
- )
- ).toEqual({
- apat: 'lima',
- foo: ['bar', 'baz', 'qux'],
- host_filter: 'ansible_facts__ansible_lo__ipv6[]__scope="host"',
- order_by: '-name',
- page: 10,
- });
- });
- test('should not modify host_filter', () => {
- const object = { groups__name__icontains: '1' };
- expect(
- modifyHostFilter('groups__name__icontains=1', {
- groups__name__icontains: '1',
- })
- ).toEqual(object);
- });
-});
diff --git a/awx/ui/src/components/Lookup/shared/LookupErrorMessage.js b/awx/ui/src/components/Lookup/shared/LookupErrorMessage.js
deleted file mode 100644
index 590a90d99ad0..000000000000
--- a/awx/ui/src/components/Lookup/shared/LookupErrorMessage.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react';
-
-import { t } from '@lingui/macro';
-
-function LookupErrorMessage({ error }) {
- if (!error) {
- return null;
- }
-
- return (
-
- {error.message || t`An error occurred`}
-
- );
-}
-
-export default LookupErrorMessage;
diff --git a/awx/ui/src/components/Lookup/shared/reducer.js b/awx/ui/src/components/Lookup/shared/reducer.js
deleted file mode 100644
index e2fe3137f470..000000000000
--- a/awx/ui/src/components/Lookup/shared/reducer.js
+++ /dev/null
@@ -1,96 +0,0 @@
-export default function reducer(state, action) {
- switch (action.type) {
- case 'SELECT_ITEM':
- return selectItem(state, action.item);
- case 'DESELECT_ITEM':
- return deselectItem(state, action.item);
- case 'TOGGLE_MODAL':
- return toggleModal(state);
- case 'CLOSE_MODAL':
- return closeModal(state);
- case 'SET_MULTIPLE':
- return { ...state, multiple: action.value };
- case 'SET_VALUE':
- return { ...state, value: action.value };
- case 'SET_SELECTED_ITEMS':
- return { ...state, selectedItems: action.selectedItems };
- default:
- throw new Error(`Unrecognized action type: ${action.type}`);
- }
-}
-
-function selectItem(state, item) {
- const { selectedItems, multiple } = state;
- if (!multiple) {
- return {
- ...state,
- selectedItems: [item],
- };
- }
- const index = selectedItems.findIndex((i) => i.id === item.id);
- if (index > -1) {
- return state;
- }
- return {
- ...state,
- selectedItems: [...selectedItems, item],
- };
-}
-
-function deselectItem(state, item) {
- return {
- ...state,
- selectedItems: state.selectedItems.filter((i) => i.id !== item.id),
- };
-}
-
-function toggleModal(state) {
- const { isModalOpen, value, multiple } = state;
- if (isModalOpen) {
- return closeModal(state);
- }
- let selectedItems = [];
- if (multiple) {
- selectedItems = [...value];
- } else if (value) {
- selectedItems.push(value);
- }
- return {
- ...state,
- isModalOpen: !isModalOpen,
- selectedItems,
- };
-}
-
-function closeModal(state) {
- return {
- ...state,
- isModalOpen: false,
- };
-}
-
-export function initReducer({ value, multiple = false, required = false }) {
- assertCorrectValueType(value, multiple);
- let selectedItems = [];
- if (value) {
- selectedItems = multiple ? [...value] : [value];
- }
- return {
- selectedItems,
- value,
- multiple,
- isModalOpen: false,
- required,
- };
-}
-
-function assertCorrectValueType(value, multiple) {
- if (!multiple && Array.isArray(value)) {
- throw new Error(
- 'Lookup value must not be an array unless `multiple` is set'
- );
- }
- if (multiple && !Array.isArray(value)) {
- throw new Error('Lookup value must be an array if `multiple` is set');
- }
-}
diff --git a/awx/ui/src/components/Lookup/shared/reducer.test.js b/awx/ui/src/components/Lookup/shared/reducer.test.js
deleted file mode 100644
index 62c963cbfbf9..000000000000
--- a/awx/ui/src/components/Lookup/shared/reducer.test.js
+++ /dev/null
@@ -1,280 +0,0 @@
-import reducer, { initReducer } from './reducer';
-
-describe('Lookup reducer', () => {
- describe('SELECT_ITEM', () => {
- it('should add item to selected items (multiple select)', () => {
- const state = {
- selectedItems: [{ id: 1 }],
- multiple: true,
- };
- const result = reducer(state, {
- type: 'SELECT_ITEM',
- item: { id: 2 },
- });
- expect(result).toEqual({
- selectedItems: [{ id: 1 }, { id: 2 }],
- multiple: true,
- });
- });
-
- it('should not duplicate item if already selected (multiple select)', () => {
- const state = {
- selectedItems: [{ id: 1 }],
- multiple: true,
- };
- const result = reducer(state, {
- type: 'SELECT_ITEM',
- item: { id: 1 },
- });
- expect(result).toEqual({
- selectedItems: [{ id: 1 }],
- multiple: true,
- });
- });
-
- it('should replace selected item (single select)', () => {
- const state = {
- selectedItems: [{ id: 1 }],
- multiple: false,
- };
- const result = reducer(state, {
- type: 'SELECT_ITEM',
- item: { id: 2 },
- });
- expect(result).toEqual({
- selectedItems: [{ id: 2 }],
- multiple: false,
- });
- });
-
- it('should not duplicate item if already selected (single select)', () => {
- const state = {
- selectedItems: [{ id: 1 }],
- multiple: false,
- };
- const result = reducer(state, {
- type: 'SELECT_ITEM',
- item: { id: 1 },
- });
- expect(result).toEqual({
- selectedItems: [{ id: 1 }],
- multiple: false,
- });
- });
- });
-
- describe('DESELECT_ITEM', () => {
- it('should de-select item (multiple)', () => {
- const state = {
- selectedItems: [{ id: 1 }, { id: 2 }],
- multiple: true,
- };
- const result = reducer(state, {
- type: 'DESELECT_ITEM',
- item: { id: 1 },
- });
- expect(result).toEqual({
- selectedItems: [{ id: 2 }],
- multiple: true,
- });
- });
-
- it('should not change list if item not selected (multiple)', () => {
- const state = {
- selectedItems: [{ id: 1 }, { id: 2 }],
- multiple: true,
- };
- const result = reducer(state, {
- type: 'DESELECT_ITEM',
- item: { id: 3 },
- });
- expect(result).toEqual({
- selectedItems: [{ id: 1 }, { id: 2 }],
- multiple: true,
- });
- });
-
- it('should de-select item (single select)', () => {
- const state = {
- selectedItems: [{ id: 1 }],
- multiple: true,
- };
- const result = reducer(state, {
- type: 'DESELECT_ITEM',
- item: { id: 1 },
- });
- expect(result).toEqual({
- selectedItems: [],
- multiple: true,
- });
- });
- });
-
- describe('TOGGLE_MODAL', () => {
- it('should open the modal (single)', () => {
- const state = {
- isModalOpen: false,
- selectedItems: [],
- value: { id: 1 },
- multiple: false,
- };
- const result = reducer(state, {
- type: 'TOGGLE_MODAL',
- });
- expect(result).toEqual({
- isModalOpen: true,
- selectedItems: [{ id: 1 }],
- value: { id: 1 },
- multiple: false,
- });
- });
-
- it('should set null value to empty array', () => {
- const state = {
- isModalOpen: false,
- selectedItems: [{ id: 1 }],
- value: null,
- multiple: false,
- };
- const result = reducer(state, {
- type: 'TOGGLE_MODAL',
- });
- expect(result).toEqual({
- isModalOpen: true,
- selectedItems: [],
- value: null,
- multiple: false,
- });
- });
-
- it('should open the modal (multiple)', () => {
- const state = {
- isModalOpen: false,
- selectedItems: [],
- value: [{ id: 1 }],
- multiple: true,
- };
- const result = reducer(state, {
- type: 'TOGGLE_MODAL',
- });
- expect(result).toEqual({
- isModalOpen: true,
- selectedItems: [{ id: 1 }],
- value: [{ id: 1 }],
- multiple: true,
- });
- });
-
- it('should close the modal', () => {
- const state = {
- isModalOpen: true,
- selectedItems: [{ id: 1 }],
- value: [{ id: 1 }],
- multiple: true,
- };
- const result = reducer(state, {
- type: 'TOGGLE_MODAL',
- });
- expect(result).toEqual({
- isModalOpen: false,
- selectedItems: [{ id: 1 }],
- value: [{ id: 1 }],
- multiple: true,
- });
- });
- });
-
- describe('CLOSE_MODAL', () => {
- it('should close the modal', () => {
- const state = {
- isModalOpen: true,
- selectedItems: [{ id: 1 }],
- value: [{ id: 1 }],
- multiple: true,
- };
- const result = reducer(state, {
- type: 'CLOSE_MODAL',
- });
- expect(result).toEqual({
- isModalOpen: false,
- selectedItems: [{ id: 1 }],
- value: [{ id: 1 }],
- multiple: true,
- });
- });
- });
-
- describe('SET_MULTIPLE', () => {
- it('should set multiple to true', () => {
- const state = {
- isModalOpen: false,
- selectedItems: [{ id: 1 }],
- value: [{ id: 1 }],
- multiple: false,
- };
- const result = reducer(state, {
- type: 'SET_MULTIPLE',
- value: true,
- });
- expect(result).toEqual({
- isModalOpen: false,
- selectedItems: [{ id: 1 }],
- value: [{ id: 1 }],
- multiple: true,
- });
- });
-
- it('should set multiple to false', () => {
- const state = {
- isModalOpen: false,
- selectedItems: [{ id: 1 }],
- value: [{ id: 1 }],
- multiple: true,
- };
- const result = reducer(state, {
- type: 'SET_MULTIPLE',
- value: false,
- });
- expect(result).toEqual({
- isModalOpen: false,
- selectedItems: [{ id: 1 }],
- value: [{ id: 1 }],
- multiple: false,
- });
- });
- });
-
- describe('SET_VALUE', () => {
- it('should set the value', () => {
- const state = {
- value: [{ id: 1 }],
- multiple: true,
- };
- const result = reducer(state, {
- type: 'SET_VALUE',
- value: [{ id: 3 }],
- });
- expect(result).toEqual({
- value: [{ id: 3 }],
- multiple: true,
- });
- });
- });
-});
-
-describe('initReducer', () => {
- it('should init', () => {
- const state = initReducer({
- value: [],
- multiple: true,
- required: true,
- });
- expect(state).toEqual({
- selectedItems: [],
- value: [],
- multiple: true,
- isModalOpen: false,
- required: true,
- });
- });
-});
diff --git a/awx/ui/src/components/MultiButtonToggle/ButtonGroup.js b/awx/ui/src/components/MultiButtonToggle/ButtonGroup.js
deleted file mode 100644
index c350047b49ed..000000000000
--- a/awx/ui/src/components/MultiButtonToggle/ButtonGroup.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import React from 'react';
-import styled from 'styled-components';
-
-const Group = styled.div`
- display: inline-flex;
-
- & > .pf-c-button:not(:last-child) {
- &,
- &::after {
- border-top-right-radius: 0;
- border-bottom-right-radius: 0;
- }
- }
-
- & > .pf-c-button:not(:first-child) {
- &,
- &::after {
- border-top-left-radius: 0;
- border-bottom-left-radius: 0;
- }
- }
-`;
-
-function ButtonGroup({ children }) {
- return {children};
-}
-
-export default ButtonGroup;
diff --git a/awx/ui/src/components/MultiButtonToggle/MultiButtonToggle.js b/awx/ui/src/components/MultiButtonToggle/MultiButtonToggle.js
deleted file mode 100644
index 2b8c8da7f764..000000000000
--- a/awx/ui/src/components/MultiButtonToggle/MultiButtonToggle.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import React from 'react';
-import { func, string } from 'prop-types';
-import styled from 'styled-components';
-import { Button } from '@patternfly/react-core';
-import ButtonGroup from './ButtonGroup';
-
-const SmallButton = styled(Button)`
- && {
- padding: 3px 8px;
- font-size: var(--pf-global--FontSize--xs);
- }
-`;
-SmallButton.displayName = 'SmallButton';
-
-function MultiButtonToggle({ buttons, value, onChange, name }) {
- const setValue = (newValue) => {
- if (value !== newValue) {
- onChange(newValue);
- }
- };
-
- return (
-
- {buttons &&
- buttons.map(([buttonValue, buttonLabel]) => (
- setValue(buttonValue)}
- variant={buttonValue === value ? 'primary' : 'secondary'}
- >
- {buttonLabel}
-
- ))}
-
- );
-}
-
-const buttonsPropType = {
- isRequired: ({ buttons }) => {
- if (!buttons) {
- return new Error(
- `The prop buttons is marked as required in MultiButtonToggle, but its value is '${buttons}'`
- );
- }
- // We expect this data structure to look like:
- // [[value(unrestricted type), label(string)], [value(unrestricted type), label(string)], ...]
- if (
- !Array.isArray(buttons) ||
- buttons.length < 2 ||
- buttons.reduce(
- (prevVal, button) => prevVal || typeof button[1] !== 'string',
- false
- )
- ) {
- return new Error(
- `Invalid prop buttons supplied to MultiButtonToggle. Validation failed.`
- );
- }
-
- return null;
- },
-};
-
-MultiButtonToggle.propTypes = {
- buttons: buttonsPropType.isRequired,
- value: string.isRequired,
- onChange: func.isRequired,
- name: string.isRequired,
-};
-
-export default MultiButtonToggle;
diff --git a/awx/ui/src/components/MultiButtonToggle/MultiButtonToggle.test.js b/awx/ui/src/components/MultiButtonToggle/MultiButtonToggle.test.js
deleted file mode 100644
index c37e43b72901..000000000000
--- a/awx/ui/src/components/MultiButtonToggle/MultiButtonToggle.test.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import MultiButtonToggle from './MultiButtonToggle';
-
-describe('', () => {
- let wrapper;
- const onChange = jest.fn();
-
- beforeAll(() => {
- wrapper = shallow(
-
- );
- });
-
- it('should render buttons successfully', () => {
- const buttons = wrapper.find('SmallButton');
- expect(buttons.length).toBe(2);
- expect(buttons.at(0).props().variant).toBe('primary');
- expect(buttons.at(1).props().variant).toBe('secondary');
- });
-
- it('should call onChange function when button clicked', () => {
- const buttons = wrapper.find('SmallButton');
- buttons.at(1).simulate('click');
- expect(onChange).toHaveBeenCalledWith('json');
- });
-});
diff --git a/awx/ui/src/components/MultiButtonToggle/index.js b/awx/ui/src/components/MultiButtonToggle/index.js
deleted file mode 100644
index 9332082446e5..000000000000
--- a/awx/ui/src/components/MultiButtonToggle/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './MultiButtonToggle';
diff --git a/awx/ui/src/components/MultiSelect/TagMultiSelect.js b/awx/ui/src/components/MultiSelect/TagMultiSelect.js
deleted file mode 100644
index b6b21c36635a..000000000000
--- a/awx/ui/src/components/MultiSelect/TagMultiSelect.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import React, { useState } from 'react';
-import { func, string } from 'prop-types';
-import { t } from '@lingui/macro';
-import { Select, SelectOption, SelectVariant } from '@patternfly/react-core';
-import { arrayToString, stringToArray } from 'util/strings';
-
-function TagMultiSelect({ onChange, value }) {
- const selections = stringToArray(value);
- const [options, setOptions] = useState(selections);
- const [isExpanded, setIsExpanded] = useState(false);
-
- const onSelect = (event, item) => {
- let newValue;
- if (selections.includes(item)) {
- newValue = selections.filter((i) => i !== item);
- } else {
- newValue = selections.concat(item);
- }
- onChange(arrayToString(newValue));
- };
-
- const toggleExpanded = (toggleValue) => {
- setIsExpanded(toggleValue);
- };
-
- const renderOptions = (opts) =>
- opts.map((option) => (
-
- {option}
-
- ));
-
- const onFilter = (event) => {
- if (event) {
- const str = event.target.value.toLowerCase();
- const matches = options.filter((o) => o.toLowerCase().includes(str));
- return renderOptions(matches);
- }
- return null;
- };
-
- return (
-
- );
-}
-
-TagMultiSelect.propTypes = {
- onChange: func.isRequired,
- value: string.isRequired,
-};
-
-export default TagMultiSelect;
diff --git a/awx/ui/src/components/MultiSelect/TagMultiSelect.test.js b/awx/ui/src/components/MultiSelect/TagMultiSelect.test.js
deleted file mode 100644
index 9fdb35843477..000000000000
--- a/awx/ui/src/components/MultiSelect/TagMultiSelect.test.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import React from 'react';
-import { mount } from 'enzyme';
-import TagMultiSelect from './TagMultiSelect';
-
-describe('', () => {
- it('should render Select', () => {
- const wrapper = mount(
-
- );
- wrapper.find('input').simulate('focus');
- const options = wrapper.find('Chip');
- expect(options).toHaveLength(2);
- expect(options.at(0).text()).toEqual('foo');
- expect(options.at(1).text()).toEqual('bar');
- });
-
- it('should not treat empty string as an option', () => {
- const wrapper = mount();
- wrapper.find('SelectToggle').simulate('click');
- expect(wrapper.find('Select').prop('isOpen')).toEqual(true);
- expect(wrapper.find('Chip')).toHaveLength(0);
- });
-
- it('should trigger onChange', () => {
- const onChange = jest.fn();
- const wrapper = mount(
-
- );
- wrapper.find('input').simulate('focus');
-
- wrapper.find('Select').invoke('onSelect')(null, 'baz');
- expect(onChange).toHaveBeenCalledWith('foo,bar,baz');
- });
-});
diff --git a/awx/ui/src/components/MultiSelect/index.js b/awx/ui/src/components/MultiSelect/index.js
deleted file mode 100644
index cbe4fe49e227..000000000000
--- a/awx/ui/src/components/MultiSelect/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as TagMultiSelect } from './TagMultiSelect';
-export { default as useSyncedSelectValue } from './useSyncedSelectValue';
diff --git a/awx/ui/src/components/MultiSelect/useSyncedSelectValue.js b/awx/ui/src/components/MultiSelect/useSyncedSelectValue.js
deleted file mode 100644
index 38e226b89579..000000000000
--- a/awx/ui/src/components/MultiSelect/useSyncedSelectValue.js
+++ /dev/null
@@ -1,76 +0,0 @@
-import { useState, useEffect } from 'react';
-import useIsMounted from 'hooks/useIsMounted';
-
-/*
- Hook for using PatternFly's
- }
- value="day"
- isChecked={runOn.value === 'day'}
- onChange={(value, event) => {
- event.target.value = 'day';
- runOn.onChange(event);
- }}
- />
-
-
- The
-
-
-
- {frequency === 'year' && (
- <>
-
- of
-
-
- >
- )}
-
- }
- value="the"
- isChecked={runOn.value === 'the'}
- onChange={(value, event) => {
- event.target.value = 'the';
- runOn.onChange(event);
- }}
- />
-
- )}
-
- {
- event.target.value = 'never';
- end.onChange(event);
- }}
- ouiaId={`end-never-radio-button-${id}`}
- />
- {
- event.target.value = 'after';
- end.onChange(event);
- }}
- ouiaId={`end-after-radio-button-${id}`}
- />
- {
- event.target.value = 'onDate';
- end.onChange(event);
- }}
- ouiaId={`end-on-radio-button-${id}`}
- />
-
- {end?.value === 'after' && (
-
- )}
- {end?.value === 'onDate' && (
-
- )}
- >
- );
-};
-
-export default FrequencyDetailSubform;
diff --git a/awx/ui/src/components/Schedule/shared/FrequencySelect.js b/awx/ui/src/components/Schedule/shared/FrequencySelect.js
deleted file mode 100644
index b76d6357aa00..000000000000
--- a/awx/ui/src/components/Schedule/shared/FrequencySelect.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import React, { useState } from 'react';
-import { arrayOf, string } from 'prop-types';
-import { Select, SelectOption, SelectVariant } from '@patternfly/react-core';
-
-export default function FrequencySelect({
- id,
- value,
- onChange,
- onBlur,
- placeholderText,
- children,
-}) {
- const [isOpen, setIsOpen] = useState(false);
-
- const onSelect = (event, selectedValue) => {
- if (selectedValue === 'none') {
- onChange([]);
- setIsOpen(false);
- return;
- }
- const index = value.indexOf(selectedValue);
- if (index === -1) {
- onChange(value.concat(selectedValue));
- } else {
- onChange(value.slice(0, index).concat(value.slice(index + 1)));
- }
- };
-
- const onToggle = (val) => {
- if (!val) {
- onBlur();
- }
- setIsOpen(val);
- };
-
- return (
-
- );
-}
-
-FrequencySelect.propTypes = {
- value: arrayOf(string).isRequired,
-};
-
-export { SelectOption, SelectVariant };
diff --git a/awx/ui/src/components/Schedule/shared/ScheduleForm.js b/awx/ui/src/components/Schedule/shared/ScheduleForm.js
deleted file mode 100644
index 43407d2737c2..000000000000
--- a/awx/ui/src/components/Schedule/shared/ScheduleForm.js
+++ /dev/null
@@ -1,598 +0,0 @@
-import React, { useEffect, useCallback, useState, useRef } from 'react';
-import { shape, func } from 'prop-types';
-import { DateTime } from 'luxon';
-import { t } from '@lingui/macro';
-import { Formik } from 'formik';
-import { RRule } from 'rrule';
-import { Button, Form, ActionGroup } from '@patternfly/react-core';
-import { Config } from 'contexts/Config';
-import { JobTemplatesAPI, SchedulesAPI, WorkflowJobTemplatesAPI } from 'api';
-import { dateToInputDateTime } from 'util/dates';
-import useRequest from 'hooks/useRequest';
-import { parseVariableField } from 'util/yaml';
-import ContentError from '../../ContentError';
-import ContentLoading from '../../ContentLoading';
-import { FormSubmitError } from '../../FormField';
-import { FormColumnLayout, FormFullWidthLayout } from '../../FormLayout';
-import SchedulePromptableFields from './SchedulePromptableFields';
-import ScheduleFormFields from './ScheduleFormFields';
-import UnsupportedScheduleForm from './UnsupportedScheduleForm';
-import parseRuleObj, { UnsupportedRRuleError } from './parseRuleObj';
-import buildRuleObj from './buildRuleObj';
-import buildRuleSet from './buildRuleSet';
-
-const NUM_DAYS_PER_FREQUENCY = {
- week: 7,
- month: 31,
- year: 365,
-};
-
-function ScheduleForm({
- hasDaysToKeepField,
- handleCancel,
- handleSubmit: submitSchedule,
- schedule,
- submitError,
- resource,
- launchConfig,
- surveyConfig,
- resourceDefaultCredentials,
-}) {
- const [isWizardOpen, setIsWizardOpen] = useState(false);
- const [isSaveDisabled, setIsSaveDisabled] = useState(false);
- const originalLabels = useRef([]);
- const originalInstanceGroups = useRef([]);
-
- let rruleError;
- const now = DateTime.now();
- const closestQuarterHour = DateTime.fromMillis(
- Math.ceil(now.ts / 900000) * 900000
- );
- const tomorrow = closestQuarterHour.plus({ days: 1 });
- const isTemplate =
- resource.type === 'workflow_job_template' ||
- resource.type === 'job_template';
- const {
- request: loadScheduleData,
- error: contentError,
- isLoading: contentLoading,
- result: { zoneOptions, zoneLinks, credentials },
- } = useRequest(
- useCallback(async () => {
- const { data } = await SchedulesAPI.readZoneInfo();
-
- let creds = [];
- let allLabels = [];
- let allInstanceGroups = [];
- if (schedule.id) {
- if (
- resource.type === 'job_template' &&
- launchConfig?.ask_credential_on_launch
- ) {
- const {
- data: { results },
- } = await SchedulesAPI.readCredentials(schedule.id);
- creds = results;
- }
- if (launchConfig?.ask_labels_on_launch) {
- const {
- data: { results },
- } = await SchedulesAPI.readAllLabels(schedule.id);
- allLabels = results;
- }
- if (
- resource.type === 'job_template' &&
- launchConfig?.ask_instance_groups_on_launch
- ) {
- const {
- data: { results },
- } = await SchedulesAPI.readInstanceGroups(schedule.id);
- allInstanceGroups = results;
- }
- } else {
- if (resource.type === 'job_template') {
- if (launchConfig?.ask_labels_on_launch) {
- const {
- data: { results },
- } = await JobTemplatesAPI.readAllLabels(resource.id);
- allLabels = results;
- }
- }
- if (
- resource.type === 'workflow_job_template' &&
- launchConfig?.ask_labels_on_launch
- ) {
- const {
- data: { results },
- } = await WorkflowJobTemplatesAPI.readAllLabels(resource.id);
- allLabels = results;
- }
- }
-
- const zones = (data.zones || []).map((zone) => ({
- value: zone,
- key: zone,
- label: zone,
- }));
-
- originalLabels.current = allLabels;
- originalInstanceGroups.current = allInstanceGroups;
-
- return {
- zoneOptions: zones,
- zoneLinks: data.links,
- credentials: creds,
- };
- }, [schedule, resource.id, resource.type, launchConfig]),
- {
- zonesOptions: [],
- zoneLinks: {},
- credentials: [],
- isLoading: true,
- }
- );
-
- useEffect(() => {
- loadScheduleData();
- }, [loadScheduleData]);
-
- const missingRequiredInventory = useCallback(() => {
- let missingInventory = false;
- if (
- launchConfig?.inventory_needed_to_start &&
- !schedule?.summary_fields?.inventory?.id
- ) {
- missingInventory = true;
- }
- return missingInventory;
- }, [launchConfig, schedule]);
-
- const hasMissingSurveyValue = useCallback(() => {
- let missingValues = false;
- if (launchConfig?.survey_enabled) {
- surveyConfig.spec.forEach((question) => {
- const hasDefaultValue = Boolean(question.default);
- const hasSchedule = Object.keys(schedule).length;
- const isRequired = question.required;
- if (isRequired && !hasDefaultValue) {
- if (!hasSchedule) {
- missingValues = true;
- } else {
- const hasMatchingKey = Object.keys(schedule?.extra_data).includes(
- question.variable
- );
- Object.values(schedule?.extra_data).forEach((value) => {
- if (!value || !hasMatchingKey) {
- missingValues = true;
- } else {
- missingValues = false;
- }
- });
- if (!Object.values(schedule.extra_data).length) {
- missingValues = true;
- }
- }
- }
- });
- }
- return missingValues;
- }, [launchConfig, schedule, surveyConfig]);
-
- const hasCredentialsThatPrompt = useCallback(() => {
- if (launchConfig?.ask_credential_on_launch) {
- if (Object.keys(schedule).length > 0) {
- const defaultCredsWithoutOverrides = [];
-
- const credentialHasOverride = (templateDefaultCred) => {
- let hasOverride = false;
- credentials.forEach((nodeCredential) => {
- if (
- templateDefaultCred.credential_type ===
- nodeCredential.credential_type
- ) {
- if (
- (!templateDefaultCred.vault_id &&
- !nodeCredential.inputs.vault_id) ||
- (templateDefaultCred.vault_id &&
- nodeCredential.inputs.vault_id &&
- templateDefaultCred.vault_id ===
- nodeCredential.inputs.vault_id)
- ) {
- hasOverride = true;
- }
- }
- });
-
- return hasOverride;
- };
-
- if (resourceDefaultCredentials) {
- resourceDefaultCredentials.forEach((defaultCred) => {
- if (!credentialHasOverride(defaultCred)) {
- defaultCredsWithoutOverrides.push(defaultCred);
- }
- });
- }
-
- return (
- credentials
- .concat(defaultCredsWithoutOverrides)
- .filter((credential) => {
- let credentialRequiresPass = false;
-
- Object.entries(credential.inputs).forEach(([key, value]) => {
- if (key !== 'vault_id' && value === 'ASK') {
- credentialRequiresPass = true;
- }
- });
-
- return credentialRequiresPass;
- }).length > 0
- );
- }
-
- return launchConfig?.defaults?.credentials
- ? launchConfig.defaults.credentials.filter(
- (credential) => credential?.passwords_needed.length > 0
- ).length > 0
- : false;
- }
-
- return false;
- }, [launchConfig, schedule, credentials, resourceDefaultCredentials]);
-
- useEffect(() => {
- if (
- isTemplate &&
- (missingRequiredInventory() ||
- hasMissingSurveyValue() ||
- hasCredentialsThatPrompt())
- ) {
- setIsSaveDisabled(true);
- }
- }, [
- isTemplate,
- hasMissingSurveyValue,
- missingRequiredInventory,
- hasCredentialsThatPrompt,
- ]);
-
- let showPromptButton = false;
-
- if (
- launchConfig &&
- (launchConfig.ask_inventory_on_launch ||
- launchConfig.ask_variables_on_launch ||
- launchConfig.ask_job_type_on_launch ||
- launchConfig.ask_limit_on_launch ||
- launchConfig.ask_credential_on_launch ||
- launchConfig.ask_scm_branch_on_launch ||
- launchConfig.ask_tags_on_launch ||
- launchConfig.ask_skip_tags_on_launch ||
- launchConfig.ask_execution_environment_on_launch ||
- launchConfig.ask_labels_on_launch ||
- launchConfig.ask_forks_on_launch ||
- launchConfig.ask_job_slice_count_on_launch ||
- launchConfig.ask_timeout_on_launch ||
- launchConfig.ask_instance_groups_on_launch ||
- launchConfig.survey_enabled ||
- launchConfig.inventory_needed_to_start ||
- launchConfig.variables_needed_to_start?.length > 0)
- ) {
- showPromptButton = true;
- }
- const [currentDate, time] = dateToInputDateTime(closestQuarterHour.toISO());
-
- const [tomorrowDate] = dateToInputDateTime(tomorrow.toISO());
- const initialFrequencyOptions = {
- minute: {
- interval: 1,
- end: 'never',
- occurrences: 1,
- endDate: tomorrowDate,
- endTime: time,
- },
- hour: {
- interval: 1,
- end: 'never',
- occurrences: 1,
- endDate: tomorrowDate,
- endTime: time,
- },
- day: {
- interval: 1,
- end: 'never',
- occurrences: 1,
- endDate: tomorrowDate,
- endTime: time,
- },
- week: {
- interval: 1,
- end: 'never',
- occurrences: 1,
- endDate: tomorrowDate,
- endTime: time,
- daysOfWeek: [],
- },
- month: {
- interval: 1,
- end: 'never',
- occurrences: 1,
- endDate: tomorrowDate,
- endTime: time,
- runOn: 'day',
- runOnTheOccurrence: 1,
- runOnTheDay: 'sunday',
- runOnDayNumber: 1,
- },
- year: {
- interval: 1,
- end: 'never',
- occurrences: 1,
- endDate: tomorrowDate,
- endTime: time,
- runOn: 'day',
- runOnTheOccurrence: 1,
- runOnTheDay: 'sunday',
- runOnTheMonth: 1,
- runOnDayMonth: 1,
- runOnDayNumber: 1,
- },
- };
-
- const initialValues = {
- description: schedule.description || '',
- frequency: [],
- exceptionFrequency: [],
- frequencyOptions: initialFrequencyOptions,
- exceptionOptions: initialFrequencyOptions,
- name: schedule.name || '',
- startDate: currentDate,
- startTime: time,
- timezone: schedule.timezone || now.zoneName,
- };
-
- if (hasDaysToKeepField) {
- let initialDaysToKeep = 30;
- if (schedule?.extra_data) {
- if (
- typeof schedule?.extra_data === 'string' &&
- schedule?.extra_data !== ''
- ) {
- initialDaysToKeep = parseVariableField(schedule?.extra_data).days;
- }
- if (typeof schedule?.extra_data === 'object') {
- initialDaysToKeep = schedule?.extra_data?.days;
- }
- }
- initialValues.daysToKeep = initialDaysToKeep;
- }
-
- let overriddenValues = {};
- if (schedule.rrule) {
- try {
- overriddenValues = parseRuleObj(schedule);
- } catch (error) {
- if (error instanceof UnsupportedRRuleError) {
- return (
-
- );
- }
- rruleError = error;
- }
- } else if (schedule.id) {
- rruleError = new Error(t`Schedule is missing rrule`);
- }
-
- if (contentError || rruleError) {
- return ;
- }
-
- if (contentLoading) {
- return ;
- }
-
- const validate = (values) => {
- const errors = {};
-
- values.frequency.forEach((freq) => {
- const options = values.frequencyOptions[freq];
- const freqErrors = {};
-
- if (
- (freq === 'month' || freq === 'year') &&
- options.runOn === 'day' &&
- (options.runOnDayNumber < 1 || options.runOnDayNumber > 31)
- ) {
- freqErrors.runOn = t`Please select a day number between 1 and 31.`;
- }
-
- if (options.end === 'after' && !options.occurrences) {
- freqErrors.occurrences = t`Please enter a number of occurrences.`;
- }
-
- if (options.end === 'onDate') {
- if (
- DateTime.fromFormat(
- `${values.startDate} ${values.startTime}`,
- 'yyyy-LL-dd h:mm a'
- ).toMillis() >=
- DateTime.fromFormat(
- `${options.endDate} ${options.endTime}`,
- 'yyyy-LL-dd h:mm a'
- ).toMillis()
- ) {
- freqErrors.endDate = t`Please select an end date/time that comes after the start date/time.`;
- }
-
- if (
- DateTime.fromISO(options.endDate)
- .diff(DateTime.fromISO(values.startDate), 'days')
- .toObject().days < NUM_DAYS_PER_FREQUENCY[freq]
- ) {
- const rule = new RRule(
- buildRuleObj({
- startDate: values.startDate,
- startTime: values.startTime,
- frequency: freq,
- ...options,
- })
- );
- if (rule.all().length === 0) {
- errors.startDate = t`Selected date range must have at least 1 schedule occurrence.`;
- freqErrors.endDate = t`Selected date range must have at least 1 schedule occurrence.`;
- }
- }
- }
- if (Object.keys(freqErrors).length > 0) {
- if (!errors.frequencyOptions) {
- errors.frequencyOptions = {};
- }
- errors.frequencyOptions[freq] = freqErrors;
- }
- });
-
- if (values.exceptionFrequency.length > 0 && !scheduleHasInstances(values)) {
- errors.exceptionFrequency = t`This schedule has no occurrences due to the selected exceptions.`;
- }
-
- return errors;
- };
-
- return (
-
- {() => (
- {
- submitSchedule(
- values,
- launchConfig,
- surveyConfig,
- originalInstanceGroups.current,
- originalLabels.current,
- credentials
- );
- }}
- validate={validate}
- >
- {(formik) => (
-
- )}
-
- )}
-
- );
-}
-
-ScheduleForm.propTypes = {
- handleCancel: func.isRequired,
- handleSubmit: func.isRequired,
- schedule: shape({}),
- submitError: shape(),
-};
-
-ScheduleForm.defaultProps = {
- schedule: {},
- submitError: null,
-};
-
-export default ScheduleForm;
-
-function scheduleHasInstances(values) {
- let rangeToCheck = 1;
- values.frequency.forEach((freq) => {
- if (NUM_DAYS_PER_FREQUENCY[freq] > rangeToCheck) {
- rangeToCheck = NUM_DAYS_PER_FREQUENCY[freq];
- }
- });
-
- const ruleSet = buildRuleSet(values, true);
- const startDate = DateTime.fromISO(values.startDate);
- const endDate = startDate.plus({ days: rangeToCheck });
- const instances = ruleSet.between(
- startDate.toJSDate(),
- endDate.toJSDate(),
- true,
- (date, i) => i === 0
- );
-
- return instances.length > 0;
-}
diff --git a/awx/ui/src/components/Schedule/shared/ScheduleForm.test.js b/awx/ui/src/components/Schedule/shared/ScheduleForm.test.js
deleted file mode 100644
index 61c631309338..000000000000
--- a/awx/ui/src/components/Schedule/shared/ScheduleForm.test.js
+++ /dev/null
@@ -1,1577 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { DateTime } from 'luxon';
-
-import { dateToInputDateTime } from 'util/dates';
-import { SchedulesAPI, JobTemplatesAPI, InventoriesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import ScheduleForm from './ScheduleForm';
-
-jest.mock('../../../api/models/Schedules');
-jest.mock('../../../api/models/JobTemplates');
-jest.mock('../../../api/models/Inventories');
-
-const credentials = {
- data: {
- results: [
- {
- id: 1,
- kind: 'cloud',
- name: 'Cred 1',
- url: 'www.google.com',
- inputs: {},
- },
- { id: 2, kind: 'ssh', name: 'Cred 2', url: 'www.google.com', inputs: {} },
- {
- id: 3,
- kind: 'Ansible',
- name: 'Cred 3',
- url: 'www.google.com',
- inputs: {},
- },
- {
- id: 4,
- kind: 'Machine',
- name: 'Cred 4',
- url: 'www.google.com',
- inputs: {},
- },
- {
- id: 5,
- kind: 'Machine',
- name: 'Cred 5',
- url: 'www.google.com',
- inputs: {},
- },
- ],
- },
-};
-const launchData = {
- data: {
- can_start_without_user_input: false,
- passwords_needed_to_start: [],
- ask_scm_branch_on_launch: false,
- ask_variables_on_launch: false,
- ask_tags_on_launch: false,
- ask_diff_mode_on_launch: false,
- ask_skip_tags_on_launch: false,
- ask_job_type_on_launch: false,
- ask_limit_on_launch: false,
- ask_verbosity_on_launch: false,
- ask_inventory_on_launch: true,
- ask_credential_on_launch: false,
- ask_execution_environment_on_launch: false,
- ask_labels_on_launch: false,
- ask_forks_on_launch: false,
- ask_job_slice_count_on_launch: false,
- ask_timeout_on_launch: false,
- ask_instance_groups_on_launch: false,
- survey_enabled: false,
- variables_needed_to_start: [],
- credential_needed_to_start: false,
- inventory_needed_to_start: true,
- job_template_data: {
- name: 'Demo Job Template',
- id: 7,
- description: '',
- },
- },
-};
-const mockSchedule = {
- rrule:
- 'DTSTART;TZID=America/New_York:20200402T144500 RRULE:INTERVAL=1;COUNT=1;FREQ=MINUTELY',
- id: 27,
- type: 'schedule',
- url: '/api/v2/schedules/27/',
- summary_fields: {
- user_capabilities: {
- edit: true,
- delete: true,
- },
- },
- created: '2020-04-02T18:43:12.664142Z',
- modified: '2020-04-02T18:43:12.664185Z',
- name: 'mock schedule',
- description: 'test description',
- extra_data: {},
- inventory: 1,
- scm_branch: null,
- job_type: null,
- job_tags: null,
- skip_tags: null,
- limit: null,
- diff_mode: null,
- verbosity: null,
- unified_job_template: 11,
- enabled: true,
- dtstart: '2020-04-02T18:45:00Z',
- dtend: '2020-04-02T18:45:00Z',
- next_run: '2020-04-02T18:45:00Z',
- timezone: 'America/New_York',
- until: '',
-};
-
-let wrapper;
-
-const defaultFieldsVisible = (isExceptionsVisible) => {
- expect(wrapper.find('FormGroup[label="Name"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Description"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Start date/time"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Local time zone"]').length).toBe(1);
- expect(
- wrapper.find('FormGroup[label="Local time zone"]').find('HelpIcon').length
- ).toBe(1);
- if (isExceptionsVisible) {
- expect(wrapper.find('FrequencySelect').length).toBe(2);
- } else {
- expect(wrapper.find('FrequencySelect').length).toBe(1);
- }
-};
-
-const nonRRuleValuesMatch = () => {
- expect(wrapper.find('input#schedule-name').prop('value')).toBe(
- 'mock schedule'
- );
- expect(wrapper.find('input#schedule-description').prop('value')).toBe(
- 'test description'
- );
-
- expect(
- wrapper.find('DatePicker[aria-label="Start date"]').prop('value')
- ).toBe('2020-04-02');
- expect(wrapper.find('TimePicker[aria-label="Start time"]').prop('time')).toBe(
- '2:45 PM'
- );
- expect(wrapper.find('select#schedule-timezone').prop('value')).toBe(
- 'America/New_York'
- );
-};
-
-describe('', () => {
- describe('Error', () => {
- test('should display error when error occurs while loading', async () => {
- SchedulesAPI.readZoneInfo.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'get',
- url: '/api/v2/schedules/zoneinfo',
- },
- data: 'An error occurred',
- status: 500,
- },
- })
- );
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
- expect(wrapper.find('ContentError').length).toBe(1);
- });
- });
-
- describe('Cancel', () => {
- test('should make the appropriate callback', async () => {
- const handleCancel = jest.fn();
- JobTemplatesAPI.readLaunch.mockResolvedValue(launchData);
-
- SchedulesAPI.readCredentials.mockResolvedValue(credentials);
- SchedulesAPI.readZoneInfo.mockResolvedValue({
- data: [
- {
- name: 'America/New_York',
- },
- ],
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('button[aria-label="Cancel"]').simulate('click');
- });
- expect(handleCancel).toHaveBeenCalledTimes(1);
- });
- });
-
- describe('Prompted Schedule', () => {
- let promptWrapper;
-
- beforeEach(async () => {
- SchedulesAPI.readZoneInfo.mockResolvedValue({
- data: [
- {
- name: 'America/New_York',
- },
- ],
- });
- await act(async () => {
- promptWrapper = mountWithContexts(
-
- );
- });
- waitForElement(
- promptWrapper,
- 'Button[aria-label="Prompt"]',
- (el) => el.length > 0
- );
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should open prompt modal with proper steps and default values', async () => {
- await act(async () =>
- promptWrapper.find('Button[aria-label="Prompt"]').prop('onClick')()
- );
- promptWrapper.update();
- waitForElement(promptWrapper, 'Wizard', (el) => el.length > 0);
- expect(promptWrapper.find('Wizard').length).toBe(1);
- expect(promptWrapper.find('StepName#inventory-step').length).toBe(2);
- expect(promptWrapper.find('StepName#preview-step').length).toBe(1);
- expect(promptWrapper.find('WizardNavItem').length).toBe(2);
- });
-
- test('should render disabled save button due to missing required surevy values', () => {
- expect(
- promptWrapper.find('Button[aria-label="Save"]').prop('isDisabled')
- ).toBe(true);
- });
-
- test('should update prompt modal data', async () => {
- InventoriesAPI.read.mockResolvedValue({
- data: {
- count: 2,
- results: [
- {
- name: 'Foo',
- id: 1,
- url: '',
- },
- {
- name: 'Bar',
- id: 2,
- url: '',
- },
- ],
- },
- });
- InventoriesAPI.readOptions.mockResolvedValue({
- data: {
- related_search_fields: [],
- actions: {
- GET: {
- filterable: true,
- },
- },
- },
- });
-
- await act(async () =>
- promptWrapper.find('Button[aria-label="Prompt"]').prop('onClick')()
- );
- promptWrapper.update();
- expect(promptWrapper.find('WizardNavItem').at(0).prop('isCurrent')).toBe(
- true
- );
- await act(async () => {
- promptWrapper
- .find('td#check-action-item-1')
- .find('input')
- .simulate('click');
- });
- promptWrapper.update();
- expect(
- promptWrapper
- .find('td#check-action-item-1')
- .find('input')
- .prop('checked')
- ).toBe(true);
- await act(async () =>
- promptWrapper.find('WizardFooterInternal').prop('onNext')()
- );
- promptWrapper.update();
- expect(promptWrapper.find('WizardNavItem').at(1).prop('isCurrent')).toBe(
- true
- );
- await act(async () =>
- promptWrapper.find('WizardFooterInternal').prop('onNext')()
- );
- promptWrapper.update();
- expect(promptWrapper.find('Wizard').length).toBe(0);
- });
-
- test('should render prompt button with disabled save button', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- waitForElement(
- wrapper,
- 'Button[aria-label="Prompt"]',
- (el) => el.length > 0
- );
- expect(wrapper.find('Button[aria-label="Save"]').prop('isDisabled')).toBe(
- true
- );
- });
- });
-
- describe('Add', () => {
- beforeAll(async () => {
- SchedulesAPI.readZoneInfo.mockResolvedValue({
- data: [
- {
- name: 'America/New_York',
- },
- ],
- });
-
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- test('initially renders expected fields and values', () => {
- const now = DateTime.now();
- const closestQuarterHour = DateTime.fromMillis(
- Math.ceil(now.ts / 900000) * 900000
- );
- const [date, time] = dateToInputDateTime(closestQuarterHour.toISO());
- expect(wrapper.find('ScheduleForm').length).toBe(1);
- defaultFieldsVisible();
- expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="On days"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="Run on"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="End"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="End date/time"]').length).toBe(0);
-
- expect(wrapper.find('input#schedule-name').prop('value')).toBe('');
- expect(wrapper.find('input#schedule-description').prop('value')).toBe('');
-
- expect(wrapper.find('DatePicker').prop('value')).toMatch(`${date}`);
- expect(wrapper.find('TimePicker').prop('time')).toMatch(`${time}`);
- expect(wrapper.find('select#schedule-timezone').prop('value')).toBe(
- 'UTC'
- );
- expect(
- wrapper.find('FrequencySelect#schedule-frequency').prop('value')
- ).toEqual([]);
- });
-
- test('correct frequency details fields and values shown when frequency changed to minute', async () => {
- const runFrequencySelect = wrapper.find(
- 'FrequencySelect#schedule-frequency'
- );
- await act(async () => {
- runFrequencySelect.invoke('onChange')(['minute']);
- });
- wrapper.update();
- defaultFieldsVisible(true);
- expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="End"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="On days"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="Run on"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="End date/time"]').length).toBe(0);
-
- expect(
- wrapper
- .find('input#schedule-run-every-frequencyOptions-minute')
- .prop('value')
- ).toBe(1);
- expect(
- wrapper.find('input#end-never-frequencyOptions-minute').prop('checked')
- ).toBe(true);
- expect(
- wrapper.find('input#end-after-frequencyOptions-minute').prop('checked')
- ).toBe(false);
- expect(
- wrapper
- .find('input#end-on-date-frequencyOptions-minute')
- .prop('checked')
- ).toBe(false);
- });
-
- test('correct frequency details fields and values shown when frequency changed to hour', async () => {
- const runFrequencySelect = wrapper.find(
- 'FrequencySelect#schedule-frequency'
- );
- await act(async () => {
- runFrequencySelect.invoke('onChange')(['hour']);
- });
- wrapper.update();
- defaultFieldsVisible(true);
- expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="End"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="On days"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="Run on"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="End date/time"]').length).toBe(0);
-
- expect(
- wrapper
- .find('input#schedule-run-every-frequencyOptions-hour')
- .prop('value')
- ).toBe(1);
- expect(
- wrapper.find('input#end-never-frequencyOptions-hour').prop('checked')
- ).toBe(true);
- expect(
- wrapper.find('input#end-after-frequencyOptions-hour').prop('checked')
- ).toBe(false);
- expect(
- wrapper.find('input#end-on-date-frequencyOptions-hour').prop('checked')
- ).toBe(false);
- });
-
- test('correct frequency details fields and values shown when frequency changed to day', async () => {
- const runFrequencySelect = wrapper.find(
- 'FrequencySelect#schedule-frequency'
- );
- await act(async () => {
- runFrequencySelect.invoke('onChange')(['day']);
- });
- wrapper.update();
- defaultFieldsVisible(true);
- expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="End"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="On days"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="Run on"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="End date/time"]').length).toBe(0);
-
- expect(
- wrapper
- .find('input#schedule-run-every-frequencyOptions-day')
- .prop('value')
- ).toBe(1);
- expect(
- wrapper.find('input#end-never-frequencyOptions-day').prop('checked')
- ).toBe(true);
- expect(
- wrapper.find('input#end-after-frequencyOptions-day').prop('checked')
- ).toBe(false);
- expect(
- wrapper.find('input#end-on-date-frequencyOptions-day').prop('checked')
- ).toBe(false);
- });
-
- test('correct frequency details fields and values shown when frequency changed to week', async () => {
- const runFrequencySelect = wrapper.find(
- 'FrequencySelect#schedule-frequency'
- );
- await act(async () => {
- runFrequencySelect.invoke('onChange')(['week']);
- });
- wrapper.update();
- defaultFieldsVisible(true);
- expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="End"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="On days"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Run on"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="End date/time"]').length).toBe(0);
-
- expect(
- wrapper
- .find('input#schedule-run-every-frequencyOptions-week')
- .prop('value')
- ).toBe(1);
- expect(
- wrapper.find('input#end-never-frequencyOptions-week').prop('checked')
- ).toBe(true);
- expect(
- wrapper.find('input#end-after-frequencyOptions-week').prop('checked')
- ).toBe(false);
- expect(
- wrapper.find('input#end-on-date-frequencyOptions-week').prop('checked')
- ).toBe(false);
- });
-
- test('correct frequency details fields and values shown when frequency changed to month', async () => {
- const runFrequencySelect = wrapper.find(
- 'FrequencySelect#schedule-frequency'
- );
- await act(async () => {
- runFrequencySelect.invoke('onChange')(['month']);
- });
- wrapper.update();
- defaultFieldsVisible(true);
- expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="End"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="On days"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="Run on"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="End date/time"]').length).toBe(0);
-
- expect(
- wrapper
- .find('input#schedule-run-every-frequencyOptions-month')
- .prop('value')
- ).toBe(1);
- expect(
- wrapper.find('input#end-never-frequencyOptions-month').prop('checked')
- ).toBe(true);
- expect(
- wrapper.find('input#end-after-frequencyOptions-month').prop('checked')
- ).toBe(false);
- expect(
- wrapper.find('input#end-on-date-frequencyOptions-month').prop('checked')
- ).toBe(false);
- expect(
- wrapper
- .find('input#schedule-run-on-day-frequencyOptions-month')
- .prop('checked')
- ).toBe(true);
- expect(
- wrapper
- .find('input#schedule-run-on-day-number-frequencyOptions-month')
- .prop('value')
- ).toBe(1);
- expect(
- wrapper
- .find('input#schedule-run-on-the-frequencyOptions-month')
- .prop('checked')
- ).toBe(false);
- expect(wrapper.find('select#schedule-run-on-day-month').length).toBe(0);
- expect(wrapper.find('select#schedule-run-on-the-month').length).toBe(0);
- });
-
- test('correct frequency details fields and values shown when frequency changed to year', async () => {
- const runFrequencySelect = wrapper.find(
- 'FrequencySelect#schedule-frequency'
- );
- await act(async () => {
- runFrequencySelect.invoke('onChange')(['year']);
- });
- wrapper.update();
- defaultFieldsVisible(true);
- expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="End"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="On days"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="Run on"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="End date/time"]').length).toBe(0);
-
- expect(
- wrapper
- .find('input#schedule-run-every-frequencyOptions-year')
- .prop('value')
- ).toBe(1);
- expect(
- wrapper.find('input#end-never-frequencyOptions-year').prop('checked')
- ).toBe(true);
- expect(
- wrapper.find('input#end-after-frequencyOptions-year').prop('checked')
- ).toBe(false);
- expect(
- wrapper.find('input#end-on-date-frequencyOptions-year').prop('checked')
- ).toBe(false);
- expect(
- wrapper
- .find('input#schedule-run-on-day-frequencyOptions-year')
- .prop('checked')
- ).toBe(true);
- expect(
- wrapper
- .find('input#schedule-run-on-day-number-frequencyOptions-year')
- .prop('value')
- ).toBe(1);
- expect(
- wrapper
- .find('input#schedule-run-on-the-frequencyOptions-year')
- .prop('checked')
- ).toBe(false);
- expect(
- wrapper.find('select#schedule-run-on-day-month-frequencyOptions-year')
- .length
- ).toBe(1);
- expect(
- wrapper.find('select#schedule-run-on-the-month-frequencyOptions-year')
- .length
- ).toBe(1);
- });
-
- test('occurrences field properly shown when end after selection is made', async () => {
- await act(async () => {
- wrapper.find('FrequencySelect#schedule-frequency').invoke('onChange')([
- 'minute',
- ]);
- });
- wrapper.update();
- await act(async () => {
- wrapper
- .find('Radio#end-after-frequencyOptions-minute')
- .invoke('onChange')('after', {
- target: { name: 'frequencyOptions.minute.end' },
- });
- });
- wrapper.update();
- expect(
- wrapper.find('input#end-never-frequencyOptions-minute').prop('checked')
- ).toBe(false);
- expect(
- wrapper.find('input#end-after-frequencyOptions-minute').prop('checked')
- ).toBe(true);
- expect(
- wrapper
- .find('input#end-on-date-frequencyOptions-minute')
- .prop('checked')
- ).toBe(false);
- expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(1);
- expect(
- wrapper
- .find('input#schedule-occurrences-frequencyOptions-minute')
- .prop('value')
- ).toBe(1);
- await act(async () => {
- wrapper
- .find('Radio#end-never-frequencyOptions-minute')
- .invoke('onChange')('never', {
- target: { name: 'frequencyOptions.minute.end' },
- });
- });
- wrapper.update();
- expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(0);
- });
-
- test('error shown when end date/time comes before start date/time', async () => {
- await act(async () => {
- wrapper.find('FrequencySelect#schedule-frequency').invoke('onChange')([
- 'minute',
- ]);
- });
- wrapper.update();
- expect(
- wrapper.find('input#end-never-frequencyOptions-minute').prop('checked')
- ).toBe(true);
- expect(
- wrapper.find('input#end-after-frequencyOptions-minute').prop('checked')
- ).toBe(false);
- expect(
- wrapper
- .find('input#end-on-date-frequencyOptions-minute')
- .prop('checked')
- ).toBe(false);
- await act(async () => {
- wrapper
- .find('Radio#end-on-date-frequencyOptions-minute')
- .invoke('onChange')('onDate', {
- target: { name: 'frequencyOptions.minute.end' },
- });
- });
- wrapper.update();
- expect(
- wrapper.find('input#end-never-frequencyOptions-minute').prop('checked')
- ).toBe(false);
- expect(
- wrapper.find('input#end-after-frequencyOptions-minute').prop('checked')
- ).toBe(false);
- expect(
- wrapper
- .find('input#end-on-date-frequencyOptions-minute')
- .prop('checked')
- ).toBe(true);
- await act(async () => {
- wrapper.find('DatePicker[aria-label="End date"]').prop('onChange')(
- '2020-03-14',
- new Date('2020-03-14')
- );
- });
-
- wrapper.update();
- expect(
- wrapper
- .find('FormGroup[data-cy="schedule-End date/time"]')
- .prop('helperTextInvalid')
- ).toBe(
- 'Please select an end date/time that comes after the start date/time.'
- );
- });
-
- test('should create schedule with the same start and end date provided that the end date is at a later time', async () => {
- const today = DateTime.now().toFormat('yyyy-LL-dd');
- const laterTime = DateTime.now().plus({ hours: 1 }).toFormat('h:mm a');
- await act(async () => {
- wrapper.find('DatePicker[aria-label="End date"]').prop('onChange')(
- today,
- new Date(today)
- );
- });
- wrapper.update();
- expect(
- wrapper
- .find('FormGroup[data-cy="schedule-End date/time"]')
- .prop('helperTextInvalid')
- ).toBe(
- 'Please select an end date/time that comes after the start date/time.'
- );
- await act(async () => {
- wrapper.find('TimePicker[aria-label="End time"]').prop('onChange')(
- laterTime
- );
- });
- wrapper.update();
- expect(
- wrapper
- .find('FormGroup[data-cy="schedule-End date/time"]')
- .prop('helperTextInvalid')
- ).toBe(undefined);
- });
-
- test('error shown when on day number is not between 1 and 31', async () => {
- await act(async () => {
- wrapper.find('FrequencySelect#schedule-frequency').invoke('onChange')([
- 'month',
- ]);
- });
- wrapper.update();
-
- act(() => {
- wrapper
- .find('input#schedule-run-on-day-number-frequencyOptions-month')
- .simulate('change', {
- target: {
- value: 32,
- name: 'frequencyOptions.month.runOnDayNumber',
- },
- });
- });
- wrapper.update();
-
- expect(
- wrapper
- .find('input#schedule-run-on-day-number-frequencyOptions-month')
- .prop('value')
- ).toBe(32);
-
- await act(async () => {
- wrapper.find('button[aria-label="Save"]').simulate('click');
- });
- wrapper.update();
-
- expect(
- wrapper.find('#schedule-run-on-frequencyOptions-month-helper').text()
- ).toBe('Please select a day number between 1 and 31.');
- });
- });
-
- describe('Edit', () => {
- beforeEach(async () => {
- SchedulesAPI.readZoneInfo.mockResolvedValue({
- data: [
- {
- name: 'America/New_York',
- },
- ],
- });
-
- SchedulesAPI.readCredentials.mockResolvedValue(credentials);
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should make API calls to fetch credentials, labels, and zone info', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(SchedulesAPI.readZoneInfo).toBeCalled();
- expect(SchedulesAPI.readCredentials).toBeCalledWith(27);
- expect(SchedulesAPI.readAllLabels).toBeCalledWith(27);
- });
-
- test('should not call API to get credentials ', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
-
- expect(SchedulesAPI.readCredentials).not.toBeCalled();
- });
-
- test('should render prompt button with enabled save button for project', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- waitForElement(
- wrapper,
- 'Button[aria-label="Prompt"]',
- (el) => el.length > 0
- );
-
- expect(wrapper.find('Button[aria-label="Save"]').prop('isDisabled')).toBe(
- false
- );
- });
-
- test('initially renders expected fields and values with existing schedule that runs once', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
- expect(wrapper.find('ScheduleForm').length).toBe(1);
- defaultFieldsVisible();
- expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="On days"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="Run on"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="End"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="End date/time"]').length).toBe(0);
-
- nonRRuleValuesMatch();
- expect(
- wrapper.find('FrequencySelect#schedule-frequency').prop('value')
- ).toEqual([]);
- });
-
- test('initially renders expected fields and values with existing schedule that runs every 10 minutes', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
-
- expect(wrapper.find('ScheduleForm').length).toBe(1);
- defaultFieldsVisible(true);
- expect(wrapper.find('FormGroup[label="End"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="On days"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="Run on"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="End date/time"]').length).toBe(0);
-
- nonRRuleValuesMatch();
- expect(
- wrapper.find('FrequencySelect#schedule-frequency').prop('value')
- ).toEqual(['minute']);
- expect(
- wrapper
- .find('input#schedule-run-every-frequencyOptions-minute')
- .prop('value')
- ).toBe(10);
- expect(
- wrapper.find('input#end-never-frequencyOptions-minute').prop('checked')
- ).toBe(true);
- expect(
- wrapper.find('input#end-after-frequencyOptions-minute').prop('checked')
- ).toBe(false);
- expect(
- wrapper
- .find('input#end-on-date-frequencyOptions-minute')
- .prop('checked')
- ).toBe(false);
- });
-
- test('initially renders expected fields and values with existing schedule that runs every hour 10 times', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
-
- expect(wrapper.find('ScheduleForm').length).toBe(1);
- defaultFieldsVisible(true);
- expect(wrapper.find('FormGroup[label="End"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="On days"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="Run on"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="End date/time"]').length).toBe(0);
-
- nonRRuleValuesMatch();
- expect(
- wrapper.find('FrequencySelect#schedule-frequency').prop('value')
- ).toEqual(['hour']);
- expect(
- wrapper
- .find('input#schedule-run-every-frequencyOptions-hour')
- .prop('value')
- ).toBe(1);
- expect(
- wrapper.find('input#end-never-frequencyOptions-hour').prop('checked')
- ).toBe(false);
- expect(
- wrapper.find('input#end-after-frequencyOptions-hour').prop('checked')
- ).toBe(true);
- expect(
- wrapper.find('input#end-on-date-frequencyOptions-hour').prop('checked')
- ).toBe(false);
- expect(
- wrapper
- .find('input#schedule-occurrences-frequencyOptions-hour')
- .prop('value')
- ).toBe(10);
- });
-
- test('initially renders expected fields and values with existing schedule that runs every day', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
-
- expect(wrapper.find('ScheduleForm').length).toBe(1);
- defaultFieldsVisible(true);
- expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="End"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="On days"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="Run on"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="End date/time"]').length).toBe(0);
-
- nonRRuleValuesMatch();
- expect(
- wrapper.find('FrequencySelect#schedule-frequency').prop('value')
- ).toEqual(['day']);
- expect(
- wrapper.find('input#end-never-frequencyOptions-day').prop('checked')
- ).toBe(true);
- expect(
- wrapper.find('input#end-after-frequencyOptions-day').prop('checked')
- ).toBe(false);
- expect(
- wrapper.find('input#end-on-date-frequencyOptions-day').prop('checked')
- ).toBe(false);
- expect(
- wrapper
- .find('input#schedule-run-every-frequencyOptions-day')
- .prop('value')
- ).toBe(1);
- });
-
- test('initially renders expected fields and values with existing schedule that runs every week on m/w/f until Jan 1, 2020', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
-
- expect(wrapper.find('ScheduleForm').length).toBe(1);
- defaultFieldsVisible(true);
- expect(wrapper.find('FormGroup[label="End"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="End date/time"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="On days"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="Run on"]').length).toBe(0);
-
- nonRRuleValuesMatch();
- expect(
- wrapper.find('FrequencySelect#schedule-frequency').prop('value')
- ).toEqual(['week']);
- expect(
- wrapper
- .find('input#schedule-run-every-frequencyOptions-week')
- .prop('value')
- ).toBe(1);
- expect(
- wrapper.find('input#end-never-frequencyOptions-week').prop('checked')
- ).toBe(false);
- expect(
- wrapper.find('input#end-after-frequencyOptions-week').prop('checked')
- ).toBe(false);
- expect(
- wrapper.find('input#end-on-date-frequencyOptions-week').prop('checked')
- ).toBe(true);
- expect(
- wrapper
- .find('input#schedule-days-of-week-sun-frequencyOptions-week')
- .prop('checked')
- ).toBe(false);
- expect(
- wrapper
- .find('input#schedule-days-of-week-mon-frequencyOptions-week')
- .prop('checked')
- ).toBe(true);
- expect(
- wrapper
- .find('input#schedule-days-of-week-tue-frequencyOptions-week')
- .prop('checked')
- ).toBe(false);
- expect(
- wrapper
- .find('input#schedule-days-of-week-wed-frequencyOptions-week')
- .prop('checked')
- ).toBe(true);
- expect(
- wrapper
- .find('input#schedule-days-of-week-thu-frequencyOptions-week')
- .prop('checked')
- ).toBe(false);
- expect(
- wrapper
- .find('input#schedule-days-of-week-fri-frequencyOptions-week')
- .prop('checked')
- ).toBe(true);
- expect(
- wrapper
- .find('input#schedule-days-of-week-sat-frequencyOptions-week')
- .prop('checked')
- ).toBe(false);
- expect(
- wrapper.find('DatePicker[aria-label="End date"]').prop('value')
- ).toBe('2021-01-01');
- expect(
- wrapper.find('TimePicker[aria-label="End time"]').prop('value')
- ).toBe('12:00 AM');
- });
-
- test('initially renders expected fields and values with existing schedule that runs every month on the last weekday', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
-
- expect(wrapper.find('ScheduleForm').length).toBe(1);
- defaultFieldsVisible(true);
- expect(wrapper.find('FormGroup[label="End"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Run on"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="End date/time"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="On days"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(0);
-
- nonRRuleValuesMatch();
- expect(
- wrapper.find('FrequencySelect#schedule-frequency').prop('value')
- ).toEqual(['month']);
- expect(
- wrapper.find('input#end-never-frequencyOptions-month').prop('checked')
- ).toBe(true);
- expect(
- wrapper.find('input#end-after-frequencyOptions-month').prop('checked')
- ).toBe(false);
- expect(
- wrapper.find('input#end-on-date-frequencyOptions-month').prop('checked')
- ).toBe(false);
- expect(
- wrapper
- .find('input#schedule-run-every-frequencyOptions-month')
- .prop('value')
- ).toBe(1);
- expect(
- wrapper
- .find('input#schedule-run-on-day-frequencyOptions-month')
- .prop('checked')
- ).toBe(false);
- expect(
- wrapper
- .find('input#schedule-run-on-the-frequencyOptions-month')
- .prop('checked')
- ).toBe(true);
- expect(
- wrapper
- .find('select#schedule-run-on-the-occurrence-frequencyOptions-month')
- .prop('value')
- ).toBe(-1);
- expect(
- wrapper
- .find('select#schedule-run-on-the-day-frequencyOptions-month')
- .prop('value')
- ).toBe('weekday');
- });
-
- test('initially renders expected fields and values with existing schedule that runs every year on the May 6', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
-
- expect(wrapper.find('ScheduleForm').length).toBe(1);
- defaultFieldsVisible(true);
- expect(wrapper.find('FormGroup[label="End"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Run on"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="End date/time"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="On days"]').length).toBe(0);
- expect(wrapper.find('FormGroup[label="Occurrences"]').length).toBe(0);
-
- nonRRuleValuesMatch();
- expect(
- wrapper.find('FrequencySelect#schedule-frequency').prop('value')
- ).toEqual(['year']);
- expect(
- wrapper.find('input#end-never-frequencyOptions-year').prop('checked')
- ).toBe(true);
- expect(
- wrapper.find('input#end-after-frequencyOptions-year').prop('checked')
- ).toBe(false);
- expect(
- wrapper.find('input#end-on-date-frequencyOptions-year').prop('checked')
- ).toBe(false);
- expect(
- wrapper
- .find('input#schedule-run-every-frequencyOptions-year')
- .prop('value')
- ).toBe(1);
- expect(
- wrapper
- .find('input#schedule-run-on-day-frequencyOptions-year')
- .prop('checked')
- ).toBe(true);
- expect(
- wrapper
- .find('input#schedule-run-on-the-frequencyOptions-year')
- .prop('checked')
- ).toBe(false);
- expect(
- wrapper
- .find('select#schedule-run-on-day-month-frequencyOptions-year')
- .prop('value')
- ).toBe(5);
- expect(
- wrapper
- .find('input#schedule-run-on-day-number-frequencyOptions-year')
- .prop('value')
- ).toBe(6);
- });
- });
-});
diff --git a/awx/ui/src/components/Schedule/shared/ScheduleFormFields.js b/awx/ui/src/components/Schedule/shared/ScheduleFormFields.js
deleted file mode 100644
index adf784d30223..000000000000
--- a/awx/ui/src/components/Schedule/shared/ScheduleFormFields.js
+++ /dev/null
@@ -1,207 +0,0 @@
-import React, { useState } from 'react';
-import { useField } from 'formik';
-import { FormGroup, Title } from '@patternfly/react-core';
-import { t } from '@lingui/macro';
-import styled from 'styled-components';
-import 'styled-components/macro';
-import FormField from 'components/FormField';
-import { required } from 'util/validators';
-import { useConfig } from 'contexts/Config';
-import Popover from '../../Popover';
-import AnsibleSelect from '../../AnsibleSelect';
-import FrequencySelect, { SelectOption } from './FrequencySelect';
-import getHelpText from '../../../screens/Template/shared/JobTemplate.helptext';
-import { SubFormLayout, FormColumnLayout } from '../../FormLayout';
-import FrequencyDetailSubform from './FrequencyDetailSubform';
-import DateTimePicker from './DateTimePicker';
-import sortFrequencies from './sortFrequencies';
-
-const SelectClearOption = styled(SelectOption)`
- & > input[type='checkbox'] {
- display: none;
- }
-`;
-
-export default function ScheduleFormFields({
- hasDaysToKeepField,
- zoneOptions,
- zoneLinks,
-}) {
- const helpText = getHelpText();
- const [timezone, timezoneMeta] = useField({
- name: 'timezone',
- validate: required(t`Select a value for this field`),
- });
- const [frequency, frequencyMeta, frequencyHelper] = useField({
- name: 'frequency',
- validate: required(t`Select a value for this field`),
- });
- const [timezoneMessage, setTimezoneMessage] = useState('');
- const warnLinkedTZ = (event, selectedValue) => {
- if (zoneLinks[selectedValue]) {
- setTimezoneMessage(
- t`Warning: ${selectedValue} is a link to ${zoneLinks[selectedValue]} and will be saved as that.`
- );
- } else {
- setTimezoneMessage('');
- }
- timezone.onChange(event, selectedValue);
- };
- let timezoneValidatedStatus = 'default';
- if (timezoneMeta.touched && timezoneMeta.error) {
- timezoneValidatedStatus = 'error';
- } else if (timezoneMessage) {
- timezoneValidatedStatus = 'warning';
- }
- const config = useConfig();
-
- const [exceptionFrequency, exceptionFrequencyMeta, exceptionFrequencyHelper] =
- useField({
- name: 'exceptionFrequency',
- validate: required(t`Select a value for this field`),
- });
-
- const updateFrequency = (setFrequency) => (values) => {
- setFrequency(values.sort(sortFrequencies));
- };
-
- return (
- <>
-
-
-
- }
- >
-
-
-
-
- {t`None (run once)`}
- {t`Minute`}
- {t`Hour`}
- {t`Day`}
- {t`Week`}
- {t`Month`}
- {t`Year`}
-
-
- {hasDaysToKeepField ? (
-
- ) : null}
- {frequency.value.length ? (
-
-
- {t`Frequency Details`}
-
- {frequency.value.map((val) => (
-
-
-
- ))}
- {t`Exceptions`}
-
-
-
- {t`None`}
- {t`Minute`}
- {t`Hour`}
- {t`Day`}
- {t`Week`}
- {t`Month`}
- {t`Year`}
-
-
-
- {exceptionFrequency.value.map((val) => (
-
-
-
- ))}
-
- ) : null}
- >
- );
-}
diff --git a/awx/ui/src/components/Schedule/shared/SchedulePromptableFields.js b/awx/ui/src/components/Schedule/shared/SchedulePromptableFields.js
deleted file mode 100644
index d0faf3248d87..000000000000
--- a/awx/ui/src/components/Schedule/shared/SchedulePromptableFields.js
+++ /dev/null
@@ -1,132 +0,0 @@
-import React, { useState } from 'react';
-import { ExpandableSection, Wizard } from '@patternfly/react-core';
-import { t } from '@lingui/macro';
-import { useFormikContext } from 'formik';
-import { useDismissableError } from 'hooks/useRequest';
-import AlertModal from '../../AlertModal';
-import ContentError from '../../ContentError';
-import ContentLoading from '../../ContentLoading';
-import useSchedulePromptSteps from './useSchedulePromptSteps';
-
-function SchedulePromptableFields({
- schedule,
- surveyConfig,
- launchConfig,
- onCloseWizard,
- onSave,
- credentials,
- resource,
- resourceDefaultCredentials,
- labels,
- instanceGroups,
-}) {
- const { setFieldTouched, values, initialValues, resetForm } =
- useFormikContext();
- const {
- steps,
- visitStep,
- visitAllSteps,
- validateStep,
- contentError,
- isReady,
- } = useSchedulePromptSteps(
- surveyConfig,
- launchConfig,
- schedule,
- resource,
- credentials,
- resourceDefaultCredentials,
- labels,
- instanceGroups
- );
- const [showDescription, setShowDescription] = useState(false);
- const { error, dismissError } = useDismissableError(contentError);
- const cancelPromptableValues = async () => {
- resetForm({
- values: {
- ...initialValues,
- description: values.description,
- frequency: values.frequency,
- name: values.name,
- startDateTime: values.startDateTime,
- timezone: values.timezone,
- },
- });
- onCloseWizard();
- };
-
- if (error) {
- return (
- {
- dismissError();
- onCloseWizard();
- }}
- >
-
-
- );
- }
- return (
- {
- validateStep(nextStep.id);
- }}
- onNext={async (nextStep, prevStep) => {
- if (nextStep.id === 'preview') {
- visitAllSteps(setFieldTouched);
- } else {
- visitStep(prevStep.prevId, setFieldTouched);
- validateStep(nextStep.id);
- }
- }}
- onGoToStep={async (nextStep, prevStep) => {
- if (nextStep.id === 'preview') {
- visitAllSteps(setFieldTouched);
- } else {
- visitStep(prevStep.prevId, setFieldTouched);
- validateStep(nextStep.id);
- }
- }}
- title={t`Prompt | ${resource.name}`}
- description={
- resource.description.length > 512 ? (
- {
- setShowDescription(!showDescription);
- }}
- isExpanded={showDescription}
- >
- {resource.description}
-
- ) : (
- resource.description
- )
- }
- steps={
- isReady
- ? steps
- : [
- {
- name: t`Content Loading`,
- component: ,
- },
- ]
- }
- backButtonText={t`Back`}
- cancelButtonText={t`Cancel`}
- nextButtonText={t`Next`}
- />
- );
-}
-
-export default SchedulePromptableFields;
diff --git a/awx/ui/src/components/Schedule/shared/UnsupportedScheduleForm.js b/awx/ui/src/components/Schedule/shared/UnsupportedScheduleForm.js
deleted file mode 100644
index 195101d73f28..000000000000
--- a/awx/ui/src/components/Schedule/shared/UnsupportedScheduleForm.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import React from 'react';
-import { t } from '@lingui/macro';
-import { Button, Form, ActionGroup, Alert } from '@patternfly/react-core';
-
-export default function UnsupportedScheduleForm({ schedule, handleCancel }) {
- return (
-
- );
-}
diff --git a/awx/ui/src/components/Schedule/shared/buildRuleObj.js b/awx/ui/src/components/Schedule/shared/buildRuleObj.js
deleted file mode 100644
index c631ed959d18..000000000000
--- a/awx/ui/src/components/Schedule/shared/buildRuleObj.js
+++ /dev/null
@@ -1,128 +0,0 @@
-import { t } from '@lingui/macro';
-import { RRule } from 'rrule';
-import { DateTime } from 'luxon';
-import { getRRuleDayConstants } from 'util/dates';
-
-window.RRule = RRule;
-window.DateTime = DateTime;
-
-const parseTime = (time) => [
- DateTime.fromFormat(time, 'h:mm a').hour,
- DateTime.fromFormat(time, 'h:mm a').minute,
-];
-
-export function buildDtStartObj(values) {
- // Dates are formatted like "YYYY-MM-DD"
- const [startYear, startMonth, startDay] = values.startDate.split('-');
- // Times are formatted like "HH:MM:SS" or "HH:MM" if no seconds
- // have been specified
- const [startHour, startMinute] = parseTime(values.startTime);
-
- const dateString = `${startYear}${pad(startMonth)}${pad(startDay)}T${pad(
- startHour
- )}${pad(startMinute)}00`;
- const rruleString = values.timezone
- ? `DTSTART;TZID=${values.timezone}:${dateString}`
- : `DTSTART:${dateString}Z`;
- const rule = RRule.fromString(rruleString);
-
- return rule;
-}
-
-function pad(num) {
- if (typeof num === 'string') {
- return num;
- }
- return num < 10 ? `0${num}` : num;
-}
-
-export default function buildRuleObj(values, includeStart) {
- const ruleObj = {
- interval: values.interval,
- };
-
- if (includeStart) {
- ruleObj.dtstart = buildDateTime(
- values.startDate,
- values.startTime,
- values.timezone
- );
- }
-
- switch (values.frequency) {
- case 'none':
- ruleObj.count = 1;
- ruleObj.freq = RRule.MINUTELY;
- break;
- case 'minute':
- ruleObj.freq = RRule.MINUTELY;
- break;
- case 'hour':
- ruleObj.freq = RRule.HOURLY;
- break;
- case 'day':
- ruleObj.freq = RRule.DAILY;
- break;
- case 'week':
- ruleObj.freq = RRule.WEEKLY;
- ruleObj.byweekday = values.daysOfWeek;
- break;
- case 'month':
- ruleObj.freq = RRule.MONTHLY;
- if (values.runOn === 'day') {
- ruleObj.bymonthday = values.runOnDayNumber;
- } else if (values.runOn === 'the') {
- ruleObj.bysetpos = parseInt(values.runOnTheOccurrence, 10);
- ruleObj.byweekday = getRRuleDayConstants(values.runOnTheDay);
- }
- break;
- case 'year':
- ruleObj.freq = RRule.YEARLY;
- if (values.runOn === 'day') {
- ruleObj.bymonth = parseInt(values.runOnDayMonth, 10);
- ruleObj.bymonthday = values.runOnDayNumber;
- } else if (values.runOn === 'the') {
- ruleObj.bysetpos = parseInt(values.runOnTheOccurrence, 10);
- ruleObj.byweekday = getRRuleDayConstants(values.runOnTheDay);
- ruleObj.bymonth = parseInt(values.runOnTheMonth, 10);
- }
- break;
- default:
- throw new Error(t`Frequency did not match an expected value`);
- }
-
- if (values.frequency !== 'none') {
- switch (values.end) {
- case 'never':
- break;
- case 'after':
- ruleObj.count = values.occurrences;
- break;
- case 'onDate': {
- ruleObj.until = buildDateTime(
- values.endDate,
- values.endTime,
- values.timezone
- );
- break;
- }
- default:
- throw new Error(t`End did not match an expected value (${values.end})`);
- }
- }
-
- return ruleObj;
-}
-
-function buildDateTime(dateString, timeString, timezone) {
- const localDate = DateTime.fromISO(`${dateString}T000000`, {
- zone: timezone,
- });
- const [hour, minute] = parseTime(timeString);
- const localTime = localDate.set({
- hour,
- minute,
- second: 0,
- });
- return localTime.toJSDate();
-}
diff --git a/awx/ui/src/components/Schedule/shared/buildRuleSet.js b/awx/ui/src/components/Schedule/shared/buildRuleSet.js
deleted file mode 100644
index 070a0ef66315..000000000000
--- a/awx/ui/src/components/Schedule/shared/buildRuleSet.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import { RRule, RRuleSet } from 'rrule';
-import buildRuleObj, { buildDtStartObj } from './buildRuleObj';
-
-window.RRuleSet = RRuleSet;
-
-const frequencies = ['minute', 'hour', 'day', 'week', 'month', 'year'];
-export default function buildRuleSet(values, useUTCStart) {
- const set = new RRuleSet();
-
- if (!useUTCStart) {
- const startRule = buildDtStartObj({
- startDate: values.startDate,
- startTime: values.startTime,
- timezone: values.timezone,
- });
- set.rrule(startRule);
- }
-
- if (values.frequency.length === 0) {
- const rule = buildRuleObj(
- {
- startDate: values.startDate,
- startTime: values.startTime,
- timezone: values.timezone,
- frequency: 'none',
- interval: 1,
- },
- useUTCStart
- );
- set.rrule(new RRule(rule));
- }
-
- frequencies.forEach((frequency) => {
- if (!values.frequency.includes(frequency)) {
- return;
- }
- const rule = buildRuleObj(
- {
- startDate: values.startDate,
- startTime: values.startTime,
- timezone: values.timezone,
- frequency,
- ...values.frequencyOptions[frequency],
- },
- useUTCStart
- );
- set.rrule(new RRule(rule));
- });
-
- frequencies.forEach((frequency) => {
- if (!values.exceptionFrequency?.includes(frequency)) {
- return;
- }
- const rule = buildRuleObj(
- {
- startDate: values.startDate,
- startTime: values.startTime,
- timezone: values.timezone,
- frequency,
- ...values.exceptionOptions[frequency],
- },
- useUTCStart
- );
- set.exrule(new RRule(rule));
- });
-
- return set;
-}
diff --git a/awx/ui/src/components/Schedule/shared/buildRuleSet.test.js b/awx/ui/src/components/Schedule/shared/buildRuleSet.test.js
deleted file mode 100644
index 42fde679812f..000000000000
--- a/awx/ui/src/components/Schedule/shared/buildRuleSet.test.js
+++ /dev/null
@@ -1,499 +0,0 @@
-import { RRule } from 'rrule';
-import buildRuleSet from './buildRuleSet';
-
-import { DateTime } from 'luxon';
-
-describe('buildRuleSet', () => {
- test('should build minutely recurring rrule', () => {
- const values = {
- startDate: '2022-06-13',
- startTime: '12:30 PM',
- frequency: ['minute'],
- frequencyOptions: {
- minute: {
- interval: 1,
- end: 'never',
- },
- },
- };
-
- const ruleSet = buildRuleSet(values);
- expect(ruleSet.toString()).toEqual(
- 'DTSTART:20220613T123000Z\nRRULE:INTERVAL=1;FREQ=MINUTELY'
- );
- });
-
- test('should build hourly recurring rrule', () => {
- const values = {
- startDate: '2022-06-13',
- startTime: '12:30 PM',
- frequency: ['hour'],
- frequencyOptions: {
- hour: {
- interval: 1,
- end: 'never',
- },
- },
- };
-
- const ruleSet = buildRuleSet(values);
- expect(ruleSet.toString()).toEqual(
- 'DTSTART:20220613T123000Z\nRRULE:INTERVAL=1;FREQ=HOURLY'
- );
- });
-
- test('should build daily recurring rrule', () => {
- const values = {
- startDate: '2022-06-13',
- startTime: '12:30 PM',
- frequency: ['day'],
- frequencyOptions: {
- day: {
- interval: 1,
- end: 'never',
- },
- },
- };
-
- const ruleSet = buildRuleSet(values);
- expect(ruleSet.toString()).toEqual(
- 'DTSTART:20220613T123000Z\nRRULE:INTERVAL=1;FREQ=DAILY'
- );
- });
-
- test('should build weekly recurring rrule', () => {
- const values = {
- startDate: '2022-06-13',
- startTime: '12:30 PM',
- frequency: ['week'],
- frequencyOptions: {
- week: {
- interval: 1,
- end: 'never',
- daysOfWeek: [RRule.SU],
- },
- },
- };
-
- const ruleSet = buildRuleSet(values);
- expect(ruleSet.toString()).toEqual(
- 'DTSTART:20220613T123000Z\nRRULE:INTERVAL=1;FREQ=WEEKLY;BYDAY=SU'
- );
- });
-
- test('should build monthly by day recurring rrule', () => {
- const values = {
- startDate: '2022-06-13',
- startTime: '12:30 PM',
- frequency: ['month'],
- frequencyOptions: {
- month: {
- interval: 1,
- end: 'never',
- runOn: 'day',
- runOnDayNumber: 15,
- },
- },
- };
-
- const ruleSet = buildRuleSet(values);
- expect(ruleSet.toString()).toEqual(
- 'DTSTART:20220613T123000Z\nRRULE:INTERVAL=1;FREQ=MONTHLY;BYMONTHDAY=15'
- );
- });
-
- test('should build monthly by weekday recurring rrule', () => {
- const values = {
- startDate: '2022-06-13',
- startTime: '12:30 PM',
- frequency: ['month'],
- frequencyOptions: {
- month: {
- interval: 1,
- end: 'never',
- runOn: 'the',
- runOnTheOccurrence: 2,
- runOnTheDay: 'monday',
- },
- },
- };
-
- const ruleSet = buildRuleSet(values);
- expect(ruleSet.toString()).toEqual(
- 'DTSTART:20220613T123000Z\nRRULE:INTERVAL=1;FREQ=MONTHLY;BYSETPOS=2;BYDAY=MO'
- );
- });
-
- test('should build yearly by day recurring rrule', () => {
- const values = {
- startDate: '2022-06-13',
- startTime: '12:30 PM',
- frequency: ['year'],
- frequencyOptions: {
- year: {
- interval: 1,
- end: 'never',
- runOn: 'day',
- runOnDayMonth: 3,
- runOnDayNumber: 15,
- },
- },
- };
-
- const ruleSet = buildRuleSet(values);
- expect(ruleSet.toString()).toEqual(
- 'DTSTART:20220613T123000Z\nRRULE:INTERVAL=1;FREQ=YEARLY;BYMONTH=3;BYMONTHDAY=15'
- );
- });
-
- test('should build yearly by weekday recurring rrule', () => {
- const values = {
- startDate: '2022-06-13',
- startTime: '12:30 PM',
- frequency: ['year'],
- frequencyOptions: {
- year: {
- interval: 1,
- end: 'never',
- runOn: 'the',
- runOnTheOccurrence: 4,
- runOnTheDay: 'monday',
- runOnTheMonth: 6,
- },
- },
- };
-
- const ruleSet = buildRuleSet(values);
- expect(ruleSet.toString()).toEqual(
- 'DTSTART:20220613T123000Z\nRRULE:INTERVAL=1;FREQ=YEARLY;BYSETPOS=4;BYDAY=MO;BYMONTH=6'
- );
- });
-
- test('should build combined frequencies', () => {
- const values = {
- startDate: '2022-06-13',
- startTime: '12:30 PM',
- frequency: ['minute', 'month'],
- frequencyOptions: {
- minute: {
- interval: 1,
- end: 'never',
- },
- month: {
- interval: 1,
- end: 'never',
- runOn: 'the',
- runOnTheOccurrence: 2,
- runOnTheDay: 'monday',
- },
- },
- };
-
- const ruleSet = buildRuleSet(values);
- expect(ruleSet.toString()).toEqual(`DTSTART:20220613T123000Z
-RRULE:INTERVAL=1;FREQ=MINUTELY
-RRULE:INTERVAL=1;FREQ=MONTHLY;BYSETPOS=2;BYDAY=MO`);
- });
-
- test('should build combined frequencies with end dates', () => {
- const values = {
- startDate: '2022-06-01',
- startTime: '12:30 PM',
- timezone: 'US/Eastern',
- frequency: ['hour', 'month'],
- frequencyOptions: {
- hour: {
- interval: 2,
- end: 'onDate',
- endDate: '2026-07-02',
- endTime: '1:00 PM',
- occurrences: 1,
- },
- month: {
- interval: 1,
- end: 'onDate',
- runOn: 'the',
- runOnTheOccurrence: 2,
- runOnTheDay: 'monday',
- runOnDayNumber: 1,
- endDate: '2026-06-02',
- endTime: '1:00 PM',
- occurrences: 1,
- },
- },
- exceptionFrequency: [],
- exceptionOptions: {},
- };
-
- const ruleSet = buildRuleSet(values);
- expect(ruleSet.toString()).toEqual(`DTSTART;TZID=US/Eastern:20220601T123000
-RRULE:INTERVAL=2;FREQ=HOURLY;UNTIL=20260702T170000Z
-RRULE:INTERVAL=1;FREQ=MONTHLY;BYSETPOS=2;BYDAY=MO;UNTIL=20260602T170000Z`);
- });
-
- test('should build single occurence', () => {
- const values = {
- startDate: '2022-06-13',
- startTime: '12:30 PM',
- frequency: [],
- frequencyOptions: {},
- };
-
- const ruleSet = buildRuleSet(values);
- expect(ruleSet.toString()).toEqual(`DTSTART:20220613T123000Z
-RRULE:INTERVAL=1;COUNT=1;FREQ=MINUTELY`);
- });
-
- test('should build minutely exception', () => {
- const values = {
- startDate: '2022-06-13',
- startTime: '12:30 PM',
- frequency: ['minute'],
- frequencyOptions: {
- minute: {
- interval: 1,
- end: 'never',
- },
- },
- exceptionFrequency: ['minute'],
- exceptionOptions: {
- minute: {
- interval: 3,
- end: 'never',
- },
- },
- };
-
- const ruleSet = buildRuleSet(values);
- expect(ruleSet.toString()).toEqual(
- [
- 'DTSTART:20220613T123000Z',
- 'RRULE:INTERVAL=1;FREQ=MINUTELY',
- 'EXRULE:INTERVAL=3;FREQ=MINUTELY',
- ].join('\n')
- );
- });
-
- test('should build hourly exception', () => {
- const values = {
- startDate: '2022-06-13',
- startTime: '12:30 PM',
- frequency: ['minute'],
- frequencyOptions: {
- minute: {
- interval: 1,
- end: 'never',
- },
- },
- exceptionFrequency: ['hour'],
- exceptionOptions: {
- hour: {
- interval: 3,
- end: 'never',
- },
- },
- };
-
- const ruleSet = buildRuleSet(values);
- expect(ruleSet.toString()).toEqual(
- [
- 'DTSTART:20220613T123000Z',
- 'RRULE:INTERVAL=1;FREQ=MINUTELY',
- 'EXRULE:INTERVAL=3;FREQ=HOURLY',
- ].join('\n')
- );
- });
-
- test('should build daily exception', () => {
- const values = {
- startDate: '2022-06-13',
- startTime: '12:30 PM',
- frequency: ['minute'],
- frequencyOptions: {
- minute: {
- interval: 1,
- end: 'never',
- },
- },
- exceptionFrequency: ['day'],
- exceptionOptions: {
- day: {
- interval: 3,
- end: 'never',
- },
- },
- };
-
- const ruleSet = buildRuleSet(values);
- expect(ruleSet.toString()).toEqual(
- [
- 'DTSTART:20220613T123000Z',
- 'RRULE:INTERVAL=1;FREQ=MINUTELY',
- 'EXRULE:INTERVAL=3;FREQ=DAILY',
- ].join('\n')
- );
- });
-
- test('should build weekly exception', () => {
- const values = {
- startDate: '2022-06-13',
- startTime: '12:30 PM',
- frequency: ['minute'],
- frequencyOptions: {
- minute: {
- interval: 1,
- end: 'never',
- },
- },
- exceptionFrequency: ['week'],
- exceptionOptions: {
- week: {
- interval: 3,
- end: 'never',
- daysOfWeek: [RRule.SU],
- },
- },
- };
-
- const ruleSet = buildRuleSet(values);
- expect(ruleSet.toString()).toEqual(
- [
- 'DTSTART:20220613T123000Z',
- 'RRULE:INTERVAL=1;FREQ=MINUTELY',
- 'EXRULE:INTERVAL=3;FREQ=WEEKLY;BYDAY=SU',
- ].join('\n')
- );
- });
-
- test('should build monthly exception by day', () => {
- const values = {
- startDate: '2022-06-13',
- startTime: '12:30 PM',
- frequency: ['minute'],
- frequencyOptions: {
- minute: {
- interval: 1,
- end: 'never',
- },
- },
- exceptionFrequency: ['month'],
- exceptionOptions: {
- month: {
- interval: 3,
- end: 'never',
- runOn: 'day',
- runOnDayNumber: 15,
- },
- },
- };
-
- const ruleSet = buildRuleSet(values);
- expect(ruleSet.toString()).toEqual(
- [
- 'DTSTART:20220613T123000Z',
- 'RRULE:INTERVAL=1;FREQ=MINUTELY',
- 'EXRULE:INTERVAL=3;FREQ=MONTHLY;BYMONTHDAY=15',
- ].join('\n')
- );
- });
-
- test('should build monthly exception by weekday', () => {
- const values = {
- startDate: '2022-06-13',
- startTime: '12:30 PM',
- frequency: ['minute'],
- frequencyOptions: {
- minute: {
- interval: 1,
- end: 'never',
- },
- },
- exceptionFrequency: ['month'],
- exceptionOptions: {
- month: {
- interval: 3,
- end: 'never',
- runOn: 'the',
- runOnTheOccurrence: 2,
- runOnTheDay: 'monday',
- },
- },
- };
-
- const ruleSet = buildRuleSet(values);
- expect(ruleSet.toString()).toEqual(
- [
- 'DTSTART:20220613T123000Z',
- 'RRULE:INTERVAL=1;FREQ=MINUTELY',
- 'EXRULE:INTERVAL=3;FREQ=MONTHLY;BYSETPOS=2;BYDAY=MO',
- ].join('\n')
- );
- });
-
- test('should build annual exception by day', () => {
- const values = {
- startDate: '2022-06-13',
- startTime: '12:30 PM',
- frequency: ['minute'],
- frequencyOptions: {
- minute: {
- interval: 1,
- end: 'never',
- },
- },
- exceptionFrequency: ['year'],
- exceptionOptions: {
- year: {
- interval: 1,
- end: 'never',
- runOn: 'day',
- runOnDayMonth: 3,
- runOnDayNumber: 15,
- },
- },
- };
-
- const ruleSet = buildRuleSet(values);
- expect(ruleSet.toString()).toEqual(
- [
- 'DTSTART:20220613T123000Z',
- 'RRULE:INTERVAL=1;FREQ=MINUTELY',
- 'EXRULE:INTERVAL=1;FREQ=YEARLY;BYMONTH=3;BYMONTHDAY=15',
- ].join('\n')
- );
- });
-
- test('should build annual exception by weekday', () => {
- const values = {
- startDate: '2022-06-13',
- startTime: '12:30 PM',
- frequency: ['minute'],
- frequencyOptions: {
- minute: {
- interval: 1,
- end: 'never',
- },
- },
- exceptionFrequency: ['year'],
- exceptionOptions: {
- year: {
- interval: 1,
- end: 'never',
- runOn: 'the',
- runOnTheOccurrence: 4,
- runOnTheDay: 'monday',
- runOnTheMonth: 6,
- },
- },
- };
-
- const ruleSet = buildRuleSet(values);
- expect(ruleSet.toString()).toEqual(
- [
- 'DTSTART:20220613T123000Z',
- 'RRULE:INTERVAL=1;FREQ=MINUTELY',
- 'EXRULE:INTERVAL=1;FREQ=YEARLY;BYSETPOS=4;BYDAY=MO;BYMONTH=6',
- ].join('\n')
- );
- });
-});
diff --git a/awx/ui/src/components/Schedule/shared/parseRuleObj.js b/awx/ui/src/components/Schedule/shared/parseRuleObj.js
deleted file mode 100644
index a67623119306..000000000000
--- a/awx/ui/src/components/Schedule/shared/parseRuleObj.js
+++ /dev/null
@@ -1,278 +0,0 @@
-import { RRule, rrulestr } from 'rrule';
-import { dateToInputDateTime } from 'util/dates';
-import { DateTime } from 'luxon';
-import sortFrequencies from './sortFrequencies';
-
-export class UnsupportedRRuleError extends Error {
- constructor(message) {
- super(message);
- this.name = 'UnsupportedRRuleError';
- }
-}
-
-export default function parseRuleObj(schedule) {
- let values = {
- frequency: [],
- frequencyOptions: {},
- exceptionFrequency: [],
- exceptionOptions: {},
- timezone: schedule.timezone,
- };
- const ruleset = rrulestr(schedule.rrule.replace(' ', '\n'), {
- forceset: true,
- });
-
- const ruleStrings = ruleset.valueOf();
- ruleStrings.forEach((ruleString) => {
- const type = ruleString.match(/^[A-Z]+/)[0];
- switch (type) {
- case 'DTSTART':
- values = parseDtstart(schedule, values);
- break;
- case 'RRULE':
- values = parseRrule(ruleString, schedule, values);
- break;
- case 'EXRULE':
- values = parseExRule(ruleString, schedule, values);
- break;
- default:
- throw new UnsupportedRRuleError(`Unsupported rrule type: ${type}`);
- }
- });
-
- if (isSingleOccurrence(values)) {
- values.frequency = [];
- values.frequencyOptions = {};
- }
-
- return values;
-}
-
-function isSingleOccurrence(values) {
- if (values.frequency.length > 1) {
- return false;
- }
- if (values.frequency[0] !== 'minute') {
- return false;
- }
- const options = values.frequencyOptions.minute;
- return options.end === 'after' && options.occurrences === 1;
-}
-
-function parseDtstart(schedule, values) {
- // TODO: should this rely on DTSTART in rruleset rather than schedule.dtstart?
- const [startDate, startTime] = dateToInputDateTime(
- schedule.dtstart,
- schedule.timezone
- );
- return {
- ...values,
- startDate,
- startTime,
- };
-}
-
-const frequencyTypes = {
- [RRule.MINUTELY]: 'minute',
- [RRule.HOURLY]: 'hour',
- [RRule.DAILY]: 'day',
- [RRule.WEEKLY]: 'week',
- [RRule.MONTHLY]: 'month',
- [RRule.YEARLY]: 'year',
-};
-
-function parseRrule(rruleString, schedule, values) {
- const { frequency, options } = parseRule(
- rruleString,
- schedule,
- values.exceptionFrequency
- );
-
- if (values.frequencyOptions[frequency]) {
- throw new UnsupportedRRuleError(
- 'Duplicate exception frequency types not supported'
- );
- }
-
- return {
- ...values,
- frequency: [...values.frequency, frequency].sort(sortFrequencies),
- frequencyOptions: {
- ...values.frequencyOptions,
- [frequency]: options,
- },
- };
-}
-
-function parseExRule(exruleString, schedule, values) {
- const { frequency, options } = parseRule(
- exruleString,
- schedule,
- values.exceptionFrequency
- );
-
- if (values.exceptionOptions[frequency]) {
- throw new UnsupportedRRuleError(
- 'Duplicate exception frequency types not supported'
- );
- }
-
- return {
- ...values,
- exceptionFrequency: [...values.exceptionFrequency, frequency].sort(
- sortFrequencies
- ),
- exceptionOptions: {
- ...values.exceptionOptions,
- [frequency]: options,
- },
- };
-}
-
-function parseRule(ruleString, schedule, frequencies) {
- const {
- origOptions: {
- bymonth,
- bymonthday,
- bysetpos,
- byweekday,
- count,
- freq,
- interval,
- until,
- },
- } = RRule.fromString(ruleString);
-
- const now = DateTime.now();
- const closestQuarterHour = DateTime.fromMillis(
- Math.ceil(now.ts / 900000) * 900000
- );
- const tomorrow = closestQuarterHour.plus({ days: 1 });
- const [, time] = dateToInputDateTime(closestQuarterHour.toISO());
- const [tomorrowDate] = dateToInputDateTime(tomorrow.toISO());
-
- const options = {
- endDate: tomorrowDate,
- endTime: time,
- occurrences: 1,
- interval: 1,
- end: 'never',
- };
-
- if (until) {
- options.end = 'onDate';
- const end = DateTime.fromISO(until.toISOString());
- const [endDate, endTime] = dateToInputDateTime(end, schedule.timezone);
- options.endDate = endDate;
- options.endTime = endTime;
- } else if (count) {
- options.end = 'after';
- options.occurrences = count;
- }
-
- if (interval) {
- options.interval = interval;
- }
-
- if (typeof freq !== 'number') {
- throw new Error(`Unexpected rrule frequency: ${freq}`);
- }
- const frequency = frequencyTypes[freq];
- if (frequencies.includes(frequency)) {
- throw new Error(`Duplicate frequency types not supported (${frequency})`);
- }
-
- if (freq === RRule.WEEKLY && byweekday) {
- options.daysOfWeek = byweekday;
- }
-
- if (freq === RRule.MONTHLY) {
- options.runOn = 'day';
- options.runOnTheOccurrence = 1;
- options.runOnTheDay = 'sunday';
- options.runOnDayNumber = 1;
-
- if (bymonthday) {
- options.runOnDayNumber = bymonthday;
- }
- if (bysetpos) {
- options.runOn = 'the';
- options.runOnTheOccurrence = bysetpos;
- options.runOnTheDay = generateRunOnTheDay(byweekday);
- }
- }
-
- if (freq === RRule.YEARLY) {
- options.runOn = 'day';
- options.runOnTheOccurrence = 1;
- options.runOnTheDay = 'sunday';
- options.runOnTheMonth = 1;
- options.runOnDayMonth = 1;
- options.runOnDayNumber = 1;
-
- if (bymonthday) {
- options.runOnDayNumber = bymonthday;
- options.runOnDayMonth = bymonth;
- }
- if (bysetpos) {
- options.runOn = 'the';
- options.runOnTheOccurrence = bysetpos;
- options.runOnTheDay = generateRunOnTheDay(byweekday);
- options.runOnTheMonth = bymonth;
- }
- }
-
- return {
- frequency,
- options,
- };
-}
-
-function generateRunOnTheDay(days = []) {
- if (
- [
- RRule.MO,
- RRule.TU,
- RRule.WE,
- RRule.TH,
- RRule.FR,
- RRule.SA,
- RRule.SU,
- ].every((element) => days.indexOf(element) > -1)
- ) {
- return 'day';
- }
- if (
- [RRule.MO, RRule.TU, RRule.WE, RRule.TH, RRule.FR].every(
- (element) => days.indexOf(element) > -1
- )
- ) {
- return 'weekday';
- }
- if ([RRule.SA, RRule.SU].every((element) => days.indexOf(element) > -1)) {
- return 'weekendDay';
- }
- if (days.indexOf(RRule.MO) > -1) {
- return 'monday';
- }
- if (days.indexOf(RRule.TU) > -1) {
- return 'tuesday';
- }
- if (days.indexOf(RRule.WE) > -1) {
- return 'wednesday';
- }
- if (days.indexOf(RRule.TH) > -1) {
- return 'thursday';
- }
- if (days.indexOf(RRule.FR) > -1) {
- return 'friday';
- }
- if (days.indexOf(RRule.SA) > -1) {
- return 'saturday';
- }
- if (days.indexOf(RRule.SU) > -1) {
- return 'sunday';
- }
-
- return null;
-}
diff --git a/awx/ui/src/components/Schedule/shared/parseRuleObj.test.js b/awx/ui/src/components/Schedule/shared/parseRuleObj.test.js
deleted file mode 100644
index 426998bcc28f..000000000000
--- a/awx/ui/src/components/Schedule/shared/parseRuleObj.test.js
+++ /dev/null
@@ -1,291 +0,0 @@
-import { DateTime, Settings } from 'luxon';
-import { RRule } from 'rrule';
-import parseRuleObj from './parseRuleObj';
-import buildRuleSet from './buildRuleSet';
-
-describe(parseRuleObj, () => {
- let origNow = Settings.now;
- beforeEach(() => {
- const expectedNow = DateTime.local(2022, 6, 1, 13, 0, 0);
- Settings.now = () => expectedNow.toMillis();
- });
-
- afterEach(() => {
- Settings.now = origNow;
- });
-
- test('should parse weekly recurring rrule', () => {
- const schedule = {
- rrule:
- 'DTSTART;TZID=US/Eastern:20220608T123000 RRULE:INTERVAL=1;FREQ=WEEKLY;BYDAY=MO',
- dtstart: '2022-06-13T16:30:00Z',
- timezone: 'US/Eastern',
- until: '',
- dtend: null,
- };
-
- const parsed = parseRuleObj(schedule);
-
- expect(parsed).toEqual({
- startDate: '2022-06-13',
- startTime: '12:30 PM',
- timezone: 'US/Eastern',
- frequency: ['week'],
- frequencyOptions: {
- week: {
- interval: 1,
- end: 'never',
- occurrences: 1,
- endDate: '2022-06-02',
- endTime: '1:00 PM',
- daysOfWeek: [RRule.MO],
- },
- },
- exceptionFrequency: [],
- exceptionOptions: {},
- });
- });
-
- test('should parse weekly recurring rrule with end date', () => {
- const schedule = {
- rrule:
- 'DTSTART;TZID=America/New_York:20200402T144500 RRULE:INTERVAL=1;FREQ=WEEKLY;BYDAY=MO,WE,FR;UNTIL=20210101T050000Z',
- dtstart: '2020-04-02T18:45:00Z',
- timezone: 'America/New_York',
- };
-
- const parsed = parseRuleObj(schedule);
-
- expect(parsed).toEqual({
- startDate: '2020-04-02',
- startTime: '2:45 PM',
- timezone: 'America/New_York',
- frequency: ['week'],
- frequencyOptions: {
- week: {
- interval: 1,
- end: 'onDate',
- occurrences: 1,
- endDate: '2021-01-01',
- endTime: '12:00 AM',
- daysOfWeek: [RRule.MO, RRule.WE, RRule.FR],
- },
- },
- exceptionFrequency: [],
- exceptionOptions: {},
- });
- });
-
- test('should parse hourly rule with end date', () => {
- const schedule = {
- rrule:
- 'DTSTART;TZID=US/Eastern:20220608T123000 RRULE:INTERVAL=1;FREQ=HOURLY;UNTIL=20230608T170000Z',
- dtstart: '2022-06-08T16:30:00Z',
- timezone: 'US/Eastern',
- };
-
- const parsed = parseRuleObj(schedule);
-
- expect(parsed).toEqual({
- startDate: '2022-06-08',
- startTime: '12:30 PM',
- timezone: 'US/Eastern',
- frequency: ['hour'],
- frequencyOptions: {
- hour: {
- interval: 1,
- end: 'onDate',
- occurrences: 1,
- endDate: '2023-06-08',
- endTime: '1:00 PM',
- },
- },
- exceptionFrequency: [],
- exceptionOptions: {},
- });
- });
-
- // TODO: do we need to support this? It's technically invalid RRULE, but the
- // API has historically supported it as a special case (but cast to UTC?)
- test.skip('should parse hourly rule with end date in local time', () => {
- const schedule = {
- rrule:
- 'DTSTART;TZID=US/Eastern:20220608T123000 RRULE:INTERVAL=1;FREQ=HOURLY;UNTIL=20230608T130000',
- dtstart: '2022-06-08T16:30:00',
- timezone: 'US/Eastern',
- };
-
- const parsed = parseRuleObj(schedule);
-
- expect(parsed).toEqual({
- startDate: '2022-06-08',
- startTime: '12:30 PM',
- timezone: 'US/Eastern',
- frequency: ['hour'],
- frequencyOptions: {
- hour: {
- interval: 1,
- end: 'onDate',
- occurrences: 1,
- endDate: '2023-06-08',
- endTime: '1:00 PM',
- },
- },
- exceptionFrequency: [],
- exceptionOptions: {},
- });
- });
-
- test('should parse non-recurring rrule', () => {
- const schedule = {
- rrule:
- 'DTSTART;TZID=America/New_York:20220610T130000 RRULE:INTERVAL=1;COUNT=1;FREQ=MINUTELY',
- dtstart: '2022-06-10T17:00:00Z',
- dtend: '2022-06-10T17:00:00Z',
- timezone: 'US/Eastern',
- until: '',
- };
-
- expect(parseRuleObj(schedule)).toEqual({
- startDate: '2022-06-10',
- startTime: '1:00 PM',
- timezone: 'US/Eastern',
- frequency: [],
- frequencyOptions: {},
- exceptionFrequency: [],
- exceptionOptions: {},
- });
- });
-
- // buildRuleSet is well-tested; use it to verify this does the inverse
- test('should re-parse built complex schedule', () => {
- const values = {
- startDate: '2022-06-01',
- startTime: '12:30 PM',
- timezone: 'US/Eastern',
- frequency: ['minute', 'month'],
- frequencyOptions: {
- minute: {
- interval: 1,
- end: 'never',
- endDate: '2022-06-02',
- endTime: '1:00 PM',
- occurrences: 1,
- },
- month: {
- interval: 1,
- end: 'never',
- runOn: 'the',
- runOnTheOccurrence: 2,
- runOnTheDay: 'monday',
- runOnDayNumber: 1,
- endDate: '2022-06-02',
- endTime: '1:00 PM',
- occurrences: 1,
- },
- },
- exceptionFrequency: [],
- exceptionOptions: {},
- };
-
- const ruleSet = buildRuleSet(values);
- const parsed = parseRuleObj({
- rrule: ruleSet.toString(),
- dtstart: '2022-06-01T12:30:00',
- dtend: '2022-06-01T12:30:00',
- timezone: 'US/Eastern',
- });
-
- expect(parsed).toEqual(values);
- });
-
- test('should parse built complex schedule with end dates', () => {
- const rulesetString = `DTSTART;TZID=US/Eastern:20220601T123000
-RRULE:INTERVAL=2;FREQ=HOURLY;UNTIL=20260702T170000Z
-RRULE:INTERVAL=1;FREQ=MONTHLY;BYSETPOS=2;BYDAY=MO;UNTIL=20260602T170000Z`;
- const values = {
- startDate: '2022-06-01',
- startTime: '12:30 PM',
- timezone: 'US/Eastern',
- frequency: ['hour', 'month'],
- frequencyOptions: {
- hour: {
- interval: 2,
- end: 'onDate',
- endDate: '2026-07-02',
- endTime: '1:00 PM',
- occurrences: 1,
- },
- month: {
- interval: 1,
- end: 'onDate',
- runOn: 'the',
- runOnTheOccurrence: 2,
- runOnTheDay: 'monday',
- runOnDayNumber: 1,
- endDate: '2026-06-02',
- endTime: '1:00 PM',
- occurrences: 1,
- },
- },
- exceptionFrequency: [],
- exceptionOptions: {},
- };
-
- const parsed = parseRuleObj({
- rrule: rulesetString,
- dtstart: '2022-06-01T16:30:00Z',
- dtend: '2026-06-07T16:30:00Z',
- timezone: 'US/Eastern',
- });
-
- expect(parsed).toEqual(values);
- });
-
- test('should parse exemptions', () => {
- const schedule = {
- rrule: [
- 'DTSTART;TZID=US/Eastern:20220608T123000',
- 'RRULE:INTERVAL=1;FREQ=WEEKLY;BYDAY=MO',
- 'EXRULE:INTERVAL=1;FREQ=MONTHLY;BYSETPOS=1;BYDAY=MO',
- ].join(' '),
- dtstart: '2022-06-13T16:30:00Z',
- timezone: 'US/Eastern',
- until: '',
- dtend: null,
- };
-
- const parsed = parseRuleObj(schedule);
-
- expect(parsed).toEqual({
- startDate: '2022-06-13',
- startTime: '12:30 PM',
- timezone: 'US/Eastern',
- frequency: ['week'],
- frequencyOptions: {
- week: {
- interval: 1,
- end: 'never',
- occurrences: 1,
- endDate: '2022-06-02',
- endTime: '1:00 PM',
- daysOfWeek: [RRule.MO],
- },
- },
- exceptionFrequency: ['month'],
- exceptionOptions: {
- month: {
- interval: 1,
- end: 'never',
- endDate: '2022-06-02',
- endTime: '1:00 PM',
- occurrences: 1,
- runOn: 'the',
- runOnDayNumber: 1,
- runOnTheOccurrence: 1,
- runOnTheDay: 'monday',
- },
- },
- });
- });
-});
diff --git a/awx/ui/src/components/Schedule/shared/sortFrequencies.js b/awx/ui/src/components/Schedule/shared/sortFrequencies.js
deleted file mode 100644
index 4212a6bc12e7..000000000000
--- a/awx/ui/src/components/Schedule/shared/sortFrequencies.js
+++ /dev/null
@@ -1,18 +0,0 @@
-const ORDER = {
- minute: 1,
- hour: 2,
- day: 3,
- week: 4,
- month: 5,
- year: 6,
-};
-
-export default function sortFrequencies(a, b) {
- if (ORDER[a] < ORDER[b]) {
- return -1;
- }
- if (ORDER[a] > ORDER[b]) {
- return 1;
- }
- return 0;
-}
diff --git a/awx/ui/src/components/Schedule/shared/useSchedulePromptSteps.js b/awx/ui/src/components/Schedule/shared/useSchedulePromptSteps.js
deleted file mode 100644
index 630cc119baec..000000000000
--- a/awx/ui/src/components/Schedule/shared/useSchedulePromptSteps.js
+++ /dev/null
@@ -1,147 +0,0 @@
-import { useState, useEffect } from 'react';
-import { useFormikContext } from 'formik';
-import { t } from '@lingui/macro';
-import useInventoryStep from '../../LaunchPrompt/steps/useInventoryStep';
-import useCredentialsStep from '../../LaunchPrompt/steps/useCredentialsStep';
-import useExecutionEnvironmentStep from '../../LaunchPrompt/steps/useExecutionEnvironmentStep';
-import useInstanceGroupsStep from '../../LaunchPrompt/steps/useInstanceGroupsStep';
-import useOtherPromptsStep from '../../LaunchPrompt/steps/useOtherPromptsStep';
-import useSurveyStep from '../../LaunchPrompt/steps/useSurveyStep';
-import usePreviewStep from '../../LaunchPrompt/steps/usePreviewStep';
-
-export default function useSchedulePromptSteps(
- surveyConfig,
- launchConfig,
- schedule,
- resource,
- scheduleCredentials,
- resourceDefaultCredentials,
- labels,
- instanceGroups
-) {
- const sourceOfValues =
- (Object.keys(schedule).length > 0 && schedule) || resource;
- const { resetForm, values } = useFormikContext();
- const [visited, setVisited] = useState({});
-
- const steps = [
- useInventoryStep(launchConfig, sourceOfValues, visited),
- useCredentialsStep(
- launchConfig,
- sourceOfValues,
- resourceDefaultCredentials
- ),
- useExecutionEnvironmentStep(launchConfig, resource),
- useInstanceGroupsStep(launchConfig, resource, instanceGroups),
- useOtherPromptsStep(launchConfig, sourceOfValues, labels),
- useSurveyStep(launchConfig, surveyConfig, sourceOfValues, visited),
- ];
-
- const hasErrors = steps.some((step) => step.hasError);
-
- steps.push(
- usePreviewStep(
- launchConfig,
- resource,
- surveyConfig,
- hasErrors,
- true,
- t`Save`
- )
- );
-
- const pfSteps = steps.map((s) => s.step).filter((s) => s != null);
- const isReady = !steps.some((s) => !s.isReady);
-
- useEffect(() => {
- if (launchConfig && surveyConfig && isReady) {
- let initialValues = {};
- initialValues = steps.reduce(
- (acc, cur) => ({
- ...acc,
- ...cur.initialValues,
- }),
- {}
- );
-
- if (launchConfig.ask_credential_on_launch) {
- const defaultCredsWithoutOverrides = [];
-
- const credentialHasOverride = (templateDefaultCred) => {
- let hasOverride = false;
- scheduleCredentials.forEach((scheduleCredential) => {
- if (
- templateDefaultCred.credential_type ===
- scheduleCredential.credential_type
- ) {
- if (
- (!templateDefaultCred.inputs.vault_id &&
- !scheduleCredential.inputs.vault_id) ||
- (templateDefaultCred.inputs.vault_id &&
- scheduleCredential.inputs.vault_id &&
- templateDefaultCred.inputs.vault_id ===
- scheduleCredential.inputs.vault_id)
- ) {
- hasOverride = true;
- }
- }
- });
-
- return hasOverride;
- };
-
- if (resourceDefaultCredentials) {
- resourceDefaultCredentials.forEach((defaultCred) => {
- if (!credentialHasOverride(defaultCred)) {
- defaultCredsWithoutOverrides.push(defaultCred);
- }
- });
- }
-
- initialValues.credentials = scheduleCredentials.concat(
- defaultCredsWithoutOverrides
- );
- }
-
- resetForm({
- values: {
- ...initialValues,
- ...values,
- },
- });
- }
-
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [launchConfig, surveyConfig, isReady]);
-
- const stepWithError = steps.find((s) => s.contentError);
- const contentError = stepWithError ? stepWithError.contentError : null;
-
- return {
- isReady,
- validateStep: (stepId) => {
- steps.find((s) => s?.step?.id === stepId).validate();
- },
- steps: pfSteps,
- visitStep: (prevStepId, setFieldTouched) => {
- setVisited({
- ...visited,
- [prevStepId]: true,
- });
- steps.find((s) => s?.step?.id === prevStepId).setTouched(setFieldTouched);
- },
- visitAllSteps: (setFieldTouched) => {
- setVisited({
- inventory: true,
- credentials: true,
- executionEnvironment: true,
- instanceGroups: true,
- other: true,
- survey: true,
- preview: true,
- });
- steps.forEach((s) => s.setTouched(setFieldTouched));
- },
- contentError,
- };
-}
diff --git a/awx/ui/src/components/ScreenHeader/ScreenHeader.js b/awx/ui/src/components/ScreenHeader/ScreenHeader.js
deleted file mode 100644
index eeb2b5f9909a..000000000000
--- a/awx/ui/src/components/ScreenHeader/ScreenHeader.js
+++ /dev/null
@@ -1,151 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import useTitle from 'hooks/useTitle';
-
-import { t } from '@lingui/macro';
-import {
- Button,
- PageSection,
- PageSectionVariants,
- Breadcrumb,
- BreadcrumbItem,
- Title,
- Tooltip,
-} from '@patternfly/react-core';
-import { HistoryIcon } from '@patternfly/react-icons';
-import { Link, Route, useRouteMatch, useLocation } from 'react-router-dom';
-
-const ScreenHeader = ({ breadcrumbConfig, streamType }) => {
- const { light } = PageSectionVariants;
- const oneCrumbMatch = useRouteMatch({
- path: Object.keys(breadcrumbConfig)[0],
- strict: true,
- });
-
- const location = useLocation();
- const parts = location.pathname.split('/');
- if (parts.length > 2) {
- parts.pop();
- }
-
- const pathTitle = breadcrumbConfig[parts.join('/')];
- useTitle(pathTitle);
-
- const isOnlyOneCrumb = oneCrumbMatch && oneCrumbMatch.isExact;
-
- return (
-
-
-
- {!isOnlyOneCrumb && (
-
-
-
-
-
- )}
-
-
- {streamType !== 'none' && (
-
-
-
-
-
- )}
-
-
- );
-};
-
-const ActualTitle = ({ breadcrumbConfig }) => {
- const match = useRouteMatch();
- const title = breadcrumbConfig[match.url];
- let titleElement;
-
- if (match.isExact) {
- titleElement = (
-
- {title}
-
- );
- }
-
- if (!title) {
- titleElement = null;
- }
-
- return (
- <>
- {titleElement}
-
-
-
- >
- );
-};
-
-const Crumb = ({ breadcrumbConfig, showDivider }) => {
- const match = useRouteMatch();
- const crumb = breadcrumbConfig[match.url];
-
- let crumbElement = (
-
- {crumb}
-
- );
-
- if (match.isExact) {
- crumbElement = null;
- }
-
- if (!crumb) {
- crumbElement = null;
- }
- return (
- <>
- {crumbElement}
-
-
-
- >
- );
-};
-
-ScreenHeader.propTypes = {
- breadcrumbConfig: PropTypes.objectOf(PropTypes.string).isRequired,
-};
-
-Crumb.propTypes = {
- breadcrumbConfig: PropTypes.objectOf(PropTypes.string).isRequired,
-};
-
-export default ScreenHeader;
diff --git a/awx/ui/src/components/ScreenHeader/ScreenHeader.test.js b/awx/ui/src/components/ScreenHeader/ScreenHeader.test.js
deleted file mode 100644
index a0ba2b35dcaf..000000000000
--- a/awx/ui/src/components/ScreenHeader/ScreenHeader.test.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import React from 'react';
-import { MemoryRouter } from 'react-router-dom';
-
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-
-import ScreenHeader from './ScreenHeader';
-
-describe('', () => {
- let breadcrumbWrapper;
- let breadcrumb;
- let breadcrumbItem;
- let breadcrumbHeading;
-
- const config = {
- '/foo': 'Foo',
- '/foo/1': 'One',
- '/foo/1/bar': 'Bar',
- '/foo/1/bar/fiz': 'Fiz',
- };
-
- const findChildren = () => {
- breadcrumb = breadcrumbWrapper.find('ScreenHeader');
- breadcrumbItem = breadcrumbWrapper.find('BreadcrumbItem');
- breadcrumbHeading = breadcrumbWrapper.find('Title');
- };
-
- test('initially renders successfully', () => {
- breadcrumbWrapper = mountWithContexts(
-
-
-
- );
-
- findChildren();
-
- expect(breadcrumb).toHaveLength(1);
- expect(breadcrumbItem).toHaveLength(2);
- expect(breadcrumbHeading).toHaveLength(1);
- expect(breadcrumbItem.first().text()).toBe('Foo');
- expect(breadcrumbItem.last().text()).toBe('One');
- expect(breadcrumbHeading.text()).toBe('Bar');
- });
-
- test('renders breadcrumb items defined in breadcrumbConfig', () => {
- const routes = [
- ['/fo', 0],
- ['/foo', 0],
- ['/foo/1', 1],
- ['/foo/baz', 1],
- ['/foo/1/bar', 2],
- ['/foo/1/bar/fiz', 3],
- ];
-
- routes.forEach(([location, crumbLength]) => {
- breadcrumbWrapper = mountWithContexts(
-
-
-
- );
-
- expect(breadcrumbWrapper.find('BreadcrumbItem')).toHaveLength(
- crumbLength
- );
- });
- });
-});
diff --git a/awx/ui/src/components/ScreenHeader/index.js b/awx/ui/src/components/ScreenHeader/index.js
deleted file mode 100644
index 7f5ab3273325..000000000000
--- a/awx/ui/src/components/ScreenHeader/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ScreenHeader';
diff --git a/awx/ui/src/components/Search/AdvancedSearch.js b/awx/ui/src/components/Search/AdvancedSearch.js
deleted file mode 100644
index 88da024b59af..000000000000
--- a/awx/ui/src/components/Search/AdvancedSearch.js
+++ /dev/null
@@ -1,352 +0,0 @@
-import 'styled-components/macro';
-import React, { useEffect, useState } from 'react';
-import { string, func, bool, arrayOf } from 'prop-types';
-import { t } from '@lingui/macro';
-import {
- Button,
- ButtonVariant,
- Divider,
- InputGroup,
- Select,
- SelectGroup,
- SelectOption,
- SelectVariant,
- TextInput,
- Tooltip,
-} from '@patternfly/react-core';
-import { SearchIcon, QuestionCircleIcon } from '@patternfly/react-icons';
-import styled from 'styled-components';
-import { useLocation } from 'react-router-dom';
-import { useConfig } from 'contexts/Config';
-import getDocsBaseUrl from 'util/getDocsBaseUrl';
-import { SearchableKeys } from 'types';
-import RelatedLookupTypeInput from './RelatedLookupTypeInput';
-import LookupTypeInput from './LookupTypeInput';
-
-const AdvancedGroup = styled.div`
- display: flex;
-
- @media (max-width: 991px) {
- display: grid;
- grid-gap: var(--pf-c-toolbar__expandable-content--m-expanded--GridRowGap);
- }
-
- & .pf-c-select {
- min-width: 150px;
- }
-`;
-
-function AdvancedSearch({
- onSearch,
- searchableKeys,
- relatedSearchableKeys,
- maxSelectHeight,
- enableNegativeFiltering,
- enableRelatedFuzzyFiltering,
- handleIsAnsibleFactsSelected,
- isFilterCleared,
-}) {
- const relatedKeys = relatedSearchableKeys.filter(
- (sKey) => !searchableKeys.map(({ key }) => key).includes(sKey)
- );
- const [isPrefixDropdownOpen, setIsPrefixDropdownOpen] = useState(false);
- const [isKeyDropdownOpen, setIsKeyDropdownOpen] = useState(false);
- const [prefixSelection, setPrefixSelection] = useState(null);
- const [lookupSelection, setLookupSelection] = useState(null);
- const [keySelection, setKeySelection] = useState(null);
- const [searchValue, setSearchValue] = useState('');
- const [isTextInputDisabled, setIsTextInputDisabled] = useState(false);
- const { pathname, search } = useLocation();
-
- useEffect(() => {
- if (keySelection === 'ansible_facts') {
- handleIsAnsibleFactsSelected(true);
- setPrefixSelection(null);
- } else {
- handleIsAnsibleFactsSelected(false);
- }
- }, [keySelection]); // eslint-disable-line react-hooks/exhaustive-deps
-
- useEffect(() => {
- if (isFilterCleared && keySelection === 'ansible_facts') {
- setIsTextInputDisabled(false);
- }
- }, [isFilterCleared, keySelection]);
-
- useEffect(() => {
- if (
- (pathname.includes('edit') || pathname.includes('add')) &&
- keySelection === 'ansible_facts' &&
- search.includes('ansible_facts')
- ) {
- setIsTextInputDisabled(true);
- } else {
- setIsTextInputDisabled(false);
- }
- }, [keySelection, pathname, search]);
-
- const config = useConfig();
-
- const selectedKey = searchableKeys.find((k) => k.key === keySelection);
- const relatedSearchKeySelected =
- keySelection &&
- relatedSearchableKeys.indexOf(keySelection) > -1 &&
- !selectedKey;
- const lookupKeyType =
- keySelection && !relatedSearchKeySelected ? selectedKey?.type : null;
-
- useEffect(() => {
- if (relatedSearchKeySelected && keySelection !== 'ansible_facts') {
- setLookupSelection('name__icontains');
- } else {
- setLookupSelection(null);
- }
- }, [keySelection]); // eslint-disable-line react-hooks/exhaustive-deps
-
- useEffect(() => {
- if (lookupSelection === 'search') {
- setPrefixSelection(null);
- }
- }, [lookupSelection]); // eslint-disable-line react-hooks/exhaustive-deps
-
- const handleAdvancedSearch = (e) => {
- // keeps page from fully reloading
- e.preventDefault();
-
- if (searchValue) {
- const actualPrefix = prefixSelection === 'and' ? null : prefixSelection;
- const actualSearchKey = [actualPrefix, keySelection, lookupSelection]
- .filter((val) => !!val)
- .join('__');
- if (keySelection === 'ansible_facts') {
- const ansibleFactValue = `${actualSearchKey}__${searchValue}`;
- onSearch('host_filter', ansibleFactValue);
- } else {
- onSearch(actualSearchKey, searchValue);
- }
- setSearchValue('');
- }
- };
-
- const handleAdvancedTextKeyDown = (e) => {
- if (e.key && e.key === 'Enter') {
- handleAdvancedSearch(e);
- }
- };
-
- const renderSetType = () => (
-
- );
-
- const renderLookupType = () => {
- if (keySelection === 'ansible_facts') return null;
-
- return relatedSearchKeySelected ? (
-
- ) : (
-
- );
- };
-
- const renderTextInput = () => {
- let placeholderText;
- if (keySelection === 'labels' && lookupSelection === 'search') {
- placeholderText = 'e.g. label_1,label_2';
- }
-
- if (isTextInputDisabled) {
- return (
-
-
-
- );
- }
-
- return (
-
- );
- };
-
- const renderLookupSelection = () => {
- if (keySelection === 'ansible_facts') return null;
- return lookupSelection === 'search' ? (
-
- {renderSetType()}
-
- ) : (
- renderSetType()
- );
- };
-
- return (
-
- {renderLookupSelection()}
-
- {renderLookupType()}
-
-
- {renderTextInput()}
-
-
-
-
-
-
-
-
- );
-}
-
-AdvancedSearch.propTypes = {
- onSearch: func.isRequired,
- searchableKeys: SearchableKeys,
- relatedSearchableKeys: arrayOf(string),
- maxSelectHeight: string,
- enableNegativeFiltering: bool,
- enableRelatedFuzzyFiltering: bool,
- handleIsAnsibleFactsSelected: func,
-};
-
-AdvancedSearch.defaultProps = {
- searchableKeys: [],
- relatedSearchableKeys: [],
- maxSelectHeight: '300px',
- enableNegativeFiltering: true,
- enableRelatedFuzzyFiltering: true,
- handleIsAnsibleFactsSelected: () => {},
-};
-
-export default AdvancedSearch;
diff --git a/awx/ui/src/components/Search/AdvancedSearch.test.js b/awx/ui/src/components/Search/AdvancedSearch.test.js
deleted file mode 100644
index 8258ef681225..000000000000
--- a/awx/ui/src/components/Search/AdvancedSearch.test.js
+++ /dev/null
@@ -1,431 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import AdvancedSearch from './AdvancedSearch';
-
-describe('', () => {
- let wrapper;
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('Remove duplicates from searchableKeys/relatedSearchableKeys list', () => {
- wrapper = mountWithContexts(
-
- );
- wrapper
- .find('Select[aria-label="Key select"] SelectToggle')
- .simulate('click');
- expect(
- wrapper.find('Select[aria-label="Key select"] SelectOption')
- ).toHaveLength(3);
- });
-
- test("Don't call onSearch unless a search value is set", () => {
- const advancedSearchMock = jest.fn();
- wrapper = mountWithContexts(
-
- );
- wrapper
- .find('Select[aria-label="Key select"] SelectToggle')
- .simulate('click');
- wrapper
- .find('Select[aria-label="Key select"] SelectOption')
- .at(1)
- .simulate('click');
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .prop('onKeyDown')({ key: 'Enter', preventDefault: jest.fn });
- expect(advancedSearchMock).toBeCalledTimes(0);
- act(() => {
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .invoke('onChange')('foo');
- });
- wrapper.update();
- act(() => {
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .prop('onKeyDown')({ key: 'Enter', preventDefault: jest.fn });
- });
- wrapper.update();
- expect(advancedSearchMock).toBeCalledTimes(1);
- });
-
- test('Disable searchValue input until a key is set', () => {
- wrapper = mountWithContexts(
-
- );
- expect(
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .prop('isDisabled')
- ).toBe(true);
- act(() => {
- wrapper.find('Select[aria-label="Key select"]').invoke('onCreateOption')(
- 'foo'
- );
- });
- wrapper.update();
- expect(
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .prop('isDisabled')
- ).toBe(false);
- });
-
- test('Strip and__ set type from key', () => {
- const advancedSearchMock = jest.fn();
- wrapper = mountWithContexts(
-
- );
- act(() => {
- wrapper.find('Select[aria-label="Set type select"]').invoke('onSelect')(
- {},
- 'and'
- );
- wrapper.find('Select[aria-label="Key select"]').invoke('onCreateOption')(
- 'foo'
- );
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .invoke('onChange')('bar');
- });
- wrapper.update();
- act(() => {
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .prop('onKeyDown')({ key: 'Enter', preventDefault: jest.fn });
- });
- wrapper.update();
- expect(advancedSearchMock).toBeCalledWith('foo', 'bar');
- jest.clearAllMocks();
- act(() => {
- wrapper.find('Select[aria-label="Set type select"]').invoke('onSelect')(
- {},
- 'or'
- );
- wrapper.find('Select[aria-label="Key select"]').invoke('onCreateOption')(
- 'foo'
- );
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .invoke('onChange')('bar');
- });
- wrapper.update();
- act(() => {
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .prop('onKeyDown')({ key: 'Enter', preventDefault: jest.fn });
- });
- wrapper.update();
- expect(advancedSearchMock).toBeCalledWith('or__foo', 'bar');
- });
-
- test('Add __search lookup to key when applicable', () => {
- const advancedSearchMock = jest.fn();
- wrapper = mountWithContexts(
-
- );
- act(() => {
- wrapper.find('Select[aria-label="Key select"]').invoke('onCreateOption')(
- 'foo'
- );
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .invoke('onChange')('bar');
- });
- wrapper.update();
- act(() => {
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .prop('onKeyDown')({ key: 'Enter', preventDefault: jest.fn });
- });
- wrapper.update();
- expect(advancedSearchMock).toBeCalledWith('foo', 'bar');
- jest.clearAllMocks();
- act(() => {
- wrapper.find('Select[aria-label="Key select"]').invoke('onCreateOption')(
- 'bar'
- );
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .invoke('onChange')('bar');
- });
- wrapper.update();
- act(() => {
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .prop('onKeyDown')({ key: 'Enter', preventDefault: jest.fn });
- });
- wrapper.update();
- expect(advancedSearchMock).toBeCalledWith('bar', 'bar');
- jest.clearAllMocks();
- act(() => {
- wrapper.find('Select[aria-label="Key select"]').invoke('onCreateOption')(
- 'baz'
- );
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .invoke('onChange')('bar');
- });
- wrapper.update();
- act(() => {
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .prop('onKeyDown')({ key: 'Enter', preventDefault: jest.fn });
- });
- wrapper.update();
- expect(advancedSearchMock).toBeCalledWith('baz__name__icontains', 'bar');
- jest.clearAllMocks();
- act(() => {
- wrapper.find('Select[aria-label="Key select"]').invoke('onCreateOption')(
- 'baz'
- );
- });
- wrapper.update();
- act(() => {
- wrapper
- .find('Select[aria-label="Related search type"]')
- .invoke('onSelect')({}, 'search');
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .invoke('onChange')('bar');
- });
- wrapper.update();
- act(() => {
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .prop('onKeyDown')({ key: 'Enter', preventDefault: jest.fn });
- });
- wrapper.update();
- expect(advancedSearchMock).toBeCalledWith('baz__search', 'bar');
- });
-
- test('Key should be properly constructed from three typeaheads', () => {
- const advancedSearchMock = jest.fn();
- wrapper = mountWithContexts(
-
- );
- act(() => {
- wrapper.find('Select[aria-label="Set type select"]').invoke('onSelect')(
- {},
- 'or'
- );
- wrapper.find('Select[aria-label="Key select"]').invoke('onSelect')(
- {},
- 'foo'
- );
- });
- wrapper.update();
- act(() => {
- wrapper.find('Select[aria-label="Lookup select"]').invoke('onSelect')(
- {},
- 'exact'
- );
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .invoke('onChange')('bar');
- });
- wrapper.update();
- act(() => {
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .prop('onKeyDown')({ key: 'Enter', preventDefault: jest.fn });
- });
- wrapper.update();
- expect(advancedSearchMock).toBeCalledWith('or__foo__exact', 'bar');
- });
-
- test('searchValue should clear after onSearch is called', () => {
- const advancedSearchMock = jest.fn();
- wrapper = mountWithContexts(
-
- );
- act(() => {
- wrapper.find('Select[aria-label="Set type select"]').invoke('onSelect')(
- {},
- 'or'
- );
- wrapper.find('Select[aria-label="Key select"]').invoke('onCreateOption')(
- 'foo'
- );
- });
- wrapper.update();
- act(() => {
- wrapper.find('Select[aria-label="Lookup select"]').invoke('onSelect')(
- {},
- 'exact'
- );
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .invoke('onChange')('bar');
- });
- wrapper.update();
- act(() => {
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .prop('onKeyDown')({ key: 'Enter', preventDefault: jest.fn });
- });
- wrapper.update();
- expect(advancedSearchMock).toBeCalledWith('or__foo__exact', 'bar');
- expect(
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .prop('value')
- ).toBe('');
- });
-
- test('typeahead onClear should remove key components', () => {
- const advancedSearchMock = jest.fn();
- wrapper = mountWithContexts(
-
- );
- act(() => {
- wrapper.find('Select[aria-label="Set type select"]').invoke('onSelect')(
- {},
- 'or'
- );
- wrapper.find('Select[aria-label="Key select"]').invoke('onCreateOption')(
- 'foo'
- );
- });
- wrapper.update();
- act(() => {
- wrapper.find('Select[aria-label="Lookup select"]').invoke('onSelect')(
- {},
- 'exact'
- );
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .invoke('onChange')('bar');
- });
- wrapper.update();
- act(() => {
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .prop('onKeyDown')({ key: 'Enter', preventDefault: jest.fn });
- });
- wrapper.update();
- expect(advancedSearchMock).toBeCalledWith('or__foo__exact', 'bar');
- jest.clearAllMocks();
- act(() => {
- wrapper.find('Select[aria-label="Set type select"]').invoke('onClear')();
- wrapper.find('Select[aria-label="Key select"]').invoke('onClear')();
- wrapper.find('Select[aria-label="Lookup select"]').invoke('onClear')();
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .invoke('onChange')('baz');
- });
- wrapper.update();
- act(() => {
- wrapper
- .find('TextInputBase[aria-label="Advanced search value input"]')
- .prop('onKeyDown')({ key: 'Enter', preventDefault: jest.fn });
- });
- wrapper.update();
- expect(advancedSearchMock).toBeCalledWith('', 'baz');
- });
-
- test('Remove not operator from set type', () => {
- wrapper = mountWithContexts(
-
- );
- wrapper
- .find('Select[aria-label="Set type select"] SelectToggle')
- .simulate('click');
- const selectOptions = wrapper.find(
- 'Select[aria-label="Set type select"] SelectOption'
- );
- expect(selectOptions).toHaveLength(2);
- expect(
- selectOptions.find('SelectOption[id="or-option-select"]').prop('value')
- ).toBe('or');
- expect(
- selectOptions.find('SelectOption[id="and-option-select"]').prop('value')
- ).toBe('and');
- });
-
- test('Remove search option from related search type', () => {
- wrapper = mountWithContexts(
-
- );
- act(() => {
- wrapper.find('Select[aria-label="Key select"]').invoke('onCreateOption')(
- 'baz'
- );
- });
- wrapper.update();
- wrapper
- .find('Select[aria-label="Related search type"] SelectToggle')
- .simulate('click');
- const selectOptions = wrapper.find(
- 'Select[aria-label="Related search type"] SelectOption'
- );
- expect(selectOptions).toHaveLength(3);
- expect(
- selectOptions.find('SelectOption[id="name-option-select"]').prop('value')
- ).toBe('name__icontains');
- expect(
- selectOptions.find('SelectOption[id="id-option-select"]').prop('value')
- ).toBe('id');
- });
-});
diff --git a/awx/ui/src/components/Search/LookupTypeInput.js b/awx/ui/src/components/Search/LookupTypeInput.js
deleted file mode 100644
index bb5eea710689..000000000000
--- a/awx/ui/src/components/Search/LookupTypeInput.js
+++ /dev/null
@@ -1,157 +0,0 @@
-import React, { useState } from 'react';
-import { string, oneOfType, arrayOf, func } from 'prop-types';
-import { t } from '@lingui/macro';
-import { Select, SelectOption, SelectVariant } from '@patternfly/react-core';
-
-function Option({ show, ...props }) {
- if (!show) {
- return null;
- }
- return ;
-}
-Option.defaultProps = {
- show: true,
-};
-
-function LookupTypeInput({ value, type, setValue, maxSelectHeight }) {
- const [isOpen, setIsOpen] = useState(false);
-
- return (
-
- );
-}
-LookupTypeInput.propTypes = {
- type: string,
- value: oneOfType([string, arrayOf(string)]),
- setValue: func.isRequired,
- maxSelectHeight: string,
-};
-LookupTypeInput.defaultProps = {
- type: 'string',
- value: '',
- maxSelectHeight: '300px',
-};
-
-export default LookupTypeInput;
diff --git a/awx/ui/src/components/Search/RelatedLookupTypeInput.js b/awx/ui/src/components/Search/RelatedLookupTypeInput.js
deleted file mode 100644
index 008c83164bc5..000000000000
--- a/awx/ui/src/components/Search/RelatedLookupTypeInput.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import React, { useState } from 'react';
-import { t } from '@lingui/macro';
-import { Select, SelectOption, SelectVariant } from '@patternfly/react-core';
-
-function RelatedLookupTypeInput({
- value,
- setValue,
- maxSelectHeight,
- enableFuzzyFiltering,
-}) {
- const [isOpen, setIsOpen] = useState(false);
-
- return (
-
- );
-}
-
-export default RelatedLookupTypeInput;
diff --git a/awx/ui/src/components/Search/Search.js b/awx/ui/src/components/Search/Search.js
deleted file mode 100644
index fb869b72f84e..000000000000
--- a/awx/ui/src/components/Search/Search.js
+++ /dev/null
@@ -1,319 +0,0 @@
-import 'styled-components/macro';
-import React, { useState, useEffect } from 'react';
-import PropTypes from 'prop-types';
-
-import { t } from '@lingui/macro';
-import { useLocation } from 'react-router-dom';
-import {
- Button,
- ButtonVariant,
- InputGroup,
- Select,
- SelectOption,
- SelectVariant,
- TextInput,
- ToolbarGroup,
- ToolbarItem,
- ToolbarFilter,
-} from '@patternfly/react-core';
-import { SearchIcon } from '@patternfly/react-icons';
-import styled from 'styled-components';
-import { parseQueryString } from 'util/qs';
-import { QSConfig, SearchColumns, SearchableKeys } from 'types';
-import AdvancedSearch from './AdvancedSearch';
-import getChipsByKey from './getChipsByKey';
-
-const NoOptionDropdown = styled.div`
- align-self: stretch;
- border: 1px solid var(--pf-global--BorderColor--300);
- padding: 5px 15px;
- white-space: nowrap;
- border-bottom-color: var(--pf-global--BorderColor--200);
-`;
-
-function Search({
- columns,
- onSearch,
- onReplaceSearch,
- onRemove,
- qsConfig,
- searchableKeys,
- relatedSearchableKeys,
- onShowAdvancedSearch,
- isDisabled,
- maxSelectHeight,
- enableNegativeFiltering,
- enableRelatedFuzzyFiltering,
- handleIsAnsibleFactsSelected,
- isFilterCleared,
-}) {
- const location = useLocation();
- const [isSearchDropdownOpen, setIsSearchDropdownOpen] = useState(false);
- const [searchKey, setSearchKey] = useState(
- (() => {
- const defaultColumn = columns.filter((col) => col.isDefault);
-
- if (defaultColumn.length !== 1) {
- throw new Error(
- 'One (and only one) searchColumn must be marked isDefault: true'
- );
- }
-
- return defaultColumn[0]?.key;
- })()
- );
- const [searchValue, setSearchValue] = useState('');
- const [isFilterDropdownOpen, setIsFilterDropdownOpen] = useState(false);
-
- const params = parseQueryString(qsConfig, location.search);
- if (params?.host_filter) {
- params.ansible_facts = params.host_filter.substring(
- 'ansible_facts__'.length
- );
- delete params.host_filter;
- }
-
- const searchChips = getChipsByKey(params, columns, qsConfig);
- const [chipsByKey, setChipsByKey] = useState(
- JSON.parse(JSON.stringify(searchChips))
- );
-
- useEffect(() => {
- Object.keys(chipsByKey).forEach((el) => {
- chipsByKey[el].chips = [];
- });
- setChipsByKey({ ...chipsByKey, ...searchChips });
- }, [location.search]); // eslint-disable-line react-hooks/exhaustive-deps
-
- const handleDropdownSelect = ({ target }) => {
- const { key: actualSearchKey } = columns.find(
- ({ name }) => name === target.innerText
- );
- onShowAdvancedSearch(actualSearchKey === 'advanced');
- setIsFilterDropdownOpen(false);
- setSearchKey(actualSearchKey);
- };
-
- const handleSearch = (e) => {
- // keeps page from fully reloading
- e.preventDefault();
-
- if (searchValue) {
- onSearch(searchKey, searchValue);
- setSearchValue('');
- }
- };
-
- const handleTextKeyDown = (e) => {
- if (e.key && e.key === 'Enter') {
- handleSearch(e);
- }
- };
-
- const handleFilterDropdownSelect = (key, event, actualValue) => {
- if (event.target.checked) {
- onSearch(key, actualValue);
- } else {
- onRemove(key, actualValue);
- }
- };
-
- const { name: searchColumnName } = columns.find(
- ({ key }) => key === searchKey
- );
-
- const searchOptions = columns
- .filter(({ key }) => key !== searchKey)
- .map(({ key, name }) => (
-
- {name}
-
- ));
-
- return (
-
-
- {searchOptions.length > 0 ? (
-
- ) : (
- {searchColumnName}
- )}
-
- {columns.map(({ key, name, options, isBoolean, booleanLabels = {} }) => (
- {
- const [columnKey, ...value] = chip.key.split(':');
- onRemove(columnKey, value.join(':'));
- }}
- categoryName={chipsByKey[key] ? chipsByKey[key].label : key}
- key={key}
- showToolbarItem={searchKey === key}
- >
- {(key === 'advanced' && (
-
- )) ||
- (options && (
-
- )) ||
- (isBoolean && (
-
- )) || (
-
- {/* TODO: add support for dates:
- qsConfig.dateFields.filter(field => field === key).length && "date" */}
- field === searchKey
- ) &&
- 'number') ||
- 'search'
- }
- aria-label={t`Search text input`}
- value={searchValue}
- onChange={setSearchValue}
- onKeyDown={handleTextKeyDown}
- isDisabled={isDisabled}
- />
-
-
-
-
- )}
-
- ))}
- {/* Add a ToolbarFilter for any key that doesn't have it's own
- search column so the chips show up */}
- {Object.keys(chipsByKey)
- .filter((val) => columns.map((val2) => val2.key).indexOf(val) === -1)
- .map((leftoverKey) => (
- {
- const [columnKey, ...value] = chip.key.split(':');
- if (columnKey === 'ansible_facts') {
- onRemove('host_filter', `${columnKey}__${value}`);
- } else {
- onRemove(columnKey, value.join(':'));
- }
- }}
- categoryName={
- chipsByKey[leftoverKey]
- ? chipsByKey[leftoverKey].label
- : leftoverKey
- }
- key={leftoverKey}
- />
- ))}
-
- );
-}
-
-Search.propTypes = {
- qsConfig: QSConfig.isRequired,
- columns: SearchColumns.isRequired,
- onSearch: PropTypes.func,
- onRemove: PropTypes.func,
- onShowAdvancedSearch: PropTypes.func.isRequired,
- isDisabled: PropTypes.bool,
- maxSelectHeight: PropTypes.string,
- enableNegativeFiltering: PropTypes.bool,
- enableRelatedFuzzyFiltering: PropTypes.bool,
- searchableKeys: SearchableKeys,
-};
-
-Search.defaultProps = {
- onSearch: null,
- onRemove: null,
- isDisabled: false,
- maxSelectHeight: '300px',
- enableNegativeFiltering: true,
- enableRelatedFuzzyFiltering: true,
- searchableKeys: [],
-};
-
-export default Search;
diff --git a/awx/ui/src/components/Search/Search.test.js b/awx/ui/src/components/Search/Search.test.js
deleted file mode 100644
index a22fba9aaf59..000000000000
--- a/awx/ui/src/components/Search/Search.test.js
+++ /dev/null
@@ -1,368 +0,0 @@
-import React from 'react';
-import { Toolbar, ToolbarContent } from '@patternfly/react-core';
-import { createMemoryHistory } from 'history';
-import { act } from 'react-dom/test-utils';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import Search from './Search';
-
-describe('', () => {
- let search;
-
- const QS_CONFIG = {
- namespace: 'organization',
- dateFields: ['modified', 'created'],
- defaultParams: { page: 1, page_size: 5, order_by: 'name' },
- integerFields: ['page', 'page_size'],
- };
-
- afterEach(() => {
- if (search) {
- search = null;
- }
- });
-
- test('it triggers the expected callbacks', () => {
- const columns = [{ name: 'Name', key: 'name__icontains', isDefault: true }];
-
- const searchBtn = 'button[aria-label="Search submit button"]';
- const searchTextInput = 'input[aria-label="Search text input"]';
-
- const onSearch = jest.fn();
-
- search = mountWithContexts(
- {}}
- collapseListedFiltersBreakpoint="lg"
- >
-
-
-
-
- );
-
- search.find(searchTextInput).instance().value = 'test-321';
- search.find(searchTextInput).simulate('change');
- search.find(searchBtn).simulate('click');
-
- expect(onSearch).toHaveBeenCalledTimes(1);
- expect(onSearch).toBeCalledWith('name__icontains', 'test-321');
- });
-
- test('changing key select updates which key is called for onSearch', () => {
- const searchButton = 'button[aria-label="Search submit button"]';
- const searchTextInput = 'input[aria-label="Search text input"]';
- const columns = [
- { name: 'Name', key: 'name__icontains', isDefault: true },
- { name: 'Description', key: 'description__icontains' },
- ];
- const onSearch = jest.fn();
- const wrapper = mountWithContexts(
- {}}
- collapseListedFiltersBreakpoint="lg"
- >
-
-
-
-
- );
-
- act(() => {
- wrapper.find('Select[aria-label="Simple key select"]').invoke('onSelect')(
- { target: { innerText: 'Description' } }
- );
- });
- wrapper.update();
- wrapper.find(searchTextInput).instance().value = 'test-321';
- wrapper.find(searchTextInput).simulate('change');
- wrapper.find(searchButton).simulate('click');
-
- expect(onSearch).toHaveBeenCalledTimes(1);
- expect(onSearch).toBeCalledWith('description__icontains', 'test-321');
- });
-
- test('changing key select to and from advanced causes onShowAdvancedSearch callback to be invoked', () => {
- const columns = [
- { name: 'Name', key: 'name__icontains', isDefault: true },
- { name: 'Description', key: 'description__icontains' },
- { name: 'Advanced', key: 'advanced' },
- ];
- const onSearch = jest.fn();
- const onShowAdvancedSearch = jest.fn();
- const wrapper = mountWithContexts(
- {}}
- collapseListedFiltersBreakpoint="lg"
- >
-
-
-
-
- );
-
- act(() => {
- wrapper.find('Select[aria-label="Simple key select"]').invoke('onSelect')(
- { target: { innerText: 'Advanced' } }
- );
- });
- wrapper.update();
- expect(onShowAdvancedSearch).toHaveBeenCalledTimes(1);
- expect(onShowAdvancedSearch).toBeCalledWith(true);
- jest.clearAllMocks();
- act(() => {
- wrapper.find('Select[aria-label="Simple key select"]').invoke('onSelect')(
- { target: { innerText: 'Description' } }
- );
- });
- wrapper.update();
- expect(onShowAdvancedSearch).toHaveBeenCalledTimes(1);
- expect(onShowAdvancedSearch).toBeCalledWith(false);
- });
-
- test('attempt to search with empty string', () => {
- const searchButton = 'button[aria-label="Search submit button"]';
- const searchTextInput = 'input[aria-label="Search text input"]';
- const columns = [{ name: 'Name', key: 'name__icontains', isDefault: true }];
- const onSearch = jest.fn();
- const wrapper = mountWithContexts(
- {}}
- collapseListedFiltersBreakpoint="lg"
- >
-
-
-
-
- );
-
- wrapper.find(searchTextInput).instance().value = '';
- wrapper.find(searchTextInput).simulate('change');
- wrapper.find(searchButton).simulate('click');
-
- expect(onSearch).toHaveBeenCalledTimes(0);
- });
-
- test('search with a valid string', () => {
- const searchButton = 'button[aria-label="Search submit button"]';
- const searchTextInput = 'input[aria-label="Search text input"]';
- const columns = [{ name: 'Name', key: 'name__icontains', isDefault: true }];
- const onSearch = jest.fn();
- const wrapper = mountWithContexts(
- {}}
- collapseListedFiltersBreakpoint="lg"
- >
-
-
-
-
- );
-
- wrapper.find(searchTextInput).instance().value = 'test-321';
- wrapper.find(searchTextInput).simulate('change');
- wrapper.find(searchButton).simulate('click');
-
- expect(onSearch).toHaveBeenCalledTimes(1);
- expect(onSearch).toBeCalledWith('name__icontains', 'test-321');
- });
-
- test('filter keys are properly labeled', () => {
- const columns = [
- { name: 'Name', key: 'name__icontains', isDefault: true },
- { name: 'Type', key: 'or__scm_type', options: [['foo', 'Foo Bar!']] },
- { name: 'Description', key: 'description' },
- ];
- const query =
- '?organization.or__scm_type=foo&organization.name__icontains=bar&item.page_size=10';
- const history = createMemoryHistory({
- initialEntries: [`/organizations/${query}`],
- });
- const wrapper = mountWithContexts(
- {}}
- collapseListedFiltersBreakpoint="lg"
- >
-
-
-
- ,
- { context: { router: { history } } }
- );
- const typeFilterWrapper = wrapper.find(
- 'ToolbarFilter[categoryName="Type (or__scm_type)"]'
- );
- expect(typeFilterWrapper.prop('chips')[0].key).toEqual('or__scm_type:foo');
- const nameFilterWrapper = wrapper.find(
- 'ToolbarFilter[categoryName="Name (name__icontains)"]'
- );
- expect(nameFilterWrapper.prop('chips')[0].key).toEqual(
- 'name__icontains:bar'
- );
- });
-
- test('should test handle remove of option-based key', async () => {
- const qsConfigNew = {
- namespace: 'item',
- defaultParams: { page: 1, page_size: 5, order_by: '-type' },
- integerFields: [],
- };
- const columns = [
- {
- name: 'type',
- key: 'type',
- options: [['foo', 'Foo Bar!']],
- isDefault: true,
- },
- ];
- const query = '?item.or__type=foo&item.page_size=10';
- const history = createMemoryHistory({
- initialEntries: [`/organizations/1/teams${query}`],
- });
- const onRemove = jest.fn();
- const wrapper = mountWithContexts(
- {}}
- collapseListedFiltersBreakpoint="lg"
- >
-
-
-
- ,
- { context: { router: { history } } }
- );
- expect(history.location.search).toEqual(query);
- // click remove button on chip
- await act(async () => {
- wrapper
- .find('.pf-c-chip button[aria-label="close"]')
- .at(0)
- .simulate('click');
- });
- expect(onRemove).toBeCalledWith('or__type', 'foo');
- });
-
- test('should test handle remove of option-based with empty string value', async () => {
- const qsConfigNew = {
- namespace: 'item',
- defaultParams: { page: 1, page_size: 5, order_by: '-type' },
- integerFields: [],
- };
- const columns = [
- {
- name: 'type',
- key: 'type',
- options: [['', 'manual']],
- isDefault: true,
- },
- ];
- const query = '?item.or__type=&item.page_size=10';
- const history = createMemoryHistory({
- initialEntries: [`/organizations/1/teams${query}`],
- });
- const onRemove = jest.fn();
- const wrapper = mountWithContexts(
- {}}
- collapseListedFiltersBreakpoint="lg"
- >
-
-
-
- ,
- { context: { router: { history } } }
- );
- expect(history.location.search).toEqual(query);
- // click remove button on chip
- await act(async () => {
- wrapper
- .find('.pf-c-chip button[aria-label="close"]')
- .at(0)
- .simulate('click');
- });
- expect(onRemove).toBeCalledWith('or__type', '');
- });
-
- test("ToolbarFilter added for any key that doesn't have search column", () => {
- const columns = [
- { name: 'Name', key: 'name__icontains', isDefault: true },
- { name: 'Type', key: 'or__scm_type', options: [['foo', 'Foo Bar!']] },
- { name: 'Description', key: 'description' },
- ];
- const query =
- '?organization.or__scm_type=foo&organization.name__icontains=bar&organization.name__exact=baz&item.page_size=10&organization.foo=bar';
- const history = createMemoryHistory({
- initialEntries: [`/organizations/${query}`],
- });
- const wrapper = mountWithContexts(
- {}}
- collapseListedFiltersBreakpoint="lg"
- >
-
-
-
- ,
- { context: { router: { history } } }
- );
- const nameExactFilterWrapper = wrapper.find(
- 'ToolbarFilter[categoryName="name__exact"]'
- );
- expect(nameExactFilterWrapper.prop('chips')[0].key).toEqual(
- 'name__exact:baz'
- );
- const fooFilterWrapper = wrapper.find('ToolbarFilter[categoryName="foo"]');
- expect(fooFilterWrapper.prop('chips')[0].key).toEqual('foo:bar');
- });
-});
diff --git a/awx/ui/src/components/Search/getChipsByKey.js b/awx/ui/src/components/Search/getChipsByKey.js
deleted file mode 100644
index c1fe02585986..000000000000
--- a/awx/ui/src/components/Search/getChipsByKey.js
+++ /dev/null
@@ -1,56 +0,0 @@
-function filterDefaultParams(paramsArr, config) {
- const defaultParamsKeys = Object.keys(config.defaultParams || {});
- return paramsArr.filter((key) => defaultParamsKeys.indexOf(key) === -1);
-}
-
-function getLabelFromValue(columns, value, colKey) {
- let label = value;
- const currentSearchColumn = columns.find(({ key }) => key === colKey);
- if (currentSearchColumn?.options?.length) {
- [, label] = currentSearchColumn.options.find(
- ([optVal]) => optVal === value
- );
- } else if (currentSearchColumn?.booleanLabels) {
- label = currentSearchColumn.booleanLabels[value];
- }
- return (label || colKey).toString();
-}
-
-export default function getChipsByKey(queryParams, columns, qsConfig) {
- const queryParamsByKey = {};
- columns.forEach(({ name, key }) => {
- queryParamsByKey[key] = { key, label: `${name} (${key})`, chips: [] };
- });
- const nonDefaultParams = filterDefaultParams(
- Object.keys(queryParams || {}),
- qsConfig
- );
-
- nonDefaultParams.forEach((key) => {
- const columnKey = key;
- const label = columns.filter(
- ({ key: keyToCheck }) => columnKey === keyToCheck
- ).length
- ? `${
- columns.find(({ key: keyToCheck }) => columnKey === keyToCheck).name
- } (${key})`
- : columnKey;
-
- queryParamsByKey[columnKey] = { key, label, chips: [] };
-
- if (Array.isArray(queryParams[key])) {
- queryParams[key].forEach((val) =>
- queryParamsByKey[columnKey].chips.push({
- key: `${key}:${val}`,
- node: getLabelFromValue(columns, val, columnKey),
- })
- );
- } else {
- queryParamsByKey[columnKey].chips.push({
- key: `${key}:${queryParams[key]}`,
- node: getLabelFromValue(columns, queryParams[key], columnKey),
- });
- }
- });
- return queryParamsByKey;
-}
diff --git a/awx/ui/src/components/Search/getChipsByKey.test.js b/awx/ui/src/components/Search/getChipsByKey.test.js
deleted file mode 100644
index cf53a2641cee..000000000000
--- a/awx/ui/src/components/Search/getChipsByKey.test.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import getChipsByKey from './getChipsByKey';
-
-describe('getChipsByKey', () => {
- const qsConfig = {
- namespace: 'job',
- defaultParams: {
- order_by: '-finished',
- page: 1,
- page_size: 20,
- },
- integerFields: ['id', 'page', 'page_size'],
- dateFields: ['modified', 'created'],
- };
- const columns = [
- { name: 'Name', key: 'name__icontains', isDefault: true },
- { name: 'ID', key: 'id' },
- {
- name: 'Job Type',
- key: 'or__type',
- options: [
- ['project_update', 'Source Control Update'],
- ['inventory_update', 'Inventory Sync'],
- ['job', 'Playbook Run'],
- ['ad_hoc_command', 'Command'],
- ['system_job', 'Management Job'],
- ['workflow_job', 'Workflow Job'],
- ],
- },
- { name: 'Limit', key: 'job__limit' },
- ];
- const defaultQueryParams = {
- page: 1,
- page_size: 20,
- order_by: '-finished',
- };
-
- test('should get initial chips', () => {
- expect(getChipsByKey(defaultQueryParams, columns, qsConfig)).toEqual({
- id: {
- key: 'id',
- label: 'ID (id)',
- chips: [],
- },
- job__limit: {
- key: 'job__limit',
- label: 'Limit (job__limit)',
- chips: [],
- },
- name__icontains: {
- key: 'name__icontains',
- label: 'Name (name__icontains)',
- chips: [],
- },
- or__type: {
- key: 'or__type',
- label: 'Job Type (or__type)',
- chips: [],
- },
- });
- });
-
- test('should get chips from query string', () => {
- const queryParams = {
- page: 1,
- page_size: 20,
- order_by: '-finished',
- name__icontains: 'job',
- };
-
- expect(getChipsByKey(queryParams, columns, qsConfig)).toEqual({
- id: {
- key: 'id',
- label: 'ID (id)',
- chips: [],
- },
- job__limit: {
- key: 'job__limit',
- label: 'Limit (job__limit)',
- chips: [],
- },
- name__icontains: {
- key: 'name__icontains',
- label: 'Name (name__icontains)',
- chips: [
- {
- key: 'name__icontains:job',
- node: 'job',
- },
- ],
- },
- or__type: {
- key: 'or__type',
- label: 'Job Type (or__type)',
- chips: [],
- },
- });
- });
-});
diff --git a/awx/ui/src/components/Search/index.js b/awx/ui/src/components/Search/index.js
deleted file mode 100644
index 85bb434b226e..000000000000
--- a/awx/ui/src/components/Search/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './Search';
diff --git a/awx/ui/src/components/SelectableCard/SelectableCard.js b/awx/ui/src/components/SelectableCard/SelectableCard.js
deleted file mode 100644
index 559eb00e465e..000000000000
--- a/awx/ui/src/components/SelectableCard/SelectableCard.js
+++ /dev/null
@@ -1,76 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import styled from 'styled-components';
-
-const SelectableItem = styled.div`
- min-width: 200px;
- border: 1px solid var(--pf-global--BorderColor--200);
- border-radius: var(--pf-global--BorderRadius--sm);
- border: 1px solid;
- border-color: ${(props) =>
- props.isSelected
- ? 'var(--pf-global--active-color--100)'
- : 'var(--pf-global--BorderColor--200)'};
- margin-right: 20px;
- display: flex;
- cursor: pointer;
-`;
-
-const Indicator = styled.div`
- display: flex;
- flex: 0 0 5px;
- background-color: ${(props) =>
- props.isSelected ? 'var(--pf-global--active-color--100)' : null};
-`;
-
-const Contents = styled.div`
- padding: 10px 20px;
-`;
-
-const Description = styled.p`
- font-size: 14px;
-`;
-
-function SelectableCard({
- label,
- description,
- onClick,
- isSelected,
- dataCy,
- ariaLabel,
-}) {
- return (
-
-
-
- {label}
- {description}
-
-
- );
-}
-
-SelectableCard.propTypes = {
- label: PropTypes.string,
- description: PropTypes.string,
- onClick: PropTypes.func.isRequired,
- isSelected: PropTypes.bool,
- ariaLabel: PropTypes.string,
-};
-
-SelectableCard.defaultProps = {
- label: '',
- description: '',
- isSelected: false,
- ariaLabel: '',
-};
-
-export default SelectableCard;
diff --git a/awx/ui/src/components/SelectableCard/SelectableCard.test.js b/awx/ui/src/components/SelectableCard/SelectableCard.test.js
deleted file mode 100644
index 75cab3dee6bf..000000000000
--- a/awx/ui/src/components/SelectableCard/SelectableCard.test.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import SelectableCard from './SelectableCard';
-
-describe('', () => {
- let wrapper;
- const onClick = jest.fn();
- test('initially renders without crashing when not selected', () => {
- wrapper = shallow();
- expect(wrapper.length).toBe(1);
- });
-
- test('initially renders without crashing when selected', () => {
- wrapper = shallow();
- expect(wrapper.length).toBe(1);
- });
-});
diff --git a/awx/ui/src/components/SelectableCard/index.js b/awx/ui/src/components/SelectableCard/index.js
deleted file mode 100644
index 7488713156be..000000000000
--- a/awx/ui/src/components/SelectableCard/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './SelectableCard';
diff --git a/awx/ui/src/components/SelectedList/DraggableSelectedList.js b/awx/ui/src/components/SelectedList/DraggableSelectedList.js
deleted file mode 100644
index 8724e0220d3e..000000000000
--- a/awx/ui/src/components/SelectedList/DraggableSelectedList.js
+++ /dev/null
@@ -1,139 +0,0 @@
-import React, { useState } from 'react';
-import PropTypes from 'prop-types';
-import {
- Button,
- DataList,
- DataListAction,
- DataListItem,
- DataListCell,
- DataListItemRow,
- DataListControl,
- DataListDragButton,
- DataListItemCells,
-} from '@patternfly/react-core';
-import { TimesIcon } from '@patternfly/react-icons';
-import styled from 'styled-components';
-import { t } from '@lingui/macro';
-
-const RemoveActionSection = styled(DataListAction)`
- && {
- align-items: center;
- padding: 0;
- }
-`;
-
-function DraggableSelectedList({ selected, onRemove, onRowDrag }) {
- const [liveText, setLiveText] = useState('');
- const [id, setId] = useState('');
- const [isDragging, setIsDragging] = useState(false);
-
- const onDragStart = (newId) => {
- setId(newId);
- setLiveText(t`Dragging started for item id: ${newId}.`);
- setIsDragging(true);
- };
-
- const onDragMove = (oldIndex, newIndex) => {
- setLiveText(
- t`Dragging item ${id}. Item with index ${oldIndex} in now ${newIndex}.`
- );
- };
-
- const onDragCancel = () => {
- setLiveText(t`Dragging cancelled. List is unchanged.`);
- setIsDragging(false);
- };
-
- const onDragFinish = (newItemOrder) => {
- const selectedItems = newItemOrder.map((item) =>
- selected.find((i) => i.name === item)
- );
- onRowDrag(selectedItems);
- setIsDragging(false);
- };
-
- const removeItem = (item) => {
- onRemove(selected.find((i) => i.name === item));
- };
-
- if (selected.length <= 0) {
- return null;
- }
-
- const orderedList = selected.map((item) => item?.name);
-
- return (
- <>
-
-
- {liveText}
-
- >
- );
-}
-
-const ListItem = PropTypes.shape({
- id: PropTypes.number.isRequired,
- name: PropTypes.string.isRequired,
-});
-DraggableSelectedList.propTypes = {
- onRemove: PropTypes.func,
- onRowDrag: PropTypes.func,
- selected: PropTypes.arrayOf(ListItem),
-};
-DraggableSelectedList.defaultProps = {
- onRemove: () => null,
- onRowDrag: () => null,
- selected: [],
-};
-
-export default DraggableSelectedList;
diff --git a/awx/ui/src/components/SelectedList/DraggableSelectedList.test.js b/awx/ui/src/components/SelectedList/DraggableSelectedList.test.js
deleted file mode 100644
index fef0e1e48d81..000000000000
--- a/awx/ui/src/components/SelectedList/DraggableSelectedList.test.js
+++ /dev/null
@@ -1,133 +0,0 @@
-// These tests have been turned off because they fail due to a console wanring coming from patternfly.
-// The warning is that the onDrag api has been deprecated. It's replacement is a DragDrop component,
-// however that component is not keyboard accessible. Therefore we have elected to turn off these tests.
-//github.com/patternfly/patternfly-react/issues/6317s
-
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import DraggableSelectedList from './DraggableSelectedList';
-
-describe.skip('', () => {
- let wrapper;
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should render expected rows', () => {
- const mockSelected = [
- {
- id: 1,
- name: 'foo',
- },
- {
- id: 2,
- name: 'bar',
- },
- ];
- wrapper = mountWithContexts(
- {}}
- onRowDrag={() => {}}
- />
- );
- expect(wrapper.find('DraggableSelectedList').length).toBe(1);
- expect(wrapper.find('DataListItem').length).toBe(2);
- expect(
- wrapper
- .find('DataListItem DataListCell')
- .first()
- .containsMatchingElement(1. foo)
- ).toEqual(true);
- expect(
- wrapper
- .find('DataListItem DataListCell')
- .last()
- .containsMatchingElement(2. bar)
- ).toEqual(true);
- });
-
- test('should not render when selected list is empty', () => {
- wrapper = mountWithContexts(
- {}}
- onRowDrag={() => {}}
- />
- );
- expect(wrapper.find('DataList').length).toBe(0);
- });
-
- test('should call onRemove callback prop on remove button click', () => {
- const onRemove = jest.fn();
- const mockSelected = [
- {
- id: 1,
- name: 'foo',
- },
- ];
- wrapper = mountWithContexts(
-
- );
- expect(
- wrapper
- .find('DataListDragButton[aria-label="Reorder"]')
- .prop('isDisabled')
- ).toBe(true);
- wrapper
- .find('DataListItem[id="foo"] Button[aria-label="Remove"]')
- .simulate('click');
- expect(onRemove).toBeCalledWith({
- id: 1,
- name: 'foo',
- });
- });
-
- test('should disable remove button when dragging item', () => {
- const mockSelected = [
- {
- id: 1,
- name: 'foo',
- },
- {
- id: 2,
- name: 'bar',
- },
- ];
- wrapper = mountWithContexts(
- {}}
- onRowDrag={() => {}}
- />
- );
-
- expect(
- wrapper.find('Button[aria-label="Remove"]').at(0).prop('isDisabled')
- ).toBe(false);
- expect(
- wrapper.find('Button[aria-label="Remove"]').at(1).prop('isDisabled')
- ).toBe(false);
- act(() => {
- wrapper.find('DataList').prop('onDragStart')();
- });
- wrapper.update();
- expect(
- wrapper.find('Button[aria-label="Remove"]').at(0).prop('isDisabled')
- ).toBe(true);
- expect(
- wrapper.find('Button[aria-label="Remove"]').at(1).prop('isDisabled')
- ).toBe(true);
- act(() => {
- wrapper.find('DataList').prop('onDragCancel')();
- });
- wrapper.update();
- expect(
- wrapper.find('Button[aria-label="Remove"]').at(0).prop('isDisabled')
- ).toBe(false);
- expect(
- wrapper.find('Button[aria-label="Remove"]').at(1).prop('isDisabled')
- ).toBe(false);
- });
-});
diff --git a/awx/ui/src/components/SelectedList/SelectedList.js b/awx/ui/src/components/SelectedList/SelectedList.js
deleted file mode 100644
index aaf7dca4892f..000000000000
--- a/awx/ui/src/components/SelectedList/SelectedList.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { Chip, Split as PFSplit, SplitItem } from '@patternfly/react-core';
-
-import styled from 'styled-components';
-import ChipGroup from '../ChipGroup';
-
-const Split = styled(PFSplit)`
- margin: 20px 0 5px 0 !important;
- align-items: baseline;
-`;
-
-const SplitLabelItem = styled(SplitItem)`
- font-weight: bold;
- margin-right: 32px;
- word-break: initial;
-`;
-
-function SelectedList(props) {
- const { label, selected, onRemove, displayKey, isReadOnly, renderItemChip } =
- props;
-
- const renderChip =
- renderItemChip ||
- (({ item, removeItem }) => (
-
- {item[displayKey]}
-
- ));
-
- return (
-
- {label}
-
-
- {selected.map((item) =>
- renderChip({
- item,
- removeItem: () => onRemove(item),
- canDelete: !isReadOnly,
- })
- )}
-
-
-
- );
-}
-
-SelectedList.propTypes = {
- displayKey: PropTypes.string,
- label: PropTypes.string,
- onRemove: PropTypes.func,
- selected: PropTypes.arrayOf(PropTypes.object).isRequired,
- isReadOnly: PropTypes.bool,
- renderItemChip: PropTypes.func,
-};
-
-SelectedList.defaultProps = {
- displayKey: 'name',
- label: 'Selected',
- onRemove: () => null,
- isReadOnly: false,
- renderItemChip: null,
-};
-
-export default SelectedList;
diff --git a/awx/ui/src/components/SelectedList/SelectedList.test.js b/awx/ui/src/components/SelectedList/SelectedList.test.js
deleted file mode 100644
index f9d74f7c4ee5..000000000000
--- a/awx/ui/src/components/SelectedList/SelectedList.test.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import ChipGroup from '../ChipGroup';
-
-import SelectedList from './SelectedList';
-
-describe('', () => {
- test('initially renders successfully', () => {
- const mockSelected = [
- {
- id: 1,
- name: 'foo',
- },
- {
- id: 2,
- name: 'bar',
- },
- ];
- const wrapper = mountWithContexts(
- {}}
- />
- );
- expect(wrapper.length).toBe(1);
- });
-
- test('showOverflow should set showOverflow on ChipGroup', () => {
- const wrapper = mountWithContexts(
- {}} />
- );
- const chipGroup = wrapper.find(ChipGroup);
- expect(chipGroup).toHaveLength(1);
- expect(chipGroup.prop('numChips')).toEqual(5);
- });
-
- test('Clicking remove on chip calls onRemove callback prop with correct params', () => {
- const onRemove = jest.fn();
- const mockSelected = [
- {
- id: 1,
- name: 'foo',
- },
- ];
- const wrapper = mountWithContexts(
-
- );
- wrapper.find('.pf-c-chip button').first().simulate('click');
- expect(onRemove).toBeCalledWith({
- id: 1,
- name: 'foo',
- });
- });
-});
diff --git a/awx/ui/src/components/SelectedList/index.js b/awx/ui/src/components/SelectedList/index.js
deleted file mode 100644
index 34e5e539127d..000000000000
--- a/awx/ui/src/components/SelectedList/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as SelectedList } from './SelectedList';
-export { default as DraggableSelectedList } from './DraggableSelectedList';
diff --git a/awx/ui/src/components/Sort/Sort.js b/awx/ui/src/components/Sort/Sort.js
deleted file mode 100644
index 40d666245efb..000000000000
--- a/awx/ui/src/components/Sort/Sort.js
+++ /dev/null
@@ -1,161 +0,0 @@
-/* eslint-disable react/jsx-no-useless-fragment */
-import React, { useState } from 'react';
-import PropTypes from 'prop-types';
-
-import { useLocation } from 'react-router-dom';
-import { t } from '@lingui/macro';
-import {
- Button,
- ButtonVariant,
- Dropdown,
- DropdownPosition,
- DropdownToggle,
- DropdownItem,
- InputGroup,
-} from '@patternfly/react-core';
-import {
- SortAlphaDownIcon,
- SortAlphaDownAltIcon,
- SortNumericDownIcon,
- SortNumericDownAltIcon,
-} from '@patternfly/react-icons';
-
-import styled from 'styled-components';
-import { parseQueryString } from 'util/qs';
-import { SortColumns, QSConfig } from 'types';
-
-const NoOptionDropdown = styled.div`
- align-self: stretch;
- border: 1px solid var(--pf-global--BorderColor--300);
- padding: 5px 15px;
- white-space: nowrap;
- border-bottom-color: var(--pf-global--BorderColor--200);
-`;
-
-function Sort({ columns, qsConfig, onSort }) {
- const location = useLocation();
- const [isSortDropdownOpen, setIsSortDropdownOpen] = useState(false);
-
- let sortKey;
- let sortOrder;
- let isNumeric;
-
- const queryParams = parseQueryString(qsConfig, location.search);
- if (queryParams.order_by && queryParams.order_by.startsWith('-')) {
- sortKey = queryParams.order_by.substr(1);
- sortOrder = 'descending';
- } else if (queryParams.order_by) {
- sortKey = queryParams.order_by;
- sortOrder = 'ascending';
- }
-
- if (qsConfig.integerFields.find((field) => field === sortKey)) {
- isNumeric = true;
- } else {
- isNumeric = false;
- }
-
- const handleDropdownToggle = (isOpen) => {
- setIsSortDropdownOpen(isOpen);
- };
-
- const handleDropdownSelect = ({ target }) => {
- const { innerText } = target;
-
- const [{ key }] = columns.filter(({ name }) => name === innerText);
- sortKey = key;
- if (qsConfig.integerFields.find((field) => field === key)) {
- isNumeric = true;
- } else {
- isNumeric = false;
- }
-
- setIsSortDropdownOpen(false);
- onSort(sortKey, sortOrder);
- };
-
- const handleSort = () => {
- onSort(sortKey, sortOrder === 'ascending' ? 'descending' : 'ascending');
- };
-
- const { up } = DropdownPosition;
-
- const defaultSortedColumn = columns.find(({ key }) => key === sortKey);
-
- if (!defaultSortedColumn) {
- throw new Error(
- 'sortKey must match one of the column keys, check the sortColumns prop passed to '
- );
- }
-
- const sortedColumnName = defaultSortedColumn?.name;
-
- const sortDropdownItems = columns
- .filter(({ key }) => key !== sortKey)
- .map(({ key, name }) => (
-
- {name}
-
- ));
-
- let SortIcon;
- if (isNumeric) {
- SortIcon =
- sortOrder === 'ascending' ? SortNumericDownIcon : SortNumericDownAltIcon;
- } else {
- SortIcon =
- sortOrder === 'ascending' ? SortAlphaDownIcon : SortAlphaDownAltIcon;
- }
- return (
- <>
- {sortedColumnName && (
-
- {(sortDropdownItems.length > 0 && (
-
- {sortedColumnName}
-
- }
- dropdownItems={sortDropdownItems}
- />
- )) || {sortedColumnName}}
-
-
-
- )}
- >
- );
-}
-
-Sort.propTypes = {
- qsConfig: QSConfig.isRequired,
- columns: SortColumns.isRequired,
- onSort: PropTypes.func,
-};
-
-Sort.defaultProps = {
- onSort: null,
-};
-
-export default Sort;
diff --git a/awx/ui/src/components/Sort/Sort.test.js b/awx/ui/src/components/Sort/Sort.test.js
deleted file mode 100644
index 4e3fe7428384..000000000000
--- a/awx/ui/src/components/Sort/Sort.test.js
+++ /dev/null
@@ -1,237 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { shallow } from 'enzyme';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../testUtils/enzymeHelpers';
-
-import Sort from './Sort';
-
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useLocation: () => ({
- pathname: '/organizations',
- }),
-}));
-
-describe('', () => {
- let sort;
-
- afterEach(() => {
- if (sort) {
- sort = null;
- }
- });
-
- test('should trigger onSort callback', () => {
- const qsConfig = {
- namespace: 'item',
- defaultParams: { page: 1, page_size: 5, order_by: 'name' },
- integerFields: ['page', 'page_size'],
- };
-
- const columns = [
- {
- name: 'Name',
- key: 'name',
- },
- ];
-
- const sortBtn = 'button[aria-label="Sort"]';
-
- const onSort = jest.fn();
-
- const wrapper = mountWithContexts(
-
- ).find('Sort');
-
- wrapper.find(sortBtn).simulate('click');
-
- expect(onSort).toHaveBeenCalledTimes(1);
- expect(onSort).toBeCalledWith('name', 'descending');
- });
-
- test('onSort properly passes back descending when ascending was passed as prop', () => {
- const qsConfig = {
- namespace: 'item',
- defaultParams: { page: 1, page_size: 5, order_by: 'foo' },
- integerFields: ['page', 'page_size'],
- };
-
- const columns = [
- {
- name: 'Foo',
- key: 'foo',
- },
- {
- name: 'Bar',
- key: 'bar',
- },
- {
- name: 'Bakery',
- key: 'bakery',
- },
- ];
-
- const onSort = jest.fn();
-
- const wrapper = mountWithContexts(
-
- ).find('Sort');
- const sortDropdownToggle = wrapper.find('Button');
- expect(sortDropdownToggle.length).toBe(1);
- sortDropdownToggle.simulate('click');
- expect(onSort).toHaveBeenCalledWith('foo', 'descending');
- });
-
- test('onSort properly passes back ascending when descending was passed as prop', () => {
- const qsConfig = {
- namespace: 'item',
- defaultParams: { page: 1, page_size: 5, order_by: '-foo' },
- integerFields: ['page', 'page_size'],
- };
-
- const columns = [
- {
- name: 'Foo',
- key: 'foo',
- },
- {
- name: 'Bar',
- key: 'bar',
- },
- {
- name: 'Bakery',
- key: 'bakery',
- },
- ];
-
- const onSort = jest.fn();
-
- const wrapper = mountWithContexts(
-
- ).find('Sort');
- const sortDropdownToggle = wrapper.find('Button');
- expect(sortDropdownToggle.length).toBe(1);
- sortDropdownToggle.simulate('click');
- expect(onSort).toHaveBeenCalledWith('foo', 'ascending');
- });
-
- test('Changing dropdown correctly passes back new sort key', async () => {
- const qsConfig = {
- namespace: 'item',
- defaultParams: { page: 1, page_size: 5, order_by: 'foo' },
- integerFields: ['page', 'page_size'],
- };
-
- const columns = [
- {
- name: 'Foo',
- key: 'foo',
- },
- {
- name: 'Bar',
- key: 'bar',
- },
- {
- name: 'Bakery',
- key: 'bakery',
- },
- ];
-
- const onSort = jest.fn();
-
- const wrapper = mountWithContexts(
-
- );
- act(() => wrapper.find('Dropdown').invoke('onToggle')(true));
- wrapper.update();
- await waitForElement(
- wrapper,
- 'Dropdown',
- (el) => el.prop('isOpen') === true
- );
- act(() =>
- wrapper.find('li').at(0).prop('onClick')({ target: { innerText: 'Bar' } })
- );
- wrapper.update();
- expect(onSort).toBeCalledWith('bar', 'ascending');
- });
-
- test('should display numeric descending icon', () => {
- const qsConfigNumDown = {
- namespace: 'item',
- defaultParams: { page: 1, page_size: 5, order_by: '-id' },
- integerFields: ['page', 'page_size', 'id'],
- };
- const numericColumns = [{ name: 'ID', key: 'id' }];
-
- const wrapper = shallow(
-
- );
-
- expect(wrapper.find('SortNumericDownAltIcon')).toHaveLength(1);
- });
-
- test('should display numeric ascending icon', () => {
- const qsConfigNumUp = {
- namespace: 'item',
- defaultParams: { page: 1, page_size: 5, order_by: 'id' },
- integerFields: ['page', 'page_size', 'id'],
- };
- const numericColumns = [{ name: 'ID', key: 'id' }];
-
- const wrapper = shallow(
-
- );
-
- expect(wrapper.find('SortNumericDownIcon')).toHaveLength(1);
- });
-
- test('should display alphanumeric descending icon', () => {
- const qsConfigAlphaDown = {
- namespace: 'item',
- defaultParams: { page: 1, page_size: 5, order_by: '-name' },
- integerFields: ['page', 'page_size'],
- };
- const alphaColumns = [{ name: 'Name', key: 'name' }];
-
- const wrapper = shallow(
-
- );
-
- expect(wrapper.find('SortAlphaDownAltIcon')).toHaveLength(1);
- });
-
- test('should display alphanumeric ascending icon', () => {
- const qsConfigAlphaDown = {
- namespace: 'item',
- defaultParams: { page: 1, page_size: 5, order_by: 'name' },
- integerFields: ['page', 'page_size'],
- };
- const alphaColumns = [{ name: 'Name', key: 'name' }];
-
- const wrapper = shallow(
-
- );
-
- expect(wrapper.find('SortAlphaDownIcon')).toHaveLength(1);
- });
-});
diff --git a/awx/ui/src/components/Sort/index.js b/awx/ui/src/components/Sort/index.js
deleted file mode 100644
index 5be656053a18..000000000000
--- a/awx/ui/src/components/Sort/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './Sort';
diff --git a/awx/ui/src/components/Sparkline/Sparkline.js b/awx/ui/src/components/Sparkline/Sparkline.js
deleted file mode 100644
index ac59a4f2c90a..000000000000
--- a/awx/ui/src/components/Sparkline/Sparkline.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import React from 'react';
-import { arrayOf } from 'prop-types';
-
-import { Link as _Link } from 'react-router-dom';
-import { Tooltip } from '@patternfly/react-core';
-import styled from 'styled-components';
-import { t } from '@lingui/macro';
-import { formatDateString } from 'util/dates';
-import { Job } from 'types';
-import StatusIcon from '../StatusIcon';
-import { JOB_TYPE_URL_SEGMENTS } from '../../constants';
-
-/* eslint-disable react/jsx-pascal-case */
-const Link = styled((props) => <_Link {...props} />)`
- margin-right: 5px;
-`;
-
-const Wrapper = styled.div`
- display: inline-flex;
- flex-wrap: wrap;
-`;
-/* eslint-enable react/jsx-pascal-case */
-
-const Sparkline = ({ jobs }) => {
- const generateTooltip = (job) => (
- <>
-
- {t`JOB ID:`} {job.id}
-
-
- {t`STATUS:`} {job.status.toUpperCase()}
-
- {job.finished && (
-
- {t`FINISHED:`} {formatDateString(job.finished)}
-
- )}
- >
- );
-
- const statusIcons = jobs.map((job) => (
-
-
-
-
-
- ));
-
- return {statusIcons};
-};
-
-Sparkline.propTypes = {
- jobs: arrayOf(Job),
-};
-Sparkline.defaultProps = {
- jobs: [],
-};
-
-export default Sparkline;
diff --git a/awx/ui/src/components/Sparkline/Sparkline.test.js b/awx/ui/src/components/Sparkline/Sparkline.test.js
deleted file mode 100644
index a19c31e433c8..000000000000
--- a/awx/ui/src/components/Sparkline/Sparkline.test.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import React from 'react';
-
-import {
- mountWithContexts,
- shallowWithContexts,
-} from '../../../testUtils/enzymeHelpers';
-
-import Sparkline from './Sparkline';
-
-describe('Sparkline', () => {
- test('renders the expected content', () => {
- const wrapper = shallowWithContexts();
- expect(wrapper).toHaveLength(1);
- });
- test('renders an icon with tooltips and links for each job', () => {
- const jobs = [
- {
- id: 1,
- status: 'successful',
- finished: '2019-08-08T15:27:57.320120Z',
- },
- {
- id: 2,
- status: 'failed',
- finished: '2019-08-09T15:27:57.320120Z',
- },
- ];
- const wrapper = mountWithContexts();
- expect(wrapper.find('StatusIcon')).toHaveLength(2);
- expect(wrapper.find('Tooltip')).toHaveLength(2);
- expect(wrapper.find('Link')).toHaveLength(2);
- });
-});
diff --git a/awx/ui/src/components/Sparkline/index.js b/awx/ui/src/components/Sparkline/index.js
deleted file mode 100644
index 7477abfdcc77..000000000000
--- a/awx/ui/src/components/Sparkline/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './Sparkline';
diff --git a/awx/ui/src/components/StatusIcon/StatusIcon.js b/awx/ui/src/components/StatusIcon/StatusIcon.js
deleted file mode 100644
index 909e8002565c..000000000000
--- a/awx/ui/src/components/StatusIcon/StatusIcon.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import React from 'react';
-import { string } from 'prop-types';
-import icons from './icons';
-
-const green = '--pf-global--success-color--100';
-const red = '--pf-global--danger-color--100';
-const blue = '--pf-global--primary-color--100';
-const orange = '--pf-global--palette--orange-300';
-const gray = '--pf-global--Color--300';
-const colors = {
- success: green,
- successful: green,
- healthy: green,
- ok: green,
- failed: red,
- error: red,
- unreachable: red,
- running: blue,
- pending: blue,
- skipped: blue,
- waiting: gray,
- disabled: gray,
- canceled: orange,
- changed: orange,
- /* Instance statuses */
- ready: green,
- installed: blue,
- provisioning: gray,
- deprovisioning: gray,
- unavailable: red,
- 'provision-fail': red,
- 'deprovision-fail': red,
-};
-
-function StatusIcon({ status, ...props }) {
- const color = colors[status] || '--pf-chart-global--Fill--Color--500';
- const Icon = icons[status];
- return (
-
- {Icon ? (
-
-
-
- ) : null}
-
{status}
-
- );
-}
-
-StatusIcon.propTypes = {
- status: string.isRequired,
-};
-
-export default StatusIcon;
diff --git a/awx/ui/src/components/StatusIcon/StatusIcon.test.js b/awx/ui/src/components/StatusIcon/StatusIcon.test.js
deleted file mode 100644
index 783bc8745f5a..000000000000
--- a/awx/ui/src/components/StatusIcon/StatusIcon.test.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import React from 'react';
-import { mount } from 'enzyme';
-import StatusIcon from './StatusIcon';
-
-describe('StatusIcon', () => {
- test('renders the successful status', () => {
- const wrapper = mount();
- expect(wrapper).toHaveLength(1);
- expect(wrapper.find('CheckCircleIcon')).toHaveLength(1);
- });
-
- test('renders running status', () => {
- const wrapper = mount();
- expect(wrapper).toHaveLength(1);
- expect(wrapper.find('RunningIcon')).toHaveLength(1);
- });
-
- test('renders waiting status', () => {
- const wrapper = mount();
- expect(wrapper).toHaveLength(1);
- expect(wrapper.find('ClockIcon')).toHaveLength(1);
- });
-
- test('renders failed status', () => {
- const wrapper = mount();
- expect(wrapper).toHaveLength(1);
- expect(wrapper.find('ExclamationCircleIcon')).toHaveLength(1);
- });
-
- test('renders a successful status when host status is "ok"', () => {
- const wrapper = mount();
- expect(wrapper).toHaveLength(1);
- expect(wrapper.find('CheckCircleIcon')).toHaveLength(1);
- });
-
- test('renders "failed" host status', () => {
- const wrapper = mount();
- expect(wrapper).toHaveLength(1);
- expect(wrapper.find('ExclamationCircleIcon')).toHaveLength(1);
- });
-
- test('renders "changed" host status', () => {
- const wrapper = mount();
- expect(wrapper).toHaveLength(1);
- expect(wrapper.find('ExclamationTriangleIcon')).toHaveLength(1);
- });
-
- test('renders "skipped" host status', () => {
- const wrapper = mount();
- expect(wrapper).toHaveLength(1);
- expect(wrapper.find('MinusCircleIcon')).toHaveLength(1);
- });
-
- test('renders "unreachable" host status', () => {
- const wrapper = mount();
- expect(wrapper).toHaveLength(1);
- expect(wrapper.find('ExclamationCircleIcon')).toHaveLength(1);
- });
-});
diff --git a/awx/ui/src/components/StatusIcon/icons.js b/awx/ui/src/components/StatusIcon/icons.js
deleted file mode 100644
index 74b50a31ed9c..000000000000
--- a/awx/ui/src/components/StatusIcon/icons.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import styled, { keyframes } from 'styled-components';
-import {
- CheckCircleIcon,
- ExclamationCircleIcon,
- SyncAltIcon,
- ExclamationTriangleIcon,
- ClockIcon,
- MinusCircleIcon,
- InfoCircleIcon,
- PlusCircleIcon,
-} from '@patternfly/react-icons';
-
-const Spin = keyframes`
- from {
- transform: rotate(0);
- }
- to {
- transform: rotate(1turn);
- }
-`;
-
-const RunningIcon = styled(SyncAltIcon)`
- animation: ${Spin} 1.75s linear infinite;
-`;
-RunningIcon.displayName = 'RunningIcon';
-
-const icons = {
- approved: CheckCircleIcon,
- denied: InfoCircleIcon,
- success: CheckCircleIcon,
- healthy: CheckCircleIcon,
- successful: CheckCircleIcon,
- ok: CheckCircleIcon,
- failed: ExclamationCircleIcon,
- error: ExclamationCircleIcon,
- unreachable: ExclamationCircleIcon,
- running: RunningIcon,
- pending: ClockIcon,
- waiting: ClockIcon,
- disabled: MinusCircleIcon,
- skipped: MinusCircleIcon,
- canceled: ExclamationTriangleIcon,
- changed: ExclamationTriangleIcon,
- /* Instance statuses */
- ready: CheckCircleIcon,
- installed: ClockIcon,
- provisioning: PlusCircleIcon,
- deprovisioning: MinusCircleIcon,
- unavailable: ExclamationCircleIcon,
- 'provision-fail': ExclamationCircleIcon,
- 'deprovision-fail': ExclamationCircleIcon,
-};
-export default icons;
diff --git a/awx/ui/src/components/StatusIcon/index.js b/awx/ui/src/components/StatusIcon/index.js
deleted file mode 100644
index d026e41e3e36..000000000000
--- a/awx/ui/src/components/StatusIcon/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './StatusIcon';
diff --git a/awx/ui/src/components/StatusLabel/StatusLabel.js b/awx/ui/src/components/StatusLabel/StatusLabel.js
deleted file mode 100644
index 284e2c8d5fa9..000000000000
--- a/awx/ui/src/components/StatusLabel/StatusLabel.js
+++ /dev/null
@@ -1,115 +0,0 @@
-/* eslint-disable react/jsx-no-useless-fragment */
-import 'styled-components/macro';
-import React from 'react';
-import { t } from '@lingui/macro';
-import { oneOf } from 'prop-types';
-import { Label, Tooltip } from '@patternfly/react-core';
-import icons from '../StatusIcon/icons';
-
-const colors = {
- approved: 'green',
- denied: 'red',
- success: 'green',
- successful: 'green',
- ok: 'green',
- healthy: 'green',
- failed: 'red',
- error: 'red',
- unreachable: 'red',
- running: 'blue',
- pending: 'blue',
- skipped: 'blue',
- timedOut: 'red',
- waiting: 'grey',
- disabled: 'grey',
- canceled: 'orange',
- changed: 'orange',
- /* Instance statuses */
- ready: 'green',
- installed: 'blue',
- provisioning: 'gray',
- deprovisioning: 'gray',
- unavailable: 'red',
- 'provision-fail': 'red',
- 'deprovision-fail': 'red',
-};
-
-export default function StatusLabel({ status, tooltipContent = '', children }) {
- const upperCaseStatus = {
- approved: t`Approved`,
- denied: t`Denied`,
- success: t`Success`,
- healthy: t`Healthy`,
- successful: t`Successful`,
- ok: t`OK`,
- failed: t`Failed`,
- error: t`Error`,
- unreachable: t`Unreachable`,
- running: t`Running`,
- pending: t`Pending`,
- skipped: t`Skipped'`,
- timedOut: t`Timed out`,
- waiting: t`Waiting`,
- disabled: t`Disabled`,
- canceled: t`Canceled`,
- changed: t`Changed`,
- /* Instance statuses */
- ready: t`Ready`,
- installed: t`Installed`,
- provisioning: t`Provisioning`,
- deprovisioning: t`Deprovisioning`,
- unavailable: t`Unavailable`,
- 'provision-fail': t`Provisioning fail`,
- 'deprovision-fail': t`Deprovisioning fail`,
- };
- const label = upperCaseStatus[status] || status;
- const color = colors[status] || 'grey';
- const Icon = icons[status];
-
- const renderLabel = () => (
- : null}>
- {children || label}
-
- );
-
- return (
- <>
- {tooltipContent ? (
-
- {renderLabel()}
-
- ) : (
- renderLabel()
- )}
- >
- );
-}
-
-StatusLabel.propTypes = {
- status: oneOf([
- 'approved',
- 'denied',
- 'success',
- 'successful',
- 'ok',
- 'healthy',
- 'failed',
- 'error',
- 'unreachable',
- 'running',
- 'pending',
- 'skipped',
- 'timedOut',
- 'waiting',
- 'disabled',
- 'canceled',
- 'changed',
- 'ready',
- 'installed',
- 'provisioning',
- 'deprovisioning',
- 'unavailable',
- 'provision-fail',
- 'deprovision-fail',
- ]).isRequired,
-};
diff --git a/awx/ui/src/components/StatusLabel/StatusLabel.test.js b/awx/ui/src/components/StatusLabel/StatusLabel.test.js
deleted file mode 100644
index 4296f90d8364..000000000000
--- a/awx/ui/src/components/StatusLabel/StatusLabel.test.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import React from 'react';
-import { mount } from 'enzyme';
-import StatusLabel from './StatusLabel';
-
-describe('StatusLabel', () => {
- test('should render success', () => {
- const wrapper = mount();
- expect(wrapper).toHaveLength(1);
- expect(wrapper.find('CheckCircleIcon')).toHaveLength(1);
- expect(wrapper.find('Label').prop('color')).toEqual('green');
- expect(wrapper.text()).toEqual('Success');
- expect(wrapper.find('Tooltip')).toHaveLength(0);
- });
-
- test('should render failed', () => {
- const wrapper = mount();
- expect(wrapper).toHaveLength(1);
- expect(wrapper.find('ExclamationCircleIcon')).toHaveLength(1);
- expect(wrapper.find('Label').prop('color')).toEqual('red');
- expect(wrapper.text()).toEqual('Failed');
- });
-
- test('should render error', () => {
- const wrapper = mount();
- expect(wrapper).toHaveLength(1);
- expect(wrapper.find('ExclamationCircleIcon')).toHaveLength(1);
- expect(wrapper.find('Label').prop('color')).toEqual('red');
- expect(wrapper.text()).toEqual('Error');
- });
-
- test('should render running', () => {
- const wrapper = mount();
- expect(wrapper).toHaveLength(1);
- expect(wrapper.find('SyncAltIcon')).toHaveLength(1);
- expect(wrapper.find('Label').prop('color')).toEqual('blue');
- expect(wrapper.text()).toEqual('Running');
- });
-
- test('should render pending', () => {
- const wrapper = mount();
- expect(wrapper).toHaveLength(1);
- expect(wrapper.find('ClockIcon')).toHaveLength(1);
- expect(wrapper.find('Label').prop('color')).toEqual('blue');
- expect(wrapper.text()).toEqual('Pending');
- });
-
- test('should render waiting', () => {
- const wrapper = mount();
- expect(wrapper).toHaveLength(1);
- expect(wrapper.find('ClockIcon')).toHaveLength(1);
- expect(wrapper.find('Label').prop('color')).toEqual('grey');
- expect(wrapper.text()).toEqual('Waiting');
- });
-
- test('should render disabled', () => {
- const wrapper = mount();
- expect(wrapper).toHaveLength(1);
- expect(wrapper.find('MinusCircleIcon')).toHaveLength(1);
- expect(wrapper.find('Label').prop('color')).toEqual('grey');
- expect(wrapper.text()).toEqual('Disabled');
- });
-
- test('should render canceled', () => {
- const wrapper = mount();
- expect(wrapper).toHaveLength(1);
- expect(wrapper.find('ExclamationTriangleIcon')).toHaveLength(1);
- expect(wrapper.find('Label').prop('color')).toEqual('orange');
- expect(wrapper.text()).toEqual('Canceled');
- });
-
- test('should render tooltip', () => {
- const wrapper = mount(
-
- );
- expect(wrapper).toHaveLength(1);
- expect(wrapper.find('CheckCircleIcon')).toHaveLength(1);
- expect(wrapper.find('Label').prop('color')).toEqual('green');
- expect(wrapper.text()).toEqual('Success');
- expect(wrapper.find('Tooltip')).toHaveLength(1);
- expect(wrapper.find('Tooltip').prop('content')).toEqual('Foo');
- });
-
- test('should render children', () => {
- const wrapper = mount(
-
- );
- expect(wrapper.text()).toEqual('children');
- });
-});
diff --git a/awx/ui/src/components/StatusLabel/index.js b/awx/ui/src/components/StatusLabel/index.js
deleted file mode 100644
index b9dfc8cd9971..000000000000
--- a/awx/ui/src/components/StatusLabel/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './StatusLabel';
diff --git a/awx/ui/src/components/TemplateList/TemplateList.js b/awx/ui/src/components/TemplateList/TemplateList.js
deleted file mode 100644
index 65a198abdfdc..000000000000
--- a/awx/ui/src/components/TemplateList/TemplateList.js
+++ /dev/null
@@ -1,309 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-import { useLocation, Link } from 'react-router-dom';
-import { t, Plural } from '@lingui/macro';
-import { Card, DropdownItem } from '@patternfly/react-core';
-import {
- JobTemplatesAPI,
- UnifiedJobTemplatesAPI,
- WorkflowJobTemplatesAPI,
-} from 'api';
-import useRequest, { useDeleteItems } from 'hooks/useRequest';
-import useSelected from 'hooks/useSelected';
-import useExpanded from 'hooks/useExpanded';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import useWsTemplates from 'hooks/useWsTemplates';
-import useToast, { AlertVariant } from 'hooks/useToast';
-import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
-import AlertModal from '../AlertModal';
-import DatalistToolbar from '../DataListToolbar';
-import ErrorDetail from '../ErrorDetail';
-import PaginatedTable, {
- HeaderRow,
- HeaderCell,
- ToolbarDeleteButton,
- getSearchableKeys,
-} from '../PaginatedTable';
-import AddDropDownButton from '../AddDropDownButton';
-import TemplateListItem from './TemplateListItem';
-
-function TemplateList({ defaultParams }) {
- // The type value in const qsConfig below does not have a space between job_template and
- // workflow_job_template so the params sent to the API match what the api expects.
- const qsConfig = getQSConfig(
- 'template',
- {
- page: 1,
- page_size: 20,
- order_by: 'name',
- type: 'job_template,workflow_job_template',
- ...defaultParams,
- },
- ['id', 'page', 'page_size']
- );
-
- const location = useLocation();
- const { addToast, Toast, toastProps } = useToast();
-
- const {
- result: {
- results,
- count,
- jtActions,
- wfjtActions,
- relatedSearchableKeys,
- searchableKeys,
- },
- error: contentError,
- isLoading,
- request: fetchTemplates,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(qsConfig, location.search);
- const responses = await Promise.all([
- UnifiedJobTemplatesAPI.read(params),
- JobTemplatesAPI.readOptions(),
- WorkflowJobTemplatesAPI.readOptions(),
- UnifiedJobTemplatesAPI.readOptions(),
- ]);
- return {
- results: responses[0].data.results,
- count: responses[0].data.count,
- jtActions: responses[1].data.actions,
- wfjtActions: responses[2].data.actions,
- relatedSearchableKeys: (
- responses[3]?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(responses[3].data.actions?.GET),
- };
- }, [location]), // eslint-disable-line react-hooks/exhaustive-deps
- {
- results: [],
- count: 0,
- jtActions: {},
- wfjtActions: {},
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- useEffect(() => {
- fetchTemplates();
- }, [fetchTemplates]);
-
- const templates = useWsTemplates(results);
-
- const { selected, isAllSelected, handleSelect, selectAll, clearSelected } =
- useSelected(templates);
-
- const { expanded, isAllExpanded, handleExpand, expandAll } =
- useExpanded(templates);
-
- const {
- isLoading: isDeleteLoading,
- deleteItems: deleteTemplates,
- deletionError,
- clearDeletionError,
- } = useDeleteItems(
- useCallback(
- () =>
- Promise.all(
- selected.map(({ type, id }) => {
- if (type === 'job_template') {
- return JobTemplatesAPI.destroy(id);
- }
- if (type === 'workflow_job_template') {
- return WorkflowJobTemplatesAPI.destroy(id);
- }
- return false;
- })
- ),
- [selected]
- ),
- {
- qsConfig,
- allItemsSelected: isAllSelected,
- fetchItems: fetchTemplates,
- }
- );
-
- const handleCopy = useCallback(
- (newTemplateId) => {
- addToast({
- id: newTemplateId,
- title: t`Template copied successfully`,
- variant: AlertVariant.success,
- hasTimeout: true,
- });
- },
- [addToast]
- );
-
- const handleTemplateDelete = async () => {
- await deleteTemplates();
- clearSelected();
- };
-
- const canAddJT =
- jtActions && Object.prototype.hasOwnProperty.call(jtActions, 'POST');
- const canAddWFJT =
- wfjtActions && Object.prototype.hasOwnProperty.call(wfjtActions, 'POST');
-
- const addTemplate = t`Add job template`;
- const addWFTemplate = t`Add workflow template`;
- const addDropDownButton = [];
- if (canAddJT) {
- addDropDownButton.push(
-
- {addTemplate}
-
- );
- }
- if (canAddWFJT) {
- addDropDownButton.push(
-
- {addWFTemplate}
-
- );
- }
- const addButton = (
-
- );
-
- const deleteDetailsRequests = relatedResourceDeleteRequests.template(
- selected[0]
- );
-
- return (
- <>
-
-
- {t`Name`}
- {t`Type`}
- {t`Organization`}
- {t`Last Ran`}
- {t`Actions`}
-
- }
- renderToolbar={(props) => (
-
- }
- />,
- ]}
- />
- )}
- renderRow={(template, index) => (
- handleSelect(template)}
- isExpanded={expanded.some((row) => row.id === template.id)}
- onExpand={() => handleExpand(template)}
- onCopy={handleCopy}
- isSelected={selected.some((row) => row.id === template.id)}
- fetchTemplates={fetchTemplates}
- rowIndex={index}
- />
- )}
- emptyStateControls={(canAddJT || canAddWFJT) && addButton}
- />
-
-
-
- {t`Failed to delete one or more templates.`}
-
-
- >
- );
-}
-
-export default TemplateList;
diff --git a/awx/ui/src/components/TemplateList/TemplateList.test.js b/awx/ui/src/components/TemplateList/TemplateList.test.js
deleted file mode 100644
index c260b2eed3bf..000000000000
--- a/awx/ui/src/components/TemplateList/TemplateList.test.js
+++ /dev/null
@@ -1,321 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import {
- JobTemplatesAPI,
- UnifiedJobTemplatesAPI,
- WorkflowJobTemplatesAPI,
-} from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../testUtils/enzymeHelpers';
-
-import TemplateList from './TemplateList';
-
-jest.mock('../../api');
-
-const mockTemplates = [
- {
- id: 1,
- name: 'Job Template 1',
- url: '/templates/job_template/1',
- type: 'job_template',
- summary_fields: {
- user_capabilities: {
- delete: true,
- edit: true,
- copy: true,
- },
- },
- },
- {
- id: 2,
- name: 'Job Template 2',
- url: '/templates/job_template/2',
- type: 'job_template',
- summary_fields: {
- user_capabilities: {
- delete: true,
- },
- },
- },
- {
- id: 3,
- name: 'Job Template 3',
- url: '/templates/job_template/3',
- type: 'job_template',
- summary_fields: {
- user_capabilities: {
- delete: true,
- },
- },
- },
- {
- id: 4,
- name: 'Workflow Job Template 1',
- url: '/templates/workflow_job_template/4',
- type: 'workflow_job_template',
- summary_fields: {
- user_capabilities: {
- delete: true,
- },
- },
- },
- {
- id: 5,
- name: 'Workflow Job Template 2',
- url: '/templates/workflow_job_template/5',
- type: 'workflow_job_template',
- summary_fields: {
- user_capabilities: {
- delete: false,
- },
- },
- },
-];
-
-describe('', () => {
- let debug;
- beforeEach(() => {
- UnifiedJobTemplatesAPI.read.mockResolvedValue({
- data: {
- count: mockTemplates.length,
- results: mockTemplates,
- },
- });
-
- UnifiedJobTemplatesAPI.readOptions.mockResolvedValue({
- data: {
- actions: [],
- },
- });
- debug = global.console.debug; // eslint-disable-line prefer-destructuring
- global.console.debug = () => {};
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- global.console.debug = debug;
- });
-
- test('initially renders successfully', async () => {
- await act(async () => {
- mountWithContexts(
-
- );
- });
- });
-
- test('Templates are retrieved from the api and the components finishes loading', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- expect(UnifiedJobTemplatesAPI.read).toBeCalled();
- await act(async () => {
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
- expect(wrapper.find('TemplateListItem').length).toEqual(5);
- });
-
- test('handleSelect is called when a template list item is selected', async () => {
- const wrapper = mountWithContexts();
- await act(async () => {
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
- const checkBox = wrapper.find('TemplateListItem').at(1).find('input');
-
- checkBox.simulate('change', {
- target: {
- id: 2,
- name: 'Job Template 2',
- url: '/templates/job_template/2',
- type: 'job_template',
- summary_fields: { user_capabilities: { delete: true } },
- },
- });
-
- expect(wrapper.find('TemplateListItem').at(1).prop('isSelected')).toBe(
- true
- );
- });
-
- test('handleSelectAll is called when a template list item is selected', async () => {
- const wrapper = mountWithContexts();
- await act(async () => {
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
- expect(wrapper.find('Checkbox#select-all').prop('isChecked')).toBe(false);
-
- const toolBarCheckBox = wrapper.find('Checkbox#select-all');
- act(() => {
- toolBarCheckBox.prop('onChange')(true);
- });
- wrapper.update();
- expect(wrapper.find('Checkbox#select-all').prop('isChecked')).toBe(true);
- });
-
- test('delete button is disabled if user does not have delete capabilities on a selected template', async () => {
- const wrapper = mountWithContexts();
- await act(async () => {
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
- const deleteableItem = wrapper.find('TemplateListItem').at(0).find('input');
- const nonDeleteableItem = wrapper
- .find('TemplateListItem')
- .at(4)
- .find('input');
-
- deleteableItem.simulate('change', {
- id: 1,
- name: 'Job Template 1',
- url: '/templates/job_template/1',
- type: 'job_template',
- summary_fields: {
- user_capabilities: {
- delete: true,
- },
- },
- });
-
- expect(wrapper.find('Button[aria-label="Delete"]').prop('isDisabled')).toBe(
- false
- );
- deleteableItem.simulate('change', {
- id: 1,
- name: 'Job Template 1',
- url: '/templates/job_template/1',
- type: 'job_template',
- summary_fields: {
- user_capabilities: {
- delete: true,
- },
- },
- });
- expect(wrapper.find('Button[aria-label="Delete"]').prop('isDisabled')).toBe(
- true
- );
- nonDeleteableItem.simulate('change', {
- id: 5,
- name: 'Workflow Job Template 2',
- url: '/templates/workflow_job_template/5',
- type: 'workflow_job_template',
- summary_fields: {
- user_capabilities: {
- delete: false,
- },
- },
- });
- expect(wrapper.find('Button[aria-label="Delete"]').prop('isDisabled')).toBe(
- true
- );
- });
-
- test('api is called to delete templates for each selected template.', async () => {
- const wrapper = mountWithContexts();
- await act(async () => {
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
- const jobTemplate = wrapper.find('TemplateListItem').at(1).find('input');
- const workflowJobTemplate = wrapper
- .find('TemplateListItem')
- .at(3)
- .find('input');
-
- jobTemplate.simulate('change', {
- target: {
- id: 2,
- name: 'Job Template 2',
- url: '/templates/job_template/2',
- type: 'job_template',
- summary_fields: { user_capabilities: { delete: true } },
- },
- });
-
- workflowJobTemplate.simulate('change', {
- target: {
- id: 4,
- name: 'Workflow Job Template 1',
- url: '/templates/workflow_job_template/4',
- type: 'workflow_job_template',
- summary_fields: {
- user_capabilities: {
- delete: true,
- },
- },
- },
- });
-
- await act(async () => {
- wrapper.find('button[aria-label="Delete"]').prop('onClick')();
- });
- wrapper.update();
- await act(async () => {
- await wrapper
- .find('button[aria-label="confirm delete"]')
- .prop('onClick')();
- });
- expect(JobTemplatesAPI.destroy).toBeCalledWith(2);
- expect(WorkflowJobTemplatesAPI.destroy).toBeCalledWith(4);
- });
-
- test('error is shown when template not successfully deleted from api', async () => {
- JobTemplatesAPI.destroy.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'delete',
- url: '/api/v2/job_templates/1',
- },
- data: 'An error occurred',
- },
- })
- );
- const wrapper = mountWithContexts();
- await act(async () => {
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
- const checkBox = wrapper.find('TemplateListItem').at(1).find('input');
-
- checkBox.simulate('change', {
- target: {
- id: 'a',
- name: 'Job Template 2',
- url: '/templates/job_template/2',
- type: 'job_template',
- summary_fields: { user_capabilities: { delete: true } },
- },
- });
- await act(async () => {
- wrapper.find('button[aria-label="Delete"]').prop('onClick')();
- });
- wrapper.update();
- await act(async () => {
- await wrapper
- .find('button[aria-label="confirm delete"]')
- .prop('onClick')();
- });
-
- await waitForElement(
- wrapper,
- 'Modal[aria-label="Deletion Error"]',
- (el) => el.props().isOpen === true && el.props().title === 'Error!'
- );
- });
- test('should properly copy template', async () => {
- JobTemplatesAPI.copy.mockResolvedValue({});
- const wrapper = mountWithContexts();
- await act(async () => {
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
- await act(async () =>
- wrapper.find('Button[aria-label="Copy"]').prop('onClick')()
- );
- expect(JobTemplatesAPI.copy).toHaveBeenCalled();
- expect(UnifiedJobTemplatesAPI.read).toHaveBeenCalled();
- wrapper.update();
- });
-});
diff --git a/awx/ui/src/components/TemplateList/TemplateListItem.js b/awx/ui/src/components/TemplateList/TemplateListItem.js
deleted file mode 100644
index 349f45537902..000000000000
--- a/awx/ui/src/components/TemplateList/TemplateListItem.js
+++ /dev/null
@@ -1,378 +0,0 @@
-import 'styled-components/macro';
-import React, { useState, useCallback } from 'react';
-import { Link } from 'react-router-dom';
-import { Button, Popover, Tooltip, Chip } from '@patternfly/react-core';
-import { Tr, Td, ExpandableRowContent } from '@patternfly/react-table';
-import { t, Trans } from '@lingui/macro';
-import {
- ExclamationTriangleIcon,
- PencilAltIcon,
- ProjectDiagramIcon,
- RocketIcon,
-} from '@patternfly/react-icons';
-import styled from 'styled-components';
-import { timeOfDay, formatDateString } from 'util/dates';
-import { JobTemplatesAPI, WorkflowJobTemplatesAPI } from 'api';
-import { toTitleCase } from 'util/strings';
-import getDocsBaseUrl from 'util/getDocsBaseUrl';
-import { useConfig } from 'contexts/Config';
-import { ActionsTd, ActionItem, TdBreakWord } from '../PaginatedTable';
-import { DetailList, Detail, DeletedDetail } from '../DetailList';
-import ChipGroup from '../ChipGroup';
-import CredentialChip from '../CredentialChip';
-import ExecutionEnvironmentDetail from '../ExecutionEnvironmentDetail';
-import { LaunchButton } from '../LaunchButton';
-import Sparkline from '../Sparkline';
-import CopyButton from '../CopyButton';
-
-const ExclamationTriangleIconWarning = styled(ExclamationTriangleIcon)`
- color: var(--pf-global--warning-color--100);
- margin-left: 18px;
- cursor: pointer;
-`;
-
-ExclamationTriangleIconWarning.displayName = 'ExclamationTriangleIconWarning';
-
-function TemplateListItem({
- isExpanded,
- onExpand,
- template,
- isSelected,
- onSelect,
- onCopy,
- detailUrl,
- fetchTemplates,
- rowIndex,
-}) {
- const config = useConfig();
- const [isDisabled, setIsDisabled] = useState(false);
- const labelId = `check-action-${template.id}`;
-
- const docsLink = `${getDocsBaseUrl(
- config
- )}/html/upgrade-migration-guide/upgrade_to_ees.html`;
-
- const copyTemplate = useCallback(async () => {
- let response;
- if (template.type === 'job_template') {
- response = await JobTemplatesAPI.copy(template.id, {
- name: `${template.name} @ ${timeOfDay()}`,
- });
- } else {
- response = await WorkflowJobTemplatesAPI.copy(template.id, {
- name: `${template.name} @ ${timeOfDay()}`,
- });
- }
- if (response.status === 201) {
- onCopy(response.data.id);
- }
- await fetchTemplates();
- }, [fetchTemplates, template.id, template.name, template.type, onCopy]);
-
- const handleCopyStart = useCallback(() => {
- setIsDisabled(true);
- }, []);
-
- const handleCopyFinish = useCallback(() => {
- setIsDisabled(false);
- }, []);
-
- const {
- summary_fields: summaryFields,
- ask_inventory_on_launch: askInventoryOnLaunch,
- } = template;
-
- const missingResourceIcon =
- template.type === 'job_template' &&
- (!summaryFields.project ||
- (!summaryFields.inventory && !askInventoryOnLaunch));
-
- const missingExecutionEnvironment =
- template.type === 'job_template' &&
- template.custom_virtualenv &&
- !template.execution_environment;
-
- const inventoryValue = (kind, id) => {
- const inventorykind = kind === 'smart' ? 'smart_inventory' : 'inventory';
-
- return askInventoryOnLaunch ? (
- <>
-
- {summaryFields.inventory.name}
-
- {t`(Prompt on launch)`}
- >
- ) : (
-
- {summaryFields.inventory.name}
-
- );
- };
- let lastRun = '';
- const mostRecentJob = template.summary_fields.recent_jobs
- ? template.summary_fields.recent_jobs[0]
- : null;
- if (mostRecentJob) {
- lastRun = mostRecentJob.finished
- ? formatDateString(mostRecentJob.finished)
- : t`Running`;
- }
-
- return (
- <>
-
- |
- |
-
-
- {template.name}
-
- {missingResourceIcon && (
-
-
-
-
-
- )}
- {missingExecutionEnvironment && (
-
- {t`Execution Environment Missing`}}
- bodyContent={
-
-
- Custom virtual environment {template.custom_virtualenv}{' '}
- must be replaced by an execution environment. For more
- information about migrating to execution environments see{' '}
-
- the documentation.
-
-
-
- }
- position="right"
- >
-
-
-
- )}
-
- {toTitleCase(template.type)} |
-
- {summaryFields.organization ? (
-
- {summaryFields.organization.name}
-
- ) : null}
- |
- {lastRun} |
-
-
-
-
-
-
- {({ handleLaunch, isLaunching }) => (
-
- )}
-
-
-
-
-
-
-
-
-
-
-
- |
-
-
-
-
- {summaryFields.recent_jobs ? (
- }
- dataCy={`template-${template.id}-activity`}
- isEmpty={summaryFields.recent_jobs.length === 0}
- />
- ) : null}
- {summaryFields.inventory ? (
-
- ) : (
- !askInventoryOnLaunch &&
- template.type === 'job_template' && (
-
- )
- )}
- {summaryFields.project && (
-
- {summaryFields.project.name}
-
- }
- dataCy={`template-${template.id}-project`}
- />
- )}
- {template.type === 'job_template' && (
-
- )}
-
- {summaryFields.credentials ? (
-
- {summaryFields.credentials.map((c) => (
-
- ))}
-
- }
- dataCy={`template-${template.id}-credentials`}
- isEmpty={summaryFields.credentials.length === 0}
- />
- ) : null}
- {summaryFields.labels && (
-
- {summaryFields.labels.results.map((l) => (
-
- {l.name}
-
- ))}
-
- }
- dataCy={`template-${template.id}-labels`}
- isEmpty={summaryFields.labels.results.length === 0}
- />
- )}
-
-
- |
-
- >
- );
-}
-
-export { TemplateListItem as _TemplateListItem };
-export default TemplateListItem;
diff --git a/awx/ui/src/components/TemplateList/TemplateListItem.test.js b/awx/ui/src/components/TemplateList/TemplateListItem.test.js
deleted file mode 100644
index 612f3868fff2..000000000000
--- a/awx/ui/src/components/TemplateList/TemplateListItem.test.js
+++ /dev/null
@@ -1,532 +0,0 @@
-import React from 'react';
-
-import { createMemoryHistory } from 'history';
-import { act } from 'react-dom/test-utils';
-import { JobTemplatesAPI } from 'api';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import mockJobTemplateData from './data.job_template.json';
-import TemplateListItem from './TemplateListItem';
-
-jest.mock('../../api');
-
-describe('', () => {
- test('should display expected data', () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('Td[dataLabel="Name"]').text()).toBe('Template 1');
- expect(wrapper.find('Td[dataLabel="Type"]').text()).toBe('Job Template');
- expect(wrapper.find('Td[dataLabel="Organization"]').text()).toBe('Foo');
- expect(
- wrapper.find('Td[dataLabel="Organization"]').find('Link').prop('to')
- ).toBe('/organizations/1/details');
- expect(wrapper.find('Td[dataLabel="Last Ran"]').text()).toBe(
- '2/26/2020, 10:38:41 PM'
- );
- });
-
- test('launch button shown to users with start capabilities', () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('LaunchButton').exists()).toBeTruthy();
- });
- test('launch button hidden from users without start capabilities', () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('LaunchButton').exists()).toBeFalsy();
- });
- test('edit button shown to users with edit capabilities', () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
- });
- test('edit button hidden from users without edit capabilities', () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
- });
- test('missing resource icon is shown.', () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('ExclamationTriangleIcon').exists()).toBe(true);
- });
- test('missing resource icon is not shown when there is a project and an inventory.', () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('ExclamationTriangleIcon').exists()).toBe(false);
- });
- test('missing resource icon is not shown when inventory is prompt_on_launch, and a project', () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('ExclamationTriangleIcon').exists()).toBe(false);
- });
- test('missing resource icon is not shown type is workflow_job_template', () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('ExclamationTriangleIcon').exists()).toBe(false);
- });
- test('clicking on template from templates list navigates properly', () => {
- const history = createMemoryHistory({
- initialEntries: ['/templates'],
- });
- const wrapper = mountWithContexts(
- ,
- { context: { router: { history } } }
- );
- wrapper.find('Link').simulate('click', { button: 0 });
- expect(history.location.pathname).toEqual(
- '/templates/job_template/1/details'
- );
- });
- test('should call api to copy template', async () => {
- JobTemplatesAPI.copy.mockResolvedValue();
-
- const wrapper = mountWithContexts(
-
- );
- await act(async () =>
- wrapper.find('Button[aria-label="Copy"]').prop('onClick')()
- );
- expect(JobTemplatesAPI.copy).toHaveBeenCalled();
- jest.clearAllMocks();
- });
-
- test('should render proper alert modal on copy error', async () => {
- JobTemplatesAPI.copy.mockRejectedValue(new Error());
-
- const wrapper = mountWithContexts(
-
- );
- await act(async () =>
- wrapper.find('Button[aria-label="Copy"]').prop('onClick')()
- );
- wrapper.update();
- expect(wrapper.find('Modal').prop('isOpen')).toBe(true);
- jest.clearAllMocks();
- });
-
- test('should not render copy button', async () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('CopyButton').length).toBe(0);
- });
-
- test('should render visualizer button for workflow', async () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('ProjectDiagramIcon').length).toBe(1);
- });
-
- test('should not render visualizer button for job template', async () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('ProjectDiagramIcon').length).toBe(0);
- });
-
- test('should render warning about missing execution environment', () => {
- const wrapper = mountWithContexts(
-
- );
-
- expect(wrapper.find('ExclamationTriangleIconWarning').length).toBe(1);
- });
-
- test('should render expected details in expanded section', async () => {
- const wrapper = mountWithContexts(
-
- );
-
- wrapper.update();
- expect(wrapper.find('Tr').last().prop('isExpanded')).toBe(true);
-
- function assertDetail(label, value) {
- expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
- expect(wrapper.find(`Detail[label="${label}"] dd`).text()).toBe(value);
- }
-
- assertDetail('Description', 'mock description');
- assertDetail('Inventory', "Mike's Inventory");
- assertDetail('Project', "Mike's Project");
- assertDetail('Execution Environment', 'Mock EE 1.2.3');
- expect(
- wrapper.find('Detail[label="Credentials"]').containsAllMatchingElements([
-
- SSH:Credential 1
- ,
-
- Awx:Credential 2
- ,
- ])
- ).toEqual(true);
- expect(
- wrapper
- .find('Detail[label="Labels"]')
- .containsAllMatchingElements([L_91o2])
- ).toEqual(true);
- expect(wrapper.find(`Detail[label="Activity"] Sparkline`)).toHaveLength(1);
- });
-
- test('should not load Activity', async () => {
- const wrapper = mountWithContexts(
-
- );
- const activity_detail = wrapper.find(`Detail[label="Activity"]`).at(0);
- expect(activity_detail.prop('isEmpty')).toEqual(true);
- });
-
- test('should not load Credentials', async () => {
- const wrapper = mountWithContexts(
-
- );
- const credentials_detail = wrapper
- .find(`Detail[label="Credentials"]`)
- .at(0);
- expect(credentials_detail.prop('isEmpty')).toEqual(true);
- });
-
- test('should not load Labels', async () => {
- const wrapper = mountWithContexts(
-
- );
- const labels_detail = wrapper.find(`Detail[label="Labels"]`).at(0);
- expect(labels_detail.prop('isEmpty')).toEqual(true);
- });
-});
diff --git a/awx/ui/src/components/TemplateList/data.job_template.json b/awx/ui/src/components/TemplateList/data.job_template.json
deleted file mode 100644
index 8e1a41b47369..000000000000
--- a/awx/ui/src/components/TemplateList/data.job_template.json
+++ /dev/null
@@ -1,193 +0,0 @@
-{
- "id": 7,
- "type": "job_template",
- "url": "/api/v2/job_templates/7/",
- "related": {
- "named_url": "/api/v2/job_templates/Mike's JT/",
- "created_by": "/api/v2/users/1/",
- "modified_by": "/api/v2/users/1/",
- "labels": "/api/v2/job_templates/7/labels/",
- "inventory": "/api/v2/inventories/1/",
- "project": "/api/v2/projects/6/",
- "credentials": "/api/v2/job_templates/7/credentials/",
- "last_job": "/api/v2/jobs/12/",
- "jobs": "/api/v2/job_templates/7/jobs/",
- "schedules": "/api/v2/job_templates/7/schedules/",
- "activity_stream": "/api/v2/job_templates/7/activity_stream/",
- "launch": "/api/v2/job_templates/7/launch/",
- "notification_templates_started": "/api/v2/job_templates/7/notification_templates_started/",
- "notification_templates_success": "/api/v2/job_templates/7/notification_templates_success/",
- "notification_templates_error": "/api/v2/job_templates/7/notification_templates_error/",
- "access_list": "/api/v2/job_templates/7/access_list/",
- "survey_spec": "/api/v2/job_templates/7/survey_spec/",
- "object_roles": "/api/v2/job_templates/7/object_roles/",
- "instance_groups": "/api/v2/job_templates/7/instance_groups/",
- "slice_workflow_jobs": "/api/v2/job_templates/7/slice_workflow_jobs/",
- "copy": "/api/v2/job_templates/7/copy/",
- "webhook_receiver": "/api/v2/job_templates/7/github/",
- "webhook_key": "/api/v2/job_templates/7/webhook_key/"
- },
- "summary_fields": {
- "organization": {
- "id": 1,
- "name": "Mike's Org",
- "description": ""
- },
- "inventory": {
- "id": 1,
- "name": "Mike's Inventory",
- "description": "",
- "has_active_failures": false,
- "total_hosts": 1,
- "hosts_with_active_failures": 0,
- "total_groups": 0,
- "groups_with_active_failures": 0,
- "has_inventory_sources": false,
- "total_inventory_sources": 0,
- "inventory_sources_with_failures": 0,
- "organization_id": 1,
- "kind": ""
- },
- "project": {
- "id": 6,
- "name": "Mike's Project",
- "description": "",
- "status": "successful",
- "scm_type": "git"
- },
- "last_job": {
- "id": 12,
- "name": "Mike's JT",
- "description": "",
- "finished": "2019-10-01T14:34:35.142483Z",
- "status": "successful",
- "failed": false
- },
- "last_update": {
- "id": 12,
- "name": "Mike's JT",
- "description": "",
- "status": "successful",
- "failed": false
- },
- "created_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "object_roles": {
- "admin_role": {
- "description": "Can manage all aspects of the job template",
- "name": "Admin",
- "id": 24
- },
- "execute_role": {
- "description": "May run the job template",
- "name": "Execute",
- "id": 25
- },
- "read_role": {
- "description": "May view settings for the job template",
- "name": "Read",
- "id": 26
- }
- },
- "user_capabilities": {
- "edit": true,
- "delete": true,
- "start": true,
- "schedule": true,
- "copy": true
- },
- "labels": {
- "count": 1,
- "results": [{
- "id": 91,
- "name": "L_91o2"
- }]
- },
- "survey": {
- "title": "",
- "description": ""
- },
- "recent_jobs": [{
- "id": 12,
- "status": "successful",
- "finished": "2019-10-01T14:34:35.142483Z",
- "type": "job"
- }],
- "credentials": [{
- "id": 1,
- "kind": "ssh",
- "name": "Credential 1"
- },
- {
- "id": 2,
- "kind": "awx",
- "name": "Credential 2"
- }
- ],
- "webhook_credential": {
- "id": "1",
- "name": "Webhook Credential"
-
- },
- "execution_environment": {
- "description": "",
- "id": 1,
- "image": "foo.io/mock/test-ee:1.2.3",
- "name": "Mock EE 1.2.3"
- }
- },
- "created": "2019-09-30T16:18:34.564820Z",
- "modified": "2019-10-01T14:47:31.818431Z",
- "name": "Mike's JT",
- "description": "",
- "job_type": "run",
- "inventory": 1,
- "project": 6,
- "playbook": "ping.yml",
- "scm_branch": "Foo branch",
- "forks": 0,
- "limit": "",
- "verbosity": 0,
- "extra_vars": "",
- "job_tags": "T_100,T_200",
- "force_handlers": false,
- "skip_tags": "S_100,S_200",
- "start_at_task": "",
- "timeout": 0,
- "use_fact_cache": true,
- "last_job_run": "2019-10-01T14:34:35.142483Z",
- "last_job_failed": false,
- "next_job_run": null,
- "status": "successful",
- "host_config_key": "",
- "ask_scm_branch_on_launch": false,
- "ask_diff_mode_on_launch": false,
- "ask_variables_on_launch": false,
- "ask_limit_on_launch": false,
- "ask_tags_on_launch": false,
- "ask_skip_tags_on_launch": false,
- "ask_job_type_on_launch": false,
- "ask_verbosity_on_launch": false,
- "ask_inventory_on_launch": false,
- "ask_credential_on_launch": false,
- "survey_enabled": true,
- "become_enabled": false,
- "diff_mode": false,
- "allow_simultaneous": false,
- "custom_virtualenv": null,
- "execution_environment": 1,
- "job_slice_count": 1,
- "webhook_credential": 1,
- "webhook_key": "asertdyuhjkhgfd234567kjgfds",
- "webhook_service": "github"
-}
diff --git a/awx/ui/src/components/TemplateList/index.js b/awx/ui/src/components/TemplateList/index.js
deleted file mode 100644
index f5cdbd115cf8..000000000000
--- a/awx/ui/src/components/TemplateList/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default } from './TemplateList';
-export { default as TemplateListItem } from './TemplateListItem';
diff --git a/awx/ui/src/components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js b/awx/ui/src/components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js
deleted file mode 100644
index c61c5e50ee38..000000000000
--- a/awx/ui/src/components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js
+++ /dev/null
@@ -1,154 +0,0 @@
-import React, { useState, useCallback } from 'react';
-import { t } from '@lingui/macro';
-import { useParams, useRouteMatch } from 'react-router-dom';
-import styled from 'styled-components';
-import useRequest from 'hooks/useRequest';
-import useSelected from 'hooks/useSelected';
-import SelectableCard from '../SelectableCard';
-import Wizard from '../Wizard/Wizard';
-import SelectResourceStep from '../AddRole/SelectResourceStep';
-import SelectRoleStep from '../AddRole/SelectRoleStep';
-import getResourceAccessConfig from './getResourceAccessConfig';
-
-const Grid = styled.div`
- display: grid;
- grid-gap: 20px;
- grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
-`;
-
-function UserAndTeamAccessAdd({
- title,
- onFetchData,
- apiModel,
- onClose,
- onError,
-}) {
- const [selectedResourceType, setSelectedResourceType] = useState(null);
- const [stepIdReached, setStepIdReached] = useState(1);
- const { id: userId } = useParams();
- const teamsRouteMatch = useRouteMatch({
- path: '/teams/:id/roles',
- exact: true,
- });
-
- const { selected: resourcesSelected, handleSelect: handleResourceSelect } =
- useSelected([]);
-
- const { selected: rolesSelected, handleSelect: handleRoleSelect } =
- useSelected([]);
-
- const { request: handleWizardSave, error: saveError } = useRequest(
- useCallback(async () => {
- const roleRequests = [];
- const resourceRolesTypes = resourcesSelected.flatMap((resource) =>
- Object.values(resource.summary_fields.object_roles)
- );
-
- rolesSelected.map((role) =>
- resourceRolesTypes.forEach((rolename) => {
- if (rolename.name === role.name) {
- roleRequests.push(apiModel.associateRole(userId, rolename.id));
- }
- })
- );
-
- await Promise.all(roleRequests);
- onFetchData();
- }, [onFetchData, rolesSelected, apiModel, userId, resourcesSelected]),
- {}
- );
-
- // Object roles can be user only, so we remove them when
- // showing role choices for team access
- const selectableRoles = {
- ...resourcesSelected[0]?.summary_fields?.object_roles,
- };
- if (teamsRouteMatch && resourcesSelected[0]?.type === 'organization') {
- Object.keys(selectableRoles).forEach((key) => {
- if (selectableRoles[key].user_only) {
- delete selectableRoles[key];
- }
- });
- }
-
- const steps = [
- {
- id: 1,
- name: t`Add resource type`,
- component: (
-
- {getResourceAccessConfig().map((resource) => (
- setSelectedResourceType(resource)}
- />
- ))}
-
- ),
- enableNext: selectedResourceType !== null,
- },
- {
- id: 2,
- name: t`Select items from list`,
- component: selectedResourceType && (
-
- ),
- enableNext: resourcesSelected.length > 0,
- canJumpTo: stepIdReached >= 2,
- },
- {
- id: 3,
- name: t`Select roles to apply`,
- component: resourcesSelected?.length > 0 && (
-
- ),
- nextButtonText: t`Save`,
- canJumpTo: stepIdReached >= 3,
- },
- ];
-
- if (saveError) {
- onError(saveError);
- onClose();
- }
-
- return (
-
- setStepIdReached(stepIdReached < id ? id : stepIdReached)
- }
- onSave={handleWizardSave}
- />
- );
-}
-
-export default UserAndTeamAccessAdd;
diff --git a/awx/ui/src/components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.test.js b/awx/ui/src/components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.test.js
deleted file mode 100644
index cd0386fbd260..000000000000
--- a/awx/ui/src/components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.test.js
+++ /dev/null
@@ -1,249 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { UsersAPI, JobTemplatesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../testUtils/enzymeHelpers';
-import UserAndTeamAccessAdd from './UserAndTeamAccessAdd';
-
-jest.mock('../../api');
-
-const onError = jest.fn();
-const onClose = jest.fn();
-
-describe('', () => {
- const resources = {
- data: {
- results: [
- {
- id: 1,
- name: 'Job Template Foo Bar',
- url: '/api/v2/job_template/1/',
- summary_fields: {
- object_roles: {
- admin_role: {
- description: 'Can manage all aspects of the job template',
- name: 'Admin',
- id: 164,
- },
- execute_role: {
- description: 'May run the job template',
- name: 'Execute',
- id: 165,
- },
- read_role: {
- description: 'May view settings for the job template',
- name: 'Read',
- id: 166,
- },
- },
- },
- },
- ],
- count: 1,
- },
- };
- const options = {
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- related_search_fields: [],
- },
- };
- let wrapper;
- beforeEach(async () => {
- await act(async () => {
- wrapper = mountWithContexts(
- {}}
- onClose={onClose}
- title="Add user permissions"
- onError={onError}
- />
- );
- });
- wrapper.update();
- });
- afterEach(() => {
- jest.resetAllMocks();
- });
- test('should mount properly', async () => {
- expect(wrapper.find('PFWizard').length).toBe(1);
- });
- test('should disable steps', async () => {
- expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe(true);
- expect(
- wrapper
- .find('WizardNavItem[content="Select items from list"]')
- .prop('isDisabled')
- ).toBe(true);
- expect(
- wrapper
- .find('WizardNavItem[content="Select roles to apply"]')
- .prop('isDisabled')
- ).toBe(true);
- await act(async () =>
- wrapper.find('SelectableCard[label="Job templates"]').prop('onClick')({
- fetchItems: JobTemplatesAPI.read,
- label: 'Job template',
- selectedResource: 'jobTemplate',
- searchColumns: [
- { name: 'Name', key: 'name__icontains', isDefault: true },
- ],
- sortColumns: [{ name: 'Name', key: 'name' }],
- })
- );
- await act(async () =>
- wrapper.find('Button[type="submit"]').prop('onClick')()
- );
- wrapper.update();
- expect(
- wrapper
- .find('WizardNavItem[content="Add resource type"]')
- .prop('isDisabled')
- ).toBe(false);
- expect(
- wrapper
- .find('WizardNavItem[content="Select items from list"]')
- .prop('isDisabled')
- ).toBe(false);
- expect(
- wrapper
- .find('WizardNavItem[content="Select roles to apply"]')
- .prop('isDisabled')
- ).toBe(true);
- });
-
- test('should call api to associate role', async () => {
- JobTemplatesAPI.read.mockResolvedValue(resources);
- JobTemplatesAPI.readOptions.mockResolvedValue(options);
- UsersAPI.associateRole.mockResolvedValue({});
- await act(async () =>
- wrapper.find('SelectableCard[label="Job templates"]').prop('onClick')({
- fetchItems: JobTemplatesAPI.read,
- fetchOptions: JobTemplatesAPI.readOptions,
- label: 'Job template',
- selectedResource: 'jobTemplate',
- searchColumns: [
- { name: 'Name', key: 'name__icontains', isDefault: true },
- ],
- sortColumns: [{ name: 'Name', key: 'name' }],
- })
- );
- await act(async () =>
- wrapper.find('Button[type="submit"]').prop('onClick')()
- );
- expect(JobTemplatesAPI.read).toHaveBeenCalledWith({
- order_by: 'name',
- page: 1,
- page_size: 5,
- });
-
- await waitForElement(wrapper, 'SelectResourceStep', (el) => el.length > 0);
- expect(JobTemplatesAPI.read).toHaveBeenCalled();
- await act(async () =>
- wrapper
- .find('CheckboxListItem')
- .first()
- .find('input[type="checkbox"]')
- .simulate('click')
- );
-
- wrapper.update();
-
- await act(async () =>
- wrapper.find('Button[type="submit"]').prop('onClick')()
- );
-
- wrapper.update();
-
- expect(wrapper.find('RolesStep').length).toBe(1);
-
- await act(async () =>
- wrapper.find('CheckboxCard').first().prop('onSelect')()
- );
-
- await act(async () =>
- wrapper.find('Button[type="submit"]').prop('onClick')()
- );
-
- await expect(UsersAPI.associateRole).toHaveBeenCalled();
- });
-
- test('should close wizard on cancel', async () => {
- await act(async () =>
- wrapper.find('Button[children="Cancel"]').prop('onClick')()
- );
- wrapper.update();
- expect(onClose).toHaveBeenCalledTimes(1);
- });
-
- test('should throw error', async () => {
- expect(onError).toHaveBeenCalledTimes(0);
- JobTemplatesAPI.read.mockResolvedValue(resources);
- JobTemplatesAPI.readOptions.mockResolvedValue(options);
- UsersAPI.associateRole.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'post',
- url: '/api/v2/users/a/roles',
- },
- data: 'An error occurred',
- status: 403,
- },
- })
- );
-
- await act(async () =>
- wrapper.find('SelectableCard[label="Job templates"]').prop('onClick')({
- fetchItems: JobTemplatesAPI.read,
- fetchOptions: JobTemplatesAPI.readOptions,
- label: 'Job template',
- selectedResource: 'jobTemplate',
- searchColumns: [
- { name: 'Name', key: 'name__icontains', isDefault: true },
- ],
- sortColumns: [{ name: 'Name', key: 'name' }],
- })
- );
- await act(async () =>
- wrapper.find('Button[type="submit"]').prop('onClick')()
- );
- await waitForElement(wrapper, 'SelectResourceStep', (el) => el.length > 0);
- expect(JobTemplatesAPI.read).toHaveBeenCalled();
- await act(async () =>
- wrapper
- .find('CheckboxListItem')
- .first()
- .find('input[type="checkbox"]')
- .simulate('click')
- );
-
- wrapper.update();
-
- await act(async () =>
- wrapper.find('Button[type="submit"]').prop('onClick')()
- );
-
- wrapper.update();
-
- expect(wrapper.find('RolesStep').length).toBe(1);
-
- await act(async () =>
- wrapper.find('CheckboxCard').first().prop('onSelect')()
- );
-
- await act(async () =>
- wrapper.find('Button[type="submit"]').prop('onClick')()
- );
-
- await expect(UsersAPI.associateRole).toHaveBeenCalled();
- wrapper.update();
- expect(onError).toHaveBeenCalled();
- });
-});
diff --git a/awx/ui/src/components/UserAndTeamAccessAdd/getResourceAccessConfig.js b/awx/ui/src/components/UserAndTeamAccessAdd/getResourceAccessConfig.js
deleted file mode 100644
index 84fbe8eeca81..000000000000
--- a/awx/ui/src/components/UserAndTeamAccessAdd/getResourceAccessConfig.js
+++ /dev/null
@@ -1,214 +0,0 @@
-import { t } from '@lingui/macro';
-import {
- JobTemplatesAPI,
- WorkflowJobTemplatesAPI,
- CredentialsAPI,
- InventoriesAPI,
- ProjectsAPI,
- OrganizationsAPI,
-} from 'api';
-
-export default function getResourceAccessConfig() {
- return [
- {
- selectedResource: 'jobTemplate',
- label: t`Job templates`,
- searchColumns: [
- {
- name: t`Name`,
- key: 'name__icontains',
- isDefault: true,
- },
- {
- name: t`Playbook name`,
- key: 'playbook__icontains',
- },
- {
- name: t`Created By (Username)`,
- key: 'created_by__username__icontains',
- },
- {
- name: t`Modified By (Username)`,
- key: 'modified_by__username__icontains',
- },
- ],
- sortColumns: [
- {
- name: t`Name`,
- key: 'name',
- },
- ],
- fetchItems: (queryParams) => JobTemplatesAPI.read(queryParams),
- fetchOptions: () => JobTemplatesAPI.readOptions(),
- },
- {
- selectedResource: 'workflowJobTemplate',
- label: t`Workflow job templates`,
- searchColumns: [
- {
- name: t`Name`,
- key: 'name__icontains',
- isDefault: true,
- },
- {
- name: t`Playbook name`,
- key: 'playbook__icontains',
- },
- {
- name: t`Created By (Username)`,
- key: 'created_by__username__icontains',
- },
- {
- name: t`Modified By (Username)`,
- key: 'modified_by__username__icontains',
- },
- ],
- sortColumns: [
- {
- name: t`Name`,
- key: 'name',
- },
- ],
- fetchItems: (queryParams) => WorkflowJobTemplatesAPI.read(queryParams),
- fetchOptions: () => WorkflowJobTemplatesAPI.readOptions(),
- },
- {
- selectedResource: 'credential',
- label: t`Credentials`,
- searchColumns: [
- {
- name: t`Name`,
- key: 'name__icontains',
- isDefault: true,
- },
- {
- name: t`Type`,
- key: 'or__scm_type',
- options: [
- [``, t`Manual`],
- [`git`, t`Git`],
- [`svn`, t`Subversion`],
- [`archive`, t`Remote Archive`],
- [`insights`, t`Red Hat Insights`],
- ],
- },
- {
- name: t`Source Control URL`,
- key: 'scm_url__icontains',
- },
- {
- name: t`Modified By (Username)`,
- key: 'modified_by__username__icontains',
- },
- {
- name: t`Created By (Username)`,
- key: 'created_by__username__icontains',
- },
- ],
- sortColumns: [
- {
- name: t`Name`,
- key: 'name',
- },
- ],
- fetchItems: (queryParams) => CredentialsAPI.read(queryParams),
- fetchOptions: () => CredentialsAPI.readOptions(),
- },
- {
- selectedResource: 'inventory',
- label: t`Inventories`,
- searchColumns: [
- {
- name: t`Name`,
- key: 'name__icontains',
- isDefault: true,
- },
- {
- name: t`Created By (Username)`,
- key: 'created_by__username__icontains',
- },
- {
- name: t`Modified By (Username)`,
- key: 'modified_by__username__icontains',
- },
- ],
- sortColumns: [
- {
- name: t`Name`,
- key: 'name',
- },
- ],
- fetchItems: (queryParams) => InventoriesAPI.read(queryParams),
- fetchOptions: () => InventoriesAPI.readOptions(),
- },
- {
- selectedResource: 'project',
- label: t`Projects`,
- searchColumns: [
- {
- name: t`Name`,
- key: 'name__icontains',
- isDefault: true,
- },
- {
- name: t`Type`,
- key: 'or__scm_type',
- options: [
- [``, t`Manual`],
- [`git`, t`Git`],
- [`svn`, t`Subversion`],
- [`archive`, t`Remote Archive`],
- [`insights`, t`Red Hat Insights`],
- ],
- },
- {
- name: t`Source Control URL`,
- key: 'scm_url__icontains',
- },
- {
- name: t`Modified By (Username)`,
- key: 'modified_by__username__icontains',
- },
- {
- name: t`Created By (Username)`,
- key: 'created_by__username__icontains',
- },
- ],
- sortColumns: [
- {
- name: t`Name`,
- key: 'name',
- },
- ],
- fetchItems: (queryParams) => ProjectsAPI.read(queryParams),
- fetchOptions: () => ProjectsAPI.readOptions(),
- },
- {
- selectedResource: 'organization',
- label: t`Organizations`,
- searchColumns: [
- {
- name: t`Name`,
- key: 'name__icontains',
- isDefault: true,
- },
- {
- name: t`Created By (Username)`,
- key: 'created_by__username__icontains',
- },
- {
- name: t`Modified By (Username)`,
- key: 'modified_by__username__icontains',
- },
- ],
- sortColumns: [
- {
- name: t`Name`,
- key: 'name',
- },
- ],
- fetchItems: (queryParams) => OrganizationsAPI.read(queryParams),
- fetchOptions: () => OrganizationsAPI.readOptions(),
- },
- ];
-}
diff --git a/awx/ui/src/components/UserAndTeamAccessAdd/index.js b/awx/ui/src/components/UserAndTeamAccessAdd/index.js
deleted file mode 100644
index c445f018ba9d..000000000000
--- a/awx/ui/src/components/UserAndTeamAccessAdd/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './UserAndTeamAccessAdd';
diff --git a/awx/ui/src/components/VerbositySelectField/VerbositySelectField.js b/awx/ui/src/components/VerbositySelectField/VerbositySelectField.js
deleted file mode 100644
index 42399cdef0e2..000000000000
--- a/awx/ui/src/components/VerbositySelectField/VerbositySelectField.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import React from 'react';
-import { t } from '@lingui/macro';
-import { useField } from 'formik';
-import { FormGroup } from '@patternfly/react-core';
-import Popover from 'components/Popover';
-import AnsibleSelect from 'components/AnsibleSelect';
-import FieldWithPrompt from 'components/FieldWithPrompt';
-
-const VERBOSITY = () => ({
- 0: t`0 (Normal)`,
- 1: t`1 (Verbose)`,
- 2: t`2 (More Verbose)`,
- 3: t`3 (Debug)`,
- 4: t`4 (Connection Debug)`,
- 5: t`5 (WinRM Debug)`,
-});
-
-function VerbositySelectField({
- fieldId,
- promptId,
- promptName,
- tooltip,
- isValid,
-}) {
- const VERBOSE_OPTIONS = Object.entries(VERBOSITY()).map(([k, v]) => ({
- key: `${k}`,
- value: `${k}`,
- label: v,
- }));
- const [verbosityField, , verbosityHelpers] = useField('verbosity');
- return promptId ? (
-
-
-
- ) : (
- }
- >
- verbosityHelpers.setValue(value)}
- />
-
- );
-}
-
-export { VerbositySelectField, VERBOSITY };
diff --git a/awx/ui/src/components/VerbositySelectField/index.js b/awx/ui/src/components/VerbositySelectField/index.js
deleted file mode 100644
index 8da8d324db8f..000000000000
--- a/awx/ui/src/components/VerbositySelectField/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { VERBOSITY, VerbositySelectField } from './VerbositySelectField';
diff --git a/awx/ui/src/components/Wizard/Wizard.js b/awx/ui/src/components/Wizard/Wizard.js
deleted file mode 100644
index 9f737d76913d..000000000000
--- a/awx/ui/src/components/Wizard/Wizard.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Wizard } from '@patternfly/react-core';
-import styled from 'styled-components';
-
-Wizard.displayName = 'PFWizard';
-export default styled(Wizard)`
- .pf-c-toolbar__content {
- padding: 0 !important;
- }
-`;
diff --git a/awx/ui/src/components/Wizard/Wizard.test.js b/awx/ui/src/components/Wizard/Wizard.test.js
deleted file mode 100644
index 35e8adf410e2..000000000000
--- a/awx/ui/src/components/Wizard/Wizard.test.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import React from 'react';
-import { mount } from 'enzyme';
-import Wizard from './Wizard';
-
-describe('Wizard', () => {
- test('renders the expected content', () => {
- const wrapper = mount(
- Step 1
}]}
- />
- );
- expect(wrapper).toHaveLength(1);
- });
-});
diff --git a/awx/ui/src/components/Wizard/index.js b/awx/ui/src/components/Wizard/index.js
deleted file mode 100644
index 40da120187c7..000000000000
--- a/awx/ui/src/components/Wizard/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './Wizard';
diff --git a/awx/ui/src/components/Workflow/WorkflowActionTooltip.js b/awx/ui/src/components/Workflow/WorkflowActionTooltip.js
deleted file mode 100644
index 02ec532c95ca..000000000000
--- a/awx/ui/src/components/Workflow/WorkflowActionTooltip.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import React from 'react';
-import styled from 'styled-components';
-import { node, number } from 'prop-types';
-
-const TooltipContents = styled.div`
- display: flex;
-`;
-
-const TooltipArrow = styled.div`
- width: 10px;
-`;
-
-const TooltipArrowOuter = styled.div`
- border-bottom: 10px solid transparent;
- border-right: 10px solid #c4c4c4;
- border-top: 10px solid transparent;
- height: 0;
- margin: auto;
- position: absolute;
- top: calc(50% - 10px);
- width: 0;
-`;
-
-const TooltipArrowInner = styled.div`
- border-bottom: 10px solid transparent;
- border-right: 10px solid white;
- border-top: 10px solid transparent;
- height: 0;
- left: 2px;
- margin: auto;
- position: absolute;
- top: calc(50% - 10px);
- width: 0;
-`;
-
-const TooltipActions = styled.div`
- background-color: white;
- border-radius: 2px;
- border: 1px solid #c4c4c4;
- padding: 5px;
-`;
-
-function WorkflowActionTooltip({ actions, pointX, pointY }) {
- const tipHeight = 25 * actions.length + 5 * actions.length - 1 + 10;
- return (
-
-
-
-
-
-
- {actions}
-
-
- );
-}
-
-WorkflowActionTooltip.propTypes = {
- actions: node.isRequired,
- pointX: number.isRequired,
- pointY: number.isRequired,
-};
-
-export default WorkflowActionTooltip;
diff --git a/awx/ui/src/components/Workflow/WorkflowActionTooltip.test.js b/awx/ui/src/components/Workflow/WorkflowActionTooltip.test.js
deleted file mode 100644
index aa3c6ba4e4e3..000000000000
--- a/awx/ui/src/components/Workflow/WorkflowActionTooltip.test.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import React from 'react';
-import { mount } from 'enzyme';
-import WorkflowActionTooltip from './WorkflowActionTooltip';
-
-describe('WorkflowActionTooltip', () => {
- test('successfully mounts', () => {
- const wrapper = mount(
-
- );
- expect(wrapper).toHaveLength(1);
- });
-});
diff --git a/awx/ui/src/components/Workflow/WorkflowActionTooltipItem.js b/awx/ui/src/components/Workflow/WorkflowActionTooltipItem.js
deleted file mode 100644
index d035938255cb..000000000000
--- a/awx/ui/src/components/Workflow/WorkflowActionTooltipItem.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import React from 'react';
-import styled from 'styled-components';
-import { func, string } from 'prop-types';
-
-const TooltipItem = styled.div`
- align-items: center;
- border-radius: 2px;
- cursor: pointer;
- display: flex;
- font-size: 12px;
- height: 25px;
- justify-content: center;
- width: 25px;
-
- &:hover {
- color: white;
- background-color: #c4c4c4;
- }
-
- &:not(:last-of-type) {
- margin-bottom: 5px;
- }
-`;
-
-function WorkflowActionTooltipItem({
- children,
- id,
- onClick,
- onMouseEnter,
- onMouseLeave,
-}) {
- return (
-
- {children}
-
- );
-}
-
-WorkflowActionTooltipItem.propTypes = {
- id: string.isRequired,
- onClick: func,
- onMouseEnter: func,
- onMouseLeave: func,
-};
-
-WorkflowActionTooltipItem.defaultProps = {
- onClick: () => {},
- onMouseEnter: () => {},
- onMouseLeave: () => {},
-};
-
-export default WorkflowActionTooltipItem;
diff --git a/awx/ui/src/components/Workflow/WorkflowActionTooltipItem.test.js b/awx/ui/src/components/Workflow/WorkflowActionTooltipItem.test.js
deleted file mode 100644
index 4f06a7f02ff3..000000000000
--- a/awx/ui/src/components/Workflow/WorkflowActionTooltipItem.test.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from 'react';
-import { mount } from 'enzyme';
-import WorkflowActionTooltipItem from './WorkflowActionTooltipItem';
-
-describe('WorkflowActionTooltipItem', () => {
- test('successfully mounts', () => {
- const wrapper = mount();
- expect(wrapper).toHaveLength(1);
- });
-});
diff --git a/awx/ui/src/components/Workflow/WorkflowHelp.js b/awx/ui/src/components/Workflow/WorkflowHelp.js
deleted file mode 100644
index 4b2251a7cef4..000000000000
--- a/awx/ui/src/components/Workflow/WorkflowHelp.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from 'react';
-import styled from 'styled-components';
-
-const Outer = styled.div`
- height: 0;
- pointer-events: none;
- position: relative;
-`;
-
-const Inner = styled.div`
- background-color: #383f44;
- border-radius: 2px;
- color: white;
- left: 10px;
- max-width: 300px;
- padding: 5px 10px;
- position: absolute;
- top: 10px;
-`;
-
-function WorkflowHelp({ children }) {
- return (
-
- {children}
-
- );
-}
-
-export default WorkflowHelp;
diff --git a/awx/ui/src/components/Workflow/WorkflowHelp.test.js b/awx/ui/src/components/Workflow/WorkflowHelp.test.js
deleted file mode 100644
index 110270988928..000000000000
--- a/awx/ui/src/components/Workflow/WorkflowHelp.test.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from 'react';
-import { mount } from 'enzyme';
-import WorkflowHelp from './WorkflowHelp';
-
-describe('WorkflowHelp', () => {
- test('successfully mounts', () => {
- const wrapper = mount();
- expect(wrapper).toHaveLength(1);
- });
-});
diff --git a/awx/ui/src/components/Workflow/WorkflowLegend.js b/awx/ui/src/components/Workflow/WorkflowLegend.js
deleted file mode 100644
index eca1b82bef2b..000000000000
--- a/awx/ui/src/components/Workflow/WorkflowLegend.js
+++ /dev/null
@@ -1,137 +0,0 @@
-import React, { useContext } from 'react';
-
-import { t } from '@lingui/macro';
-import styled from 'styled-components';
-import {
- ExclamationTriangleIcon,
- PauseIcon,
- TimesIcon,
-} from '@patternfly/react-icons';
-import { WorkflowDispatchContext } from 'contexts/Workflow';
-
-const Wrapper = styled.div`
- background-color: white;
- border: 1px solid #c7c7c7;
- margin-left: 20px;
- min-width: 100px;
- position: relative;
-`;
-
-const Header = styled.div`
- border-bottom: 1px solid #c7c7c7;
- padding: 10px;
- position: relative;
-`;
-
-const Legend = styled.ul`
- padding: 5px 10px;
-
- li {
- align-items: center;
- display: flex;
- padding: 5px 0px;
- }
-`;
-
-const NodeTypeLetter = styled.div`
- background-color: #393f43;
- border-radius: 50%;
- color: white;
- font-size: 10px;
- height: 20px;
- line-height: 20px;
- margin-right: 10px;
- text-align: center;
- width: 20px;
-`;
-
-const StyledExclamationTriangleIcon = styled(ExclamationTriangleIcon)`
- color: #f0ad4d;
- height: 20px;
- margin-right: 10px;
- width: 20px;
-`;
-
-const Link = styled.div`
- height: 5px;
- margin-right: 10px;
- width: 20px;
-`;
-
-const SuccessLink = styled(Link)`
- background-color: #5cb85c;
-`;
-
-const FailureLink = styled(Link)`
- background-color: #d9534f;
-`;
-
-const AlwaysLink = styled(Link)`
- background-color: #337ab7;
-`;
-
-const Close = styled(TimesIcon)`
- cursor: pointer;
- position: absolute;
- right: 10px;
- top: 15px;
-`;
-
-function WorkflowLegend() {
- const dispatch = useContext(WorkflowDispatchContext);
-
- return (
-
-
- {t`Legend`}
- dispatch({ type: 'TOGGLE_LEGEND' })} />
-
-
-
- );
-}
-
-export default WorkflowLegend;
diff --git a/awx/ui/src/components/Workflow/WorkflowLegend.test.js b/awx/ui/src/components/Workflow/WorkflowLegend.test.js
deleted file mode 100644
index 86795ec06f39..000000000000
--- a/awx/ui/src/components/Workflow/WorkflowLegend.test.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import WorkflowLegend from './WorkflowLegend';
-
-describe('WorkflowLegend', () => {
- test('renders the expected content', () => {
- const wrapper = mountWithContexts( {}} />);
- expect(wrapper).toHaveLength(1);
- });
-});
diff --git a/awx/ui/src/components/Workflow/WorkflowLinkHelp.js b/awx/ui/src/components/Workflow/WorkflowLinkHelp.js
deleted file mode 100644
index 756eb5022f3e..000000000000
--- a/awx/ui/src/components/Workflow/WorkflowLinkHelp.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import React from 'react';
-
-import { t } from '@lingui/macro';
-import styled from 'styled-components';
-import { shape } from 'prop-types';
-
-const GridDL = styled.dl`
- column-gap: 15px;
- display: grid;
- grid-template-columns: max-content;
- row-gap: 0px;
- dt {
- grid-column-start: 1;
- }
- dd {
- grid-column-start: 2;
- }
-`;
-
-function WorkflowLinkHelp({ link }) {
- let linkType;
- switch (link.linkType) {
- case 'always':
- linkType = t`Always`;
- break;
- case 'success':
- linkType = t`On Success`;
- break;
- case 'failure':
- linkType = t`On Failure`;
- break;
- default:
- linkType = '';
- }
-
- return (
-
-
- {t`Run`}
-
- {linkType}
-
- );
-}
-
-WorkflowLinkHelp.propTypes = {
- link: shape().isRequired,
-};
-
-export default WorkflowLinkHelp;
diff --git a/awx/ui/src/components/Workflow/WorkflowLinkHelp.test.js b/awx/ui/src/components/Workflow/WorkflowLinkHelp.test.js
deleted file mode 100644
index d941c350f9fc..000000000000
--- a/awx/ui/src/components/Workflow/WorkflowLinkHelp.test.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import WorkflowLinkHelp from './WorkflowLinkHelp';
-
-describe('WorkflowLinkHelp', () => {
- test('successfully mounts', () => {
- const wrapper = mountWithContexts();
- expect(wrapper).toHaveLength(1);
- });
- test('renders the expected content for an on success link', () => {
- const link = {
- linkType: 'success',
- };
- const wrapper = mountWithContexts();
- expect(wrapper.find('#workflow-link-help-type').text()).toBe('On Success');
- });
- test('renders the expected content for an on failure link', () => {
- const link = {
- linkType: 'failure',
- };
- const wrapper = mountWithContexts();
- expect(wrapper.find('#workflow-link-help-type').text()).toBe('On Failure');
- });
- test('renders the expected content for an always link', () => {
- const link = {
- linkType: 'always',
- };
- const wrapper = mountWithContexts();
- expect(wrapper.find('#workflow-link-help-type').text()).toBe('Always');
- });
-});
diff --git a/awx/ui/src/components/Workflow/WorkflowNodeHelp.js b/awx/ui/src/components/Workflow/WorkflowNodeHelp.js
deleted file mode 100644
index 30b423df9e32..000000000000
--- a/awx/ui/src/components/Workflow/WorkflowNodeHelp.js
+++ /dev/null
@@ -1,212 +0,0 @@
-import 'styled-components/macro';
-import React from 'react';
-
-import { t, Trans } from '@lingui/macro';
-import styled from 'styled-components';
-import { ExclamationTriangleIcon } from '@patternfly/react-icons';
-import { shape } from 'prop-types';
-import { secondsToHHMMSS } from 'util/dates';
-import { stringIsUUID } from 'util/strings';
-
-const GridDL = styled.dl`
- column-gap: 15px;
- display: grid;
- grid-template-columns: max-content;
- row-gap: 0px;
- dt {
- grid-column-start: 1;
- }
- dd {
- grid-column-start: 2;
- }
-`;
-
-const ResourceDeleted = styled.p`
- margin-bottom: ${(props) => (props.job ? '10px' : '0px')};
-`;
-
-const StyledExclamationTriangleIcon = styled(ExclamationTriangleIcon)`
- color: #f0ad4d;
- height: 20px;
- margin-right: 10px;
- width: 20px;
-`;
-
-function WorkflowNodeHelp({ node }) {
- let nodeType;
- const job = node?.originalNodeObject?.summary_fields?.job;
- const unifiedJobTemplate =
- node?.fullUnifiedJobTemplate ||
- node?.originalNodeObject?.summary_fields?.unified_job_template;
- let identifier = null;
- if (node?.identifier) {
- ({ identifier } = node);
- } else if (
- node?.originalNodeObject?.identifier &&
- !stringIsUUID(node.originalNodeObject.identifier)
- ) {
- ({
- originalNodeObject: { identifier },
- } = node);
- }
- if (unifiedJobTemplate || job) {
- const type = unifiedJobTemplate
- ? unifiedJobTemplate.unified_job_type || unifiedJobTemplate.type
- : job.type;
- switch (type) {
- case 'job_template':
- case 'job':
- nodeType = t`Job Template`;
- break;
- case 'workflow_job_template':
- case 'workflow_job':
- nodeType = t`Workflow Job Template`;
- break;
- case 'project':
- case 'project_update':
- nodeType = t`Project Update`;
- break;
- case 'inventory_source':
- case 'inventory_update':
- nodeType = t`Inventory Update`;
- break;
- case 'workflow_approval_template':
- case 'workflow_approval':
- nodeType = t`Workflow Approval`;
- break;
- case 'system_job_template':
- case 'system_job':
- nodeType = t`Management Job`;
- break;
- default:
- nodeType = '';
- }
- }
-
- let jobStatus;
- if (job) {
- switch (job.status) {
- case 'new':
- jobStatus = t`New`;
- break;
- case 'pending':
- jobStatus = t`Pending`;
- break;
- case 'waiting':
- jobStatus = t`Waiting`;
- break;
- case 'running':
- jobStatus = t`Running`;
- break;
- case 'successful':
- jobStatus = t`Successful`;
- break;
- case 'failed':
- jobStatus = t`Failed`;
- break;
- case 'error':
- jobStatus = t`Error`;
- break;
- case 'canceled':
- jobStatus = t`Canceled`;
- break;
- case 'never updated':
- jobStatus = t`Never Updated`;
- break;
- case 'ok':
- jobStatus = t`OK`;
- break;
- case 'missing':
- jobStatus = t`Missing`;
- break;
- case 'none':
- jobStatus = t`None`;
- break;
- case 'updating':
- jobStatus = t`Updating`;
- break;
- default:
- jobStatus = '';
- }
- }
-
- return (
- <>
- {!unifiedJobTemplate && (!job || job.type !== 'workflow_approval') && (
-
-
-
- The resource associated with this node has been deleted.
-
-
- )}
- {job && (
-
- {identifier && (
- <>
-
- {t`Node Alias`}
-
- {identifier}
- >
- )}
-
- {t`Resource Name`}
-
-
- {unifiedJobTemplate?.name || t`Deleted`}
-
-
- {t`Type`}
-
- {nodeType}
-
- {t`Job Status`}
-
- {jobStatus}
- {typeof job.elapsed === 'number' && (
- <>
-
- {t`Elapsed`}
-
-
- {secondsToHHMMSS(job.elapsed)}
-
- >
- )}
-
- )}
- {unifiedJobTemplate && !job && (
-
- {identifier && (
- <>
-
- {t`Node Alias`}
-
- {identifier}
- >
- )}
-
- {t`Resource Name`}
-
-
- {unifiedJobTemplate?.name || t`Deleted`}
-
-
- {t`Type`}
-
- {nodeType}
-
- )}
- {job && job.type !== 'workflow_approval' && (
- {t`Click to view job details`}
- )}
- >
- );
-}
-
-WorkflowNodeHelp.propTypes = {
- node: shape().isRequired,
-};
-
-export default WorkflowNodeHelp;
diff --git a/awx/ui/src/components/Workflow/WorkflowNodeHelp.test.js b/awx/ui/src/components/Workflow/WorkflowNodeHelp.test.js
deleted file mode 100644
index dbb74eac7eaf..000000000000
--- a/awx/ui/src/components/Workflow/WorkflowNodeHelp.test.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import WorkflowNodeHelp from './WorkflowNodeHelp';
-
-const node = {
- originalNodeObject: {
- identifier: 'Foo',
- summary_fields: {
- job: {
- name: 'Foo Job Template',
- elapsed: 9000,
- status: 'successful',
- type: 'job',
- },
- unified_job_template: {
- name: 'Foo Job Template',
- type: 'job_template',
- },
- },
- },
- unifiedJobTemplate: {
- name: 'Foo Job Template',
- unified_job_type: 'job',
- },
-};
-
-describe('WorkflowNodeHelp', () => {
- test('renders the expected content for a completed job template job', () => {
- const wrapper = mountWithContexts();
- expect(wrapper.find('#workflow-node-help-alias').text()).toBe('Foo');
- expect(wrapper.find('#workflow-node-help-name').text()).toBe(
- 'Foo Job Template'
- );
- expect(wrapper.find('#workflow-node-help-type').text()).toBe(
- 'Job Template'
- );
- expect(wrapper.find('#workflow-node-help-status').text()).toBe(
- 'Successful'
- );
- expect(wrapper.find('#workflow-node-help-elapsed').text()).toBe('02:30:00');
- });
-});
diff --git a/awx/ui/src/components/Workflow/WorkflowNodeTypeLetter.js b/awx/ui/src/components/Workflow/WorkflowNodeTypeLetter.js
deleted file mode 100644
index b6a880e749df..000000000000
--- a/awx/ui/src/components/Workflow/WorkflowNodeTypeLetter.js
+++ /dev/null
@@ -1,86 +0,0 @@
-import React from 'react';
-import styled from 'styled-components';
-import { shape } from 'prop-types';
-import { PauseIcon } from '@patternfly/react-icons';
-
-const NodeTypeLetter = styled.div`
- background-color: #393f43;
- border-radius: 50%;
- color: white;
- font-size: 10px;
- line-height: 20px;
- text-align: center;
- height: 20px;
- width: 20px;
-`;
-
-const CenteredPauseIcon = styled(PauseIcon)`
- vertical-align: middle !important;
-`;
-
-function WorkflowNodeTypeLetter({ node }) {
- if (
- !node?.fullUnifiedJobTemplate &&
- !node?.originalNodeObject?.summary_fields?.unified_job_template
- ) {
- return null;
- }
-
- const unifiedJobTemplate =
- node?.fullUnifiedJobTemplate ||
- node?.originalNodeObject?.summary_fields?.unified_job_template;
-
- let nodeTypeLetter;
- if (
- unifiedJobTemplate.type ||
- unifiedJobTemplate.unified_job_type ||
- node?.job?.type
- ) {
- const ujtType =
- unifiedJobTemplate.type ||
- unifiedJobTemplate.unified_job_type ||
- node.job.type;
- switch (ujtType) {
- case 'job_template':
- case 'job':
- nodeTypeLetter = 'JT';
- break;
- case 'project':
- case 'project_update':
- nodeTypeLetter = 'P';
- break;
- case 'inventory_source':
- case 'inventory_update':
- nodeTypeLetter = 'I';
- break;
- case 'system_job_template':
- case 'system_job':
- nodeTypeLetter = 'M';
- break;
- case 'workflow_job_template':
- case 'workflow_job':
- nodeTypeLetter = 'W';
- break;
- case 'workflow_approval_template':
- case 'workflow_approval':
- nodeTypeLetter = ;
- break;
- default:
- nodeTypeLetter = '';
- }
- }
-
- return (
-
-
- {nodeTypeLetter}
-
-
- );
-}
-
-WorkflowNodeTypeLetter.propTypes = {
- node: shape().isRequired,
-};
-
-export default WorkflowNodeTypeLetter;
diff --git a/awx/ui/src/components/Workflow/WorkflowNodeTypeLetter.test.js b/awx/ui/src/components/Workflow/WorkflowNodeTypeLetter.test.js
deleted file mode 100644
index ce67fe6dd76d..000000000000
--- a/awx/ui/src/components/Workflow/WorkflowNodeTypeLetter.test.js
+++ /dev/null
@@ -1,127 +0,0 @@
-import React from 'react';
-import { mount } from 'enzyme';
-import { PauseIcon } from '@patternfly/react-icons';
-import WorkflowNodeTypeLetter from './WorkflowNodeTypeLetter';
-
-describe('WorkflowNodeTypeLetter', () => {
- test('renders JT when type=job_template', () => {
- const wrapper = mount(
-
- );
- expect(wrapper).toHaveLength(1);
- expect(wrapper.text()).toBe('JT');
- });
- test('renders JT when unified_job_type=job', () => {
- const wrapper = mount(
-
- );
- expect(wrapper).toHaveLength(1);
- expect(wrapper.text()).toBe('JT');
- });
- test('renders P when type=project', () => {
- const wrapper = mount(
-
- );
- expect(wrapper).toHaveLength(1);
- expect(wrapper.text()).toBe('P');
- });
- test('renders P when unified_job_type=project_update', () => {
- const wrapper = mount(
-
- );
- expect(wrapper).toHaveLength(1);
- expect(wrapper.text()).toBe('P');
- });
- test('renders I when type=inventory_source', () => {
- const wrapper = mount(
-
- );
- expect(wrapper).toHaveLength(1);
- expect(wrapper.text()).toBe('I');
- });
- test('renders I when unified_job_type=inventory_update', () => {
- const wrapper = mount(
-
- );
- expect(wrapper).toHaveLength(1);
- expect(wrapper.text()).toBe('I');
- });
- test('renders W when type=workflow_job_template', () => {
- const wrapper = mount(
-
- );
- expect(wrapper).toHaveLength(1);
- expect(wrapper.text()).toBe('W');
- });
- test('renders W when unified_job_type=workflow_job', () => {
- const wrapper = mount(
-
- );
- expect(wrapper).toHaveLength(1);
- expect(wrapper.text()).toBe('W');
- });
- test('renders puse icon when type=workflow_approval_template', () => {
- const wrapper = mount(
-
- );
- expect(wrapper).toHaveLength(1);
- expect(wrapper.containsMatchingElement());
- });
- test('renders W when unified_job_type=workflow_approval', () => {
- const wrapper = mount(
-
- );
- expect(wrapper).toHaveLength(1);
- expect(wrapper.containsMatchingElement());
- });
-});
diff --git a/awx/ui/src/components/Workflow/WorkflowStartNode.js b/awx/ui/src/components/Workflow/WorkflowStartNode.js
deleted file mode 100644
index 01873f5b156b..000000000000
--- a/awx/ui/src/components/Workflow/WorkflowStartNode.js
+++ /dev/null
@@ -1,96 +0,0 @@
-import React, { useContext, useRef, useState } from 'react';
-import styled from 'styled-components';
-import { t } from '@lingui/macro';
-import { bool, func } from 'prop-types';
-import { PlusIcon } from '@patternfly/react-icons';
-import {
- WorkflowDispatchContext,
- WorkflowStateContext,
-} from 'contexts/Workflow';
-import WorkflowActionTooltip from './WorkflowActionTooltip';
-import WorkflowActionTooltipItem from './WorkflowActionTooltipItem';
-
-const StartG = styled.g`
- pointer-events: ${(props) => (props.ignorePointerEvents ? 'none' : 'auto')};
-`;
-
-const StartForeignObject = styled.foreignObject`
- overflow: visible;
-`;
-
-const StartDiv = styled.div`
- background-color: #0279bc;
- color: white;
- width: max-content;
- min-width: 80px;
- height: 40px;
- border-radius: 0.35em;
- text-align: center;
- line-height: 40px;
- padding: 0px 10px;
-`;
-
-function WorkflowStartNode({ onUpdateHelpText, showActionTooltip }) {
- const ref = useRef(null);
- const startNodeRef = useRef(null);
- const [hovering, setHovering] = useState(false);
- const dispatch = useContext(WorkflowDispatchContext);
- const { addingLink, nodePositions } = useContext(WorkflowStateContext);
-
- const handleNodeMouseEnter = () => {
- ref.current.parentNode.appendChild(ref.current);
- setHovering(true);
- };
-
- return (
- setHovering(false)}
- ref={ref}
- transform={`translate(${nodePositions[1].x},0)`}
- >
-
- {t`START`}
-
- {showActionTooltip && hovering && (
- onUpdateHelpText(t`Add a new node`)}
- onMouseLeave={() => onUpdateHelpText(null)}
- onClick={() => {
- onUpdateHelpText(null);
- setHovering(false);
- dispatch({ type: 'START_ADD_NODE', sourceNodeId: 1 });
- }}
- >
-
- ,
- ]}
- pointX={startNodeRef.current.offsetWidth}
- pointY={startNodeRef.current.offsetHeight / 2 + 10}
- />
- )}
-
- );
-}
-
-WorkflowStartNode.propTypes = {
- showActionTooltip: bool.isRequired,
- onUpdateHelpText: func,
-};
-
-WorkflowStartNode.defaultProps = {
- onUpdateHelpText: () => {},
-};
-
-export default WorkflowStartNode;
diff --git a/awx/ui/src/components/Workflow/WorkflowStartNode.test.js b/awx/ui/src/components/Workflow/WorkflowStartNode.test.js
deleted file mode 100644
index f82be231822b..000000000000
--- a/awx/ui/src/components/Workflow/WorkflowStartNode.test.js
+++ /dev/null
@@ -1,57 +0,0 @@
-import React from 'react';
-
-import { en } from 'make-plural/plurals';
-import { I18nProvider } from '@lingui/react';
-import { i18n } from '@lingui/core';
-import { WorkflowStateContext } from 'contexts/Workflow';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import english from '../../locales/en/messages';
-import WorkflowStartNode from './WorkflowStartNode';
-
-const nodePositions = {
- 1: {
- x: 0,
- y: 0,
- },
-};
-
-i18n.loadLocaleData({ en: { plurals: en } });
-i18n.load({ en: english });
-i18n.activate('en');
-
-describe('WorkflowStartNode', () => {
- test('mounts successfully', () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper).toHaveLength(1);
- });
- test('tooltip shown on hover', () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('WorkflowActionTooltip')).toHaveLength(0);
- wrapper.find('WorkflowStartNode').simulate('mouseenter');
- expect(wrapper.find('WorkflowActionTooltip')).toHaveLength(1);
- wrapper.find('WorkflowStartNode').simulate('mouseleave');
- expect(wrapper.find('WorkflowActionTooltip')).toHaveLength(0);
- });
-});
diff --git a/awx/ui/src/components/Workflow/WorkflowTools.js b/awx/ui/src/components/Workflow/WorkflowTools.js
deleted file mode 100644
index 26b76238747e..000000000000
--- a/awx/ui/src/components/Workflow/WorkflowTools.js
+++ /dev/null
@@ -1,200 +0,0 @@
-import 'styled-components/macro';
-import React, { useContext } from 'react';
-
-import { t } from '@lingui/macro';
-import styled from 'styled-components';
-import { func, number } from 'prop-types';
-import { Button, Tooltip } from '@patternfly/react-core';
-import {
- CaretDownIcon,
- CaretLeftIcon,
- CaretRightIcon,
- CaretUpIcon,
- DesktopIcon,
- HomeIcon,
- MinusIcon,
- PlusIcon,
- TimesIcon,
-} from '@patternfly/react-icons';
-import { WorkflowDispatchContext } from 'contexts/Workflow';
-
-const Wrapper = styled.div`
- background-color: white;
- border: 1px solid #c7c7c7;
- height: 215px;
- position: relative;
-`;
-
-const Header = styled.div`
- border-bottom: 1px solid #c7c7c7;
- padding: 10px;
-`;
-
-const Pan = styled.div`
- align-items: center;
- display: flex;
-`;
-
-const PanCenter = styled.div`
- display: flex;
- flex-direction: column;
-`;
-
-const Tools = styled.div`
- align-items: center;
- display: flex;
- padding: 20px;
-`;
-
-const Close = styled(TimesIcon)`
- cursor: pointer;
- position: absolute;
- right: 10px;
- top: 15px;
-`;
-
-function WorkflowTools({
- onFitGraph,
- onPan,
- onPanToMiddle,
- onZoomChange,
- zoomPercentage,
-}) {
- const dispatch = useContext(WorkflowDispatchContext);
- const zoomIn = () => {
- const newScale =
- Math.ceil((zoomPercentage + 10) / 10) * 10 < 200
- ? Math.ceil((zoomPercentage + 10) / 10) * 10
- : 200;
- onZoomChange(newScale / 100);
- };
-
- const zoomOut = () => {
- const newScale =
- Math.floor((zoomPercentage - 10) / 10) * 10 > 10
- ? Math.floor((zoomPercentage - 10) / 10) * 10
- : 10;
- onZoomChange(newScale / 100);
- };
-
- return (
-
-
- {t`Tools`}
- dispatch({ type: 'TOGGLE_TOOLS' })} />
-
-
-
-
-
-
-
-
-
- onZoomChange(parseInt(event.target.value, 10) / 100)
- }
- step="10"
- type="range"
- value={zoomPercentage}
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-WorkflowTools.propTypes = {
- onFitGraph: func.isRequired,
- onPan: func.isRequired,
- onPanToMiddle: func.isRequired,
- onZoomChange: func.isRequired,
- zoomPercentage: number.isRequired,
-};
-
-export default WorkflowTools;
diff --git a/awx/ui/src/components/Workflow/WorkflowTools.test.js b/awx/ui/src/components/Workflow/WorkflowTools.test.js
deleted file mode 100644
index 996956d276ca..000000000000
--- a/awx/ui/src/components/Workflow/WorkflowTools.test.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import WorkflowTools from './WorkflowTools';
-
-describe('WorkflowTools', () => {
- test('renders the expected content', () => {
- const wrapper = mountWithContexts(
- {}}
- onFitGraph={() => {}}
- onPan={() => {}}
- onPanToMiddle={() => {}}
- onZoomChange={() => {}}
- zoomPercentage={100}
- />
- );
- expect(wrapper).toHaveLength(1);
- });
- test('clicking zoom/pan buttons passes callback correct values', () => {
- const pan = jest.fn();
- const zoomChange = jest.fn();
- const wrapper = mountWithContexts(
- {}}
- onFitGraph={() => {}}
- onPan={pan}
- onPanToMiddle={() => {}}
- onZoomChange={zoomChange}
- zoomPercentage={95.7}
- />
- );
- wrapper.find('PlusIcon').simulate('click');
- expect(zoomChange).toHaveBeenCalledWith(1.1);
- wrapper.find('MinusIcon').simulate('click');
- expect(zoomChange).toHaveBeenCalledWith(0.8);
- wrapper.find('CaretLeftIcon').simulate('click');
- expect(pan).toHaveBeenCalledWith('left');
- wrapper.find('CaretUpIcon').simulate('click');
- expect(pan).toHaveBeenCalledWith('up');
- wrapper.find('CaretRightIcon').simulate('click');
- expect(pan).toHaveBeenCalledWith('right');
- wrapper.find('CaretDownIcon').simulate('click');
- expect(pan).toHaveBeenCalledWith('down');
- });
-});
diff --git a/awx/ui/src/components/Workflow/WorkflowUtils.js b/awx/ui/src/components/Workflow/WorkflowUtils.js
deleted file mode 100644
index 487fc48bab04..000000000000
--- a/awx/ui/src/components/Workflow/WorkflowUtils.js
+++ /dev/null
@@ -1,190 +0,0 @@
-/* eslint-disable import/prefer-default-export */
-import * as d3 from 'd3';
-import * as dagre from 'dagre';
-
-const normalizeY = (nodePositions, y) => y - nodePositions[1].y;
-
-export const constants = {
- nodeW: 180,
- nodeH: 60,
- rootW: 72,
- rootH: 40,
-};
-
-export function getScaleAndOffsetToFit(
- gBoundingClientRect,
- svgBoundingClientRect,
- gBBoxDimensions,
- currentScale
-) {
- gBoundingClientRect.height /= currentScale;
- gBoundingClientRect.width /= currentScale;
-
- // For some reason the root width needs to be added?
- gBoundingClientRect.width += constants.rootW;
-
- const scaleNeededForMaxHeight =
- svgBoundingClientRect.height / gBoundingClientRect.height;
- const scaleNeededForMaxWidth =
- svgBoundingClientRect.width / gBoundingClientRect.width;
- const lowerScale = Math.min(scaleNeededForMaxHeight, scaleNeededForMaxWidth);
-
- let scaleToFit;
- let yTranslate;
- if (lowerScale < 0.1 || lowerScale > 2) {
- scaleToFit = lowerScale < 0.1 ? 0.1 : 2;
- yTranslate =
- svgBoundingClientRect.height / 2 - (constants.nodeH * scaleToFit) / 2;
- } else {
- scaleToFit = Math.floor(lowerScale * 1000) / 1000;
- yTranslate =
- (svgBoundingClientRect.height - gBoundingClientRect.height * scaleToFit) /
- 2 -
- (gBBoxDimensions.y / currentScale) * scaleToFit;
- }
-
- return [scaleToFit, yTranslate];
-}
-
-export function generateLine(points) {
- const line = d3
- .line()
- .x((d) => d.x)
- .y((d) => d.y);
-
- return line(points);
-}
-
-export function getLinePoints(link, nodePositions) {
- const sourceX =
- nodePositions[link.source.id].x + nodePositions[link.source.id].width + 1;
- let sourceY =
- normalizeY(nodePositions, nodePositions[link.source.id].y) +
- nodePositions[link.source.id].height / 2;
- const targetX = nodePositions[link.target.id].x - 1;
- const targetY =
- normalizeY(nodePositions, nodePositions[link.target.id].y) +
- nodePositions[link.target.id].height / 2;
-
- // There's something off with the math on the root node...
- if (link.source.id === 1) {
- sourceY += 10;
- }
-
- return [
- {
- x: sourceX,
- y: sourceY,
- },
- {
- x: targetX,
- y: targetY,
- },
- ];
-}
-
-export function getLinkOverlayPoints(link, nodePositions) {
- const sourceX =
- nodePositions[link.source.id].x + nodePositions[link.source.id].width + 1;
- let sourceY =
- normalizeY(nodePositions, nodePositions[link.source.id].y) +
- nodePositions[link.source.id].height / 2;
- const targetX = nodePositions[link.target.id].x - 1;
- const targetY =
- normalizeY(nodePositions, nodePositions[link.target.id].y) +
- nodePositions[link.target.id].height / 2;
-
- // There's something off with the math on the root node...
- if (link.source.id === 1) {
- sourceY += 10;
- }
- const slope = (targetY - sourceY) / (targetX - sourceX);
- const yIntercept = targetY - slope * targetX;
- const orthogonalDistance = 8;
-
- const pt1 = [
- targetX,
- slope * targetX +
- yIntercept +
- orthogonalDistance * Math.sqrt(1 + slope * slope),
- ].join(',');
- const pt2 = [
- sourceX,
- slope * sourceX +
- yIntercept +
- orthogonalDistance * Math.sqrt(1 + slope * slope),
- ].join(',');
- const pt3 = [
- sourceX,
- slope * sourceX +
- yIntercept -
- orthogonalDistance * Math.sqrt(1 + slope * slope),
- ].join(',');
- const pt4 = [
- targetX,
- slope * targetX +
- yIntercept -
- orthogonalDistance * Math.sqrt(1 + slope * slope),
- ].join(',');
-
- return [pt1, pt2, pt3, pt4].join(' ');
-}
-
-export function layoutGraph(nodes, links) {
- const g = new dagre.graphlib.Graph();
- g.setGraph({ rankdir: 'LR', nodesep: 30, ranksep: 120 });
-
- // This is needed for Dagre
- g.setDefaultEdgeLabel(() => ({}));
-
- nodes.forEach((node) => {
- if (node.id === 1) {
- g.setNode(node.id, {
- label: '',
- width: constants.rootW,
- height: constants.rootH,
- });
- } else {
- g.setNode(node.id, {
- label: '',
- width: constants.nodeW,
- height: constants.nodeH,
- });
- }
- });
-
- links.forEach((link) => {
- g.setEdge(link.source.id, link.target.id);
- });
-
- dagre.layout(g);
-
- return g;
-}
-
-export function getTranslatePointsForZoom(
- svgBoundingClientRect,
- currentScaleAndOffset,
- newScale
-) {
- const origScale = currentScaleAndOffset.k;
- const unscaledOffsetX =
- (currentScaleAndOffset.x +
- (svgBoundingClientRect.width * origScale - svgBoundingClientRect.width) /
- 2) /
- origScale;
- const unscaledOffsetY =
- (currentScaleAndOffset.y +
- (svgBoundingClientRect.height * origScale -
- svgBoundingClientRect.height) /
- 2) /
- origScale;
- const translateX =
- unscaledOffsetX * newScale -
- (newScale * svgBoundingClientRect.width - svgBoundingClientRect.width) / 2;
- const translateY =
- unscaledOffsetY * newScale -
- (newScale * svgBoundingClientRect.height - svgBoundingClientRect.height) /
- 2;
- return [translateX, translateY];
-}
diff --git a/awx/ui/src/components/Workflow/WorkflowUtils.test.js b/awx/ui/src/components/Workflow/WorkflowUtils.test.js
deleted file mode 100644
index 523cf55977f4..000000000000
--- a/awx/ui/src/components/Workflow/WorkflowUtils.test.js
+++ /dev/null
@@ -1,225 +0,0 @@
-import {
- getScaleAndOffsetToFit,
- generateLine,
- getLinePoints,
- getLinkOverlayPoints,
- layoutGraph,
- getTranslatePointsForZoom,
-} from './WorkflowUtils';
-
-describe('getScaleAndOffsetToFit', () => {
- const gBoundingClientRect = {
- x: 36,
- y: 11,
- width: 798,
- height: 160,
- top: 11,
- right: 834,
- bottom: 171,
- left: 36,
- };
- const svgBoundingClientRect = {
- x: 0,
- y: 56,
- width: 1680,
- height: 455,
- top: 56,
- right: 1680,
- bottom: 511,
- left: 0,
- };
- const gBBoxDimensions = {
- x: 36,
- y: -45,
- width: 726,
- height: 160,
- };
- const currentScale = 1;
- test('returns correct scale and y-offset for zooming the graph to best fit the available space', () => {
- expect(
- getScaleAndOffsetToFit(
- gBoundingClientRect,
- svgBoundingClientRect,
- gBBoxDimensions,
- currentScale
- )
- ).toEqual([1.931, 159.91499999999996]);
- });
-});
-
-describe('generateLine', () => {
- test('returns correct svg path string', () => {
- expect(
- generateLine([
- {
- x: 0,
- y: 0,
- },
- {
- x: 10,
- y: 10,
- },
- ])
- ).toEqual('M0,0L10,10');
- expect(
- generateLine([
- {
- x: 900,
- y: 44,
- },
- {
- x: 5000,
- y: 359,
- },
- ])
- ).toEqual('M900,44L5000,359');
- });
-});
-
-describe('getLinePoints', () => {
- const link = {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
- };
- const nodePositions = {
- 1: {
- width: 72,
- height: 40,
- x: 36,
- y: 130,
- },
- 2: {
- width: 180,
- height: 60,
- x: 282,
- y: 40,
- },
- };
- test('returns the correct endpoints of the line', () => {
- expect(getLinePoints(link, nodePositions)).toEqual([
- { x: 109, y: 30 },
- { x: 281, y: -60 },
- ]);
- });
-});
-
-describe('getLinkOverlayPoints', () => {
- const link = {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
- };
- const nodePositions = {
- 1: {
- width: 72,
- height: 40,
- x: 36,
- y: 130,
- },
- 2: {
- width: 180,
- height: 60,
- x: 282,
- y: 40,
- },
- };
- test('returns the four points of the polygon that will act as the overlay for the link', () => {
- expect(getLinkOverlayPoints(link, nodePositions)).toEqual(
- '281,-50.970992003685446 109,39.02900799631457 109,20.97099200368546 281,-69.02900799631456'
- );
- });
-});
-
-describe('layoutGraph', () => {
- const nodes = [
- {
- id: 1,
- },
- {
- id: 2,
- },
- {
- id: 3,
- },
- {
- id: 4,
- },
- ];
- const links = [
- {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
- },
- {
- source: {
- id: 1,
- },
- target: {
- id: 4,
- },
- },
- {
- source: {
- id: 2,
- },
- target: {
- id: 3,
- },
- },
- {
- source: {
- id: 4,
- },
- target: {
- id: 3,
- },
- },
- ];
- test('returns the correct dimensions and positions for the nodes', () => {
- expect(layoutGraph(nodes, links)._nodes).toEqual({
- 1: { height: 40, label: '', width: 72, x: 36, y: 75 },
- 2: { height: 60, label: '', width: 180, x: 282, y: 30 },
- 3: { height: 60, label: '', width: 180, x: 582, y: 75 },
- 4: { height: 60, label: '', width: 180, x: 282, y: 120 },
- });
- });
-});
-
-describe('getTranslatePointsForZoom', () => {
- const svgBoundingClientRect = {
- x: 0,
- y: 56,
- width: 1680,
- height: 455,
- top: 56,
- right: 1680,
- bottom: 511,
- left: 0,
- };
- const currentScaleAndOffset = {
- k: 2,
- x: 0,
- y: 167.5,
- };
- const newScale = 1.9;
- test('returns the correct translation point', () => {
- expect(
- getTranslatePointsForZoom(
- svgBoundingClientRect,
- currentScaleAndOffset,
- newScale
- )
- ).toEqual([42, 170.5]);
- });
-});
diff --git a/awx/ui/src/components/Workflow/index.js b/awx/ui/src/components/Workflow/index.js
deleted file mode 100644
index c6f431cccbba..000000000000
--- a/awx/ui/src/components/Workflow/index.js
+++ /dev/null
@@ -1,9 +0,0 @@
-export { default as WorkflowActionTooltip } from './WorkflowActionTooltip';
-export { default as WorkflowActionTooltipItem } from './WorkflowActionTooltipItem';
-export { default as WorkflowHelp } from './WorkflowHelp';
-export { default as WorkflowLegend } from './WorkflowLegend';
-export { default as WorkflowLinkHelp } from './WorkflowLinkHelp';
-export { default as WorkflowNodeHelp } from './WorkflowNodeHelp';
-export { default as WorkflowNodeTypeLetter } from './WorkflowNodeTypeLetter';
-export { default as WorkflowStartNode } from './WorkflowStartNode';
-export { default as WorkflowTools } from './WorkflowTools';
diff --git a/awx/ui/src/components/Workflow/workflowReducer.js b/awx/ui/src/components/Workflow/workflowReducer.js
deleted file mode 100644
index be775281367a..000000000000
--- a/awx/ui/src/components/Workflow/workflowReducer.js
+++ /dev/null
@@ -1,719 +0,0 @@
-import { t } from '@lingui/macro';
-
-export function initReducer() {
- return {
- addLinkSourceNode: null,
- addLinkTargetNode: null,
- addNodeSource: null,
- addNodeTarget: null,
- addingLink: false,
- contentError: null,
- defaultOrganization: null,
- isLoading: true,
- linkToDelete: null,
- linkToEdit: null,
- links: [],
- nextNodeId: 0,
- nodePositions: null,
- nodes: [],
- nodeToDelete: null,
- nodeToEdit: null,
- nodeToView: null,
- showDeleteAllNodesModal: false,
- showLegend: false,
- showTools: false,
- showUnsavedChangesModal: false,
- unsavedChanges: false,
- };
-}
-
-export default function visualizerReducer(state, action) {
- switch (action.type) {
- case 'CREATE_LINK':
- return createLink(state, action.linkType);
- case 'CREATE_NODE':
- return createNode(state, action.node);
- case 'CANCEL_LINK':
- case 'CANCEL_LINK_MODAL':
- return cancelLink(state);
- case 'CANCEL_NODE_MODAL':
- return {
- ...state,
- addNodeSource: null,
- addNodeTarget: null,
- nodeToEdit: null,
- };
- case 'DELETE_ALL_NODES':
- return deleteAllNodes(state);
- case 'DELETE_LINK':
- return deleteLink(state);
- case 'DELETE_NODE':
- return deleteNode(state);
- case 'GENERATE_NODES_AND_LINKS':
- return generateNodesAndLinks(state, action.nodes);
- case 'RESET':
- return initReducer();
- case 'SELECT_SOURCE_FOR_LINKING':
- return selectSourceForLinking(state, action.node);
- case 'SET_ADD_LINK_TARGET_NODE':
- return {
- ...state,
- addLinkTargetNode: action.value,
- };
- case 'SET_CONTENT_ERROR':
- return {
- ...state,
- contentError: action.value,
- };
- case 'SET_DEFAULT_ORGANIZATION':
- return {
- ...state,
- defaultOrganization: action.value,
- };
- case 'SET_IS_LOADING':
- return {
- ...state,
- isLoading: action.value,
- };
- case 'SET_LINK_TO_DELETE':
- return {
- ...state,
- linkToDelete: action.value,
- };
- case 'SET_LINK_TO_EDIT':
- return {
- ...state,
- linkToEdit: action.value,
- };
- case 'SET_NODES':
- return {
- ...state,
- nodes: action.value,
- };
- case 'SET_NODE_POSITIONS':
- return {
- ...state,
- nodePositions: action.value,
- };
- case 'SET_NODE_TO_DELETE':
- return {
- ...state,
- nodeToDelete: action.value,
- };
- case 'SET_NODE_TO_EDIT':
- return {
- ...state,
- nodeToEdit: action.value,
- };
- case 'SET_NODE_TO_VIEW':
- return {
- ...state,
- nodeToView: action.value,
- };
- case 'SET_SHOW_DELETE_ALL_NODES_MODAL':
- return {
- ...state,
- showDeleteAllNodesModal: action.value,
- };
- case 'START_ADD_NODE':
- return {
- ...state,
- addNodeSource: action.sourceNodeId,
- addNodeTarget: action.targetNodeId || null,
- };
- case 'START_DELETE_LINK':
- return startDeleteLink(state, action.link);
- case 'TOGGLE_DELETE_ALL_NODES_MODAL':
- return toggleDeleteAllNodesModal(state);
- case 'TOGGLE_LEGEND':
- return toggleLegend(state);
- case 'TOGGLE_TOOLS':
- return toggleTools(state);
- case 'TOGGLE_UNSAVED_CHANGES_MODAL':
- return toggleUnsavedChangesModal(state);
- case 'UPDATE_LINK':
- return updateLink(state, action.linkType);
- case 'UPDATE_NODE':
- return updateNode(state, action.node);
- case 'REFRESH_NODE':
- return refreshNode(state, action.node);
- default:
- throw new Error(`Unrecognized action type: ${action.type}`);
- }
-}
-
-function createLink(state, linkType) {
- const { addLinkSourceNode, addLinkTargetNode, links, nodes } = state;
- const newLinks = [...links];
- const newNodes = [...nodes];
-
- newNodes.forEach((node) => {
- node.isInvalidLinkTarget = false;
- });
-
- newLinks.push({
- source: {
- id: addLinkSourceNode.id,
- },
- target: {
- id: addLinkTargetNode.id,
- },
- linkType,
- });
-
- newLinks.forEach((link, index) => {
- if (link.source.id === 1 && link.target.id === addLinkTargetNode.id) {
- newLinks.splice(index, 1);
- }
- });
-
- return {
- ...state,
- addLinkSourceNode: null,
- addLinkTargetNode: null,
- addingLink: false,
- linkToEdit: null,
- links: newLinks,
- nodes: newNodes,
- unsavedChanges: true,
- };
-}
-
-function createNode(state, node) {
- const { addNodeSource, addNodeTarget, links, nodes, nextNodeId } = state;
- const newNodes = [...nodes];
- const newLinks = [...links];
-
- newNodes.push({
- id: nextNodeId,
- fullUnifiedJobTemplate: node.nodeResource,
- isInvalidLinkTarget: false,
- promptValues: node.promptValues,
- all_parents_must_converge: node.all_parents_must_converge,
- identifier: node.identifier,
- });
-
- // Ensures that root nodes appear to always run
- // after "START"
- if (addNodeSource === 1) {
- node.linkType = 'always';
- }
-
- newLinks.push({
- source: {
- id: addNodeSource,
- },
- target: {
- id: nextNodeId,
- },
- linkType: node.linkType,
- });
-
- if (addNodeTarget) {
- newLinks.forEach((linkToCompare) => {
- if (
- linkToCompare.source.id === addNodeSource &&
- linkToCompare.target.id === addNodeTarget
- ) {
- linkToCompare.source = {
- id: nextNodeId,
- };
- }
- });
- }
-
- return {
- ...state,
- addNodeSource: null,
- addNodeTarget: null,
- links: newLinks,
- nextNodeId: nextNodeId + 1,
- nodes: newNodes,
- unsavedChanges: true,
- };
-}
-
-function cancelLink(state) {
- const { nodes } = state;
- const newNodes = [...nodes];
-
- newNodes.forEach((node) => {
- node.isInvalidLinkTarget = false;
- });
-
- return {
- ...state,
- addLinkSourceNode: null,
- addLinkTargetNode: null,
- addingLink: false,
- linkToEdit: null,
- nodes: newNodes,
- };
-}
-
-function deleteAllNodes(state) {
- const { nodes } = state;
- return {
- ...state,
- addLinkSourceNode: null,
- addLinkTargetNode: null,
- addingLink: false,
- links: [],
- nodes: nodes.map((node) => {
- if (node.id !== 1) {
- node.isDeleted = true;
- }
-
- return node;
- }),
- showDeleteAllNodesModal: false,
- unsavedChanges: true,
- };
-}
-
-function deleteLink(state) {
- const { links, linkToDelete } = state;
- const newLinks = [...links];
-
- for (let i = newLinks.length; i--; ) {
- const link = newLinks[i];
-
- if (
- link.source.id === linkToDelete.source.id &&
- link.target.id === linkToDelete.target.id
- ) {
- newLinks.splice(i, 1);
- }
- }
-
- if (!linkToDelete.isConvergenceLink) {
- // Add a new link from the start node to the orphaned node
- newLinks.push({
- source: {
- id: 1,
- },
- target: {
- id: linkToDelete.target.id,
- },
- linkType: 'always',
- });
- }
-
- return {
- ...state,
- links: newLinks,
- linkToDelete: null,
- unsavedChanges: true,
- };
-}
-
-function addLinksFromParentsToChildren(
- parents,
- children,
- newLinks,
- linkParentMapping
-) {
- parents.forEach((parentId) => {
- children.forEach((child) => {
- if (parentId === 1) {
- // We only want to create a link from the start node to this node if it
- // doesn't have any other parents
- if (linkParentMapping[child.id].length === 1) {
- newLinks.push({
- source: {
- id: parentId,
- },
- target: {
- id: child.id,
- },
- linkType: 'always',
- });
- }
- } else if (!linkParentMapping[child.id].includes(parentId)) {
- newLinks.push({
- source: {
- id: parentId,
- },
- target: {
- id: child.id,
- },
- linkType: child.linkType,
- });
- }
- });
- });
-}
-
-function removeLinksFromDeletedNode(
- nodeId,
- newLinks,
- linkParentMapping,
- children,
- parents
-) {
- for (let i = newLinks.length; i--; ) {
- const link = newLinks[i];
-
- if (!linkParentMapping[link.target.id]) {
- linkParentMapping[link.target.id] = [];
- }
-
- linkParentMapping[link.target.id].push(link.source.id);
-
- if (link.source.id === nodeId || link.target.id === nodeId) {
- if (link.source.id === nodeId) {
- children.push({
- id: link.target.id,
- linkType: link.linkType,
- });
- } else if (link.target.id === nodeId) {
- parents.push(link.source.id);
- }
- newLinks.splice(i, 1);
- }
- }
-}
-
-function deleteNode(state) {
- const { links, nodes, nodeToDelete } = state;
-
- const nodeId = nodeToDelete.id;
- const newNodes = [...nodes];
- const newLinks = [...links];
-
- newNodes.find((node) => node.id === nodeToDelete.id).isDeleted = true;
-
- // Update the links
- const parents = [];
- const children = [];
- const linkParentMapping = {};
-
- removeLinksFromDeletedNode(
- nodeId,
- newLinks,
- linkParentMapping,
- children,
- parents
- );
-
- addLinksFromParentsToChildren(parents, children, newLinks, linkParentMapping);
-
- return {
- ...state,
- links: newLinks,
- nodeToDelete: null,
- nodes: newNodes,
- unsavedChanges: true,
- };
-}
-
-function generateNodes(workflowNodes) {
- const allNodeIds = [];
- const chartNodeIdToIndexMapping = {};
- const nodeIdToChartNodeIdMapping = {};
- let nodeIdCounter = 2;
- const arrayOfNodesForChart = [
- {
- id: 1,
- fullUnifiedJobTemplate: {
- name: t`START`,
- },
- },
- ];
- workflowNodes.forEach((node) => {
- node.workflowMakerNodeId = nodeIdCounter;
-
- const nodeObj = {
- id: nodeIdCounter,
- originalNodeObject: node,
- };
-
- if (
- node.summary_fields?.unified_job_template?.unified_job_type ===
- 'workflow_approval'
- ) {
- nodeObj.fullUnifiedJobTemplate = {
- ...node.summary_fields.unified_job_template,
- type: 'workflow_approval_template',
- };
- }
-
- arrayOfNodesForChart.push(nodeObj);
- allNodeIds.push(node.id);
- nodeIdToChartNodeIdMapping[node.id] = node.workflowMakerNodeId;
- chartNodeIdToIndexMapping[nodeIdCounter] = nodeIdCounter - 1;
- nodeIdCounter++;
- });
-
- return [
- arrayOfNodesForChart,
- allNodeIds,
- nodeIdToChartNodeIdMapping,
- chartNodeIdToIndexMapping,
- nodeIdCounter,
- ];
-}
-
-function generateLinks(
- workflowNodes,
- chartNodeIdToIndexMapping,
- nodeIdToChartNodeIdMapping,
- arrayOfNodesForChart
-) {
- const arrayOfLinksForChart = [];
- const nonRootNodeIds = [];
- workflowNodes.forEach((node) => {
- const sourceIndex = chartNodeIdToIndexMapping[node.workflowMakerNodeId];
- node.success_nodes.forEach((nodeId) => {
- const targetIndex =
- chartNodeIdToIndexMapping[nodeIdToChartNodeIdMapping[nodeId]];
- arrayOfLinksForChart.push({
- source: arrayOfNodesForChart[sourceIndex],
- target: arrayOfNodesForChart[targetIndex],
- linkType: 'success',
- });
- nonRootNodeIds.push(nodeId);
- });
- node.failure_nodes.forEach((nodeId) => {
- const targetIndex =
- chartNodeIdToIndexMapping[nodeIdToChartNodeIdMapping[nodeId]];
- arrayOfLinksForChart.push({
- source: arrayOfNodesForChart[sourceIndex],
- target: arrayOfNodesForChart[targetIndex],
- linkType: 'failure',
- });
- nonRootNodeIds.push(nodeId);
- });
- node.always_nodes.forEach((nodeId) => {
- const targetIndex =
- chartNodeIdToIndexMapping[nodeIdToChartNodeIdMapping[nodeId]];
- arrayOfLinksForChart.push({
- source: arrayOfNodesForChart[sourceIndex],
- target: arrayOfNodesForChart[targetIndex],
- linkType: 'always',
- });
- nonRootNodeIds.push(nodeId);
- });
- });
-
- return [arrayOfLinksForChart, nonRootNodeIds];
-}
-
-function generateNodesAndLinks(state, workflowNodes) {
- const [
- arrayOfNodesForChart,
- allNodeIds,
- nodeIdToChartNodeIdMapping,
- chartNodeIdToIndexMapping,
- nodeIdCounter,
- ] = generateNodes(workflowNodes);
- const [arrayOfLinksForChart, nonRootNodeIds] = generateLinks(
- workflowNodes,
- chartNodeIdToIndexMapping,
- nodeIdToChartNodeIdMapping,
- arrayOfNodesForChart
- );
-
- const uniqueNonRootNodeIds = Array.from(new Set(nonRootNodeIds));
-
- const rootNodes = allNodeIds.filter(
- (nodeId) => !uniqueNonRootNodeIds.includes(nodeId)
- );
-
- rootNodes.forEach((rootNodeId) => {
- const targetIndex =
- chartNodeIdToIndexMapping[nodeIdToChartNodeIdMapping[rootNodeId]];
- arrayOfLinksForChart.push({
- source: arrayOfNodesForChart[0],
- target: arrayOfNodesForChart[targetIndex],
- linkType: 'always',
- });
- });
-
- return {
- ...state,
- links: arrayOfLinksForChart,
- nodes: arrayOfNodesForChart,
- nextNodeId: nodeIdCounter,
- };
-}
-
-function selectSourceForLinking(state, sourceNode) {
- const { links, nodes } = state;
- const newNodes = [...nodes];
- const parentMap = {};
- const invalidLinkTargetIds = [];
- // Find and mark any ancestors as disabled to prevent cycles
- links.forEach((link) => {
- // id=1 is our artificial root node so we don't care about that
- if (link.source.id === 1) {
- return;
- }
- if (link.source.id === sourceNode.id) {
- // Disables direct children from the add link process
- invalidLinkTargetIds.push(link.target.id);
- }
- if (!parentMap[link.target.id]) {
- parentMap[link.target.id] = {
- parents: [],
- traversed: false,
- };
- }
- parentMap[link.target.id].parents.push(link.source.id);
- });
-
- const getAncestors = (id) => {
- if (parentMap[id] && !parentMap[id].traversed) {
- parentMap[id].parents.forEach((parentId) => {
- invalidLinkTargetIds.push(parentId);
- getAncestors(parentId);
- });
- parentMap[id].traversed = true;
- }
- };
-
- getAncestors(sourceNode.id);
-
- // Filter out the duplicates
- invalidLinkTargetIds
- .filter((element, index, array) => index === array.indexOf(element))
- .forEach((ancestorId) => {
- newNodes.forEach((node) => {
- if (node.id === ancestorId) {
- node.isInvalidLinkTarget = true;
- }
- });
- });
-
- return {
- ...state,
- addLinkSourceNode: sourceNode,
- addingLink: true,
- nodes: newNodes,
- };
-}
-
-function startDeleteLink(state, link) {
- const { links } = state;
- const parentMap = {};
- links.forEach((existingLink) => {
- if (!parentMap[existingLink.target.id]) {
- parentMap[existingLink.target.id] = [];
- }
- parentMap[existingLink.target.id].push(existingLink.source.id);
- });
-
- link.isConvergenceLink = parentMap[link.target.id].length > 1;
-
- return {
- ...state,
- linkToDelete: link,
- };
-}
-
-function toggleDeleteAllNodesModal(state) {
- const { showDeleteAllNodesModal } = state;
- return {
- ...state,
- showDeleteAllNodesModal: !showDeleteAllNodesModal,
- };
-}
-
-function toggleLegend(state) {
- const { showLegend } = state;
- return {
- ...state,
- showLegend: !showLegend,
- };
-}
-
-function toggleTools(state) {
- const { showTools } = state;
- return {
- ...state,
- showTools: !showTools,
- };
-}
-
-function toggleUnsavedChangesModal(state) {
- const { showUnsavedChangesModal } = state;
- return {
- ...state,
- showUnsavedChangesModal: !showUnsavedChangesModal,
- };
-}
-
-function updateLink(state, linkType) {
- const { linkToEdit, links } = state;
- const newLinks = [...links];
-
- newLinks.forEach((link) => {
- if (
- link.source.id === linkToEdit.source.id &&
- link.target.id === linkToEdit.target.id
- ) {
- link.linkType = linkType;
- }
- });
-
- return {
- ...state,
- linkToEdit: null,
- links: newLinks,
- unsavedChanges: true,
- };
-}
-
-function updateNode(state, editedNode) {
- const { nodeToEdit, nodes } = state;
- const {
- nodeResource,
- launchConfig,
- promptValues,
- all_parents_must_converge,
- identifier,
- } = editedNode;
- const newNodes = [...nodes];
-
- const matchingNode = newNodes.find((node) => node.id === nodeToEdit.id);
- matchingNode.all_parents_must_converge = all_parents_must_converge;
- matchingNode.fullUnifiedJobTemplate = nodeResource;
- matchingNode.isEdited = true;
- matchingNode.launchConfig = launchConfig;
- matchingNode.identifier = identifier;
-
- if (promptValues) {
- matchingNode.promptValues = promptValues;
- } else {
- delete matchingNode.promptValues;
- }
-
- return {
- ...state,
- nodeToEdit: null,
- nodes: newNodes,
- unsavedChanges: true,
- };
-}
-
-function refreshNode(state, refreshedNode) {
- const { nodeToView, nodes } = state;
- const newNodes = [...nodes];
-
- const matchingNode = newNodes.find((node) => node.id === nodeToView.id);
-
- if (refreshedNode.fullUnifiedJobTemplate) {
- matchingNode.fullUnifiedJobTemplate = refreshedNode.fullUnifiedJobTemplate;
- }
-
- if (refreshedNode.originalNodeCredentials) {
- matchingNode.originalNodeCredentials =
- refreshedNode.originalNodeCredentials;
- }
-
- return {
- ...state,
- nodes: newNodes,
- nodeToView: matchingNode,
- };
-}
diff --git a/awx/ui/src/components/Workflow/workflowReducer.test.js b/awx/ui/src/components/Workflow/workflowReducer.test.js
deleted file mode 100644
index e241d76bff71..000000000000
--- a/awx/ui/src/components/Workflow/workflowReducer.test.js
+++ /dev/null
@@ -1,1743 +0,0 @@
-import workflowReducer, { initReducer } from './workflowReducer';
-
-const defaultState = {
- addLinkSourceNode: null,
- addLinkTargetNode: null,
- addNodeSource: null,
- addNodeTarget: null,
- addingLink: false,
- contentError: null,
- defaultOrganization: null,
- isLoading: true,
- linkToDelete: null,
- linkToEdit: null,
- links: [],
- nextNodeId: 0,
- nodePositions: null,
- nodes: [],
- nodeToDelete: null,
- nodeToEdit: null,
- nodeToView: null,
- showDeleteAllNodesModal: false,
- showLegend: false,
- showTools: false,
- showUnsavedChangesModal: false,
- unsavedChanges: false,
-};
-
-describe('Workflow reducer', () => {
- describe('CREATE_LINK', () => {
- it('should clear the isInvalidLinkTarget flag from all nodes and add new link', () => {
- const state = {
- ...defaultState,
- addLinkSourceNode: { id: 2 },
- addLinkTargetNode: { id: 4 },
- addingLink: true,
- isLoading: false,
- links: [
- {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 2,
- },
- target: {
- id: 3,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 3,
- },
- target: {
- id: 4,
- },
- linkType: 'always',
- },
- ],
- nextNodeId: 5,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: true,
- },
- {
- id: 2,
- isInvalidLinkTarget: true,
- },
- {
- id: 3,
- isInvalidLinkTarget: true,
- },
- {
- id: 4,
- isInvalidLinkTarget: false,
- },
- ],
- };
- const result = workflowReducer(state, {
- type: 'CREATE_LINK',
- linkType: 'always',
- });
- expect(result).toEqual({
- ...defaultState,
- isLoading: false,
- links: [
- {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 2,
- },
- target: {
- id: 3,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 3,
- },
- target: {
- id: 4,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 2,
- },
- target: {
- id: 4,
- },
- linkType: 'always',
- },
- ],
- nextNodeId: 5,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: false,
- },
- {
- id: 2,
- isInvalidLinkTarget: false,
- },
- {
- id: 3,
- isInvalidLinkTarget: false,
- },
- {
- id: 4,
- isInvalidLinkTarget: false,
- },
- ],
- unsavedChanges: true,
- });
- });
- });
- describe('CREATE_NODE', () => {
- it('should add new node and link from the end of the source node when no target node present', () => {
- const state = {
- ...defaultState,
- addNodeSource: 1,
- isLoading: false,
- nextNodeId: 2,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: false,
- },
- ],
- };
- const result = workflowReducer(state, {
- type: 'CREATE_NODE',
- node: {
- linkType: 'always',
- nodeResource: {
- id: 7000,
- name: 'Foo JT',
- },
- },
- });
- expect(result).toEqual({
- ...defaultState,
- isLoading: false,
- links: [
- {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
- linkType: 'always',
- },
- ],
- nextNodeId: 3,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: false,
- },
- {
- id: 2,
- isInvalidLinkTarget: false,
- fullUnifiedJobTemplate: {
- id: 7000,
- name: 'Foo JT',
- },
- },
- ],
- unsavedChanges: true,
- });
- });
- it('should add new node and link between the source and target nodes when target node present', () => {
- const state = {
- ...defaultState,
- addNodeSource: 1,
- addNodeTarget: 2,
- isLoading: false,
- links: [
- {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
- linkType: 'always',
- },
- ],
- nextNodeId: 3,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: false,
- },
- {
- id: 2,
- isInvalidLinkTarget: false,
- },
- ],
- };
- const result = workflowReducer(state, {
- type: 'CREATE_NODE',
- node: {
- linkType: 'always',
- nodeResource: {
- id: 7000,
- name: 'Foo JT',
- },
- },
- });
- expect(result).toEqual({
- ...defaultState,
- isLoading: false,
- links: [
- {
- source: {
- id: 3,
- },
- target: {
- id: 2,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 1,
- },
- target: {
- id: 3,
- },
- linkType: 'always',
- },
- ],
- nextNodeId: 4,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: false,
- },
- {
- id: 2,
- isInvalidLinkTarget: false,
- },
- {
- id: 3,
- isInvalidLinkTarget: false,
- fullUnifiedJobTemplate: {
- id: 7000,
- name: 'Foo JT',
- },
- },
- ],
- unsavedChanges: true,
- });
- });
- });
- describe('CANCEL_LINK/CANCEL_LINK_MODAL', () => {
- it('should wipe flags that track the process of adding or editing a link', () => {
- const state = {
- ...defaultState,
- addLinkSourceNode: { id: 2 },
- addLinkTargetNode: { id: 4 },
- addingLink: true,
- isLoading: false,
- links: [
- {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 2,
- },
- target: {
- id: 3,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 3,
- },
- target: {
- id: 4,
- },
- linkType: 'always',
- },
- ],
- nextNodeId: 5,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: true,
- },
- {
- id: 2,
- isInvalidLinkTarget: true,
- },
- {
- id: 3,
- isInvalidLinkTarget: true,
- },
- {
- id: 4,
- isInvalidLinkTarget: false,
- },
- ],
- unsavedChanges: false,
- };
- const result = workflowReducer(state, {
- type: 'CANCEL_LINK',
- });
- expect(result).toEqual({
- ...defaultState,
- isLoading: false,
- links: [
- {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 2,
- },
- target: {
- id: 3,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 3,
- },
- target: {
- id: 4,
- },
- linkType: 'always',
- },
- ],
- nextNodeId: 5,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: false,
- },
- {
- id: 2,
- isInvalidLinkTarget: false,
- },
- {
- id: 3,
- isInvalidLinkTarget: false,
- },
- {
- id: 4,
- isInvalidLinkTarget: false,
- },
- ],
- });
- });
- });
- describe('CANCEL_NODE_MODAL', () => {
- it('should wipe the flags that track the process of adding a node', () => {
- const state = {
- ...defaultState,
- addNodeSource: { id: 1 },
- isLoading: false,
- nextNodeId: 5,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: false,
- },
- ],
- };
- const result = workflowReducer(state, {
- type: 'CANCEL_NODE_MODAL',
- });
- expect(result).toEqual({
- ...defaultState,
- isLoading: false,
- nextNodeId: 5,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: false,
- },
- ],
- });
- });
- it('should wipe the flags that track the process of editing a node', () => {
- const state = {
- ...defaultState,
- isLoading: false,
- nextNodeId: 5,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: false,
- },
- {
- id: 2,
- isInvalidLinkTarget: false,
- },
- ],
- nodeToEdit: {
- id: 2,
- },
- };
- const result = workflowReducer(state, {
- type: 'CANCEL_NODE_MODAL',
- });
- expect(result).toEqual({
- ...defaultState,
- isLoading: false,
- nextNodeId: 5,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: false,
- },
- {
- id: 2,
- isInvalidLinkTarget: false,
- },
- ],
- });
- });
- });
- describe('DELETE_ALL_NODES', () => {
- it('should mark all the non-start nodes as deleted and clear out the links', () => {
- const state = {
- ...defaultState,
- addLinkSourceNode: { id: 2 },
- addLinkTargetNode: { id: 4 },
- addingLink: true,
- isLoading: false,
- links: [
- {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 2,
- },
- target: {
- id: 3,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 3,
- },
- target: {
- id: 4,
- },
- linkType: 'always',
- },
- ],
- nextNodeId: 5,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: false,
- },
- {
- id: 2,
- isInvalidLinkTarget: false,
- },
- {
- id: 3,
- isInvalidLinkTarget: false,
- },
- {
- id: 4,
- isInvalidLinkTarget: false,
- },
- ],
- };
- const result = workflowReducer(state, {
- type: 'DELETE_ALL_NODES',
- });
- expect(result).toEqual({
- ...defaultState,
- isLoading: false,
- nextNodeId: 5,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: false,
- },
- {
- id: 2,
- isInvalidLinkTarget: false,
- isDeleted: true,
- },
- {
- id: 3,
- isInvalidLinkTarget: false,
- isDeleted: true,
- },
- {
- id: 4,
- isInvalidLinkTarget: false,
- isDeleted: true,
- },
- ],
- unsavedChanges: true,
- });
- });
- });
- describe('DELETE_LINK', () => {
- it('should remove the link and connect the remaining node to start if orphaned', () => {
- const state = {
- ...defaultState,
- isLoading: false,
- links: [
- {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 2,
- },
- target: {
- id: 3,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 3,
- },
- target: {
- id: 4,
- },
- linkType: 'always',
- },
- ],
- linkToDelete: {
- source: {
- id: 3,
- },
- target: {
- id: 4,
- },
- linkType: 'always',
- },
- nextNodeId: 5,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: false,
- },
- {
- id: 2,
- isInvalidLinkTarget: false,
- },
- {
- id: 3,
- isInvalidLinkTarget: false,
- },
- {
- id: 4,
- isInvalidLinkTarget: false,
- },
- ],
- };
- const result = workflowReducer(state, {
- type: 'DELETE_LINK',
- });
- expect(result).toEqual({
- ...defaultState,
- isLoading: false,
- links: [
- {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 2,
- },
- target: {
- id: 3,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 1,
- },
- target: {
- id: 4,
- },
- linkType: 'always',
- },
- ],
- nextNodeId: 5,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: false,
- },
- {
- id: 2,
- isInvalidLinkTarget: false,
- },
- {
- id: 3,
- isInvalidLinkTarget: false,
- },
- {
- id: 4,
- isInvalidLinkTarget: false,
- },
- ],
- unsavedChanges: true,
- });
- });
- });
- describe('DELETE_NODE', () => {
- it('should remove the mark the node as deleted and re-link any orphaned nodes', () => {
- const state = {
- ...defaultState,
- isLoading: false,
- links: [
- {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 2,
- },
- target: {
- id: 3,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 3,
- },
- target: {
- id: 4,
- },
- linkType: 'always',
- },
- ],
- nextNodeId: 5,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: false,
- },
- {
- id: 2,
- isInvalidLinkTarget: false,
- },
- {
- id: 3,
- isInvalidLinkTarget: false,
- },
- {
- id: 4,
- isInvalidLinkTarget: false,
- },
- ],
- nodeToDelete: {
- id: 3,
- },
- };
- const result = workflowReducer(state, {
- type: 'DELETE_NODE',
- });
- expect(result).toEqual({
- ...defaultState,
- isLoading: false,
- links: [
- {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 2,
- },
- target: {
- id: 4,
- },
- linkType: 'always',
- },
- ],
- nextNodeId: 5,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: false,
- },
- {
- id: 2,
- isInvalidLinkTarget: false,
- },
- {
- id: 3,
- isInvalidLinkTarget: false,
- isDeleted: true,
- },
- {
- id: 4,
- isInvalidLinkTarget: false,
- },
- ],
- unsavedChanges: true,
- });
- });
- });
- describe('GENERATE_NODES_AND_LINKS', () => {
- it('should generate the correct node and link arrays', () => {
- const result = workflowReducer(defaultState, {
- type: 'GENERATE_NODES_AND_LINKS',
- nodes: [
- {
- id: 1,
- success_nodes: [3],
- failure_nodes: [],
- always_nodes: [2],
- summary_fields: {
- unified_job_template: {
- id: 1,
- name: 'JT 1',
- },
- },
- },
- {
- id: 2,
- success_nodes: [],
- failure_nodes: [],
- always_nodes: [],
- summary_fields: {
- unified_job_template: {
- id: 2,
- name: 'JT 2',
- },
- },
- },
- {
- id: 3,
- success_nodes: [],
- failure_nodes: [],
- always_nodes: [],
- summary_fields: {
- unified_job_template: {
- id: 3,
- name: 'JT 3',
- },
- },
- },
- {
- id: 4,
- success_nodes: [],
- failure_nodes: [],
- always_nodes: [2],
- summary_fields: {
- unified_job_template: {
- id: 4,
- name: 'JT 4',
- },
- },
- },
- ],
- i18n: {
- _: () => {},
- },
- });
- expect(result).toEqual({
- ...defaultState,
- links: [
- {
- linkType: 'success',
- source: {
- id: 2,
- originalNodeObject: {
- always_nodes: [2],
- failure_nodes: [],
- id: 1,
- success_nodes: [3],
- summary_fields: {
- unified_job_template: {
- id: 1,
- name: 'JT 1',
- },
- },
- workflowMakerNodeId: 2,
- },
- },
- target: {
- id: 4,
- originalNodeObject: {
- always_nodes: [],
- failure_nodes: [],
- id: 3,
- success_nodes: [],
- summary_fields: {
- unified_job_template: {
- id: 3,
- name: 'JT 3',
- },
- },
- workflowMakerNodeId: 4,
- },
- },
- },
- {
- linkType: 'always',
- source: {
- id: 2,
- originalNodeObject: {
- always_nodes: [2],
- failure_nodes: [],
- id: 1,
- success_nodes: [3],
- summary_fields: {
- unified_job_template: {
- id: 1,
- name: 'JT 1',
- },
- },
- workflowMakerNodeId: 2,
- },
- },
- target: {
- id: 3,
- originalNodeObject: {
- always_nodes: [],
- failure_nodes: [],
- id: 2,
- success_nodes: [],
- summary_fields: {
- unified_job_template: {
- id: 2,
- name: 'JT 2',
- },
- },
- workflowMakerNodeId: 3,
- },
- },
- },
- {
- linkType: 'always',
- source: {
- id: 5,
- originalNodeObject: {
- always_nodes: [2],
- failure_nodes: [],
- id: 4,
- success_nodes: [],
- summary_fields: {
- unified_job_template: {
- id: 4,
- name: 'JT 4',
- },
- },
- workflowMakerNodeId: 5,
- },
- },
- target: {
- id: 3,
- originalNodeObject: {
- always_nodes: [],
- failure_nodes: [],
- id: 2,
- success_nodes: [],
- summary_fields: {
- unified_job_template: {
- id: 2,
- name: 'JT 2',
- },
- },
- workflowMakerNodeId: 3,
- },
- },
- },
- {
- linkType: 'always',
- source: {
- id: 1,
- fullUnifiedJobTemplate: {
- name: 'START',
- },
- },
- target: {
- id: 2,
- originalNodeObject: {
- always_nodes: [2],
- failure_nodes: [],
- id: 1,
- success_nodes: [3],
- summary_fields: {
- unified_job_template: {
- id: 1,
- name: 'JT 1',
- },
- },
- workflowMakerNodeId: 2,
- },
- },
- },
- {
- linkType: 'always',
- source: {
- id: 1,
- fullUnifiedJobTemplate: {
- name: 'START',
- },
- },
- target: {
- id: 5,
- originalNodeObject: {
- always_nodes: [2],
- failure_nodes: [],
- id: 4,
- success_nodes: [],
- summary_fields: {
- unified_job_template: {
- id: 4,
- name: 'JT 4',
- },
- },
- workflowMakerNodeId: 5,
- },
- },
- },
- ],
- nextNodeId: 6,
- nodes: [
- {
- id: 1,
- fullUnifiedJobTemplate: {
- name: 'START',
- },
- },
- {
- id: 2,
- originalNodeObject: {
- always_nodes: [2],
- failure_nodes: [],
- id: 1,
- success_nodes: [3],
- summary_fields: {
- unified_job_template: {
- id: 1,
- name: 'JT 1',
- },
- },
- workflowMakerNodeId: 2,
- },
- },
- {
- id: 3,
- originalNodeObject: {
- always_nodes: [],
- failure_nodes: [],
- id: 2,
- success_nodes: [],
- summary_fields: {
- unified_job_template: {
- id: 2,
- name: 'JT 2',
- },
- },
- workflowMakerNodeId: 3,
- },
- },
- {
- id: 4,
- originalNodeObject: {
- always_nodes: [],
- failure_nodes: [],
- id: 3,
- success_nodes: [],
- summary_fields: {
- unified_job_template: {
- id: 3,
- name: 'JT 3',
- },
- },
- workflowMakerNodeId: 4,
- },
- },
- {
- id: 5,
- originalNodeObject: {
- always_nodes: [2],
- failure_nodes: [],
- id: 4,
- success_nodes: [],
- summary_fields: {
- unified_job_template: {
- id: 4,
- name: 'JT 4',
- },
- },
- workflowMakerNodeId: 5,
- },
- },
- ],
- });
- });
- });
- describe('RESET', () => {
- it('should reset the state back to default values', () => {
- const state = {
- ...defaultState,
- isLoading: false,
- links: [
- {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
- },
- ],
- nextNodeId: 3,
- nodes: [
- {
- id: 1,
- },
- {
- id: 2,
- },
- ],
- };
- const result = workflowReducer(state, {
- type: 'RESET',
- });
- expect(result).toEqual(defaultState);
- });
- });
- describe('SELECT_SOURCE_FOR_LINKING', () => {
- it('should set source node and mark invalid target nodes', () => {
- const sourceNode = {
- id: 3,
- isInvalidLinkTarget: false,
- };
- const state = {
- ...defaultState,
- isLoading: false,
- links: [
- {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 1,
- },
- target: {
- id: 3,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 2,
- },
- target: {
- id: 4,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 3,
- },
- target: {
- id: 5,
- },
- linkType: 'always',
- },
- ],
- nextNodeId: 6,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: false,
- },
- {
- id: 2,
- isInvalidLinkTarget: false,
- },
- sourceNode,
- {
- id: 4,
- isInvalidLinkTarget: false,
- },
- {
- id: 5,
- isInvalidLinkTarget: false,
- },
- ],
- };
- const result = workflowReducer(state, {
- type: 'SELECT_SOURCE_FOR_LINKING',
- node: sourceNode,
- });
- expect(result).toEqual({
- ...defaultState,
- addLinkSourceNode: sourceNode,
- addingLink: true,
- isLoading: false,
- links: [
- {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 1,
- },
- target: {
- id: 3,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 2,
- },
- target: {
- id: 4,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 3,
- },
- target: {
- id: 5,
- },
- linkType: 'always',
- },
- ],
- nextNodeId: 6,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: false,
- },
- {
- id: 2,
- isInvalidLinkTarget: false,
- },
- sourceNode,
- {
- id: 4,
- isInvalidLinkTarget: false,
- },
- {
- id: 5,
- isInvalidLinkTarget: true,
- },
- ],
- });
- });
- });
- describe('SET_ADD_LINK_TARGET_NODE', () => {
- it('should set the state variable', () => {
- const result = workflowReducer(defaultState, {
- type: 'SET_ADD_LINK_TARGET_NODE',
- value: {
- id: 2,
- },
- });
- expect(result).toEqual({
- ...defaultState,
- addLinkTargetNode: {
- id: 2,
- },
- });
- });
- });
- describe('SET_CONTENT_ERROR', () => {
- it('should set the state variable', () => {
- const result = workflowReducer(defaultState, {
- type: 'SET_CONTENT_ERROR',
- value: new Error('Test Error'),
- });
- expect(result).toEqual({
- ...defaultState,
- contentError: new Error('Test Error'),
- });
- });
- });
- describe('SET_DEFAULT_ORGANIZATION', () => {
- it('should set the state variable', () => {
- const result = workflowReducer(defaultState, {
- type: 'SET_DEFAULT_ORGANIZATION',
- value: 1,
- });
- expect(result).toEqual({
- ...defaultState,
- defaultOrganization: 1,
- });
- });
- });
- describe('SET_IS_LOADING', () => {
- it('should set the state variable', () => {
- const result = workflowReducer(defaultState, {
- type: 'SET_IS_LOADING',
- value: false,
- });
- expect(result).toEqual({
- ...defaultState,
- isLoading: false,
- });
- });
- });
- describe('SET_LINK_TO_DELETE', () => {
- it('should set the state variable', () => {
- const linkToDelete = {
- source: {
- id: 2,
- },
- target: {
- id: 3,
- },
- linkType: 'always',
- };
- const result = workflowReducer(defaultState, {
- type: 'SET_LINK_TO_DELETE',
- value: linkToDelete,
- });
- expect(result).toEqual({
- ...defaultState,
- linkToDelete,
- });
- });
- });
- describe('SET_LINK_TO_EDIT', () => {
- it('should set the state variable', () => {
- const linkToEdit = {
- source: {
- id: 2,
- },
- target: {
- id: 3,
- },
- linkType: 'always',
- };
- const result = workflowReducer(defaultState, {
- type: 'SET_LINK_TO_EDIT',
- value: linkToEdit,
- });
- expect(result).toEqual({
- ...defaultState,
- linkToEdit,
- });
- });
- });
- describe('SET_NODE_POSITIONS', () => {
- it('should set the state variable', () => {
- const nodePositions = {
- label: '',
- width: 72,
- height: 40,
- x: 36,
- y: 20,
- };
- const result = workflowReducer(defaultState, {
- type: 'SET_NODE_POSITIONS',
- value: nodePositions,
- });
- expect(result).toEqual({
- ...defaultState,
- nodePositions,
- });
- });
- });
- describe('SET_NODE_TO_DELETE', () => {
- it('should set the state variable', () => {
- const nodeToDelete = {
- id: 2,
- };
- const result = workflowReducer(defaultState, {
- type: 'SET_NODE_TO_DELETE',
- value: nodeToDelete,
- });
- expect(result).toEqual({
- ...defaultState,
- nodeToDelete,
- });
- });
- });
- describe('SET_NODE_TO_EDIT', () => {
- it('should set the state variable', () => {
- const nodeToEdit = {
- id: 2,
- };
- const result = workflowReducer(defaultState, {
- type: 'SET_NODE_TO_EDIT',
- value: nodeToEdit,
- });
- expect(result).toEqual({
- ...defaultState,
- nodeToEdit,
- });
- });
- });
- describe('SET_NODE_TO_VIEW', () => {
- it('should set the state variable', () => {
- const nodeToView = {
- id: 2,
- };
- const result = workflowReducer(defaultState, {
- type: 'SET_NODE_TO_VIEW',
- value: nodeToView,
- });
- expect(result).toEqual({
- ...defaultState,
- nodeToView,
- });
- });
- });
- describe('START_ADD_NODE', () => {
- it('should set the source/target node ids to state', () => {
- const result = workflowReducer(defaultState, {
- type: 'START_ADD_NODE',
- sourceNodeId: 44,
- targetNodeId: 9000,
- });
- expect(result).toEqual({
- ...defaultState,
- addNodeSource: 44,
- addNodeTarget: 9000,
- });
- });
- });
- describe('START_DELETE_LINK', () => {
- it('should update the link to indicate whether it is a convergence link and update the state variable', () => {
- const state = {
- ...defaultState,
- isLoading: false,
- links: [
- {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 1,
- },
- target: {
- id: 3,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 2,
- },
- target: {
- id: 4,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 3,
- },
- target: {
- id: 4,
- },
- linkType: 'always',
- },
- ],
- nextNodeId: 5,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: false,
- },
- {
- id: 2,
- isInvalidLinkTarget: false,
- },
- {
- id: 3,
- isInvalidLinkTarget: false,
- },
- {
- id: 4,
- isInvalidLinkTarget: false,
- },
- ],
- };
- const result = workflowReducer(state, {
- type: 'START_DELETE_LINK',
- link: {
- source: {
- id: 3,
- },
- target: {
- id: 4,
- },
- linkType: 'always',
- },
- });
- expect(result).toEqual({
- ...state,
- linkToDelete: {
- source: {
- id: 3,
- },
- target: {
- id: 4,
- },
- isConvergenceLink: true,
- linkType: 'always',
- },
- });
- });
- });
- describe('TOGGLE_DELETE_ALL_NODES_MODAL', () => {
- it('should toggle the show delete all nodes modal flag', () => {
- const firstToggleState = workflowReducer(defaultState, {
- type: 'TOGGLE_DELETE_ALL_NODES_MODAL',
- });
- expect(firstToggleState).toEqual({
- ...defaultState,
- showDeleteAllNodesModal: true,
- });
- const secondToggleState = workflowReducer(firstToggleState, {
- type: 'TOGGLE_DELETE_ALL_NODES_MODAL',
- });
- expect(secondToggleState).toEqual(defaultState);
- });
- });
- describe('TOGGLE_LEGEND', () => {
- it('should toggle the show legend flag', () => {
- const firstToggleState = workflowReducer(defaultState, {
- type: 'TOGGLE_LEGEND',
- });
- expect(firstToggleState).toEqual({
- ...defaultState,
- showLegend: true,
- });
- const secondToggleState = workflowReducer(firstToggleState, {
- type: 'TOGGLE_LEGEND',
- });
- expect(secondToggleState).toEqual(defaultState);
- });
- });
- describe('TOGGLE_TOOLS', () => {
- it('should toggle the show legend flag', () => {
- const firstToggleState = workflowReducer(defaultState, {
- type: 'TOGGLE_TOOLS',
- });
- expect(firstToggleState).toEqual({
- ...defaultState,
- showTools: true,
- });
- const secondToggleState = workflowReducer(firstToggleState, {
- type: 'TOGGLE_TOOLS',
- });
- expect(secondToggleState).toEqual(defaultState);
- });
- });
- describe('TOGGLE_UNSAVED_CHANGES_MODAL', () => {
- it('should toggle the unsaved changes modal flag', () => {
- const firstToggleState = workflowReducer(defaultState, {
- type: 'TOGGLE_UNSAVED_CHANGES_MODAL',
- });
- expect(firstToggleState).toEqual({
- ...defaultState,
- showUnsavedChangesModal: true,
- });
- const secondToggleState = workflowReducer(firstToggleState, {
- type: 'TOGGLE_UNSAVED_CHANGES_MODAL',
- });
- expect(secondToggleState).toEqual(defaultState);
- });
- });
- describe('UPDATE_LINK', () => {
- it('should update the link type', () => {
- const state = {
- ...defaultState,
- isLoading: false,
- links: [
- {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 2,
- },
- target: {
- id: 3,
- },
- linkType: 'always',
- },
- ],
- linkToEdit: {
- source: {
- id: 2,
- },
- target: {
- id: 3,
- },
- linkType: 'always',
- },
- nextNodeId: 4,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: false,
- },
- {
- id: 2,
- isInvalidLinkTarget: false,
- },
- {
- id: 3,
- isInvalidLinkTarget: false,
- },
- ],
- };
- const firstToggleState = workflowReducer(state, {
- type: 'UPDATE_LINK',
- linkType: 'success',
- });
- expect(firstToggleState).toEqual({
- ...state,
- links: [
- {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 2,
- },
- target: {
- id: 3,
- },
- linkType: 'success',
- },
- ],
- linkToEdit: null,
- unsavedChanges: true,
- });
- });
- });
- describe('UPDATE_NODE', () => {
- it('should update the node', () => {
- const state = {
- ...defaultState,
- isLoading: false,
- links: [
- {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
- linkType: 'always',
- },
- ],
- nextNodeId: 3,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: false,
- },
- {
- id: 2,
- isEdited: false,
- isInvalidLinkTarget: false,
- fullUnifiedJobTemplate: {
- id: 703,
- name: 'Test JT',
- type: 'job_template',
- },
- },
- ],
- nodeToEdit: {
- id: 2,
- isEdited: false,
- isInvalidLinkTarget: false,
- fullUnifiedJobTemplate: {
- id: 703,
- name: 'Test JT',
- type: 'job_template',
- },
- },
- };
- const firstToggleState = workflowReducer(state, {
- type: 'UPDATE_NODE',
- node: {
- nodeResource: {
- id: 704,
- name: 'Other JT',
- type: 'job_template',
- },
- },
- });
- expect(firstToggleState).toEqual({
- ...state,
- nodes: [
- {
- id: 1,
- isInvalidLinkTarget: false,
- },
- {
- id: 2,
- isEdited: true,
- isInvalidLinkTarget: false,
- fullUnifiedJobTemplate: {
- id: 704,
- name: 'Other JT',
- type: 'job_template',
- },
- },
- ],
- nodeToEdit: null,
- unsavedChanges: true,
- });
- });
- });
- describe('initReducer', () => {
- it('should init', () => {
- const state = initReducer();
- expect(state).toEqual(defaultState);
- });
- });
-});
diff --git a/awx/ui/src/components/WorkflowOutputNavigation/WorkflowOutputNavigation.js b/awx/ui/src/components/WorkflowOutputNavigation/WorkflowOutputNavigation.js
deleted file mode 100644
index ebedbd06fc89..000000000000
--- a/awx/ui/src/components/WorkflowOutputNavigation/WorkflowOutputNavigation.js
+++ /dev/null
@@ -1,119 +0,0 @@
-import React, { useState } from 'react';
-import { useParams, Link } from 'react-router-dom';
-import { t } from '@lingui/macro';
-import {
- Select,
- SelectOption,
- SelectGroup,
- SelectVariant,
- Chip,
-} from '@patternfly/react-core';
-import ChipGroup from 'components/ChipGroup';
-import { stringIsUUID } from 'util/strings';
-
-const JOB_URL_SEGMENT_MAP = {
- job: 'playbook',
- project_update: 'project',
- system_job: 'management',
- system: 'system_job',
- inventory_update: 'inventory',
- workflow_job: 'workflow',
-};
-
-function WorkflowOutputNavigation({ relatedJobs, parentRef }) {
- const { id } = useParams();
-
- const relevantResults = relatedJobs.filter(
- ({ job: jobId, summary_fields }) =>
- jobId &&
- `${jobId}` !== id &&
- summary_fields.job.type !== 'workflow_approval'
- );
-
- const [isOpen, setIsOpen] = useState(false);
- const [filterBy, setFilterBy] = useState();
- const [sortedJobs, setSortedJobs] = useState(relevantResults);
-
- const handleFilter = (v) => {
- if (filterBy === v) {
- setSortedJobs(relevantResults);
- setFilterBy();
- } else {
- setFilterBy(v);
- setSortedJobs(
- relevantResults.filter(
- (node) =>
- node.summary_fields.job.status === v.toLowerCase() &&
- `${node.job}` !== id
- )
- );
- }
- };
-
- const numSuccessJobs = relevantResults.filter(
- (node) => node.summary_fields.job.status === 'successful'
- ).length;
- const numFailedJobs = relevantResults.length - numSuccessJobs;
-
- return (
-
- );
-}
-
-export default WorkflowOutputNavigation;
diff --git a/awx/ui/src/components/WorkflowOutputNavigation/WorkflowOutputNavigation.test.js b/awx/ui/src/components/WorkflowOutputNavigation/WorkflowOutputNavigation.test.js
deleted file mode 100644
index 7e54240c6fcd..000000000000
--- a/awx/ui/src/components/WorkflowOutputNavigation/WorkflowOutputNavigation.test.js
+++ /dev/null
@@ -1,85 +0,0 @@
-import React from 'react';
-import { within, render, screen, waitFor } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import WorkflowOutputNavigation from './WorkflowOutputNavigation';
-import { createMemoryHistory } from 'history';
-import { I18nProvider } from '@lingui/react';
-import { i18n } from '@lingui/core';
-import { en } from 'make-plural/plurals';
-import english from '../../../src/locales/en/messages';
-import { Router } from 'react-router-dom';
-
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useParams: () => ({
- id: 1,
- }),
-}));
-const jobs = [
- {
- id: 1,
- summary_fields: {
- job: {
- name: 'Ansible',
- type: 'project_update',
- id: 1,
- status: 'successful',
- },
- },
- job: 4,
- },
- {
- id: 2,
- summary_fields: {
- job: {
- name: 'Durham',
- type: 'job',
- id: 2,
- status: 'successful',
- },
- },
- job: 3,
- },
- {
- id: 3,
- summary_fields: {
- job: {
- name: 'Red hat',
- type: 'job',
- id: 3,
- status: 'successful',
- },
- },
- job: 2,
- },
-];
-
-describe('', () => {
- test('Should open modal and deprovision node', async () => {
- i18n.loadLocaleData({ en: { plurals: en } });
- i18n.load({ en: english });
- i18n.activate('en');
- const user = userEvent.setup();
- const ref = jest
- .spyOn(React, 'useRef')
- .mockReturnValueOnce({ current: 'div' });
- const history = createMemoryHistory({
- initialEntries: ['jobs/playbook/2/output'],
- });
- render(
-
-
-
-
-
- );
-
- const button = screen.getByRole('button');
- await user.click(button);
-
- await waitFor(() => screen.getByText('Workflow Nodes'));
- await waitFor(() => screen.getByText('Red hat'));
- await waitFor(() => screen.getByText('Durham'));
- await waitFor(() => screen.getByText('Ansible'));
- });
-});
diff --git a/awx/ui/src/components/WorkflowOutputNavigation/index.js b/awx/ui/src/components/WorkflowOutputNavigation/index.js
deleted file mode 100644
index d21e0320877c..000000000000
--- a/awx/ui/src/components/WorkflowOutputNavigation/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './WorkflowOutputNavigation';
diff --git a/awx/ui/src/constants.js b/awx/ui/src/constants.js
deleted file mode 100644
index d4d7259a4df8..000000000000
--- a/awx/ui/src/constants.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/* eslint-disable-next-line import/prefer-default-export */
-export const JOB_TYPE_URL_SEGMENTS = {
- job: 'playbook',
- project_update: 'project',
- system_job: 'management',
- inventory_update: 'inventory',
- ad_hoc_command: 'command',
- workflow_job: 'workflow',
-};
-
-export const SESSION_TIMEOUT_KEY = 'awx-session-timeout';
-export const SESSION_REDIRECT_URL = 'awx-redirect-url';
-export const PERSISTENT_FILTER_KEY = 'awx-persistent-filter';
-export const SESSION_USER_ID = 'awx-session-user-id';
diff --git a/awx/ui/src/contexts/Config.js b/awx/ui/src/contexts/Config.js
deleted file mode 100644
index 1ef355ead357..000000000000
--- a/awx/ui/src/contexts/Config.js
+++ /dev/null
@@ -1,124 +0,0 @@
-import React, { useCallback, useContext, useEffect, useMemo } from 'react';
-import { useRouteMatch } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-
-import { ConfigAPI, MeAPI, UsersAPI, OrganizationsAPI } from 'api';
-import useRequest, { useDismissableError } from 'hooks/useRequest';
-import AlertModal from 'components/AlertModal';
-import ErrorDetail from 'components/ErrorDetail';
-import { useSession } from './Session';
-
-// eslint-disable-next-line import/prefer-default-export
-export const ConfigContext = React.createContext({});
-ConfigContext.displayName = 'ConfigContext';
-
-export const Config = ConfigContext.Consumer;
-export const useConfig = () => {
- const context = useContext(ConfigContext);
- if (context === undefined) {
- throw new Error('useConfig must be used within a ConfigProvider');
- }
- return context;
-};
-
-export const ConfigProvider = ({ children }) => {
- const { logout } = useSession();
-
- const {
- error: configError,
- isLoading,
- request,
- result: config,
- } = useRequest(
- useCallback(async () => {
- const [
- { data },
- {
- data: {
- results: [me],
- },
- },
- ] = await Promise.all([ConfigAPI.read(), MeAPI.read()]);
-
- const [
- {
- data: { count: adminOrgCount },
- },
- {
- data: { count: notifAdminCount },
- },
- {
- data: { count: execEnvAdminCount },
- },
- ] = await Promise.all([
- UsersAPI.readAdminOfOrganizations(me?.id),
- OrganizationsAPI.read({
- page_size: 1,
- role_level: 'notification_admin_role',
- }),
- OrganizationsAPI.read({
- page_size: 1,
- role_level: 'execution_environment_admin_role',
- }),
- ]);
-
- return { ...data, me, adminOrgCount, notifAdminCount, execEnvAdminCount };
- }, []),
- { adminOrgCount: 0, notifAdminCount: 0, execEnvAdminCount: 0 }
- );
-
- const { error, dismissError } = useDismissableError(configError);
-
- useEffect(() => {
- request();
- }, [request]);
-
- useEffect(() => {
- if (error?.response?.status === 401) {
- logout();
- }
- }, [error, logout]);
-
- const value = useMemo(
- () => ({ ...config, request, isLoading }),
- [config, request, isLoading]
- );
-
- return (
-
- {error && (
-
- {t`Failed to retrieve configuration.`}
-
-
- )}
- {children}
-
- );
-};
-
-export const useUserProfile = () => {
- const config = useConfig();
- return {
- isSuperUser: !!config.me?.is_superuser,
- isSystemAuditor: !!config.me?.is_system_auditor,
- isOrgAdmin: config.adminOrgCount,
- isNotificationAdmin: config.notifAdminCount,
- isExecEnvAdmin: config.execEnvAdminCount,
- };
-};
-
-export const useAuthorizedPath = () => {
- const config = useConfig();
- const subscriptionMgmtRoute = useRouteMatch({
- path: '/subscription_management',
- });
- return !!config.license_info?.valid_key && !subscriptionMgmtRoute;
-};
diff --git a/awx/ui/src/contexts/Kebabified.js b/awx/ui/src/contexts/Kebabified.js
deleted file mode 100644
index c50431c73fce..000000000000
--- a/awx/ui/src/contexts/Kebabified.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import React, { useContext } from 'react';
-
-// eslint-disable-next-line import/prefer-default-export
-export const KebabifiedContext = React.createContext({});
-
-export const KebabifiedProvider = KebabifiedContext.Provider;
-export const Kebabified = KebabifiedContext.Consumer;
-export const useKebabifiedMenu = () => useContext(KebabifiedContext);
diff --git a/awx/ui/src/contexts/Session.js b/awx/ui/src/contexts/Session.js
deleted file mode 100644
index af3494831346..000000000000
--- a/awx/ui/src/contexts/Session.js
+++ /dev/null
@@ -1,211 +0,0 @@
-import React, {
- useContext,
- useEffect,
- useState,
- useRef,
- useCallback,
- useMemo,
-} from 'react';
-import { useHistory, Redirect } from 'react-router-dom';
-import { DateTime } from 'luxon';
-import { RootAPI, MeAPI } from 'api';
-import { isAuthenticated } from 'util/auth';
-import useRequest from 'hooks/useRequest';
-import { SESSION_TIMEOUT_KEY, SESSION_USER_ID } from '../constants';
-
-// The maximum supported timeout for setTimeout(), in milliseconds,
-// is the highest number you can represent as a signed 32bit
-// integer (approximately 25 days)
-const MAX_TIMEOUT = 2 ** (32 - 1) - 1;
-
-// The number of seconds the session timeout warning is displayed
-// before the user is logged out. Increasing this number (up to
-// the total session time, which is 1800s by default) will cause
-// the session timeout warning to display sooner.
-const SESSION_WARNING_DURATION = 10;
-
-/**
- * The useStorage hook integrates with the browser's localStorage api.
- * It accepts a storage key as its only argument and returns a state
- * variable and setter function for that state variable.
- *
- * This utility behaves much like the standard useState hook with some
- * key differences:
- * 1. You don't pass it an initial value. Instead, the provided key
- * is used to retrieve the initial value from local storage. If
- * the key doesn't exist in local storage, null is returned.
- * 2. Behind the scenes, this hook registers an event listener with
- * the Web Storage api to establish a two-way binding between the
- * state variable and its corresponding local storage value. This
- * means that updates to the state variable with the setter
- * function will produce a corresponding update to the local
- * storage value and vice-versa.
- * 3. When local storage is shared across browser tabs, the data
- * binding is also shared across browser tabs. This means that
- * updates to the state variable using the setter function on
- * one tab will also update the state variable on any other tab
- * using this hook with the same key and vice-versa.
- */
-function useStorage(key) {
- const [storageVal, setStorageVal] = useState(
- window.localStorage.getItem(key)
- );
- window.addEventListener('storage', () => {
- const newVal = window.localStorage.getItem(key);
- if (newVal !== storageVal) {
- setStorageVal(newVal);
- }
- });
- const setValue = (val) => {
- window.localStorage.setItem(key, JSON.stringify(val));
- setStorageVal(val);
- };
- return [storageVal, setValue];
-}
-
-const SessionContext = React.createContext({});
-SessionContext.displayName = 'SessionContext';
-
-function SessionProvider({ children }) {
- const history = useHistory();
- const isSessionExpired = useRef(false);
- const sessionTimeoutId = useRef(null);
- const sessionIntervalId = useRef(null);
- const [sessionTimeout, setSessionTimeout] = useStorage(SESSION_TIMEOUT_KEY);
- const [sessionCountdown, setSessionCountdown] = useState(0);
- const [authRedirectTo, setAuthRedirectTo] = useState('/');
- const [isUserBeingLoggedOut, setIsUserBeingLoggedOut] = useState(false);
-
- const {
- request: fetchLoginRedirectOverride,
- result: { loginRedirectOverride },
- isLoading,
- } = useRequest(
- useCallback(async () => {
- const { data } = await RootAPI.read();
- return {
- loginRedirectOverride: data?.login_redirect_override,
- };
- }, []),
- {
- loginRedirectOverride: null,
- isLoading: true,
- }
- );
-
- useEffect(() => {
- fetchLoginRedirectOverride();
- }, [fetchLoginRedirectOverride]);
-
- const logout = useCallback(async () => {
- setIsUserBeingLoggedOut(true);
- if (!isSessionExpired.current) {
- setAuthRedirectTo('/logout');
- window.localStorage.setItem(SESSION_USER_ID, null);
- }
- sessionStorage.clear();
- await RootAPI.logout();
- setSessionTimeout(0);
- setSessionCountdown(0);
- clearTimeout(sessionTimeoutId.current);
- clearInterval(sessionIntervalId.current);
- return ;
- }, [setSessionTimeout, setSessionCountdown]);
-
- useEffect(() => {
- if (!isAuthenticated(document.cookie)) {
- return () => {};
- }
- const calcRemaining = () => {
- if (sessionTimeout) {
- return Math.max(
- parseInt(sessionTimeout, 10) - DateTime.now().toMillis(),
- 0
- );
- }
- return 0;
- };
-
- const handleSessionTimeout = () => {
- let countDown = SESSION_WARNING_DURATION;
- setSessionCountdown(countDown);
-
- sessionIntervalId.current = setInterval(() => {
- if (countDown > 0) {
- setSessionCountdown(--countDown);
- } else {
- isSessionExpired.current = true;
- logout();
- }
- }, 1000);
- };
-
- setSessionCountdown(0);
- clearTimeout(sessionTimeoutId.current);
- clearInterval(sessionIntervalId.current);
-
- const calcTimeOut = calcRemaining() - SESSION_WARNING_DURATION * 1000;
-
- isSessionExpired.current = false;
- sessionTimeoutId.current = setTimeout(
- handleSessionTimeout,
- calcTimeOut <= 0 ? MAX_TIMEOUT : Math.min(calcTimeOut, MAX_TIMEOUT)
- );
-
- return () => {
- clearTimeout(sessionTimeoutId.current);
- clearInterval(sessionIntervalId.current);
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [history, sessionTimeout]);
-
- const handleSessionContinue = useCallback(async () => {
- await MeAPI.read();
- setSessionCountdown(0);
- clearTimeout(sessionTimeoutId.current);
- clearInterval(sessionIntervalId.current);
- }, []);
-
- const sessionValue = useMemo(
- () => ({
- authRedirectTo,
- handleSessionContinue,
- isSessionExpired,
- isUserBeingLoggedOut,
- loginRedirectOverride,
- logout,
- sessionCountdown,
- setAuthRedirectTo,
- }),
- [
- authRedirectTo,
- handleSessionContinue,
- isSessionExpired,
- isUserBeingLoggedOut,
- loginRedirectOverride,
- logout,
- sessionCountdown,
- setAuthRedirectTo,
- ]
- );
-
- if (isLoading) {
- return null;
- }
-
- return (
-
- {children}
-
- );
-}
-
-function useSession() {
- const context = useContext(SessionContext);
- if (context === undefined) {
- throw new Error('useSession must be used within a SessionProvider');
- }
- return context;
-}
-
-export { SessionContext, SessionProvider, useSession };
diff --git a/awx/ui/src/contexts/Settings.js b/awx/ui/src/contexts/Settings.js
deleted file mode 100644
index 2a5c0e31e960..000000000000
--- a/awx/ui/src/contexts/Settings.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import React, { useContext } from 'react';
-
-export const SettingsContext = React.createContext({});
-export const SettingsProvider = SettingsContext.Provider;
-
-export const useSettings = () => useContext(SettingsContext);
diff --git a/awx/ui/src/contexts/Workflow.js b/awx/ui/src/contexts/Workflow.js
deleted file mode 100644
index d79fd4008247..000000000000
--- a/awx/ui/src/contexts/Workflow.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import React from 'react';
-
-// eslint-disable-next-line import/prefer-default-export
-export const WorkflowDispatchContext = React.createContext(null);
-export const WorkflowStateContext = React.createContext(null);
diff --git a/awx/ui/src/hooks/useAutoPopulateLookup.js b/awx/ui/src/hooks/useAutoPopulateLookup.js
deleted file mode 100644
index 776ede8c13c8..000000000000
--- a/awx/ui/src/hooks/useAutoPopulateLookup.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import { useCallback, useRef } from 'react';
-
-/**
- * useAutoPopulateLookup hook [... insert description]
- * Param: [... insert params]
- * Returns: {
- * [... insert returns]
- * }
- */
-
-export default function useAutoPopulateLookup(populateLookupField) {
- const isFirst = useRef(true);
-
- return useCallback(
- (results) => {
- if (isFirst.current && results.length === 1) {
- populateLookupField(results[0]);
- }
-
- isFirst.current = false;
- },
- [populateLookupField]
- );
-}
diff --git a/awx/ui/src/hooks/useBrandName.js b/awx/ui/src/hooks/useBrandName.js
deleted file mode 100644
index dd02ee306eaa..000000000000
--- a/awx/ui/src/hooks/useBrandName.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import { useEffect, useState } from 'react';
-import { RootAPI } from 'api';
-
-export default function useBrandName() {
- const [brandName, setBrandName] = useState('');
-
- useEffect(() => {
- async function fetchBrandName() {
- const {
- data: { BRAND_NAME },
- } = await RootAPI.readAssetVariables();
- setBrandName(BRAND_NAME);
- }
- fetchBrandName();
- }, []);
-
- return brandName;
-}
diff --git a/awx/ui/src/hooks/useDebounce.js b/awx/ui/src/hooks/useDebounce.js
deleted file mode 100644
index 67c723aef5a8..000000000000
--- a/awx/ui/src/hooks/useDebounce.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import { useRef } from 'react';
-
-export default function useDebounce(fn, delay) {
- const timeOutRef = useRef(null);
-
- function debouncedFunction(...args) {
- window.clearTimeout(timeOutRef.current);
- timeOutRef.current = window.setTimeout(() => {
- fn(...args);
- }, delay);
- }
-
- return debouncedFunction;
-}
diff --git a/awx/ui/src/hooks/useDebounce.test.js b/awx/ui/src/hooks/useDebounce.test.js
deleted file mode 100644
index ea7ec119b2c8..000000000000
--- a/awx/ui/src/hooks/useDebounce.test.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from 'react';
-import { mount } from 'enzyme';
-import useDebounce from './useDebounce';
-
-function TestInner() {
- return ;
-}
-function Test({ fn, delay = 500, data }) {
- const debounce = useDebounce(fn, delay);
- debounce(data);
- return ;
-}
-
-test('useDebounce', () => {
- jest.useFakeTimers();
- const fn = jest.fn();
- mount();
- expect(fn).toHaveBeenCalledTimes(0);
- jest.advanceTimersByTime(510);
- expect(fn).toHaveBeenCalledTimes(1);
- expect(fn).toHaveBeenCalledWith({ data: 123 });
-});
diff --git a/awx/ui/src/hooks/useExpanded.js b/awx/ui/src/hooks/useExpanded.js
deleted file mode 100644
index 4c2c5d411c75..000000000000
--- a/awx/ui/src/hooks/useExpanded.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import { useState, useCallback } from 'react';
-
-export default function useExpanded(list = []) {
- const [expanded, setExpanded] = useState([]);
- const isAllExpanded = expanded.length > 0 && expanded.length === list.length;
-
- const handleExpand = (row) => {
- if (!row.id) {
- throw new Error(`Selected row does not have an id`);
- }
- if (expanded.some((s) => s.id === row.id)) {
- setExpanded((prevState) => [...prevState.filter((i) => i.id !== row.id)]);
- } else {
- setExpanded((prevState) => [...prevState, row]);
- }
- };
-
- const expandAll = useCallback(
- (isExpanded) => {
- setExpanded(isExpanded ? [...list] : []);
- },
- [list]
- );
-
- return {
- expanded,
- isAllExpanded,
- handleExpand,
- setExpanded,
- expandAll,
- };
-}
diff --git a/awx/ui/src/hooks/useExpanded.test.js b/awx/ui/src/hooks/useExpanded.test.js
deleted file mode 100644
index 597680de2c03..000000000000
--- a/awx/ui/src/hooks/useExpanded.test.js
+++ /dev/null
@@ -1,96 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { mount } from 'enzyme';
-import useExpanded from './useExpanded';
-
-const array = [{ id: '1' }, { id: '2' }, { id: '3' }];
-
-const TestHook = ({ callback }) => {
- callback();
- return null;
-};
-
-const testHook = (callback) => {
- mount();
-};
-
-describe('useSelected hook', () => {
- let expanded;
- let isAllExpanded;
- let handleExpand;
- let setExpanded;
- let expandAll;
-
- test('should return expected initial values', () => {
- testHook(() => {
- ({ expanded, isAllExpanded, handleExpand, setExpanded, expandAll } =
- useExpanded());
- });
- expect(expanded).toEqual([]);
- expect(isAllExpanded).toEqual(false);
- expect(handleExpand).toBeInstanceOf(Function);
- expect(setExpanded).toBeInstanceOf(Function);
- });
-
- test('handleSelect should update and filter selected items', () => {
- testHook(() => {
- ({ expanded, isAllExpanded, handleExpand, setExpanded, expandAll } =
- useExpanded());
- });
-
- act(() => {
- handleExpand(array[0]);
- });
- expect(expanded).toEqual([array[0]]);
-
- act(() => {
- handleExpand(array[0]);
- });
- expect(expanded).toEqual([]);
- });
-
- test('should return expected isAllSelected value', () => {
- testHook(() => {
- ({ expanded, isAllExpanded, handleExpand, setExpanded, expandAll } =
- useExpanded(array));
- });
-
- act(() => {
- handleExpand(array[0]);
- });
- expect(expanded).toEqual([array[0]]);
- expect(isAllExpanded).toEqual(false);
-
- act(() => {
- handleExpand(array[1]);
- handleExpand(array[2]);
- });
- expect(expanded).toEqual(array);
- expect(isAllExpanded).toEqual(true);
-
- act(() => {
- setExpanded([]);
- });
- expect(expanded).toEqual([]);
- expect(isAllExpanded).toEqual(false);
- });
-
- test('should return selectAll', () => {
- testHook(() => {
- ({ expanded, isAllExpanded, handleExpand, setExpanded, expandAll } =
- useExpanded(array));
- });
-
- act(() => {
- expandAll(true);
- });
- expect(isAllExpanded).toEqual(true);
- expect(expanded).toEqual(array);
-
- act(() => {
- expandAll(false);
- });
- expect(isAllExpanded).toEqual(false);
- expect(expanded).toEqual([]);
- });
-});
diff --git a/awx/ui/src/hooks/useInterval.js b/awx/ui/src/hooks/useInterval.js
deleted file mode 100644
index 714121e54769..000000000000
--- a/awx/ui/src/hooks/useInterval.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import { useEffect, useRef } from 'react';
-
-export default function useInterval(callback, delay) {
- const savedCallbackRef = useRef();
- useEffect(() => {
- savedCallbackRef.current = callback;
- }, [callback]);
- useEffect(() => {
- const handler = (...args) => savedCallbackRef.current(...args);
- if (delay !== null) {
- const intervalId = setInterval(handler, delay);
- return () => clearInterval(intervalId);
- }
- return () => undefined;
- }, [delay]);
-}
diff --git a/awx/ui/src/hooks/useIsMounted.js b/awx/ui/src/hooks/useIsMounted.js
deleted file mode 100644
index 9a9dea82cdf0..000000000000
--- a/awx/ui/src/hooks/useIsMounted.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { useEffect, useRef } from 'react';
-
-export default function useIsMounted() {
- const isMounted = useRef(null);
- useEffect(() => {
- isMounted.current = true;
- return () => {
- isMounted.current = false;
- };
- });
- return isMounted;
-}
diff --git a/awx/ui/src/hooks/useModal.js b/awx/ui/src/hooks/useModal.js
deleted file mode 100644
index ffddd59ada07..000000000000
--- a/awx/ui/src/hooks/useModal.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import { useState } from 'react';
-
-/**
- * useModal hook provides a way to read and update modal visibility
- * Param: boolean that sets initial modal state
- * Returns: {
- * isModalOpen: boolean that indicates if modal is open
- * toggleModal: function that toggles the modal open and close
- * closeModal: function that closes modal
- * }
- */
-
-export default function useModal(isOpen = false) {
- const [isModalOpen, setIsModalOpen] = useState(isOpen);
-
- function toggleModal() {
- setIsModalOpen(!isModalOpen);
- }
-
- function closeModal() {
- setIsModalOpen(false);
- }
-
- return {
- isModalOpen,
- toggleModal,
- closeModal,
- };
-}
diff --git a/awx/ui/src/hooks/useModal.test.js b/awx/ui/src/hooks/useModal.test.js
deleted file mode 100644
index bbae1c3438e4..000000000000
--- a/awx/ui/src/hooks/useModal.test.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { mount } from 'enzyme';
-import useModal from './useModal';
-
-const TestHook = ({ callback }) => {
- callback();
- return null;
-};
-
-const testHook = (callback) => {
- mount();
-};
-
-describe('useModal hook', () => {
- let closeModal;
- let isModalOpen;
- let toggleModal;
-
- test('isModalOpen should return expected default value', () => {
- testHook(() => {
- ({ isModalOpen, toggleModal, closeModal } = useModal());
- });
- expect(isModalOpen).toEqual(false);
- expect(toggleModal).toBeInstanceOf(Function);
- expect(closeModal).toBeInstanceOf(Function);
- });
-
- test('isModalOpen should return expected initialized value', () => {
- testHook(() => {
- ({ isModalOpen, toggleModal, closeModal } = useModal(true));
- });
- expect(isModalOpen).toEqual(true);
- expect(toggleModal).toBeInstanceOf(Function);
- expect(closeModal).toBeInstanceOf(Function);
- });
-
- test('should return expected isModalOpen value after modal toggle', () => {
- testHook(() => {
- ({ isModalOpen, toggleModal, closeModal } = useModal());
- });
- expect(isModalOpen).toEqual(false);
- act(() => {
- toggleModal();
- });
- expect(isModalOpen).toEqual(true);
- });
-
- test('isModalOpen should be false after closeModal is called', () => {
- testHook(() => {
- ({ isModalOpen, toggleModal, closeModal } = useModal());
- });
- expect(isModalOpen).toEqual(false);
- act(() => {
- toggleModal();
- });
- expect(isModalOpen).toEqual(true);
- act(() => {
- closeModal();
- });
- expect(isModalOpen).toEqual(false);
- });
-});
diff --git a/awx/ui/src/hooks/useRequest.js b/awx/ui/src/hooks/useRequest.js
deleted file mode 100644
index 2da680abb685..000000000000
--- a/awx/ui/src/hooks/useRequest.js
+++ /dev/null
@@ -1,126 +0,0 @@
-import { useEffect, useState, useCallback } from 'react';
-import { useLocation, useHistory } from 'react-router-dom';
-import { parseQueryString, updateQueryString } from 'util/qs';
-import useIsMounted from './useIsMounted';
-
-/*
- * The useRequest hook accepts a request function and returns an object with
- * five values:
- * request: a function to call to invoke the request
- * result: the value returned from the request function (once invoked)
- * isLoading: boolean state indicating whether the request is in active/in flight
- * error: any caught error resulting from the request
- * setValue: setter to explicitly set the result value
- *
- * The hook also accepts an optional second parameter which is a default
- * value to set as result before the first time the request is made.
- */
-export default function useRequest(makeRequest, initialValue) {
- const [result, setResult] = useState(initialValue);
- const [error, setError] = useState(null);
- const [isLoading, setIsLoading] = useState(initialValue?.isLoading || false);
- const isMounted = useIsMounted();
-
- return {
- result,
- error,
- isLoading,
- request: useCallback(
- async (...args) => {
- setIsLoading(true);
- try {
- const response = await makeRequest(...args);
- if (isMounted.current) {
- setResult(response);
- setError(null);
- }
- } catch (err) {
- if (isMounted.current) {
- setError(err);
- setResult(initialValue);
- }
- } finally {
- if (isMounted.current) {
- setIsLoading(false);
- }
- }
- },
- /* eslint-disable-next-line react-hooks/exhaustive-deps */
- [makeRequest]
- ),
- setValue: setResult,
- };
-}
-
-/*
- * Provides controls for "dismissing" an error message
- *
- * Params: an error object
- * Returns: { error, dismissError }
- * The returned error object is the same object passed in via the paremeter,
- * until the dismissError function is called, at which point the returned
- * error will be set to null on the subsequent render.
- */
-export function useDismissableError(error) {
- const [showError, setShowError] = useState(false);
-
- useEffect(() => {
- if (error) {
- setShowError(true);
- }
- }, [error]);
-
- return {
- error: showError ? error : null,
- dismissError: () => {
- setShowError(false);
- },
- };
-}
-
-/*
- * Hook to assist with deletion of items from a paginated item list. The page
- * url will be navigated back one page on a paginated list if needed to prevent
- * the UI from re-loading an empty set and displaying a "No items found"
- * message.
- *
- * Params: a callback function that will be invoked in order to delete items,
- * and an object with structure { qsConfig, allItemsSelected, fetchItems }
- * Returns: { isLoading, deleteItems, deletionError, clearDeletionError }
- */
-export function useDeleteItems(
- makeRequest,
- { qsConfig = null, allItemsSelected = false, fetchItems = null } = {}
-) {
- const location = useLocation();
- const history = useHistory();
- const {
- error: requestError,
- isLoading,
- request,
- } = useRequest(makeRequest, null);
- const { error, dismissError } = useDismissableError(requestError);
-
- const deleteItems = async () => {
- await request();
- if (!qsConfig) {
- return;
- }
- const params = parseQueryString(qsConfig, location.search);
- if (params.page > 1 && allItemsSelected) {
- const qs = updateQueryString(qsConfig, location.search, {
- page: params.page - 1,
- });
- history.push(`${location.pathname}?${qs}`);
- } else {
- fetchItems();
- }
- };
-
- return {
- isLoading,
- deleteItems,
- deletionError: error,
- clearDeletionError: dismissError,
- };
-}
diff --git a/awx/ui/src/hooks/useRequest.test.js b/awx/ui/src/hooks/useRequest.test.js
deleted file mode 100644
index 5e506faf106e..000000000000
--- a/awx/ui/src/hooks/useRequest.test.js
+++ /dev/null
@@ -1,220 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { mount } from 'enzyme';
-import { mountWithContexts } from '../../testUtils/enzymeHelpers';
-import useRequest, { useDeleteItems } from './useRequest';
-
-function TestInner() {
- return ;
-}
-function Test({ makeRequest, initialValue = {} }) {
- const request = useRequest(makeRequest, initialValue);
- return ;
-}
-function DeleteTest({ makeRequest, args = {} }) {
- const request = useDeleteItems(makeRequest, args);
- return ;
-}
-
-describe('useRequest hooks', () => {
- describe('useRequest', () => {
- test('should return initial value as result', async () => {
- const makeRequest = jest.fn();
- makeRequest.mockResolvedValue({ data: 'foo' });
- const wrapper = mount(
-
- );
-
- expect(wrapper.find('TestInner').prop('result')).toEqual({
- initial: true,
- });
- });
-
- test('should return result', async () => {
- const makeRequest = jest.fn();
- makeRequest.mockResolvedValue({ data: 'foo' });
- const wrapper = mount();
-
- await act(async () => {
- wrapper.find('TestInner').invoke('request')();
- });
- wrapper.update();
- expect(wrapper.find('TestInner').prop('result')).toEqual({ data: 'foo' });
- });
-
- test('should is isLoading flag', async () => {
- const makeRequest = jest.fn();
- let resolve;
- const promise = new Promise((r) => {
- resolve = r;
- });
- makeRequest.mockReturnValue(promise);
- const wrapper = mount();
-
- await act(async () => {
- wrapper.find('TestInner').invoke('request')();
- });
- wrapper.update();
- expect(wrapper.find('TestInner').prop('isLoading')).toEqual(true);
- await act(async () => {
- resolve({ data: 'foo' });
- });
- wrapper.update();
- expect(wrapper.find('TestInner').prop('isLoading')).toEqual(false);
- expect(wrapper.find('TestInner').prop('result')).toEqual({ data: 'foo' });
- });
-
- test('should invoke request function', async () => {
- const makeRequest = jest.fn();
- makeRequest.mockResolvedValue({ data: 'foo' });
- const wrapper = mount();
-
- expect(makeRequest).not.toHaveBeenCalled();
- await act(async () => {
- wrapper.find('TestInner').invoke('request')();
- });
- wrapper.update();
- expect(makeRequest).toHaveBeenCalledTimes(1);
- });
-
- test('should return error thrown from request function', async () => {
- const error = new Error('error');
- const makeRequest = () => {
- throw error;
- };
- const wrapper = mount();
-
- await act(async () => {
- wrapper.find('TestInner').invoke('request')();
- });
- wrapper.update();
- expect(wrapper.find('TestInner').prop('error')).toEqual(error);
- });
-
- test('should reset error/result on each request', async () => {
- const error = new Error('error');
- const makeRequest = (throwError) => {
- if (throwError) {
- throw error;
- }
-
- return { data: 'foo' };
- };
- const wrapper = mount();
-
- await act(async () => {
- wrapper.find('TestInner').invoke('request')(true);
- });
- wrapper.update();
- expect(wrapper.find('TestInner').prop('result')).toEqual({});
- expect(wrapper.find('TestInner').prop('error')).toEqual(error);
- await act(async () => {
- wrapper.find('TestInner').invoke('request')();
- });
- wrapper.update();
- expect(wrapper.find('TestInner').prop('result')).toEqual({ data: 'foo' });
- expect(wrapper.find('TestInner').prop('error')).toEqual(null);
- await act(async () => {
- wrapper.find('TestInner').invoke('request')(true);
- });
- wrapper.update();
- expect(wrapper.find('TestInner').prop('result')).toEqual({});
- expect(wrapper.find('TestInner').prop('error')).toEqual(error);
- });
-
- test('should not update state after unmount', async () => {
- const makeRequest = jest.fn();
- let resolve;
- const promise = new Promise((r) => {
- resolve = r;
- });
- makeRequest.mockReturnValue(promise);
- const wrapper = mount();
-
- expect(makeRequest).not.toHaveBeenCalled();
- await act(async () => {
- wrapper.find('TestInner').invoke('request')();
- });
- wrapper.unmount();
- await act(async () => {
- resolve({ data: 'foo' });
- });
- });
- });
-
- describe('useDeleteItems', () => {
- test('should invoke delete function', async () => {
- const makeRequest = jest.fn();
- makeRequest.mockResolvedValue({ data: 'foo' });
- const wrapper = mountWithContexts(
- {},
- }}
- />
- );
-
- expect(makeRequest).not.toHaveBeenCalled();
- await act(async () => {
- wrapper.find('TestInner').invoke('deleteItems')();
- });
- wrapper.update();
- expect(makeRequest).toHaveBeenCalledTimes(1);
- });
-
- test('should return error object thrown by function', async () => {
- const error = new Error('error');
- const makeRequest = () => {
- throw error;
- };
- const wrapper = mountWithContexts(
- {},
- }}
- />
- );
-
- await act(async () => {
- wrapper.find('TestInner').invoke('deleteItems')();
- });
- wrapper.update();
- expect(wrapper.find('TestInner').prop('deletionError')).toEqual(error);
- });
-
- test('should dismiss error', async () => {
- const error = new Error('error');
- const makeRequest = () => {
- throw error;
- };
- const wrapper = mountWithContexts(
- {},
- }}
- />
- );
-
- await act(async () => {
- wrapper.find('TestInner').invoke('deleteItems')();
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('TestInner').invoke('clearDeletionError')();
- });
- wrapper.update();
- expect(wrapper.find('TestInner').prop('deletionError')).toEqual(null);
- });
- });
-});
diff --git a/awx/ui/src/hooks/useSelected.js b/awx/ui/src/hooks/useSelected.js
deleted file mode 100644
index f596f5ca7f7b..000000000000
--- a/awx/ui/src/hooks/useSelected.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import { useState, useCallback } from 'react';
-
-/**
- * useSelected hook provides a way to read and update a selected list
- * Param: array of list items
- * Returns: {
- * selected: array of selected list items
- * isAllSelected: boolean that indicates if all items are selected
- * handleSelect: function that adds and removes items from selected list
- * setSelected: setter function
- * clearSelected: de-select all items
- * }
- */
-
-export default function useSelected(list = [], defaultSelected = []) {
- const [selected, setSelected] = useState(defaultSelected);
- const isAllSelected = selected.length > 0 && selected.length === list.length;
-
- const handleSelect = (row) => {
- if (!row.id) {
- throw new Error(`Selected row does not have an id`);
- }
- if (selected.some((s) => s.id === row.id)) {
- setSelected((prevState) => [...prevState.filter((i) => i.id !== row.id)]);
- } else {
- setSelected((prevState) => [...prevState, row]);
- }
- };
-
- const selectAll = useCallback(
- (isSelected) => {
- setSelected(isSelected ? [...list] : []);
- },
- [list]
- );
-
- const clearSelected = useCallback(() => {
- setSelected([]);
- }, []);
-
- return {
- selected,
- isAllSelected,
- handleSelect,
- setSelected,
- selectAll,
- clearSelected,
- };
-}
diff --git a/awx/ui/src/hooks/useSelected.test.js b/awx/ui/src/hooks/useSelected.test.js
deleted file mode 100644
index 2462a44aa1f3..000000000000
--- a/awx/ui/src/hooks/useSelected.test.js
+++ /dev/null
@@ -1,118 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { mount } from 'enzyme';
-import useSelected from './useSelected';
-
-const array = [{ id: '1' }, { id: '2' }, { id: '3' }];
-
-const TestHook = ({ callback }) => {
- callback();
- return null;
-};
-
-const testHook = (callback) => {
- mount();
-};
-
-describe('useSelected hook', () => {
- let selected;
- let isAllSelected;
- let handleSelect;
- let setSelected;
- let selectAll;
- let clearSelected;
-
- test('should return expected initial values', () => {
- testHook(() => {
- ({ selected, isAllSelected, handleSelect, setSelected } = useSelected());
- });
- expect(selected).toEqual([]);
- expect(isAllSelected).toEqual(false);
- expect(handleSelect).toBeInstanceOf(Function);
- expect(setSelected).toBeInstanceOf(Function);
- });
-
- test('handleSelect should update and filter selected items', () => {
- testHook(() => {
- ({ selected, isAllSelected, handleSelect, setSelected } = useSelected());
- });
-
- act(() => {
- handleSelect(array[0]);
- });
- expect(selected).toEqual([array[0]]);
-
- act(() => {
- handleSelect(array[0]);
- });
- expect(selected).toEqual([]);
- });
-
- test('should return expected isAllSelected value', () => {
- testHook(() => {
- ({ selected, isAllSelected, handleSelect, setSelected } =
- useSelected(array));
- });
-
- act(() => {
- handleSelect(array[0]);
- });
- expect(selected).toEqual([array[0]]);
- expect(isAllSelected).toEqual(false);
-
- act(() => {
- handleSelect(array[1]);
- handleSelect(array[2]);
- });
- expect(selected).toEqual(array);
- expect(isAllSelected).toEqual(true);
-
- act(() => {
- setSelected([]);
- });
- expect(selected).toEqual([]);
- expect(isAllSelected).toEqual(false);
- });
-
- test('should return selectAll', () => {
- testHook(() => {
- ({ selected, isAllSelected, handleSelect, setSelected, selectAll } =
- useSelected(array));
- });
-
- act(() => {
- selectAll(true);
- });
- expect(isAllSelected).toEqual(true);
- expect(selected).toEqual(array);
-
- act(() => {
- selectAll(false);
- });
- expect(isAllSelected).toEqual(false);
- expect(selected).toEqual([]);
- });
-
- test('should return clearSelected', () => {
- testHook(() => {
- ({
- selected,
- isAllSelected,
- handleSelect,
- setSelected,
- selectAll,
- clearSelected,
- } = useSelected(array));
- });
-
- act(() => {
- selectAll(true);
- });
-
- act(() => {
- clearSelected();
- });
- expect(isAllSelected).toEqual(false);
- expect(selected).toEqual([]);
- });
-});
diff --git a/awx/ui/src/hooks/useThrottle.js b/awx/ui/src/hooks/useThrottle.js
deleted file mode 100644
index 242490646140..000000000000
--- a/awx/ui/src/hooks/useThrottle.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import { useState, useEffect, useRef } from 'react';
-
-export default function useThrottle(value, limit) {
- const [throttledValue, setThrottledValue] = useState(value);
- const lastRan = useRef(Date.now());
- const initialValue = useRef(value);
-
- useEffect(() => {
- if (value !== initialValue.current) {
- setThrottledValue(value);
- return () => {};
- }
-
- const handler = setTimeout(() => {
- if (Date.now() - lastRan.current >= limit) {
- lastRan.current = Date.now();
- setThrottledValue(value);
- }
- }, limit - (Date.now() - lastRan.current));
-
- return () => {
- clearTimeout(handler);
- };
- }, [value, limit]);
-
- return throttledValue;
-}
diff --git a/awx/ui/src/hooks/useTitle.js b/awx/ui/src/hooks/useTitle.js
deleted file mode 100644
index 3f960ccb2117..000000000000
--- a/awx/ui/src/hooks/useTitle.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import { useEffect } from 'react';
-import useBrandName from './useBrandName';
-
-export default function useTitle(title) {
- const brandName = useBrandName();
-
- useEffect(() => {
- const prevTitle = document.title;
- if (title) {
- document.title = `${brandName} | ${title}`;
- } else {
- document.title = brandName;
- }
-
- return () => {
- document.title = prevTitle;
- };
- }, [title, brandName]);
-}
diff --git a/awx/ui/src/hooks/useToast.js b/awx/ui/src/hooks/useToast.js
deleted file mode 100644
index 0f5ec1da612e..000000000000
--- a/awx/ui/src/hooks/useToast.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import React, { useState, useCallback } from 'react';
-import {
- AlertGroup,
- Alert,
- AlertActionCloseButton,
- AlertVariant,
-} from '@patternfly/react-core';
-import { arrayOf, func } from 'prop-types';
-import { Toast as ToastType } from 'types';
-
-export default function useToast() {
- const [toasts, setToasts] = useState([]);
-
- const addToast = useCallback((newToast) => {
- setToasts((oldToasts) => [...oldToasts, newToast]);
- }, []);
-
- const removeToast = useCallback((toastId) => {
- setToasts((oldToasts) => oldToasts.filter((t) => t.id !== toastId));
- }, []);
-
- return {
- addToast,
- removeToast,
- Toast,
- toastProps: {
- toasts,
- removeToast,
- },
- };
-}
-
-export function Toast({ toasts, removeToast }) {
- if (!toasts.length) {
- return null;
- }
-
- return (
-
- {toasts.map((toast) => (
- removeToast(toast.id)} />
- }
- onTimeout={() => removeToast(toast.id)}
- timeout={toast.hasTimeout}
- title={toast.title}
- variant={toast.variant}
- key={`toast-message-${toast.id}`}
- ouiaId={`toast-message-${toast.id}`}
- >
- {toast.message}
-
- ))}
-
- );
-}
-
-Toast.propTypes = {
- toasts: arrayOf(ToastType).isRequired,
- removeToast: func.isRequired,
-};
-
-export { AlertVariant };
diff --git a/awx/ui/src/hooks/useToast.test.js b/awx/ui/src/hooks/useToast.test.js
deleted file mode 100644
index 23b6ca845f9b..000000000000
--- a/awx/ui/src/hooks/useToast.test.js
+++ /dev/null
@@ -1,124 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { shallow, mount } from 'enzyme';
-import useToast, { Toast, AlertVariant } from './useToast';
-
-describe('useToast', () => {
- const Child = () => ;
- const Test = () => {
- const toastVals = useToast();
- return ;
- };
-
- test('should provide Toast component', () => {
- const wrapper = mount();
-
- expect(wrapper.find('Child').prop('Toast')).toEqual(Toast);
- });
-
- test('should add toast', () => {
- const wrapper = mount();
-
- expect(wrapper.find('Child').prop('toastProps').toasts).toEqual([]);
- act(() => {
- wrapper.find('Child').prop('addToast')({
- message: 'one',
- id: 1,
- variant: 'success',
- });
- });
- wrapper.update();
-
- expect(wrapper.find('Child').prop('toastProps').toasts).toEqual([
- {
- message: 'one',
- id: 1,
- variant: 'success',
- },
- ]);
- });
-
- test('should remove toast', () => {
- const wrapper = mount();
-
- act(() => {
- wrapper.find('Child').prop('addToast')({
- message: 'one',
- id: 1,
- variant: 'success',
- });
- });
- wrapper.update();
- expect(wrapper.find('Child').prop('toastProps').toasts).toHaveLength(1);
- act(() => {
- wrapper.find('Child').prop('removeToast')(1);
- });
- wrapper.update();
-
- expect(wrapper.find('Child').prop('toastProps').toasts).toHaveLength(0);
- });
-});
-
-describe('Toast', () => {
- test('should render nothing with no toasts', () => {
- const wrapper = shallow( {}} />);
- expect(wrapper).toEqual({});
- });
-
- test('should render toast alert', () => {
- const toast = {
- title: 'Inventory saved',
- variant: AlertVariant.success,
- id: 1,
- message: 'the message',
- };
- const wrapper = shallow( {}} />);
-
- const alert = wrapper.find('Alert');
- expect(alert.prop('title')).toEqual('Inventory saved');
- expect(alert.prop('variant')).toEqual('success');
- expect(alert.prop('ouiaId')).toEqual('toast-message-1');
- expect(alert.prop('children')).toEqual('the message');
- });
-
- test('should call removeToast', () => {
- const removeToast = jest.fn();
- const toast = {
- title: 'Inventory saved',
- variant: AlertVariant.success,
- id: 1,
- };
- const wrapper = shallow(
-
- );
-
- const alert = wrapper.find('Alert');
- alert.prop('actionClose').props.onClose(1);
- expect(removeToast).toHaveBeenCalledTimes(1);
- });
-
- test('should render multiple alerts', () => {
- const toasts = [
- {
- title: 'Inventory saved',
- variant: AlertVariant.success,
- id: 1,
- message: 'the message',
- },
- {
- title: 'error saving',
- variant: AlertVariant.danger,
- id: 2,
- },
- ];
- const wrapper = shallow( {}} />);
-
- const alert = wrapper.find('Alert');
- expect(alert).toHaveLength(2);
-
- expect(alert.at(0).prop('title')).toEqual('Inventory saved');
- expect(alert.at(0).prop('variant')).toEqual('success');
- expect(alert.at(1).prop('title')).toEqual('error saving');
- expect(alert.at(1).prop('variant')).toEqual('danger');
- });
-});
diff --git a/awx/ui/src/hooks/useWebsocket.js b/awx/ui/src/hooks/useWebsocket.js
deleted file mode 100644
index 8010471250ac..000000000000
--- a/awx/ui/src/hooks/useWebsocket.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import { useState, useEffect, useRef } from 'react';
-
-export default function useWebsocket(subscribeGroups) {
- const [lastMessage, setLastMessage] = useState(null);
- const ws = useRef(null);
-
- useEffect(() => {
- ws.current = new WebSocket(
- `${window.location.protocol === 'http:' ? 'ws:' : 'wss:'}//${
- window.location.host
- }${window.location.pathname}websocket/`
- );
-
- const connect = () => {
- const xrftoken = `; ${document.cookie}`
- .split('; csrftoken=')
- .pop()
- .split(';')
- .shift();
- ws.current.send(
- JSON.stringify({
- xrftoken,
- groups: subscribeGroups,
- })
- );
- };
- ws.current.onopen = connect;
-
- ws.current.onmessage = (e) => {
- setLastMessage(JSON.parse(e.data));
- };
-
- ws.current.onclose = (e) => {
- if (e.code !== 1000) {
- // eslint-disable-next-line no-console
- console.debug('Socket closed. Reconnecting...', e);
- setTimeout(() => {
- connect();
- }, 1000);
- }
- };
-
- ws.current.onerror = (err) => {
- // eslint-disable-next-line no-console
- console.debug('Socket error: ', err, 'Disconnecting...');
- ws.current.close();
- };
-
- return () => {
- ws.current.close();
- };
- }, []); // eslint-disable-line react-hooks/exhaustive-deps
-
- return lastMessage;
-}
diff --git a/awx/ui/src/hooks/useWsTemplates.js b/awx/ui/src/hooks/useWsTemplates.js
deleted file mode 100644
index a7a13ad84f88..000000000000
--- a/awx/ui/src/hooks/useWsTemplates.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import { useState, useEffect } from 'react';
-import useWebsocket from './useWebsocket';
-
-export default function useWsTemplates(initialTemplates) {
- const [templates, setTemplates] = useState(initialTemplates);
- const lastMessage = useWebsocket({
- jobs: ['status_changed'],
- control: ['limit_reached_1'],
- });
-
- useEffect(() => {
- setTemplates(initialTemplates);
- }, [initialTemplates]);
-
- useEffect(
- () => {
- if (!lastMessage?.unified_job_id) {
- return;
- }
- const index = templates.findIndex(
- (t) => t.id === lastMessage.unified_job_template_id
- );
- if (index === -1) {
- return;
- }
-
- const template = templates[index];
- const updated = [...templates];
- updated[index] = updateTemplate(template, lastMessage);
- setTemplates(updated);
- },
- [lastMessage] // eslint-disable-line react-hooks/exhaustive-deps
- );
-
- return templates;
-}
-
-function updateTemplate(template, message) {
- const recentJobs = [...(template.summary_fields.recent_jobs || [])];
- const job = {
- id: message.unified_job_id,
- status: message.status,
- finished: message.finished || null,
- type: message.type,
- };
- const index = recentJobs.findIndex((j) => j.id === job.id);
- if (index > -1) {
- recentJobs[index] = {
- ...recentJobs[index],
- ...job,
- };
- } else {
- recentJobs.unshift(job);
- }
-
- return {
- ...template,
- summary_fields: {
- ...template.summary_fields,
- recent_jobs: recentJobs.slice(0, 10),
- },
- };
-}
diff --git a/awx/ui/src/hooks/useWsTemplates.test.js b/awx/ui/src/hooks/useWsTemplates.test.js
deleted file mode 100644
index 14490638ade0..000000000000
--- a/awx/ui/src/hooks/useWsTemplates.test.js
+++ /dev/null
@@ -1,193 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import WS from 'jest-websocket-mock';
-import { mountWithContexts } from '../../testUtils/enzymeHelpers';
-import useWsTemplates from './useWsTemplates';
-
-/*
- Jest mock timers don’t play well with jest-websocket-mock,
- so we'll stub out throttling to resolve immediately
-*/
-jest.mock('./useThrottle', () => ({
- __esModule: true,
- default: jest.fn((val) => val),
-}));
-
-function TestInner() {
- return ;
-}
-function Test({ templates }) {
- const syncedTemplates = useWsTemplates(templates);
- return ;
-}
-
-describe('useWsTemplates hook', () => {
- let debug;
- let wrapper;
- beforeEach(() => {
- debug = global.console.debug; // eslint-disable-line prefer-destructuring
- global.console.debug = () => {};
- });
-
- afterEach(() => {
- global.console.debug = debug;
- });
-
- test('should return templates list', () => {
- const templates = [{ id: 1 }];
- wrapper = mountWithContexts();
-
- expect(wrapper.find('TestInner').prop('templates')).toEqual(templates);
- WS.clean();
- });
-
- test('should establish websocket connection', async () => {
- global.document.cookie = 'csrftoken=abc123';
- const mockServer = new WS('ws://localhost/websocket/');
-
- const templates = [{ id: 1 }];
- await act(async () => {
- wrapper = await mountWithContexts();
- });
-
- await mockServer.connected;
- await expect(mockServer).toReceiveMessage(
- JSON.stringify({
- xrftoken: 'abc123',
- groups: {
- jobs: ['status_changed'],
- control: ['limit_reached_1'],
- },
- })
- );
- WS.clean();
- });
-
- test('should update recent job status', async () => {
- global.document.cookie = 'csrftoken=abc123';
- const mockServer = new WS('ws://localhost/websocket/');
-
- const templates = [
- {
- id: 1,
- summary_fields: {
- recent_jobs: [
- {
- id: 10,
- type: 'job',
- status: 'running',
- },
- {
- id: 11,
- type: 'job',
- status: 'successful',
- },
- ],
- },
- },
- ];
- await act(async () => {
- wrapper = await mountWithContexts();
- });
-
- await mockServer.connected;
- await expect(mockServer).toReceiveMessage(
- JSON.stringify({
- xrftoken: 'abc123',
- groups: {
- jobs: ['status_changed'],
- control: ['limit_reached_1'],
- },
- })
- );
- expect(
- wrapper.find('TestInner').prop('templates')[0].summary_fields
- .recent_jobs[0].status
- ).toEqual('running');
- act(() => {
- mockServer.send(
- JSON.stringify({
- unified_job_template_id: 1,
- unified_job_id: 10,
- type: 'job',
- status: 'successful',
- })
- );
- });
- wrapper.update();
-
- expect(
- wrapper.find('TestInner').prop('templates')[0].summary_fields
- .recent_jobs[0].status
- ).toEqual('successful');
- WS.clean();
- });
-
- test('should add new job status', async () => {
- global.document.cookie = 'csrftoken=abc123';
- const mockServer = new WS('ws://localhost/websocket/');
-
- const templates = [
- {
- id: 1,
- summary_fields: {
- recent_jobs: [
- {
- id: 10,
- type: 'job',
- status: 'running',
- },
- {
- id: 11,
- type: 'job',
- status: 'successful',
- },
- ],
- },
- },
- ];
- await act(async () => {
- wrapper = await mountWithContexts();
- });
-
- await mockServer.connected;
- await expect(mockServer).toReceiveMessage(
- JSON.stringify({
- xrftoken: 'abc123',
- groups: {
- jobs: ['status_changed'],
- control: ['limit_reached_1'],
- },
- })
- );
- expect(
- wrapper.find('TestInner').prop('templates')[0].summary_fields
- .recent_jobs[0].status
- ).toEqual('running');
- act(() => {
- mockServer.send(
- JSON.stringify({
- unified_job_template_id: 1,
- unified_job_id: 13,
- type: 'job',
- status: 'running',
- })
- );
- });
- wrapper.update();
-
- expect(
- wrapper.find('TestInner').prop('templates')[0].summary_fields.recent_jobs
- ).toHaveLength(3);
- expect(
- wrapper.find('TestInner').prop('templates')[0].summary_fields
- .recent_jobs[0]
- ).toEqual({
- id: 13,
- status: 'running',
- finished: null,
- type: 'job',
- });
- WS.clean();
- });
-});
diff --git a/awx/ui/src/i18nLoader.js b/awx/ui/src/i18nLoader.js
deleted file mode 100644
index 4c039b406f04..000000000000
--- a/awx/ui/src/i18nLoader.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import { i18n } from '@lingui/core';
-import { en, fr, es, ko, nl, ja, zh, zu } from 'make-plural/plurals';
-
-export const locales = {
- en: 'English',
- ja: 'Japanese',
- zu: 'Zulu',
- fr: 'French',
- es: 'Spanish',
- ko: 'Korean',
- zh: 'Chinese',
- nl: 'Dutch',
-};
-
-i18n.loadLocaleData({
- en: { plurals: en },
- fr: { plurals: fr },
- es: { plurals: es },
- ko: { plurals: ko },
- nl: { plurals: nl },
- ja: { plurals: ja },
- zh: { plurals: zh },
- zu: { plurals: zu },
-});
-
-/**
- * We do a dynamic import of just the catalog that we need
- * @param locale any locale string
- */
-export async function dynamicActivate(locale) {
- const { messages } = await import(`./locales/${locale}/messages`);
- i18n.load(locale, messages);
- i18n.activate(locale);
-}
diff --git a/awx/ui/src/index.js b/awx/ui/src/index.js
deleted file mode 100644
index 080a1313c294..000000000000
--- a/awx/ui/src/index.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import './setupCSP';
-import '@patternfly/react-core/dist/styles/base.css';
-import './border.css';
-import App from './App';
-
-ReactDOM.render(
-
-
- ,
- document.getElementById('app') || document.createElement('div')
-);
diff --git a/awx/ui/src/index.test.js b/awx/ui/src/index.test.js
deleted file mode 100644
index a0419c993385..000000000000
--- a/awx/ui/src/index.test.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import App from './App';
-
-jest.mock('react-dom', () => ({ render: jest.fn() }));
-jest.mock('util/webWorker', () => jest.fn());
-
-describe('index.jsx', () => {
- it('renders ok', () => {
- const div = document.createElement('div');
- div.setAttribute('id', 'app');
- document.body.appendChild(div);
- require('./index.js'); // eslint-disable-line global-require
- expect(ReactDOM.render).toHaveBeenCalledWith(
-
-
- ,
- div
- );
- });
-});
diff --git a/awx/ui/src/locales/en/messages.po b/awx/ui/src/locales/en/messages.po
deleted file mode 100644
index 11df1f4a9950..000000000000
--- a/awx/ui/src/locales/en/messages.po
+++ /dev/null
@@ -1,11053 +0,0 @@
-msgid ""
-msgstr ""
-"POT-Creation-Date: 2018-12-10 10:08-0500\n"
-"Mime-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: @lingui/cli\n"
-"Language: en\n"
-"Project-Id-Version: \n"
-"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: \n"
-"Last-Translator: \n"
-"Language-Team: \n"
-"Plural-Forms: \n"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:43
-msgid "(Limited to first 10)"
-msgstr "(Limited to first 10)"
-
-#: components/TemplateList/TemplateListItem.js:103
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:154
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:90
-msgid "(Prompt on launch)"
-msgstr "(Prompt on launch)"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:284
-msgid "* This field will be retrieved from an external secret management system using the specified credential."
-msgstr "* This field will be retrieved from an external secret management system using the specified credential."
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:243
-msgid "/ (project root)"
-msgstr "/ (project root)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:13
-#: constants.js:19
-msgid "0 (Normal)"
-msgstr "0 (Normal)"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:34
-msgid "0 (Warning)"
-msgstr "0 (Warning)"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:35
-msgid "1 (Info)"
-msgstr "1 (Info)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:14
-#: constants.js:20
-msgid "1 (Verbose)"
-msgstr "1 (Verbose)"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:36
-msgid "2 (Debug)"
-msgstr "2 (Debug)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:15
-#: constants.js:21
-msgid "2 (More Verbose)"
-msgstr "2 (More Verbose)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:16
-#: constants.js:22
-msgid "3 (Debug)"
-msgstr "3 (Debug)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:17
-#: constants.js:23
-msgid "4 (Connection Debug)"
-msgstr "4 (Connection Debug)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:18
-#: constants.js:24
-msgid "5 (WinRM Debug)"
-msgstr "5 (WinRM Debug)"
-
-#: screens/Project/shared/Project.helptext.js:76
-msgid ""
-"A refspec to fetch (passed to the Ansible git\n"
-"module). This parameter allows access to references via\n"
-"the branch field not otherwise available."
-msgstr ""
-"A refspec to fetch (passed to the Ansible git\n"
-"module). This parameter allows access to references via\n"
-"the branch field not otherwise available."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:122
-msgid "A subscription manifest is an export of a Red Hat Subscription. To generate a subscription manifest, go to <0>access.redhat.com0>. For more information, see the <1>User Guide1>."
-msgstr "A subscription manifest is an export of a Red Hat Subscription. To generate a subscription manifest, go to <0>access.redhat.com0>. For more information, see the <1>User Guide1>."
-
-#: screens/Job/WorkflowOutput/WorkflowOutputNode.js:143
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:299
-msgid "ALL"
-msgstr "ALL"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:279
-msgid "API Service/Integration Key"
-msgstr "API Service/Integration Key"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:282
-msgid "API Token"
-msgstr "API Token"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:297
-msgid "API service/integration key"
-msgstr "API service/integration key"
-
-#: components/AppContainer/PageHeaderToolbar.js:130
-msgid "About"
-msgstr ""
-
-#: routeConfig.js:92
-#: screens/ActivityStream/ActivityStream.js:179
-#: screens/Credential/Credential.js:74
-#: screens/Credential/Credentials.js:29
-#: screens/Inventory/Inventories.js:59
-#: screens/Inventory/Inventory.js:65
-#: screens/Inventory/SmartInventory.js:67
-#: screens/Organization/Organization.js:124
-#: screens/Organization/Organizations.js:31
-#: screens/Project/Project.js:105
-#: screens/Project/Projects.js:27
-#: screens/Team/Team.js:58
-#: screens/Team/Teams.js:31
-#: screens/Template/Template.js:136
-#: screens/Template/Templates.js:45
-#: screens/Template/WorkflowJobTemplate.js:118
-msgid "Access"
-msgstr ""
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:75
-msgid "Access Token Expiration"
-msgstr "Access Token Expiration"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:347
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:408
-msgid "Account SID"
-msgstr "Account SID"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:383
-msgid "Account token"
-msgstr "Account token"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:50
-msgid "Action"
-msgstr "Action"
-
-#: components/JobList/JobList.js:249
-#: components/JobList/JobListItem.js:103
-#: components/RelatedTemplateList/RelatedTemplateList.js:189
-#: components/Schedule/ScheduleList/ScheduleList.js:171
-#: components/Schedule/ScheduleList/ScheduleListItem.js:114
-#: components/SelectedList/DraggableSelectedList.js:101
-#: components/TemplateList/TemplateList.js:246
-#: components/TemplateList/TemplateListItem.js:195
-#: screens/ActivityStream/ActivityStream.js:266
-#: screens/ActivityStream/ActivityStreamListItem.js:49
-#: screens/Application/ApplicationsList/ApplicationListItem.js:48
-#: screens/Application/ApplicationsList/ApplicationsList.js:160
-#: screens/Credential/CredentialList/CredentialList.js:166
-#: screens/Credential/CredentialList/CredentialListItem.js:66
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:177
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:38
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:168
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:87
-#: screens/Host/HostGroups/HostGroupItem.js:34
-#: screens/Host/HostGroups/HostGroupsList.js:177
-#: screens/Host/HostList/HostList.js:172
-#: screens/Host/HostList/HostListItem.js:70
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:214
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:75
-#: screens/InstanceGroup/Instances/InstanceList.js:260
-#: screens/InstanceGroup/Instances/InstanceListItem.js:171
-#: screens/Instances/InstanceList/InstanceList.js:155
-#: screens/Instances/InstanceList/InstanceListItem.js:183
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:218
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:53
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:39
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:142
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:41
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:187
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:44
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:140
-#: screens/Inventory/InventoryList/InventoryList.js:222
-#: screens/Inventory/InventoryList/InventoryListItem.js:131
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:233
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:44
-#: screens/Inventory/InventorySources/InventorySourceList.js:214
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:101
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:102
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:73
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:181
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:124
-#: screens/Organization/OrganizationList/OrganizationList.js:146
-#: screens/Organization/OrganizationList/OrganizationListItem.js:69
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:86
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:19
-#: screens/Project/ProjectList/ProjectList.js:225
-#: screens/Project/ProjectList/ProjectListItem.js:222
-#: screens/Team/TeamList/TeamList.js:144
-#: screens/Team/TeamList/TeamListItem.js:47
-#: screens/Template/Survey/SurveyList.js:105
-#: screens/Template/Survey/SurveyList.js:105
-#: screens/Template/Survey/SurveyListItem.js:90
-#: screens/User/UserList/UserList.js:164
-#: screens/User/UserList/UserListItem.js:56
-msgid "Actions"
-msgstr "Actions"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:98
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:61
-#: components/TemplateList/TemplateListItem.js:277
-#: screens/Host/HostDetail/HostDetail.js:71
-#: screens/Host/HostList/HostListItem.js:95
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:217
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:50
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:77
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:101
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:33
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:116
-msgid "Activity"
-msgstr "Activity"
-
-#: routeConfig.js:49
-#: screens/ActivityStream/ActivityStream.js:43
-#: screens/ActivityStream/ActivityStream.js:121
-#: screens/Setting/Settings.js:43
-msgid "Activity Stream"
-msgstr "Activity Stream"
-
-#: screens/ActivityStream/ActivityStream.js:124
-msgid "Activity Stream type selector"
-msgstr "Activity Stream type selector"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:210
-msgid "Actor"
-msgstr "Actor"
-
-#: components/AddDropDownButton/AddDropDownButton.js:40
-#: components/PaginatedTable/ToolbarAddButton.js:14
-msgid "Add"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkAddModal.js:14
-msgid "Add Link"
-msgstr "Add Link"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.js:78
-msgid "Add Node"
-msgstr "Add Node"
-
-#: screens/Template/Templates.js:49
-msgid "Add Question"
-msgstr "Add Question"
-
-#: components/AddRole/AddResourceRole.js:164
-msgid "Add Roles"
-msgstr ""
-
-#: components/AddRole/AddResourceRole.js:161
-msgid "Add Team Roles"
-msgstr ""
-
-#: components/AddRole/AddResourceRole.js:158
-msgid "Add User Roles"
-msgstr ""
-
-#: components/Workflow/WorkflowStartNode.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:211
-msgid "Add a new node"
-msgstr "Add a new node"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:49
-msgid "Add a new node between these two nodes"
-msgstr "Add a new node between these two nodes"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:113
-msgid "Add container group"
-msgstr "Add container group"
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:140
-msgid "Add existing group"
-msgstr "Add existing group"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:152
-msgid "Add existing host"
-msgstr "Add existing host"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:114
-msgid "Add instance group"
-msgstr "Add instance group"
-
-#: screens/Inventory/InventoryList/InventoryList.js:136
-msgid "Add inventory"
-msgstr "Add inventory"
-
-#: components/TemplateList/TemplateList.js:151
-msgid "Add job template"
-msgstr "Add job template"
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:141
-msgid "Add new group"
-msgstr "Add new group"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:153
-msgid "Add new host"
-msgstr "Add new host"
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:77
-msgid "Add resource type"
-msgstr "Add resource type"
-
-#: screens/Inventory/InventoryList/InventoryList.js:137
-msgid "Add smart inventory"
-msgstr "Add smart inventory"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:202
-msgid "Add team permissions"
-msgstr "Add team permissions"
-
-#: screens/User/UserRoles/UserRolesList.js:198
-msgid "Add user permissions"
-msgstr "Add user permissions"
-
-#: components/TemplateList/TemplateList.js:152
-msgid "Add workflow template"
-msgstr "Add workflow template"
-
-#: routeConfig.js:113
-#: screens/ActivityStream/ActivityStream.js:190
-msgid "Administration"
-msgstr ""
-
-#: components/DataListToolbar/DataListToolbar.js:139
-#: screens/Job/JobOutput/JobOutputSearch.js:137
-msgid "Advanced"
-msgstr "Advanced"
-
-#: components/Search/AdvancedSearch.js:318
-msgid "Advanced search documentation"
-msgstr "Advanced search documentation"
-
-#: components/Search/AdvancedSearch.js:211
-#: components/Search/AdvancedSearch.js:225
-msgid "Advanced search value input"
-msgstr "Advanced search value input"
-
-#: screens/Inventory/shared/Inventory.helptext.js:131
-msgid ""
-"After every project update where the SCM revision\n"
-"changes, refresh the inventory from the selected source\n"
-"before executing job tasks. This is intended for static content,\n"
-"like the Ansible inventory .ini file format."
-msgstr ""
-"After every project update where the SCM revision\n"
-"changes, refresh the inventory from the selected source\n"
-"before executing job tasks. This is intended for static content,\n"
-"like the Ansible inventory .ini file format."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:517
-msgid "After number of occurrences"
-msgstr "After number of occurrences"
-
-#: components/AlertModal/AlertModal.js:75
-msgid "Alert modal"
-msgstr "Alert modal"
-
-#: components/LaunchButton/ReLaunchDropDown.js:48
-#: components/PromptDetail/PromptDetail.js:123
-#: screens/Metrics/Metrics.js:82
-#: screens/Metrics/Metrics.js:82
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:266
-msgid "All"
-msgstr "All"
-
-#: screens/Dashboard/DashboardGraph.js:137
-msgid "All job types"
-msgstr "All job types"
-
-#: screens/Dashboard/DashboardGraph.js:162
-msgid "All jobs"
-msgstr "All jobs"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:97
-msgid "Allow Branch Override"
-msgstr "Allow Branch Override"
-
-#: components/PromptDetail/PromptProjectDetail.js:66
-#: screens/Project/ProjectDetail/ProjectDetail.js:120
-msgid "Allow branch override"
-msgstr "Allow branch override"
-
-#: screens/Project/shared/Project.helptext.js:122
-msgid ""
-"Allow changing the Source Control branch or revision in a job\n"
-"template that uses this project."
-msgstr ""
-"Allow changing the Source Control branch or revision in a job\n"
-"template that uses this project."
-
-#: screens/Application/shared/Application.helptext.js:6
-msgid "Allowed URIs list, space separated"
-msgstr "Allowed URIs list, space separated"
-
-#: components/Workflow/WorkflowLegend.js:130
-#: components/Workflow/WorkflowLinkHelp.js:24
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:58
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:46
-msgid "Always"
-msgstr "Always"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:98
-msgid "Amazon EC2"
-msgstr "Amazon EC2"
-
-#: components/Lookup/shared/LookupErrorMessage.js:12
-msgid "An error occurred"
-msgstr "An error occurred"
-
-#: components/LaunchPrompt/steps/useInventoryStep.js:35
-msgid "An inventory must be selected"
-msgstr "An inventory must be selected"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:105
-#~ msgid "Ansible Controller"
-#~ msgstr "Ansible Controller"
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:96
-msgid "Ansible Controller Documentation."
-msgstr "Ansible Controller Documentation."
-
-#: screens/Template/Survey/SurveyQuestionForm.js:43
-msgid "Answer type"
-msgstr "Answer type"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:177
-msgid "Answer variable name"
-msgstr "Answer variable name"
-
-#: components/PromptDetail/PromptDetail.js:123
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:263
-msgid "Any"
-msgstr "Any"
-
-#: components/Lookup/ApplicationLookup.js:83
-#: screens/User/UserTokenDetail/UserTokenDetail.js:38
-#: screens/User/shared/UserTokenForm.js:48
-msgid "Application"
-msgstr "Application"
-
-#: screens/User/UserTokenList/UserTokenList.js:187
-msgid "Application Name"
-msgstr "Application Name"
-
-#: screens/Application/Applications.js:67
-#: screens/Application/Applications.js:70
-msgid "Application information"
-msgstr "Application information"
-
-#: screens/User/UserTokenList/UserTokenList.js:123
-#: screens/User/UserTokenList/UserTokenList.js:134
-msgid "Application name"
-msgstr "Application name"
-
-#: screens/Application/Application/Application.js:95
-msgid "Application not found."
-msgstr "Application not found."
-
-#: components/Lookup/ApplicationLookup.js:95
-#: routeConfig.js:142
-#: screens/Application/Applications.js:26
-#: screens/Application/Applications.js:35
-#: screens/Application/ApplicationsList/ApplicationsList.js:113
-#: screens/Application/ApplicationsList/ApplicationsList.js:148
-#: util/getRelatedResourceDeleteDetails.js:208
-msgid "Applications"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStream.js:211
-msgid "Applications & Tokens"
-msgstr "Applications & Tokens"
-
-#: components/NotificationList/NotificationListItem.js:39
-#: components/NotificationList/NotificationListItem.js:40
-#: components/Workflow/WorkflowLegend.js:114
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:67
-msgid "Approval"
-msgstr "Approval"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:99
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:106
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:130
-msgid "Approve"
-msgstr "Approve"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:115
-msgid "Approve, cancel or deny"
-msgstr "Approve, cancel or deny"
-
-#: components/StatusLabel/StatusLabel.js:31
-msgid "Approved"
-msgstr "Approved"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:11
-msgid "Approved - {0}. See the Activity Stream for more information."
-msgstr "Approved - {0}. See the Activity Stream for more information."
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:7
-msgid "Approved by {0} - {1}"
-msgstr "Approved by {0} - {1}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:128
-msgid "April"
-msgstr "April"
-
-#: components/JobCancelButton/JobCancelButton.js:89
-msgid "Are you sure you want to cancel this job?"
-msgstr "Are you sure you want to cancel this job?"
-
-#: components/DeleteButton/DeleteButton.js:127
-msgid "Are you sure you want to delete:"
-msgstr ""
-
-#: screens/Setting/shared/SharedFields.js:142
-msgid "Are you sure you want to disable local authentication? Doing so could impact users' ability to log in and the system administrator's ability to reverse this change."
-msgstr "Are you sure you want to disable local authentication? Doing so could impact users' ability to log in and the system administrator's ability to reverse this change."
-
-#: screens/Setting/shared/SharedFields.js:350
-msgid "Are you sure you want to edit login redirect override URL? Doing so could impact users' ability to log in to the system once local authentication is also disabled."
-msgstr "Are you sure you want to edit login redirect override URL? Doing so could impact users' ability to log in to the system once local authentication is also disabled."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:41
-msgid "Are you sure you want to exit the Workflow Creator without saving your changes?"
-msgstr "Are you sure you want to exit the Workflow Creator without saving your changes?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:40
-msgid "Are you sure you want to remove all the nodes in this workflow?"
-msgstr "Are you sure you want to remove all the nodes in this workflow?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:56
-msgid "Are you sure you want to remove the node below:"
-msgstr "Are you sure you want to remove the node below:"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:43
-msgid "Are you sure you want to remove this link?"
-msgstr "Are you sure you want to remove this link?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:63
-msgid "Are you sure you want to remove this node?"
-msgstr "Are you sure you want to remove this node?"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:43
-msgid "Are you sure you want to remove {0} access from {1}? Doing so affects all members of the team."
-msgstr ""
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:50
-msgid "Are you sure you want to remove {0} access from {username}?"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutput.js:771
-msgid "Are you sure you want to submit the request to cancel this job?"
-msgstr "Are you sure you want to submit the request to cancel this job?"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:102
-#: components/AdHocCommands/AdHocDetailsStep.js:104
-msgid "Arguments"
-msgstr "Arguments"
-
-#: screens/Job/JobDetail/JobDetail.js:541
-msgid "Artifacts"
-msgstr "Artifacts"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:222
-#: screens/User/UserTeams/UserTeamList.js:208
-msgid "Associate"
-msgstr "Associate"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:244
-#: screens/User/UserRoles/UserRolesList.js:240
-msgid "Associate role error"
-msgstr "Associate role error"
-
-#: components/AssociateModal/AssociateModal.js:98
-msgid "Association modal"
-msgstr "Association modal"
-
-#: components/LaunchPrompt/steps/SurveyStep.js:166
-msgid "At least one value must be selected for this field."
-msgstr "At least one value must be selected for this field."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:148
-msgid "August"
-msgstr "August"
-
-#: screens/Setting/SettingList.js:52
-msgid "Authentication"
-msgstr ""
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:88
-msgid "Authorization Code Expiration"
-msgstr "Authorization Code Expiration"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:80
-#: screens/Application/shared/ApplicationForm.js:84
-msgid "Authorization grant type"
-msgstr "Authorization grant type"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:208
-#: screens/InstanceGroup/Instances/InstanceListItem.js:204
-#: screens/Instances/InstanceDetail/InstanceDetail.js:159
-#: screens/Instances/InstanceList/InstanceListItem.js:219
-msgid "Auto"
-msgstr "Auto"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:71
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:72
-msgid "Automation Analytics"
-msgstr "Automation Analytics"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:111
-msgid "Automation Analytics dashboard"
-msgstr "Automation Analytics dashboard"
-
-#: screens/Setting/Settings.js:46
-msgid "Azure AD"
-msgstr "Azure AD"
-
-#: screens/Setting/SettingList.js:57
-msgid "Azure AD settings"
-msgstr "Azure AD settings"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:50
-#: components/AddRole/AddResourceRole.js:267
-#: components/LaunchPrompt/LaunchPrompt.js:128
-#: components/Schedule/shared/SchedulePromptableFields.js:132
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:90
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:70
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:152
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:155
-msgid "Back"
-msgstr "Back"
-
-#: screens/Credential/Credential.js:65
-msgid "Back to Credentials"
-msgstr "Back to Credentials"
-
-#: components/ContentError/ContentError.js:43
-msgid "Back to Dashboard."
-msgstr "Back to Dashboard."
-
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:49
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:50
-msgid "Back to Groups"
-msgstr "Back to Groups"
-
-#: screens/Host/Host.js:50
-#: screens/Inventory/InventoryHost/InventoryHost.js:66
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:53
-msgid "Back to Hosts"
-msgstr "Back to Hosts"
-
-#: screens/InstanceGroup/InstanceGroup.js:61
-msgid "Back to Instance Groups"
-msgstr "Back to Instance Groups"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:171
-#: screens/Instances/Instance.js:18
-msgid "Back to Instances"
-msgstr "Back to Instances"
-
-#: screens/Inventory/Inventory.js:57
-#: screens/Inventory/SmartInventory.js:60
-msgid "Back to Inventories"
-msgstr "Back to Inventories"
-
-#: screens/Job/Job.js:110
-msgid "Back to Jobs"
-msgstr "Back to Jobs"
-
-#: screens/NotificationTemplate/NotificationTemplate.js:76
-msgid "Back to Notifications"
-msgstr "Back to Notifications"
-
-#: screens/Organization/Organization.js:116
-msgid "Back to Organizations"
-msgstr "Back to Organizations"
-
-#: screens/Project/Project.js:97
-msgid "Back to Projects"
-msgstr "Back to Projects"
-
-#: components/Schedule/Schedule.js:64
-msgid "Back to Schedules"
-msgstr "Back to Schedules"
-
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:44
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:78
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:44
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:58
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:95
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:69
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:43
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:90
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:49
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:45
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:25
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:49
-#: screens/Setting/UI/UIDetail/UIDetail.js:59
-msgid "Back to Settings"
-msgstr "Back to Settings"
-
-#: screens/Inventory/InventorySource/InventorySource.js:76
-msgid "Back to Sources"
-msgstr "Back to Sources"
-
-#: screens/Team/Team.js:50
-msgid "Back to Teams"
-msgstr "Back to Teams"
-
-#: screens/Template/Template.js:128
-#: screens/Template/WorkflowJobTemplate.js:110
-msgid "Back to Templates"
-msgstr "Back to Templates"
-
-#: screens/User/UserToken/UserToken.js:47
-msgid "Back to Tokens"
-msgstr "Back to Tokens"
-
-#: screens/User/User.js:57
-msgid "Back to Users"
-msgstr "Back to Users"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:69
-msgid "Back to Workflow Approvals"
-msgstr "Back to Workflow Approvals"
-
-#: screens/Application/Application/Application.js:72
-msgid "Back to applications"
-msgstr "Back to applications"
-
-#: screens/CredentialType/CredentialType.js:55
-msgid "Back to credential types"
-msgstr "Back to credential types"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:57
-msgid "Back to execution environments"
-msgstr "Back to execution environments"
-
-#: screens/InstanceGroup/ContainerGroup.js:59
-msgid "Back to instance groups"
-msgstr "Back to instance groups"
-
-#: screens/ManagementJob/ManagementJob.js:98
-msgid "Back to management jobs"
-msgstr "Back to management jobs"
-
-#: screens/Project/shared/Project.helptext.js:8
-msgid ""
-"Base path used for locating playbooks. Directories\n"
-"found inside this path will be listed in the playbook directory drop-down.\n"
-"Together the base path and selected playbook directory provide the full\n"
-"path used to locate playbooks."
-msgstr ""
-"Base path used for locating playbooks. Directories\n"
-"found inside this path will be listed in the playbook directory drop-down.\n"
-"Together the base path and selected playbook directory provide the full\n"
-"path used to locate playbooks."
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:433
-msgid "Basic auth password"
-msgstr "Basic auth password"
-
-#: screens/Project/shared/Project.helptext.js:104
-msgid ""
-"Branch to checkout. In addition to branches,\n"
-"you can input tags, commit hashes, and arbitrary refs. Some\n"
-"commit hashes and refs may not be available unless you also\n"
-"provide a custom refspec."
-msgstr ""
-"Branch to checkout. In addition to branches,\n"
-"you can input tags, commit hashes, and arbitrary refs. Some\n"
-"commit hashes and refs may not be available unless you also\n"
-"provide a custom refspec."
-
-#: components/About/About.js:45
-msgid "Brand Image"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:158
-msgid "Browse"
-msgstr "Browse"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:94
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:115
-msgid "Browse…"
-msgstr "Browse…"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:36
-#~ msgid "By default, we collect and transmit analytics data on the serice usage to Red Hat. There are two categories of data collected by the service. For more information, see <0>this Tower documentation page0>. Uncheck the following boxes to disable this feature."
-#~ msgstr "By default, we collect and transmit analytics data on the serice usage to Red Hat. There are two categories of data collected by the service. For more information, see <0>this Tower documentation page0>. Uncheck the following boxes to disable this feature."
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:36
-msgid "By default, we collect and transmit analytics data on the service usage to Red Hat. There are two categories of data collected by the service. For more information, see <0>this Tower documentation page0>. Uncheck the following boxes to disable this feature."
-msgstr "By default, we collect and transmit analytics data on the service usage to Red Hat. There are two categories of data collected by the service. For more information, see <0>this Tower documentation page0>. Uncheck the following boxes to disable this feature."
-
-#: screens/TopologyView/Legend.js:74
-msgid "C"
-msgstr "C"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:221
-#: screens/InstanceGroup/Instances/InstanceListItem.js:145
-#: screens/Instances/InstanceDetail/InstanceDetail.js:171
-#: screens/Instances/InstanceList/InstanceListItem.js:155
-msgid "CPU {0}"
-msgstr "CPU {0}"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:113
-#: components/PromptDetail/PromptProjectDetail.js:136
-#: screens/Project/ProjectDetail/ProjectDetail.js:250
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:114
-msgid "Cache Timeout"
-msgstr "Cache Timeout"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:252
-msgid "Cache timeout"
-msgstr "Cache timeout"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:114
-msgid "Cache timeout (seconds)"
-msgstr "Cache timeout (seconds)"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:51
-#: components/AddRole/AddResourceRole.js:268
-#: components/AssociateModal/AssociateModal.js:114
-#: components/AssociateModal/AssociateModal.js:119
-#: components/DeleteButton/DeleteButton.js:120
-#: components/DeleteButton/DeleteButton.js:123
-#: components/DisassociateButton/DisassociateButton.js:139
-#: components/DisassociateButton/DisassociateButton.js:142
-#: components/FormActionGroup/FormActionGroup.js:23
-#: components/FormActionGroup/FormActionGroup.js:29
-#: components/LaunchPrompt/LaunchPrompt.js:129
-#: components/Lookup/HostFilterLookup.js:387
-#: components/Lookup/Lookup.js:203
-#: components/PaginatedTable/ToolbarDeleteButton.js:282
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:37
-#: components/Schedule/shared/ScheduleForm.js:462
-#: components/Schedule/shared/ScheduleForm.js:467
-#: components/Schedule/shared/ScheduleForm.js:678
-#: components/Schedule/shared/ScheduleForm.js:683
-#: components/Schedule/shared/SchedulePromptableFields.js:133
-#: screens/Credential/shared/CredentialForm.js:343
-#: screens/Credential/shared/CredentialForm.js:348
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:100
-#: screens/Credential/shared/ExternalTestModal.js:98
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:111
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:63
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:66
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:80
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:101
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:107
-#: screens/Setting/shared/RevertAllAlert.js:32
-#: screens/Setting/shared/RevertFormActionGroup.js:31
-#: screens/Setting/shared/RevertFormActionGroup.js:37
-#: screens/Setting/shared/SharedFields.js:133
-#: screens/Setting/shared/SharedFields.js:139
-#: screens/Setting/shared/SharedFields.js:346
-#: screens/Team/TeamRoles/TeamRolesList.js:228
-#: screens/Team/TeamRoles/TeamRolesList.js:231
-#: screens/Template/Survey/SurveyList.js:78
-#: screens/Template/Survey/SurveyReorderModal.js:211
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:31
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:39
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:45
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:50
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:162
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:165
-#: screens/User/UserRoles/UserRolesList.js:224
-#: screens/User/UserRoles/UserRolesList.js:227
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:84
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:92
-msgid "Cancel"
-msgstr ""
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:316
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:112
-msgid "Cancel Inventory Source Sync"
-msgstr "Cancel Inventory Source Sync"
-
-#: components/JobCancelButton/JobCancelButton.js:55
-#: screens/Job/JobOutput/JobOutput.js:747
-#: screens/Job/JobOutput/JobOutput.js:748
-msgid "Cancel Job"
-msgstr "Cancel Job"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:303
-#: screens/Project/ProjectList/ProjectListItem.js:230
-msgid "Cancel Project Sync"
-msgstr "Cancel Project Sync"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:318
-#: screens/Project/ProjectDetail/ProjectDetail.js:305
-msgid "Cancel Sync"
-msgstr "Cancel Sync"
-
-#: screens/Job/JobOutput/JobOutput.js:755
-#: screens/Job/JobOutput/JobOutput.js:758
-msgid "Cancel job"
-msgstr "Cancel job"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:42
-msgid "Cancel link changes"
-msgstr "Cancel link changes"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:34
-msgid "Cancel link removal"
-msgstr "Cancel link removal"
-
-#: components/Lookup/Lookup.js:201
-msgid "Cancel lookup"
-msgstr "Cancel lookup"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:28
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:47
-msgid "Cancel node removal"
-msgstr "Cancel node removal"
-
-#: screens/Setting/shared/RevertAllAlert.js:29
-msgid "Cancel revert"
-msgstr "Cancel revert"
-
-#: components/JobList/JobListCancelButton.js:93
-msgid "Cancel selected job"
-msgstr "Cancel selected job"
-
-#: components/JobList/JobListCancelButton.js:94
-msgid "Cancel selected jobs"
-msgstr "Cancel selected jobs"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:77
-msgid "Cancel subscription edit"
-msgstr "Cancel subscription edit"
-
-#: components/JobList/JobListItem.js:113
-#: screens/Job/JobDetail/JobDetail.js:582
-#: screens/Job/JobOutput/shared/OutputToolbar.js:137
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:89
-msgid "Cancel {0}"
-msgstr "Cancel {0}"
-
-#: components/JobList/JobList.js:234
-#: components/StatusLabel/StatusLabel.js:46
-#: components/Workflow/WorkflowNodeHelp.js:111
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:253
-msgid "Canceled"
-msgstr "Canceled"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:58
-msgid "Cannot approve, cancel or deny completed workflow approvals"
-msgstr "Cannot approve, cancel or deny completed workflow approvals"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:129
-msgid ""
-"Cannot enable log aggregator without providing\n"
-"logging aggregator host and logging aggregator type."
-msgstr ""
-"Cannot enable log aggregator without providing\n"
-"logging aggregator host and logging aggregator type."
-
-#: screens/Instances/InstanceList/InstanceList.js:148
-msgid "Cannot run health check on hop nodes."
-msgstr "Cannot run health check on hop nodes."
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:213
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:74
-msgid "Capacity"
-msgstr "Capacity"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:218
-#: screens/InstanceGroup/Instances/InstanceList.js:258
-#: screens/InstanceGroup/Instances/InstanceListItem.js:143
-#: screens/Instances/InstanceDetail/InstanceDetail.js:168
-#: screens/Instances/InstanceList/InstanceList.js:153
-#: screens/Instances/InstanceList/InstanceListItem.js:153
-msgid "Capacity Adjustment"
-msgstr "Capacity Adjustment"
-
-#: components/Search/LookupTypeInput.js:59
-msgid "Case-insensitive version of contains"
-msgstr "Case-insensitive version of contains"
-
-#: components/Search/LookupTypeInput.js:87
-msgid "Case-insensitive version of endswith."
-msgstr "Case-insensitive version of endswith."
-
-#: components/Search/LookupTypeInput.js:45
-msgid "Case-insensitive version of exact."
-msgstr "Case-insensitive version of exact."
-
-#: components/Search/LookupTypeInput.js:100
-msgid "Case-insensitive version of regex."
-msgstr "Case-insensitive version of regex."
-
-#: components/Search/LookupTypeInput.js:73
-msgid "Case-insensitive version of startswith."
-msgstr "Case-insensitive version of startswith."
-
-#: screens/Project/shared/Project.helptext.js:14
-msgid ""
-"Change PROJECTS_ROOT when deploying\n"
-"{brandName} to change this location."
-msgstr ""
-"Change PROJECTS_ROOT when deploying\n"
-"{brandName} to change this location."
-
-#: components/StatusLabel/StatusLabel.js:47
-#: screens/Job/JobOutput/shared/HostStatusBar.js:43
-msgid "Changed"
-msgstr "Changed"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:53
-msgid "Changes"
-msgstr "Changes"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:253
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:257
-msgid "Channel"
-msgstr "Channel"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:103
-#: screens/Template/shared/JobTemplateForm.js:214
-msgid "Check"
-msgstr "Check"
-
-#: components/Search/LookupTypeInput.js:134
-msgid "Check whether the given field or related object is null; expects a boolean value."
-msgstr "Check whether the given field or related object is null; expects a boolean value."
-
-#: components/Search/LookupTypeInput.js:140
-msgid "Check whether the given field's value is present in the list provided; expects a comma-separated list of items."
-msgstr "Check whether the given field's value is present in the list provided; expects a comma-separated list of items."
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:32
-msgid "Choose a .json file"
-msgstr "Choose a .json file"
-
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:78
-msgid "Choose a Notification Type"
-msgstr "Choose a Notification Type"
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:24
-msgid "Choose a Playbook Directory"
-msgstr "Choose a Playbook Directory"
-
-#: screens/Project/shared/ProjectForm.js:224
-msgid "Choose a Source Control Type"
-msgstr "Choose a Source Control Type"
-
-#: screens/Template/shared/WebhookSubForm.js:99
-msgid "Choose a Webhook Service"
-msgstr "Choose a Webhook Service"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:96
-#: screens/Template/shared/JobTemplateForm.js:207
-msgid "Choose a job type"
-msgstr "Choose a job type"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:82
-msgid "Choose a module"
-msgstr "Choose a module"
-
-#: screens/Inventory/shared/InventorySourceForm.js:140
-msgid "Choose a source"
-msgstr "Choose a source"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:475
-msgid "Choose an HTTP method"
-msgstr "Choose an HTTP method"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:46
-msgid ""
-"Choose an answer type or format you want as the prompt for the user.\n"
-"Refer to the Ansible Controller Documentation for more additional\n"
-"information about each option."
-msgstr ""
-"Choose an answer type or format you want as the prompt for the user.\n"
-"Refer to the Ansible Controller Documentation for more additional\n"
-"information about each option."
-
-#: components/AddRole/SelectRoleStep.js:20
-msgid "Choose roles to apply to the selected resources. Note that all selected roles will be applied to all selected resources."
-msgstr "Choose roles to apply to the selected resources. Note that all selected roles will be applied to all selected resources."
-
-#: components/AddRole/SelectResourceStep.js:81
-msgid "Choose the resources that will be receiving new roles. You'll be able to select the roles to apply in the next step. Note that the resources chosen here will receive all roles chosen in the next step."
-msgstr "Choose the resources that will be receiving new roles. You'll be able to select the roles to apply in the next step. Note that the resources chosen here will receive all roles chosen in the next step."
-
-#: components/AddRole/AddResourceRole.js:174
-msgid "Choose the type of resource that will be receiving new roles. For example, if you'd like to add new roles to a set of users please choose Users and click Next. You'll be able to select the specific resources in the next step."
-msgstr "Choose the type of resource that will be receiving new roles. For example, if you'd like to add new roles to a set of users please choose Users and click Next. You'll be able to select the specific resources in the next step."
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:70
-msgid "Clean"
-msgstr "Clean"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:95
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:116
-msgid "Clear"
-msgstr "Clear"
-
-#: components/DataListToolbar/DataListToolbar.js:95
-#: screens/Job/JobOutput/JobOutputSearch.js:145
-msgid "Clear all filters"
-msgstr "Clear all filters"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:247
-msgid "Clear subscription"
-msgstr "Clear subscription"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:252
-msgid "Clear subscription selection"
-msgstr "Clear subscription selection"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerGraph.js:245
-msgid "Click an available node to create a new link. Click outside the graph to cancel."
-msgstr "Click an available node to create a new link. Click outside the graph to cancel."
-
-#: screens/TopologyView/Tooltip.js:60
-msgid "Click on a node icon to display the details."
-msgstr "Click on a node icon to display the details."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:134
-msgid "Click the Edit button below to reconfigure the node."
-msgstr "Click the Edit button below to reconfigure the node."
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:71
-msgid "Click this button to verify connection to the secret management system using the selected credential and specified inputs."
-msgstr "Click this button to verify connection to the secret management system using the selected credential and specified inputs."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:152
-msgid "Click to create a new link to this node."
-msgstr "Click to create a new link to this node."
-
-#: screens/Template/Survey/SurveyToolbar.js:64
-msgid "Click to rearrange the order of the survey questions"
-msgstr "Click to rearrange the order of the survey questions"
-
-#: screens/Template/Survey/MultipleChoiceField.js:117
-msgid "Click to toggle default value"
-msgstr "Click to toggle default value"
-
-#: components/Workflow/WorkflowNodeHelp.js:202
-msgid "Click to view job details"
-msgstr "Click to view job details"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:88
-#: screens/Application/Applications.js:84
-msgid "Client ID"
-msgstr "Client ID"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:284
-msgid "Client Identifier"
-msgstr "Client Identifier"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:305
-msgid "Client identifier"
-msgstr "Client identifier"
-
-#: screens/Application/Applications.js:97
-msgid "Client secret"
-msgstr "Client secret"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:99
-#: screens/Application/shared/ApplicationForm.js:126
-msgid "Client type"
-msgstr "Client type"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:105
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:169
-msgid "Close"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:123
-msgid "Close subscription modal"
-msgstr "Close subscription modal"
-
-#: components/CredentialChip/CredentialChip.js:11
-msgid "Cloud"
-msgstr "Cloud"
-
-#: components/ExpandCollapse/ExpandCollapse.js:41
-msgid "Collapse"
-msgstr ""
-
-#: screens/Job/JobOutput/PageControls.js:50
-msgid "Collapse all job events"
-msgstr "Collapse all job events"
-
-#: screens/Job/JobOutput/shared/JobEventLineToggle.js:39
-msgid "Collapse section"
-msgstr "Collapse section"
-
-#: components/JobList/JobList.js:214
-#: components/JobList/JobListItem.js:45
-#: screens/Job/JobOutput/HostEventModal.js:129
-msgid "Command"
-msgstr "Command"
-
-#: components/Schedule/shared/ScheduleForm.js:453
-msgid "Complex schedules are not supported in the UI yet, please use the API to manage this schedule."
-msgstr "Complex schedules are not supported in the UI yet, please use the API to manage this schedule."
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:49
-msgid "Compliant"
-msgstr "Compliant"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:68
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:36
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:130
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:58
-#: screens/Template/shared/JobTemplateForm.js:539
-msgid "Concurrent Jobs"
-msgstr "Concurrent Jobs"
-
-#: screens/Template/shared/JobTemplate.helptext.js:35
-msgid "Concurrent jobs: If enabled, simultaneous runs of this job template will be allowed."
-msgstr "Concurrent jobs: If enabled, simultaneous runs of this job template will be allowed."
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:23
-msgid "Concurrent jobs: If enabled, simultaneous runs of this workflow job template will be allowed."
-msgstr "Concurrent jobs: If enabled, simultaneous runs of this workflow job template will be allowed."
-
-#: screens/Setting/shared/SharedFields.js:121
-#: screens/Setting/shared/SharedFields.js:127
-#: screens/Setting/shared/SharedFields.js:336
-msgid "Confirm"
-msgstr "Confirm"
-
-#: components/DeleteButton/DeleteButton.js:107
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:95
-msgid "Confirm Delete"
-msgstr "Confirm Delete"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:191
-msgid "Confirm Disable Local Authorization"
-msgstr "Confirm Disable Local Authorization"
-
-#: screens/User/shared/UserForm.js:99
-msgid "Confirm Password"
-msgstr "Confirm Password"
-
-#: components/JobCancelButton/JobCancelButton.js:71
-msgid "Confirm cancel job"
-msgstr "Confirm cancel job"
-
-#: components/JobCancelButton/JobCancelButton.js:75
-msgid "Confirm cancellation"
-msgstr "Confirm cancellation"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:26
-msgid "Confirm delete"
-msgstr "Confirm delete"
-
-#: screens/User/UserRoles/UserRolesList.js:215
-msgid "Confirm disassociate"
-msgstr "Confirm disassociate"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:24
-msgid "Confirm link removal"
-msgstr "Confirm link removal"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:37
-msgid "Confirm node removal"
-msgstr "Confirm node removal"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:18
-msgid "Confirm removal of all nodes"
-msgstr "Confirm removal of all nodes"
-
-#: screens/Setting/shared/RevertAllAlert.js:20
-msgid "Confirm revert all"
-msgstr "Confirm revert all"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:91
-msgid "Confirm selection"
-msgstr "Confirm selection"
-
-#: screens/Job/JobDetail/JobDetail.js:361
-msgid "Container Group"
-msgstr "Container Group"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:47
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:57
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:68
-msgid "Container group"
-msgstr "Container group"
-
-#: screens/InstanceGroup/ContainerGroup.js:84
-msgid "Container group not found."
-msgstr "Container group not found."
-
-#: components/LaunchPrompt/LaunchPrompt.js:123
-#: components/Schedule/shared/SchedulePromptableFields.js:127
-msgid "Content Loading"
-msgstr "Content Loading"
-
-#: components/AppContainer/AppContainer.js:142
-msgid "Continue"
-msgstr "Continue"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:196
-#: screens/Instances/InstanceList/InstanceList.js:116
-msgid "Control"
-msgstr "Control"
-
-#: screens/TopologyView/Legend.js:77
-msgid "Control node"
-msgstr "Control node"
-
-#: screens/Inventory/shared/Inventory.helptext.js:79
-msgid ""
-"Control the level of output Ansible\n"
-"will produce for inventory source update jobs."
-msgstr ""
-"Control the level of output Ansible\n"
-"will produce for inventory source update jobs."
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:139
-msgid ""
-"Control the level of output ansible\n"
-"will produce as the playbook executes."
-msgstr ""
-"Control the level of output ansible\n"
-"will produce as the playbook executes."
-
-#: screens/Template/shared/JobTemplateForm.js:464
-#~ msgid ""
-#~ "Control the level of output ansible will\n"
-#~ "produce as the playbook executes."
-#~ msgstr ""
-#~ "Control the level of output ansible will\n"
-#~ "produce as the playbook executes."
-
-#: screens/Job/Job.helptext.js:14
-#: screens/Template/shared/JobTemplate.helptext.js:15
-msgid "Control the level of output ansible will produce as the playbook executes."
-msgstr "Control the level of output ansible will produce as the playbook executes."
-
-#: screens/Job/JobDetail/JobDetail.js:346
-msgid "Controller Node"
-msgstr "Controller Node"
-
-#: components/PromptDetail/PromptDetail.js:121
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:225
-msgid "Convergence"
-msgstr "Convergence"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:256
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:257
-msgid "Convergence select"
-msgstr "Convergence select"
-
-#: components/CopyButton/CopyButton.js:40
-msgid "Copy"
-msgstr "Copy"
-
-#: screens/Credential/CredentialList/CredentialListItem.js:80
-msgid "Copy Credential"
-msgstr "Copy Credential"
-
-#: components/CopyButton/CopyButton.js:48
-msgid "Copy Error"
-msgstr "Copy Error"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:104
-msgid "Copy Execution Environment"
-msgstr "Copy Execution Environment"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:154
-msgid "Copy Inventory"
-msgstr "Copy Inventory"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:152
-msgid "Copy Notification Template"
-msgstr "Copy Notification Template"
-
-#: screens/Project/ProjectList/ProjectListItem.js:262
-msgid "Copy Project"
-msgstr "Copy Project"
-
-#: components/TemplateList/TemplateListItem.js:248
-msgid "Copy Template"
-msgstr "Copy Template"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:201
-#: screens/Project/ProjectList/ProjectListItem.js:98
-msgid "Copy full revision to clipboard."
-msgstr "Copy full revision to clipboard."
-
-#: components/About/About.js:35
-msgid "Copyright"
-msgstr "Copyright"
-
-#: components/MultiSelect/TagMultiSelect.js:62
-#: screens/Credential/shared/CredentialFormFields/BecomeMethodField.js:66
-#: screens/Inventory/shared/InventoryForm.js:83
-#: screens/Template/shared/JobTemplateForm.js:387
-#: screens/Template/shared/WorkflowJobTemplateForm.js:197
-msgid "Create"
-msgstr "Create"
-
-#: screens/Application/Applications.js:27
-#: screens/Application/Applications.js:36
-msgid "Create New Application"
-msgstr "Create New Application"
-
-#: screens/Credential/Credentials.js:15
-#: screens/Credential/Credentials.js:25
-msgid "Create New Credential"
-msgstr "Create New Credential"
-
-#: screens/Host/Hosts.js:15
-#: screens/Host/Hosts.js:24
-msgid "Create New Host"
-msgstr "Create New Host"
-
-#: screens/Template/Templates.js:18
-msgid "Create New Job Template"
-msgstr "Create New Job Template"
-
-#: screens/NotificationTemplate/NotificationTemplates.js:15
-#: screens/NotificationTemplate/NotificationTemplates.js:22
-msgid "Create New Notification Template"
-msgstr "Create New Notification Template"
-
-#: screens/Organization/Organizations.js:17
-#: screens/Organization/Organizations.js:27
-msgid "Create New Organization"
-msgstr ""
-
-#: screens/Project/Projects.js:13
-#: screens/Project/Projects.js:23
-msgid "Create New Project"
-msgstr "Create New Project"
-
-#: screens/Inventory/Inventories.js:91
-#: screens/ManagementJob/ManagementJobs.js:24
-#: screens/Project/Projects.js:32
-#: screens/Template/Templates.js:52
-msgid "Create New Schedule"
-msgstr "Create New Schedule"
-
-#: screens/Team/Teams.js:16
-#: screens/Team/Teams.js:26
-msgid "Create New Team"
-msgstr "Create New Team"
-
-#: screens/User/Users.js:16
-#: screens/User/Users.js:27
-msgid "Create New User"
-msgstr "Create New User"
-
-#: screens/Template/Templates.js:19
-msgid "Create New Workflow Template"
-msgstr "Create New Workflow Template"
-
-#: screens/Host/HostList/SmartInventoryButton.js:26
-msgid "Create a new Smart Inventory with the applied filter"
-msgstr "Create a new Smart Inventory with the applied filter"
-
-#: screens/InstanceGroup/InstanceGroups.js:47
-#: screens/InstanceGroup/InstanceGroups.js:57
-msgid "Create new container group"
-msgstr "Create new container group"
-
-#: screens/CredentialType/CredentialTypes.js:23
-msgid "Create new credential Type"
-msgstr "Create new credential Type"
-
-#: screens/CredentialType/CredentialTypes.js:14
-msgid "Create new credential type"
-msgstr "Create new credential type"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:14
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:23
-msgid "Create new execution environment"
-msgstr "Create new execution environment"
-
-#: screens/Inventory/Inventories.js:75
-#: screens/Inventory/Inventories.js:82
-msgid "Create new group"
-msgstr "Create new group"
-
-#: screens/Inventory/Inventories.js:66
-#: screens/Inventory/Inventories.js:80
-msgid "Create new host"
-msgstr "Create new host"
-
-#: screens/InstanceGroup/InstanceGroups.js:46
-#: screens/InstanceGroup/InstanceGroups.js:56
-msgid "Create new instance group"
-msgstr "Create new instance group"
-
-#: screens/Inventory/Inventories.js:18
-msgid "Create new inventory"
-msgstr "Create new inventory"
-
-#: screens/Inventory/Inventories.js:19
-msgid "Create new smart inventory"
-msgstr "Create new smart inventory"
-
-#: screens/Inventory/Inventories.js:85
-msgid "Create new source"
-msgstr "Create new source"
-
-#: screens/User/Users.js:35
-msgid "Create user token"
-msgstr "Create user token"
-
-#: components/Lookup/ApplicationLookup.js:114
-#: components/PromptDetail/PromptDetail.js:145
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:270
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:104
-#: screens/Credential/CredentialDetail/CredentialDetail.js:257
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:90
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:102
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:151
-#: screens/Host/HostDetail/HostDetail.js:84
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:67
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:93
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:134
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:43
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:82
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:293
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:151
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:47
-#: screens/Job/JobDetail/JobDetail.js:516
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:388
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:115
-#: screens/Project/ProjectDetail/ProjectDetail.js:274
-#: screens/Team/TeamDetail/TeamDetail.js:47
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:339
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:188
-#: screens/User/UserDetail/UserDetail.js:82
-#: screens/User/UserTokenDetail/UserTokenDetail.js:60
-#: screens/User/UserTokenList/UserTokenList.js:150
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:240
-msgid "Created"
-msgstr ""
-
-#: components/AdHocCommands/AdHocCredentialStep.js:122
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:112
-#: components/AddRole/AddResourceRole.js:57
-#: components/AssociateModal/AssociateModal.js:144
-#: components/LaunchPrompt/steps/CredentialsStep.js:173
-#: components/LaunchPrompt/steps/InventoryStep.js:89
-#: components/Lookup/CredentialLookup.js:193
-#: components/Lookup/InventoryLookup.js:161
-#: components/Lookup/InventoryLookup.js:216
-#: components/Lookup/MultiCredentialsLookup.js:193
-#: components/Lookup/OrganizationLookup.js:133
-#: components/Lookup/ProjectLookup.js:150
-#: components/NotificationList/NotificationList.js:206
-#: components/RelatedTemplateList/RelatedTemplateList.js:166
-#: components/Schedule/ScheduleList/ScheduleList.js:197
-#: components/TemplateList/TemplateList.js:226
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:27
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:58
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:104
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:127
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:173
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:196
-#: screens/Credential/CredentialList/CredentialList.js:150
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:96
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:132
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:102
-#: screens/Host/HostGroups/HostGroupsList.js:164
-#: screens/Host/HostList/HostList.js:157
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:199
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:129
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:174
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:128
-#: screens/Inventory/InventoryList/InventoryList.js:199
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:185
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:94
-#: screens/Organization/OrganizationList/OrganizationList.js:131
-#: screens/Project/ProjectList/ProjectList.js:213
-#: screens/Team/TeamList/TeamList.js:130
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:163
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:112
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:108
-msgid "Created By (Username)"
-msgstr "Created By (Username)"
-
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:81
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:147
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:73
-msgid "Created by (username)"
-msgstr "Created by (username)"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:54
-#: components/AdHocCommands/useAdHocCredentialStep.js:24
-#: components/PromptDetail/PromptInventorySourceDetail.js:119
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:40
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:52
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:50
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:274
-#: screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.js:36
-#: screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.js:36
-#: screens/Inventory/shared/InventorySourceSubForms/GCESubForm.js:36
-#: screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.js:36
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:83
-#: screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.js:35
-#: screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.js:37
-#: util/getRelatedResourceDeleteDetails.js:166
-msgid "Credential"
-msgstr "Credential"
-
-#: util/getRelatedResourceDeleteDetails.js:73
-msgid "Credential Input Sources"
-msgstr "Credential Input Sources"
-
-#: components/Lookup/InstanceGroupsLookup.js:108
-msgid "Credential Name"
-msgstr "Credential Name"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:241
-#: screens/Credential/CredentialList/CredentialList.js:158
-#: screens/Credential/shared/CredentialForm.js:128
-#: screens/Credential/shared/CredentialForm.js:196
-msgid "Credential Type"
-msgstr "Credential Type"
-
-#: routeConfig.js:117
-#: screens/ActivityStream/ActivityStream.js:192
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:118
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:161
-#: screens/CredentialType/CredentialTypes.js:13
-#: screens/CredentialType/CredentialTypes.js:22
-msgid "Credential Types"
-msgstr ""
-
-#: screens/Credential/CredentialList/CredentialList.js:113
-msgid "Credential copied successfully"
-msgstr "Credential copied successfully"
-
-#: screens/Credential/Credential.js:98
-msgid "Credential not found."
-msgstr "Credential not found."
-
-#: components/AdHocCommands/useAdHocCredentialPasswordStep.js:23
-#: components/LaunchPrompt/steps/useCredentialPasswordsStep.js:28
-msgid "Credential passwords"
-msgstr "Credential passwords"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:53
-msgid "Credential to authenticate with Kubernetes or OpenShift"
-msgstr "Credential to authenticate with Kubernetes or OpenShift"
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:57
-msgid "Credential to authenticate with Kubernetes or OpenShift. Must be of type \"Kubernetes/OpenShift API Bearer Token\". If left blank, the underlying Pod's service account will be used."
-msgstr "Credential to authenticate with Kubernetes or OpenShift. Must be of type \"Kubernetes/OpenShift API Bearer Token\". If left blank, the underlying Pod's service account will be used."
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:21
-msgid "Credential to authenticate with a protected container registry."
-msgstr "Credential to authenticate with a protected container registry."
-
-#: screens/CredentialType/CredentialType.js:76
-msgid "Credential type not found."
-msgstr "Credential type not found."
-
-#: components/JobList/JobListItem.js:260
-#: components/LaunchPrompt/steps/CredentialsStep.js:190
-#: components/LaunchPrompt/steps/useCredentialsStep.js:62
-#: components/Lookup/MultiCredentialsLookup.js:138
-#: components/Lookup/MultiCredentialsLookup.js:210
-#: components/PromptDetail/PromptDetail.js:183
-#: components/PromptDetail/PromptJobTemplateDetail.js:186
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:324
-#: components/TemplateList/TemplateListItem.js:322
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:77
-#: routeConfig.js:70
-#: screens/ActivityStream/ActivityStream.js:167
-#: screens/Credential/CredentialList/CredentialList.js:195
-#: screens/Credential/Credentials.js:14
-#: screens/Credential/Credentials.js:24
-#: screens/Job/JobDetail/JobDetail.js:414
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:360
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:51
-#: screens/Template/shared/JobTemplateForm.js:365
-#: util/getRelatedResourceDeleteDetails.js:90
-msgid "Credentials"
-msgstr ""
-
-#: components/LaunchPrompt/steps/credentialsValidator.js:52
-msgid "Credentials that require passwords on launch are not permitted. Please remove or replace the following credentials with a credential of the same type in order to proceed: {0}"
-msgstr "Credentials that require passwords on launch are not permitted. Please remove or replace the following credentials with a credential of the same type in order to proceed: {0}"
-
-#: components/Pagination/Pagination.js:34
-msgid "Current page"
-msgstr ""
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:85
-msgid "Custom Kubernetes or OpenShift Pod specification."
-msgstr "Custom Kubernetes or OpenShift Pod specification."
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:79
-msgid "Custom pod spec"
-msgstr "Custom pod spec"
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:79
-#: screens/Organization/OrganizationList/OrganizationListItem.js:55
-#: screens/Project/ProjectList/ProjectListItem.js:188
-msgid "Custom virtual environment {0} must be replaced by an execution environment."
-msgstr "Custom virtual environment {0} must be replaced by an execution environment."
-
-#: components/TemplateList/TemplateListItem.js:163
-msgid "Custom virtual environment {0} must be replaced by an execution environment. For more information about migrating to execution environments see <0>the documentation.0>"
-msgstr "Custom virtual environment {0} must be replaced by an execution environment. For more information about migrating to execution environments see <0>the documentation.0>"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:73
-msgid "Custom virtual environment {virtualEnvironment} must be replaced by an execution environment. For more information about migrating to execution environments see <0>the documentation.0>"
-msgstr "Custom virtual environment {virtualEnvironment} must be replaced by an execution environment. For more information about migrating to execution environments see <0>the documentation.0>"
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:61
-msgid "Customize messages…"
-msgstr "Customize messages…"
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:65
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:66
-msgid "Customize pod specification"
-msgstr "Customize pod specification"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputNode.js:109
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:185
-msgid "DELETED"
-msgstr "DELETED"
-
-#: routeConfig.js:34
-#: screens/Dashboard/Dashboard.js:74
-msgid "Dashboard"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStream.js:147
-msgid "Dashboard (all activity)"
-msgstr "Dashboard (all activity)"
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:75
-msgid "Data retention period"
-msgstr "Data retention period"
-
-#: screens/Dashboard/shared/LineChart.js:168
-msgid "Date"
-msgstr "Date"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:349
-#: components/Schedule/shared/FrequencyDetailSubform.js:453
-#: components/Schedule/shared/ScheduleForm.js:156
-msgid "Day"
-msgstr "Day"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:266
-#: components/Schedule/shared/ScheduleForm.js:167
-msgid "Days of Data to Keep"
-msgstr "Days of Data to Keep"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/DaysToKeepStep.js:28
-msgid "Days of data to be retained"
-msgstr "Days of data to be retained"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:105
-msgid "Days remaining"
-msgstr "Days remaining"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useDaysToKeepStep.js:35
-msgid "Days to keep"
-msgstr "Days to keep"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:103
-msgid "Debug"
-msgstr "Debug"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:168
-msgid "December"
-msgstr "December"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:102
-#: screens/Template/Survey/SurveyList.js:104
-#: screens/Template/Survey/SurveyList.js:104
-#: screens/Template/Survey/SurveyListItem.js:63
-msgid "Default"
-msgstr "Default"
-
-#: screens/Template/Survey/SurveyReorderModal.js:219
-#: screens/Template/Survey/SurveyReorderModal.js:219
-#: screens/Template/Survey/SurveyReorderModal.js:241
-msgid "Default Answer(s)"
-msgstr "Default Answer(s)"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:40
-msgid "Default Execution Environment"
-msgstr "Default Execution Environment"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:238
-#: screens/Template/Survey/SurveyQuestionForm.js:246
-#: screens/Template/Survey/SurveyQuestionForm.js:253
-msgid "Default answer"
-msgstr "Default answer"
-
-#: screens/Setting/SettingList.js:99
-msgid "Define system-level features and functions"
-msgstr "Define system-level features and functions"
-
-#: components/DeleteButton/DeleteButton.js:75
-#: components/DeleteButton/DeleteButton.js:80
-#: components/DeleteButton/DeleteButton.js:90
-#: components/DeleteButton/DeleteButton.js:94
-#: components/DeleteButton/DeleteButton.js:114
-#: components/PaginatedTable/ToolbarDeleteButton.js:158
-#: components/PaginatedTable/ToolbarDeleteButton.js:235
-#: components/PaginatedTable/ToolbarDeleteButton.js:246
-#: components/PaginatedTable/ToolbarDeleteButton.js:250
-#: components/PaginatedTable/ToolbarDeleteButton.js:273
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:29
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:419
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:127
-#: screens/Credential/CredentialDetail/CredentialDetail.js:307
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:124
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:133
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:115
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:127
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:162
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:101
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:332
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:176
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:64
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:68
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:73
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:78
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:102
-#: screens/Job/JobDetail/JobDetail.js:594
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:431
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:199
-#: screens/Project/ProjectDetail/ProjectDetail.js:322
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:76
-#: screens/Team/TeamDetail/TeamDetail.js:70
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:528
-#: screens/Template/Survey/SurveyList.js:66
-#: screens/Template/Survey/SurveyToolbar.js:93
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:271
-#: screens/User/UserDetail/UserDetail.js:107
-#: screens/User/UserTokenDetail/UserTokenDetail.js:77
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:365
-msgid "Delete"
-msgstr ""
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:130
-msgid "Delete All Groups and Hosts"
-msgstr "Delete All Groups and Hosts"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:301
-msgid "Delete Credential"
-msgstr "Delete Credential"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:126
-msgid "Delete Execution Environment"
-msgstr "Delete Execution Environment"
-
-#: screens/Host/HostDetail/HostDetail.js:112
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:110
-msgid "Delete Host"
-msgstr "Delete Host"
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:157
-msgid "Delete Inventory"
-msgstr "Delete Inventory"
-
-#: screens/Job/JobDetail/JobDetail.js:590
-#: screens/Job/JobOutput/shared/OutputToolbar.js:195
-#: screens/Job/JobOutput/shared/OutputToolbar.js:199
-msgid "Delete Job"
-msgstr "Delete Job"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:522
-msgid "Delete Job Template"
-msgstr "Delete Job Template"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:427
-msgid "Delete Notification"
-msgstr "Delete Notification"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:193
-msgid "Delete Organization"
-msgstr "Delete Organization"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:316
-msgid "Delete Project"
-msgstr "Delete Project"
-
-#: screens/Template/Survey/SurveyList.js:52
-msgid "Delete Questions"
-msgstr "Delete Questions"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:415
-msgid "Delete Schedule"
-msgstr "Delete Schedule"
-
-#: screens/Template/Survey/SurveyList.js:52
-msgid "Delete Survey"
-msgstr "Delete Survey"
-
-#: screens/Team/TeamDetail/TeamDetail.js:66
-msgid "Delete Team"
-msgstr "Delete Team"
-
-#: screens/User/UserDetail/UserDetail.js:103
-msgid "Delete User"
-msgstr "Delete User"
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:73
-msgid "Delete User Token"
-msgstr "Delete User Token"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:361
-msgid "Delete Workflow Approval"
-msgstr "Delete Workflow Approval"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:265
-msgid "Delete Workflow Job Template"
-msgstr "Delete Workflow Job Template"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:138
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:141
-msgid "Delete all nodes"
-msgstr "Delete all nodes"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:123
-msgid "Delete application"
-msgstr "Delete application"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:116
-msgid "Delete credential type"
-msgstr "Delete credential type"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:249
-msgid "Delete error"
-msgstr "Delete error"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:109
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:121
-msgid "Delete instance group"
-msgstr "Delete instance group"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:326
-msgid "Delete inventory source"
-msgstr "Delete inventory source"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:172
-msgid "Delete smart inventory"
-msgstr "Delete smart inventory"
-
-#: screens/Template/Survey/SurveyToolbar.js:83
-msgid "Delete survey question"
-msgstr "Delete survey question"
-
-#: screens/Project/shared/Project.helptext.js:110
-msgid ""
-"Delete the local repository in its entirety prior to\n"
-"performing an update. Depending on the size of the\n"
-"repository this may significantly increase the amount\n"
-"of time required to complete an update."
-msgstr ""
-"Delete the local repository in its entirety prior to\n"
-"performing an update. Depending on the size of the\n"
-"repository this may significantly increase the amount\n"
-"of time required to complete an update."
-
-#: components/PromptDetail/PromptProjectDetail.js:51
-#: screens/Project/ProjectDetail/ProjectDetail.js:99
-msgid "Delete the project before syncing"
-msgstr "Delete the project before syncing"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:83
-msgid "Delete this link"
-msgstr "Delete this link"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:247
-msgid "Delete this node"
-msgstr "Delete this node"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:163
-msgid "Delete {pluralizedItemName}?"
-msgstr "Delete {pluralizedItemName}?"
-
-#: components/DetailList/DeletedDetail.js:19
-#: components/Workflow/WorkflowNodeHelp.js:157
-#: components/Workflow/WorkflowNodeHelp.js:193
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:272
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:40
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:51
-msgid "Deleted"
-msgstr "Deleted"
-
-#: components/TemplateList/TemplateList.js:296
-#: screens/Credential/CredentialList/CredentialList.js:211
-#: screens/Inventory/InventoryList/InventoryList.js:284
-#: screens/Project/ProjectList/ProjectList.js:290
-msgid "Deletion Error"
-msgstr "Deletion Error"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:202
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:227
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:233
-msgid "Deletion error"
-msgstr "Deletion error"
-
-#: components/StatusLabel/StatusLabel.js:32
-msgid "Denied"
-msgstr "Denied"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:21
-msgid "Denied - {0}. See the Activity Stream for more information."
-msgstr "Denied - {0}. See the Activity Stream for more information."
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:17
-msgid "Denied by {0} - {1}"
-msgstr "Denied by {0} - {1}"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:72
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:80
-msgid "Deny"
-msgstr "Deny"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:104
-msgid "Deprecated"
-msgstr "Deprecated"
-
-#: components/HostForm/HostForm.js:104
-#: components/Lookup/ApplicationLookup.js:104
-#: components/Lookup/ApplicationLookup.js:122
-#: components/Lookup/HostFilterLookup.js:422
-#: components/Lookup/HostListItem.js:9
-#: components/NotificationList/NotificationList.js:186
-#: components/PromptDetail/PromptDetail.js:110
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:253
-#: components/Schedule/ScheduleList/ScheduleList.js:193
-#: components/Schedule/shared/ScheduleForm.js:115
-#: components/TemplateList/TemplateList.js:210
-#: components/TemplateList/TemplateListItem.js:271
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:64
-#: screens/Application/ApplicationsList/ApplicationsList.js:123
-#: screens/Application/shared/ApplicationForm.js:61
-#: screens/Credential/CredentialDetail/CredentialDetail.js:223
-#: screens/Credential/CredentialList/CredentialList.js:146
-#: screens/Credential/shared/CredentialForm.js:169
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:72
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:128
-#: screens/CredentialType/shared/CredentialTypeForm.js:29
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:59
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:159
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:126
-#: screens/Host/HostDetail/HostDetail.js:73
-#: screens/Host/HostList/HostList.js:153
-#: screens/Host/HostList/HostList.js:170
-#: screens/Host/HostList/HostListItem.js:57
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:71
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:35
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:216
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:81
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:40
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:124
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:139
-#: screens/Inventory/InventoryList/InventoryList.js:195
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:213
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:105
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:37
-#: screens/Inventory/shared/InventoryForm.js:50
-#: screens/Inventory/shared/InventoryGroupForm.js:40
-#: screens/Inventory/shared/InventorySourceForm.js:109
-#: screens/Inventory/shared/SmartInventoryForm.js:55
-#: screens/Job/JobOutput/HostEventModal.js:112
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:101
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:72
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:108
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:127
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:49
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:95
-#: screens/Organization/OrganizationList/OrganizationList.js:127
-#: screens/Organization/shared/OrganizationForm.js:64
-#: screens/Project/ProjectDetail/ProjectDetail.js:176
-#: screens/Project/ProjectList/ProjectList.js:190
-#: screens/Project/ProjectList/ProjectListItem.js:281
-#: screens/Project/shared/ProjectForm.js:178
-#: screens/Team/TeamDetail/TeamDetail.js:38
-#: screens/Team/TeamList/TeamList.js:122
-#: screens/Team/shared/TeamForm.js:37
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:180
-#: screens/Template/Survey/SurveyQuestionForm.js:171
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:112
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:183
-#: screens/Template/shared/JobTemplateForm.js:247
-#: screens/Template/shared/WorkflowJobTemplateForm.js:112
-#: screens/User/UserOrganizations/UserOrganizationList.js:80
-#: screens/User/UserOrganizations/UserOrganizationListItem.js:18
-#: screens/User/UserTeams/UserTeamList.js:182
-#: screens/User/UserTeams/UserTeamListItem.js:32
-#: screens/User/UserTokenDetail/UserTokenDetail.js:44
-#: screens/User/UserTokenList/UserTokenList.js:128
-#: screens/User/UserTokenList/UserTokenList.js:138
-#: screens/User/UserTokenList/UserTokenList.js:188
-#: screens/User/UserTokenList/UserTokenListItem.js:29
-#: screens/User/shared/UserTokenForm.js:59
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:186
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:205
-msgid "Description"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:320
-msgid "Destination Channels"
-msgstr "Destination Channels"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:228
-msgid "Destination Channels or Users"
-msgstr "Destination Channels or Users"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:341
-msgid "Destination SMS Number(s)"
-msgstr "Destination SMS Number(s)"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:399
-msgid "Destination SMS number(s)"
-msgstr "Destination SMS number(s)"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:353
-msgid "Destination channels"
-msgstr "Destination channels"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:222
-msgid "Destination channels or users"
-msgstr "Destination channels or users"
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:35
-#: components/ErrorDetail/ErrorDetail.js:80
-#: components/Schedule/Schedule.js:71
-#: screens/Application/Application/Application.js:79
-#: screens/Application/Applications.js:39
-#: screens/Credential/Credential.js:72
-#: screens/Credential/Credentials.js:28
-#: screens/CredentialType/CredentialType.js:63
-#: screens/CredentialType/CredentialTypes.js:26
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:65
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:26
-#: screens/Host/Host.js:58
-#: screens/Host/Hosts.js:27
-#: screens/InstanceGroup/ContainerGroup.js:66
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:178
-#: screens/InstanceGroup/InstanceGroup.js:69
-#: screens/InstanceGroup/InstanceGroups.js:59
-#: screens/InstanceGroup/InstanceGroups.js:67
-#: screens/Instances/Instance.js:25
-#: screens/Instances/Instances.js:22
-#: screens/Inventory/Inventories.js:61
-#: screens/Inventory/Inventories.js:87
-#: screens/Inventory/Inventory.js:64
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:57
-#: screens/Inventory/InventoryHost/InventoryHost.js:73
-#: screens/Inventory/InventorySource/InventorySource.js:83
-#: screens/Inventory/SmartInventory.js:66
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:60
-#: screens/Job/Job.js:117
-#: screens/Job/JobOutput/HostEventModal.js:103
-#: screens/Job/Jobs.js:35
-#: screens/ManagementJob/ManagementJobs.js:26
-#: screens/NotificationTemplate/NotificationTemplate.js:84
-#: screens/NotificationTemplate/NotificationTemplates.js:25
-#: screens/Organization/Organization.js:123
-#: screens/Organization/Organizations.js:30
-#: screens/Project/Project.js:104
-#: screens/Project/Projects.js:26
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:51
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:51
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:65
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:76
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:50
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:97
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:56
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:52
-#: screens/Setting/Settings.js:44
-#: screens/Setting/Settings.js:47
-#: screens/Setting/Settings.js:51
-#: screens/Setting/Settings.js:54
-#: screens/Setting/Settings.js:57
-#: screens/Setting/Settings.js:60
-#: screens/Setting/Settings.js:63
-#: screens/Setting/Settings.js:66
-#: screens/Setting/Settings.js:69
-#: screens/Setting/Settings.js:72
-#: screens/Setting/Settings.js:81
-#: screens/Setting/Settings.js:82
-#: screens/Setting/Settings.js:83
-#: screens/Setting/Settings.js:84
-#: screens/Setting/Settings.js:85
-#: screens/Setting/Settings.js:86
-#: screens/Setting/Settings.js:94
-#: screens/Setting/Settings.js:97
-#: screens/Setting/Settings.js:100
-#: screens/Setting/Settings.js:103
-#: screens/Setting/Settings.js:106
-#: screens/Setting/Settings.js:109
-#: screens/Setting/Settings.js:112
-#: screens/Setting/Settings.js:115
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:56
-#: screens/Setting/UI/UIDetail/UIDetail.js:66
-#: screens/Team/Team.js:57
-#: screens/Team/Teams.js:29
-#: screens/Template/Template.js:135
-#: screens/Template/Templates.js:43
-#: screens/Template/WorkflowJobTemplate.js:117
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:140
-#: screens/TopologyView/Tooltip.js:56
-#: screens/TopologyView/Tooltip.js:70
-#: screens/User/User.js:64
-#: screens/User/UserToken/UserToken.js:54
-#: screens/User/Users.js:30
-#: screens/User/Users.js:36
-#: screens/WorkflowApproval/WorkflowApproval.js:77
-#: screens/WorkflowApproval/WorkflowApprovals.js:24
-msgid "Details"
-msgstr ""
-
-#: screens/Job/JobOutput/HostEventModal.js:100
-msgid "Details tab"
-msgstr "Details tab"
-
-#: components/Search/AdvancedSearch.js:271
-msgid "Direct Keys"
-msgstr "Direct Keys"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:204
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:263
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:308
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:366
-msgid "Disable SSL Verification"
-msgstr "Disable SSL Verification"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:180
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:231
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:270
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:341
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:446
-msgid "Disable SSL verification"
-msgstr "Disable SSL verification"
-
-#: components/InstanceToggle/InstanceToggle.js:56
-#: components/StatusLabel/StatusLabel.js:45
-#: screens/TopologyView/Legend.js:133
-msgid "Disabled"
-msgstr "Disabled"
-
-#: components/DisassociateButton/DisassociateButton.js:73
-#: components/DisassociateButton/DisassociateButton.js:97
-#: components/DisassociateButton/DisassociateButton.js:109
-#: components/DisassociateButton/DisassociateButton.js:113
-#: components/DisassociateButton/DisassociateButton.js:133
-#: screens/Team/TeamRoles/TeamRolesList.js:222
-#: screens/User/UserRoles/UserRolesList.js:218
-msgid "Disassociate"
-msgstr "Disassociate"
-
-#: screens/Host/HostGroups/HostGroupsList.js:211
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:229
-msgid "Disassociate group from host?"
-msgstr "Disassociate group from host?"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:247
-msgid "Disassociate host from group?"
-msgstr "Disassociate host from group?"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:289
-#: screens/InstanceGroup/Instances/InstanceList.js:234
-msgid "Disassociate instance from instance group?"
-msgstr "Disassociate instance from instance group?"
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:225
-msgid "Disassociate related group(s)?"
-msgstr "Disassociate related group(s)?"
-
-#: screens/User/UserTeams/UserTeamList.js:216
-msgid "Disassociate related team(s)?"
-msgstr "Disassociate related team(s)?"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:209
-#: screens/User/UserRoles/UserRolesList.js:205
-msgid "Disassociate role"
-msgstr "Disassociate role"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:212
-#: screens/User/UserRoles/UserRolesList.js:208
-msgid "Disassociate role!"
-msgstr "Disassociate role!"
-
-#: components/DisassociateButton/DisassociateButton.js:18
-msgid "Disassociate?"
-msgstr "Disassociate?"
-
-#: components/PromptDetail/PromptProjectDetail.js:46
-#: screens/Project/ProjectDetail/ProjectDetail.js:93
-msgid "Discard local changes before syncing"
-msgstr "Discard local changes before syncing"
-
-#: screens/Template/shared/JobTemplateForm.js:479
-#~ msgid ""
-#~ "Divide the work done by this job template\n"
-#~ "into the specified number of job slices, each running the\n"
-#~ "same tasks against a portion of the inventory."
-#~ msgstr ""
-#~ "Divide the work done by this job template\n"
-#~ "into the specified number of job slices, each running the\n"
-#~ "same tasks against a portion of the inventory."
-
-#: screens/Job/Job.helptext.js:15
-#: screens/Template/shared/JobTemplate.helptext.js:16
-msgid "Divide the work done by this job template into the specified number of job slices, each running the same tasks against a portion of the inventory."
-msgstr "Divide the work done by this job template into the specified number of job slices, each running the same tasks against a portion of the inventory."
-
-#: screens/Project/shared/Project.helptext.js:100
-msgid "Documentation."
-msgstr "Documentation."
-
-#: components/CodeEditor/VariablesDetail.js:117
-#: components/CodeEditor/VariablesDetail.js:123
-#: components/CodeEditor/VariablesField.js:139
-#: components/CodeEditor/VariablesField.js:145
-msgid "Done"
-msgstr "Done"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:179
-#: screens/Job/JobOutput/shared/OutputToolbar.js:184
-msgid "Download Output"
-msgstr "Download Output"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:93
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:114
-msgid "Drag a file here or browse to upload"
-msgstr "Drag a file here or browse to upload"
-
-#: components/SelectedList/DraggableSelectedList.js:68
-msgid "Draggable list to reorder and remove selected items."
-msgstr "Draggable list to reorder and remove selected items."
-
-#: components/SelectedList/DraggableSelectedList.js:43
-msgid "Dragging cancelled. List is unchanged."
-msgstr "Dragging cancelled. List is unchanged."
-
-#: components/SelectedList/DraggableSelectedList.js:38
-msgid "Dragging item {id}. Item with index {oldIndex} in now {newIndex}."
-msgstr "Dragging item {id}. Item with index {oldIndex} in now {newIndex}."
-
-#: components/SelectedList/DraggableSelectedList.js:32
-msgid "Dragging started for item id: {newId}."
-msgstr "Dragging started for item id: {newId}."
-
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:81
-msgid "E-mail"
-msgstr "E-mail"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:121
-msgid "E-mail options"
-msgstr "E-mail options"
-
-#: screens/Inventory/shared/Inventory.helptext.js:113
-msgid ""
-"Each time a job runs using this inventory,\n"
-"refresh the inventory from the selected source before\n"
-"executing job tasks."
-msgstr ""
-"Each time a job runs using this inventory,\n"
-"refresh the inventory from the selected source before\n"
-"executing job tasks."
-
-#: screens/Project/shared/Project.helptext.js:120
-msgid ""
-"Each time a job runs using this project, update the\n"
-"revision of the project prior to starting the job."
-msgstr ""
-"Each time a job runs using this project, update the\n"
-"revision of the project prior to starting the job."
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:405
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:409
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:114
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:116
-#: screens/Credential/CredentialDetail/CredentialDetail.js:294
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:109
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:120
-#: screens/Host/HostDetail/HostDetail.js:106
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:101
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:113
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:151
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:55
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:62
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:104
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:308
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:127
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:166
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:413
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:415
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:138
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:182
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:186
-#: screens/Project/ProjectDetail/ProjectDetail.js:295
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:85
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:89
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:148
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:152
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:85
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:89
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:96
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:100
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:164
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:168
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:107
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:111
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:84
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:88
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:152
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:156
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:99
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:103
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:86
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:90
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:169
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:103
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:108
-#: screens/Setting/UI/UIDetail/UIDetail.js:105
-#: screens/Setting/UI/UIDetail/UIDetail.js:110
-#: screens/Team/TeamDetail/TeamDetail.js:55
-#: screens/Team/TeamDetail/TeamDetail.js:59
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:497
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:499
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:241
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:243
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:241
-#: screens/User/UserDetail/UserDetail.js:96
-msgid "Edit"
-msgstr ""
-
-#: screens/Credential/CredentialList/CredentialListItem.js:67
-#: screens/Credential/CredentialList/CredentialListItem.js:71
-msgid "Edit Credential"
-msgstr "Edit Credential"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:37
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:42
-msgid "Edit Credential Plugin Configuration"
-msgstr "Edit Credential Plugin Configuration"
-
-#: screens/Application/Applications.js:38
-#: screens/Credential/Credentials.js:27
-#: screens/Host/Hosts.js:26
-#: screens/ManagementJob/ManagementJobs.js:27
-#: screens/NotificationTemplate/NotificationTemplates.js:24
-#: screens/Organization/Organizations.js:29
-#: screens/Project/Projects.js:25
-#: screens/Project/Projects.js:35
-#: screens/Setting/Settings.js:45
-#: screens/Setting/Settings.js:48
-#: screens/Setting/Settings.js:52
-#: screens/Setting/Settings.js:55
-#: screens/Setting/Settings.js:58
-#: screens/Setting/Settings.js:61
-#: screens/Setting/Settings.js:64
-#: screens/Setting/Settings.js:67
-#: screens/Setting/Settings.js:70
-#: screens/Setting/Settings.js:73
-#: screens/Setting/Settings.js:87
-#: screens/Setting/Settings.js:88
-#: screens/Setting/Settings.js:89
-#: screens/Setting/Settings.js:90
-#: screens/Setting/Settings.js:91
-#: screens/Setting/Settings.js:92
-#: screens/Setting/Settings.js:95
-#: screens/Setting/Settings.js:98
-#: screens/Setting/Settings.js:101
-#: screens/Setting/Settings.js:104
-#: screens/Setting/Settings.js:107
-#: screens/Setting/Settings.js:110
-#: screens/Setting/Settings.js:113
-#: screens/Setting/Settings.js:116
-#: screens/Team/Teams.js:28
-#: screens/Template/Templates.js:44
-#: screens/User/Users.js:29
-msgid "Edit Details"
-msgstr ""
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:90
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:94
-msgid "Edit Execution Environment"
-msgstr "Edit Execution Environment"
-
-#: screens/Host/HostGroups/HostGroupItem.js:37
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:46
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:46
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:51
-msgid "Edit Group"
-msgstr "Edit Group"
-
-#: screens/Host/HostList/HostListItem.js:74
-#: screens/Host/HostList/HostListItem.js:78
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:61
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:64
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:67
-msgid "Edit Host"
-msgstr "Edit Host"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:134
-#: screens/Inventory/InventoryList/InventoryListItem.js:139
-msgid "Edit Inventory"
-msgstr "Edit Inventory"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkEditModal.js:14
-msgid "Edit Link"
-msgstr "Edit Link"
-
-#: screens/Setting/shared/SharedFields.js:290
-msgid "Edit Login redirect override URL"
-msgstr "Edit Login redirect override URL"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeEditModal.js:64
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:238
-msgid "Edit Node"
-msgstr "Edit Node"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:142
-msgid "Edit Notification Template"
-msgstr "Edit Notification Template"
-
-#: screens/Template/Survey/SurveyToolbar.js:73
-msgid "Edit Order"
-msgstr "Edit Order"
-
-#: screens/Organization/OrganizationList/OrganizationListItem.js:72
-#: screens/Organization/OrganizationList/OrganizationListItem.js:76
-msgid "Edit Organization"
-msgstr "Edit Organization"
-
-#: screens/Project/ProjectList/ProjectListItem.js:248
-#: screens/Project/ProjectList/ProjectListItem.js:253
-msgid "Edit Project"
-msgstr "Edit Project"
-
-#: screens/Template/Templates.js:50
-msgid "Edit Question"
-msgstr "Edit Question"
-
-#: components/Schedule/ScheduleList/ScheduleListItem.js:118
-#: components/Schedule/ScheduleList/ScheduleListItem.js:122
-#: screens/Template/Templates.js:55
-msgid "Edit Schedule"
-msgstr "Edit Schedule"
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:131
-msgid "Edit Source"
-msgstr "Edit Source"
-
-#: screens/Template/Survey/SurveyListItem.js:92
-msgid "Edit Survey"
-msgstr "Edit Survey"
-
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:22
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:26
-#: screens/Team/TeamList/TeamListItem.js:50
-#: screens/Team/TeamList/TeamListItem.js:54
-msgid "Edit Team"
-msgstr "Edit Team"
-
-#: components/TemplateList/TemplateListItem.js:233
-#: components/TemplateList/TemplateListItem.js:239
-msgid "Edit Template"
-msgstr "Edit Template"
-
-#: screens/User/UserList/UserListItem.js:59
-#: screens/User/UserList/UserListItem.js:63
-msgid "Edit User"
-msgstr "Edit User"
-
-#: screens/Application/ApplicationsList/ApplicationListItem.js:51
-#: screens/Application/ApplicationsList/ApplicationListItem.js:55
-msgid "Edit application"
-msgstr "Edit application"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:41
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:45
-msgid "Edit credential type"
-msgstr "Edit credential type"
-
-#: screens/CredentialType/CredentialTypes.js:25
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:25
-#: screens/InstanceGroup/InstanceGroups.js:64
-#: screens/InstanceGroup/InstanceGroups.js:69
-#: screens/Inventory/Inventories.js:63
-#: screens/Inventory/Inventories.js:68
-#: screens/Inventory/Inventories.js:77
-#: screens/Inventory/Inventories.js:88
-msgid "Edit details"
-msgstr "Edit details"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:42
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:44
-msgid "Edit group"
-msgstr "Edit group"
-
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:48
-msgid "Edit host"
-msgstr "Edit host"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:78
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:82
-msgid "Edit instance group"
-msgstr "Edit instance group"
-
-#: screens/Setting/shared/SharedFields.js:320
-#: screens/Setting/shared/SharedFields.js:322
-msgid "Edit login redirect override URL"
-msgstr "Edit login redirect override URL"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:70
-msgid "Edit this link"
-msgstr "Edit this link"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:221
-msgid "Edit this node"
-msgstr "Edit this node"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:97
-msgid "Edit workflow"
-msgstr "Edit workflow"
-
-#: components/Workflow/WorkflowNodeHelp.js:170
-#: screens/Job/JobOutput/shared/OutputToolbar.js:125
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:257
-msgid "Elapsed"
-msgstr "Elapsed"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:124
-msgid "Elapsed Time"
-msgstr "Elapsed Time"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:126
-msgid "Elapsed time that the job ran"
-msgstr "Elapsed time that the job ran"
-
-#: components/NotificationList/NotificationList.js:193
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:134
-#: screens/User/UserDetail/UserDetail.js:66
-#: screens/User/UserList/UserList.js:115
-#: screens/User/shared/UserForm.js:73
-msgid "Email"
-msgstr "Email"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:175
-msgid "Email Options"
-msgstr "Email Options"
-
-#: screens/Template/shared/WorkflowJobTemplateForm.js:232
-msgid "Enable Concurrent Jobs"
-msgstr "Enable Concurrent Jobs"
-
-#: screens/Template/shared/JobTemplateForm.js:545
-msgid "Enable Fact Storage"
-msgstr "Enable Fact Storage"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:192
-msgid "Enable HTTPS certificate verification"
-msgstr "Enable HTTPS certificate verification"
-
-#: screens/Template/shared/JobTemplateForm.js:521
-#: screens/Template/shared/JobTemplateForm.js:524
-#: screens/Template/shared/WorkflowJobTemplateForm.js:213
-#: screens/Template/shared/WorkflowJobTemplateForm.js:216
-msgid "Enable Webhook"
-msgstr "Enable Webhook"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:15
-msgid "Enable Webhook for this workflow job template."
-msgstr "Enable Webhook for this workflow job template."
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:136
-msgid "Enable external logging"
-msgstr "Enable external logging"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:168
-msgid "Enable log system tracking facts individually"
-msgstr "Enable log system tracking facts individually"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:201
-#: components/AdHocCommands/AdHocDetailsStep.js:204
-msgid "Enable privilege escalation"
-msgstr "Enable privilege escalation"
-
-#: screens/Setting/SettingList.js:52
-#~ msgid "Enable simplified login for your {0} applications"
-#~ msgstr "Enable simplified login for your {0} applications"
-
-#: screens/Setting/SettingList.js:53
-msgid "Enable simplified login for your {brandName} applications"
-msgstr "Enable simplified login for your {brandName} applications"
-
-#: screens/Template/shared/JobTemplate.helptext.js:30
-msgid "Enable webhook for this template."
-msgstr "Enable webhook for this template."
-
-#: components/InstanceToggle/InstanceToggle.js:55
-#: components/Lookup/HostFilterLookup.js:110
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:46
-msgid "Enabled"
-msgstr "Enabled"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:183
-#: components/PromptDetail/PromptJobTemplateDetail.js:182
-#: components/PromptDetail/PromptProjectDetail.js:130
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:97
-#: screens/Credential/CredentialDetail/CredentialDetail.js:269
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:281
-#: screens/Project/ProjectDetail/ProjectDetail.js:284
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:351
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:200
-msgid "Enabled Options"
-msgstr "Enabled Options"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:267
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:135
-msgid "Enabled Value"
-msgstr "Enabled Value"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:262
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:125
-msgid "Enabled Variable"
-msgstr "Enabled Variable"
-
-#: screens/Template/shared/JobTemplateForm.js:568
-#~ msgid ""
-#~ "Enables creation of a provisioning\n"
-#~ "callback URL. Using the URL a host can contact {0}\n"
-#~ "and request a configuration update using this job\n"
-#~ "template."
-#~ msgstr ""
-#~ "Enables creation of a provisioning\n"
-#~ "callback URL. Using the URL a host can contact {0}\n"
-#~ "and request a configuration update using this job\n"
-#~ "template."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:209
-msgid ""
-"Enables creation of a provisioning\n"
-"callback URL. Using the URL a host can contact {brandName}\n"
-"and request a configuration update using this job\n"
-"template"
-msgstr ""
-"Enables creation of a provisioning\n"
-"callback URL. Using the URL a host can contact {brandName}\n"
-"and request a configuration update using this job\n"
-"template"
-
-#: screens/Template/shared/JobTemplateForm.js:568
-#~ msgid ""
-#~ "Enables creation of a provisioning\n"
-#~ "callback URL. Using the URL a host can contact {brandName}\n"
-#~ "and request a configuration update using this job\n"
-#~ "template."
-#~ msgstr ""
-#~ "Enables creation of a provisioning\n"
-#~ "callback URL. Using the URL a host can contact {brandName}\n"
-#~ "and request a configuration update using this job\n"
-#~ "template."
-
-#: screens/Template/shared/JobTemplate.helptext.js:28
-msgid "Enables creation of a provisioning callback URL. Using the URL a host can contact {brandName} and request a configuration update using this job template."
-msgstr "Enables creation of a provisioning callback URL. Using the URL a host can contact {brandName} and request a configuration update using this job template."
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:160
-#: screens/Setting/shared/SettingDetail.js:87
-msgid "Encrypted"
-msgstr "Encrypted"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:500
-msgid "End"
-msgstr "End"
-
-#: screens/Setting/Subscription/SubscriptionEdit/EulaStep.js:14
-msgid "End User License Agreement"
-msgstr "End User License Agreement"
-
-#: components/Schedule/shared/DateTimePicker.js:51
-msgid "End date"
-msgstr "End date"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:555
-msgid "End date/time"
-msgstr "End date/time"
-
-#: components/Schedule/shared/buildRuleObj.js:97
-msgid "End did not match an expected value"
-msgstr "End did not match an expected value"
-
-#: components/Schedule/shared/DateTimePicker.js:61
-msgid "End time"
-msgstr "End time"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:209
-msgid "End user license agreement"
-msgstr "End user license agreement"
-
-#: screens/Host/HostList/SmartInventoryButton.js:23
-msgid "Enter at least one search filter to create a new Smart Inventory"
-msgstr "Enter at least one search filter to create a new Smart Inventory"
-
-#: screens/CredentialType/shared/CredentialTypeForm.js:43
-msgid "Enter injectors using either JSON or YAML syntax. Refer to the Ansible Controller documentation for example syntax."
-msgstr "Enter injectors using either JSON or YAML syntax. Refer to the Ansible Controller documentation for example syntax."
-
-#: screens/CredentialType/shared/CredentialTypeForm.js:35
-msgid "Enter inputs using either JSON or YAML syntax. Refer to the Ansible Controller documentation for example syntax."
-msgstr "Enter inputs using either JSON or YAML syntax. Refer to the Ansible Controller documentation for example syntax."
-
-#: screens/Inventory/shared/SmartInventoryForm.js:94
-msgid ""
-"Enter inventory variables using either JSON or YAML syntax.\n"
-"Use the radio button to toggle between the two. Refer to the\n"
-"Ansible Controller documentation for example syntax."
-msgstr ""
-"Enter inventory variables using either JSON or YAML syntax.\n"
-"Use the radio button to toggle between the two. Refer to the\n"
-"Ansible Controller documentation for example syntax."
-
-#: screens/Inventory/shared/InventoryForm.js:65
-msgid "Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Controller documentation for example syntax"
-msgstr "Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Controller documentation for example syntax"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:181
-#~ msgid "Enter one Annotation Tag per line, without commas."
-#~ msgstr "Enter one Annotation Tag per line, without commas."
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:232
-#~ msgid ""
-#~ "Enter one IRC channel or username per line. The pound\n"
-#~ "symbol (#) for channels, and the at (@) symbol for users, are not\n"
-#~ "required."
-#~ msgstr ""
-#~ "Enter one IRC channel or username per line. The pound\n"
-#~ "symbol (#) for channels, and the at (@) symbol for users, are not\n"
-#~ "required."
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:365
-#~ msgid ""
-#~ "Enter one Slack channel per line. The pound symbol (#)\n"
-#~ "is required for channels."
-#~ msgstr ""
-#~ "Enter one Slack channel per line. The pound symbol (#)\n"
-#~ "is required for channels."
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:367
-#~ msgid ""
-#~ "Enter one Slack channel per line. The pound symbol (#)\n"
-#~ "is required for channels. To respond to or start a thread to a specific message add the parent message Id to the channel where the parent message Id is 16 digits. A dot (.) must be manually inserted after the 10th digit. ie:#destination-channel, 1231257890.006423. See Slack"
-#~ msgstr ""
-#~ "Enter one Slack channel per line. The pound symbol (#)\n"
-#~ "is required for channels. To respond to or start a thread to a specific message add the parent message Id to the channel where the parent message Id is 16 digits. A dot (.) must be manually inserted after the 10th digit. ie:#destination-channel, 1231257890.006423. See Slack"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:90
-#~ msgid ""
-#~ "Enter one email address per line to create a recipient\n"
-#~ "list for this type of notification."
-#~ msgstr ""
-#~ "Enter one email address per line to create a recipient\n"
-#~ "list for this type of notification."
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:413
-#~ msgid ""
-#~ "Enter one phone number per line to specify where to\n"
-#~ "route SMS messages."
-#~ msgstr ""
-#~ "Enter one phone number per line to specify where to\n"
-#~ "route SMS messages."
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:416
-#~ msgid ""
-#~ "Enter one phone number per line to specify where to\n"
-#~ "route SMS messages. Phone numbers should be formatted +11231231234. For more information see"
-#~ msgstr ""
-#~ "Enter one phone number per line to specify where to\n"
-#~ "route SMS messages. Phone numbers should be formatted +11231231234. For more information see"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:420
-#~ msgid ""
-#~ "Enter one phone number per line to specify where to\n"
-#~ "route SMS messages. Phone numbers should be formatted +11231231234. For more information see Twilio documentation"
-#~ msgstr ""
-#~ "Enter one phone number per line to specify where to\n"
-#~ "route SMS messages. Phone numbers should be formatted +11231231234. For more information see Twilio documentation"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:410
-#~ msgid ""
-#~ "Enter the number associated with the \"Messaging\n"
-#~ "Service\" in Twilio in the format +18005550199."
-#~ msgstr ""
-#~ "Enter the number associated with the \"Messaging\n"
-#~ "Service\" in Twilio in the format +18005550199."
-
-#: screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.js:60
-#~ msgid "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>Insights1> plugin configuration guide."
-#~ msgstr "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>Insights1> plugin configuration guide."
-
-#: screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.js:60
-#~ msgid "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>Tower1> plugin configuration guide."
-#~ msgstr "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>Tower1> plugin configuration guide."
-
-#: screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.js:53
-#~ msgid "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>aws_ec21> plugin configuration guide."
-#~ msgstr "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>aws_ec21> plugin configuration guide."
-
-#: screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.js:59
-#~ msgid "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>azure_rm1> plugin configuration guide."
-#~ msgstr "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>azure_rm1> plugin configuration guide."
-
-#: screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.js:59
-#~ msgid "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>foreman1> plugin configuration guide."
-#~ msgstr "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>foreman1> plugin configuration guide."
-
-#: screens/Inventory/shared/InventorySourceSubForms/GCESubForm.js:59
-#~ msgid "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>gcp_compute1> plugin configuration guide."
-#~ msgstr "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>gcp_compute1> plugin configuration guide."
-
-#: screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.js:59
-#~ msgid "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>openstack1> plugin configuration guide."
-#~ msgstr "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>openstack1> plugin configuration guide."
-
-#: screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.js:59
-#~ msgid "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>ovirt1> plugin configuration guide."
-#~ msgstr "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>ovirt1> plugin configuration guide."
-
-#: screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.js:59
-#~ msgid "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>vmware_vm_inventory1> plugin configuration guide."
-#~ msgstr "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>vmware_vm_inventory1> plugin configuration guide."
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:35
-#~ msgid "Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two."
-#~ msgstr "Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two."
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:87
-msgid "Environment variables or extra variables that specify the values a credential type can inject."
-msgstr "Environment variables or extra variables that specify the values a credential type can inject."
-
-#: components/JobList/JobList.js:233
-#: components/StatusLabel/StatusLabel.js:38
-#: components/Workflow/WorkflowNodeHelp.js:108
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:133
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:205
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:142
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:230
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:123
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:135
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:236
-#: screens/Job/JobOutput/JobOutputSearch.js:105
-#: screens/TopologyView/Legend.js:124
-msgid "Error"
-msgstr "Error"
-
-#: screens/Project/ProjectList/ProjectList.js:302
-msgid "Error fetching updated project"
-msgstr "Error fetching updated project"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:496
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:141
-msgid "Error message"
-msgstr "Error message"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:505
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:150
-msgid "Error message body"
-msgstr "Error message body"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:617
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:619
-msgid "Error saving the workflow!"
-msgstr "Error saving the workflow!"
-
-#: components/AdHocCommands/AdHocCommands.js:104
-#: components/CopyButton/CopyButton.js:51
-#: components/DeleteButton/DeleteButton.js:56
-#: components/HostToggle/HostToggle.js:76
-#: components/InstanceToggle/InstanceToggle.js:67
-#: components/JobList/JobList.js:315
-#: components/JobList/JobList.js:326
-#: components/LaunchButton/LaunchButton.js:162
-#: components/LaunchPrompt/LaunchPrompt.js:66
-#: components/NotificationList/NotificationList.js:246
-#: components/PaginatedTable/ToolbarDeleteButton.js:205
-#: components/RelatedTemplateList/RelatedTemplateList.js:241
-#: components/ResourceAccessList/ResourceAccessList.js:277
-#: components/ResourceAccessList/ResourceAccessList.js:289
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:427
-#: components/Schedule/ScheduleList/ScheduleList.js:238
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:73
-#: components/Schedule/shared/SchedulePromptableFields.js:70
-#: components/TemplateList/TemplateList.js:299
-#: contexts/Config.js:94
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:135
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:155
-#: screens/Application/ApplicationsList/ApplicationsList.js:185
-#: screens/Credential/CredentialDetail/CredentialDetail.js:315
-#: screens/Credential/CredentialList/CredentialList.js:214
-#: screens/Host/HostDetail/HostDetail.js:56
-#: screens/Host/HostDetail/HostDetail.js:121
-#: screens/Host/HostGroups/HostGroupsList.js:244
-#: screens/Host/HostList/HostList.js:233
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:303
-#: screens/InstanceGroup/Instances/InstanceList.js:297
-#: screens/InstanceGroup/Instances/InstanceListItem.js:218
-#: screens/Instances/InstanceDetail/InstanceDetail.js:249
-#: screens/Instances/InstanceList/InstanceList.js:178
-#: screens/Instances/InstanceList/InstanceListItem.js:234
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:171
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:78
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:285
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:296
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:56
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:119
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:261
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:201
-#: screens/Inventory/InventoryList/InventoryList.js:285
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:264
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:339
-#: screens/Inventory/InventorySources/InventorySourceList.js:239
-#: screens/Inventory/InventorySources/InventorySourceList.js:252
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:185
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:152
-#: screens/Inventory/shared/InventorySourceSyncButton.js:49
-#: screens/Login/Login.js:217
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:125
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:439
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:233
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:169
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:208
-#: screens/Organization/OrganizationList/OrganizationList.js:195
-#: screens/Project/ProjectDetail/ProjectDetail.js:330
-#: screens/Project/ProjectList/ProjectList.js:291
-#: screens/Project/ProjectList/ProjectList.js:303
-#: screens/Project/shared/ProjectSyncButton.js:59
-#: screens/Team/TeamDetail/TeamDetail.js:78
-#: screens/Team/TeamList/TeamList.js:192
-#: screens/Team/TeamRoles/TeamRolesList.js:247
-#: screens/Team/TeamRoles/TeamRolesList.js:258
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:537
-#: screens/Template/TemplateSurvey.js:130
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:279
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:178
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:193
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:318
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:346
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:357
-#: screens/User/UserDetail/UserDetail.js:115
-#: screens/User/UserList/UserList.js:189
-#: screens/User/UserRoles/UserRolesList.js:243
-#: screens/User/UserRoles/UserRolesList.js:254
-#: screens/User/UserTeams/UserTeamList.js:259
-#: screens/User/UserTokenDetail/UserTokenDetail.js:84
-#: screens/User/UserTokenList/UserTokenList.js:214
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:373
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:384
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:395
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:406
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:277
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:288
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:299
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:310
-msgid "Error!"
-msgstr "Error!"
-
-#: components/CodeEditor/VariablesDetail.js:105
-msgid "Error:"
-msgstr "Error:"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:260
-#: screens/Instances/InstanceDetail/InstanceDetail.js:214
-msgid "Errors"
-msgstr "Errors"
-
-#: screens/ActivityStream/ActivityStream.js:265
-#: screens/ActivityStream/ActivityStreamListItem.js:46
-#: screens/Job/JobOutput/JobOutputSearch.js:100
-msgid "Event"
-msgstr "Event"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:35
-msgid "Event detail"
-msgstr "Event detail"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:36
-msgid "Event detail modal"
-msgstr "Event detail modal"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:555
-msgid "Event summary not available"
-msgstr "Event summary not available"
-
-#: screens/ActivityStream/ActivityStream.js:234
-msgid "Events"
-msgstr "Events"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:144
-msgid "Every minute for {0} times"
-msgstr "Every minute for {0} times"
-
-#: screens/TopologyView/Legend.js:82
-msgid "Ex"
-msgstr "Ex"
-
-#: components/Search/LookupTypeInput.js:39
-msgid "Exact match (default lookup if not specified)."
-msgstr "Exact match (default lookup if not specified)."
-
-#: components/Search/RelatedLookupTypeInput.js:38
-msgid "Exact search on id field."
-msgstr "Exact search on id field."
-
-#: screens/Project/shared/Project.helptext.js:23
-msgid "Example URLs for GIT Source Control include:"
-msgstr "Example URLs for GIT Source Control include:"
-
-#: screens/Project/shared/Project.helptext.js:62
-msgid "Example URLs for Remote Archive Source Control include:"
-msgstr "Example URLs for Remote Archive Source Control include:"
-
-#: screens/Project/shared/Project.helptext.js:45
-msgid "Example URLs for Subversion Source Control include:"
-msgstr "Example URLs for Subversion Source Control include:"
-
-#: screens/Project/shared/Project.helptext.js:84
-msgid "Examples include:"
-msgstr "Examples include:"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:10
-msgid "Examples:"
-msgstr "Examples:"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:47
-msgid "Execute regardless of the parent node's final state."
-msgstr "Execute regardless of the parent node's final state."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:40
-msgid "Execute when the parent node results in a failure state."
-msgstr "Execute when the parent node results in a failure state."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:33
-msgid "Execute when the parent node results in a successful state."
-msgstr "Execute when the parent node results in a successful state."
-
-#: screens/InstanceGroup/Instances/InstanceList.js:197
-#: screens/Instances/InstanceList/InstanceList.js:117
-msgid "Execution"
-msgstr "Execution"
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:90
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:91
-#: components/AdHocCommands/AdHocPreviewStep.js:58
-#: components/AdHocCommands/useAdHocExecutionEnvironmentStep.js:15
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:41
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:105
-#: components/Lookup/ExecutionEnvironmentLookup.js:155
-#: components/Lookup/ExecutionEnvironmentLookup.js:186
-#: components/Lookup/ExecutionEnvironmentLookup.js:201
-msgid "Execution Environment"
-msgstr "Execution Environment"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:70
-#: components/TemplateList/TemplateListItem.js:160
-msgid "Execution Environment Missing"
-msgstr "Execution Environment Missing"
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:103
-#: routeConfig.js:147
-#: screens/ActivityStream/ActivityStream.js:217
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:129
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:191
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:13
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:22
-#: screens/Organization/Organization.js:127
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:78
-#: screens/Organization/Organizations.js:34
-#: util/getRelatedResourceDeleteDetails.js:80
-#: util/getRelatedResourceDeleteDetails.js:187
-msgid "Execution Environments"
-msgstr "Execution Environments"
-
-#: screens/Job/JobDetail/JobDetail.js:340
-msgid "Execution Node"
-msgstr "Execution Node"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:103
-msgid "Execution environment copied successfully"
-msgstr "Execution environment copied successfully"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:112
-msgid "Execution environment is missing or deleted."
-msgstr "Execution environment is missing or deleted."
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:83
-msgid "Execution environment not found."
-msgstr "Execution environment not found."
-
-#: screens/TopologyView/Legend.js:86
-msgid "Execution node"
-msgstr "Execution node"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:23
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:26
-msgid "Exit Without Saving"
-msgstr "Exit Without Saving"
-
-#: components/ExpandCollapse/ExpandCollapse.js:52
-msgid "Expand"
-msgstr ""
-
-#: components/DataListToolbar/DataListToolbar.js:105
-msgid "Expand all rows"
-msgstr "Expand all rows"
-
-#: components/CodeEditor/VariablesDetail.js:212
-#: components/CodeEditor/VariablesField.js:248
-msgid "Expand input"
-msgstr "Expand input"
-
-#: screens/Job/JobOutput/PageControls.js:50
-msgid "Expand job events"
-msgstr "Expand job events"
-
-#: screens/Job/JobOutput/shared/JobEventLineToggle.js:37
-msgid "Expand section"
-msgstr "Expand section"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:46
-msgid "Expected at least one of client_email, project_id or private_key to be present in the file."
-msgstr "Expected at least one of client_email, project_id or private_key to be present in the file."
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:137
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:34
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:148
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:172
-#: screens/User/UserTokenDetail/UserTokenDetail.js:55
-#: screens/User/UserTokenList/UserTokenList.js:146
-#: screens/User/UserTokenList/UserTokenList.js:190
-#: screens/User/UserTokenList/UserTokenListItem.js:35
-#: screens/User/UserTokens/UserTokens.js:89
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:192
-msgid "Expires"
-msgstr "Expires"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:84
-msgid "Expires on"
-msgstr "Expires on"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:94
-msgid "Expires on UTC"
-msgstr "Expires on UTC"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:50
-msgid "Expires on {0}"
-msgstr "Expires on {0}"
-
-#: components/JobList/JobListItem.js:306
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:222
-msgid "Explanation"
-msgstr "Explanation"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:113
-msgid "External Secret Management System"
-msgstr "External Secret Management System"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:272
-#: components/AdHocCommands/AdHocDetailsStep.js:273
-msgid "Extra variables"
-msgstr "Extra variables"
-
-#: components/Sparkline/Sparkline.js:35
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:179
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:43
-#: screens/Project/ProjectDetail/ProjectDetail.js:139
-#: screens/Project/ProjectList/ProjectListItem.js:77
-msgid "FINISHED:"
-msgstr "FINISHED:"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:73
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:135
-msgid "Fact Storage"
-msgstr "Fact Storage"
-
-#: screens/Template/shared/JobTemplate.helptext.js:36
-msgid "Fact storage: If enabled, this will store gathered facts so they can be viewed at the host level. Facts are persisted and injected into the fact cache at runtime.."
-msgstr "Fact storage: If enabled, this will store gathered facts so they can be viewed at the host level. Facts are persisted and injected into the fact cache at runtime.."
-
-#: screens/Host/Host.js:63
-#: screens/Host/HostFacts/HostFacts.js:45
-#: screens/Host/Hosts.js:28
-#: screens/Inventory/Inventories.js:71
-#: screens/Inventory/InventoryHost/InventoryHost.js:78
-#: screens/Inventory/InventoryHostFacts/InventoryHostFacts.js:39
-msgid "Facts"
-msgstr "Facts"
-
-#: components/JobList/JobList.js:232
-#: components/StatusLabel/StatusLabel.js:37
-#: components/Workflow/WorkflowNodeHelp.js:105
-#: screens/Dashboard/shared/ChartTooltip.js:66
-#: screens/Job/JobOutput/shared/HostStatusBar.js:47
-#: screens/Job/JobOutput/shared/OutputToolbar.js:113
-msgid "Failed"
-msgstr "Failed"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:112
-msgid "Failed Host Count"
-msgstr "Failed Host Count"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:114
-msgid "Failed Hosts"
-msgstr "Failed Hosts"
-
-#: components/LaunchButton/ReLaunchDropDown.js:61
-#: screens/Dashboard/Dashboard.js:87
-msgid "Failed hosts"
-msgstr "Failed hosts"
-
-#: screens/Dashboard/DashboardGraph.js:170
-msgid "Failed jobs"
-msgstr "Failed jobs"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:291
-msgid "Failed to approve one or more workflow approval."
-msgstr "Failed to approve one or more workflow approval."
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:387
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:398
-msgid "Failed to approve workflow approval."
-msgstr "Failed to approve workflow approval."
-
-#: components/ResourceAccessList/ResourceAccessList.js:281
-msgid "Failed to assign roles properly"
-msgstr "Failed to assign roles properly"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:250
-#: screens/User/UserRoles/UserRolesList.js:246
-msgid "Failed to associate role"
-msgstr "Failed to associate role"
-
-#: screens/Host/HostGroups/HostGroupsList.js:248
-#: screens/InstanceGroup/Instances/InstanceList.js:300
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:288
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:265
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:268
-#: screens/User/UserTeams/UserTeamList.js:263
-msgid "Failed to associate."
-msgstr "Failed to associate."
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:317
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:111
-msgid "Failed to cancel Inventory Source Sync"
-msgstr "Failed to cancel Inventory Source Sync"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:304
-#: screens/Project/ProjectList/ProjectListItem.js:232
-msgid "Failed to cancel Project Sync"
-msgstr "Failed to cancel Project Sync"
-
-#: components/JobList/JobList.js:329
-msgid "Failed to cancel one or more jobs."
-msgstr "Failed to cancel one or more jobs."
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:302
-msgid "Failed to cancel one or more workflow approval."
-msgstr "Failed to cancel one or more workflow approval."
-
-#: components/JobList/JobListItem.js:114
-#: screens/Job/JobDetail/JobDetail.js:583
-#: screens/Job/JobOutput/shared/OutputToolbar.js:138
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:90
-msgid "Failed to cancel {0}"
-msgstr "Failed to cancel {0}"
-
-#: screens/Credential/CredentialList/CredentialListItem.js:88
-msgid "Failed to copy credential."
-msgstr "Failed to copy credential."
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:112
-msgid "Failed to copy execution environment"
-msgstr "Failed to copy execution environment"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:162
-msgid "Failed to copy inventory."
-msgstr "Failed to copy inventory."
-
-#: screens/Project/ProjectList/ProjectListItem.js:270
-msgid "Failed to copy project."
-msgstr "Failed to copy project."
-
-#: components/TemplateList/TemplateListItem.js:253
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:160
-msgid "Failed to copy template."
-msgstr "Failed to copy template."
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:138
-msgid "Failed to delete application."
-msgstr "Failed to delete application."
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:318
-msgid "Failed to delete credential."
-msgstr "Failed to delete credential."
-
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:82
-msgid "Failed to delete group {0}."
-msgstr "Failed to delete group {0}."
-
-#: screens/Host/HostDetail/HostDetail.js:124
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:122
-msgid "Failed to delete host."
-msgstr "Failed to delete host."
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:343
-msgid "Failed to delete inventory source {name}."
-msgstr "Failed to delete inventory source {name}."
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:174
-msgid "Failed to delete inventory."
-msgstr "Failed to delete inventory."
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:540
-msgid "Failed to delete job template."
-msgstr "Failed to delete job template."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:443
-msgid "Failed to delete notification."
-msgstr "Failed to delete notification."
-
-#: screens/Application/ApplicationsList/ApplicationsList.js:188
-msgid "Failed to delete one or more applications."
-msgstr "Failed to delete one or more applications."
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:208
-msgid "Failed to delete one or more credential types."
-msgstr "Failed to delete one or more credential types."
-
-#: screens/Credential/CredentialList/CredentialList.js:217
-msgid "Failed to delete one or more credentials."
-msgstr "Failed to delete one or more credentials."
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:233
-msgid "Failed to delete one or more execution environments"
-msgstr "Failed to delete one or more execution environments"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:155
-msgid "Failed to delete one or more groups."
-msgstr "Failed to delete one or more groups."
-
-#: screens/Host/HostList/HostList.js:236
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:204
-msgid "Failed to delete one or more hosts."
-msgstr "Failed to delete one or more hosts."
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:239
-msgid "Failed to delete one or more instance groups."
-msgstr "Failed to delete one or more instance groups."
-
-#: screens/Inventory/InventoryList/InventoryList.js:288
-msgid "Failed to delete one or more inventories."
-msgstr "Failed to delete one or more inventories."
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:255
-msgid "Failed to delete one or more inventory sources."
-msgstr "Failed to delete one or more inventory sources."
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:244
-msgid "Failed to delete one or more job templates."
-msgstr "Failed to delete one or more job templates."
-
-#: components/JobList/JobList.js:318
-msgid "Failed to delete one or more jobs."
-msgstr "Failed to delete one or more jobs."
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:236
-msgid "Failed to delete one or more notification template."
-msgstr "Failed to delete one or more notification template."
-
-#: screens/Organization/OrganizationList/OrganizationList.js:198
-msgid "Failed to delete one or more organizations."
-msgstr "Failed to delete one or more organizations."
-
-#: screens/Project/ProjectList/ProjectList.js:294
-msgid "Failed to delete one or more projects."
-msgstr "Failed to delete one or more projects."
-
-#: components/Schedule/ScheduleList/ScheduleList.js:241
-msgid "Failed to delete one or more schedules."
-msgstr "Failed to delete one or more schedules."
-
-#: screens/Team/TeamList/TeamList.js:195
-msgid "Failed to delete one or more teams."
-msgstr "Failed to delete one or more teams."
-
-#: components/TemplateList/TemplateList.js:302
-msgid "Failed to delete one or more templates."
-msgstr "Failed to delete one or more templates."
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:158
-msgid "Failed to delete one or more tokens."
-msgstr "Failed to delete one or more tokens."
-
-#: screens/User/UserTokenList/UserTokenList.js:217
-msgid "Failed to delete one or more user tokens."
-msgstr "Failed to delete one or more user tokens."
-
-#: screens/User/UserList/UserList.js:192
-msgid "Failed to delete one or more users."
-msgstr "Failed to delete one or more users."
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:280
-msgid "Failed to delete one or more workflow approval."
-msgstr "Failed to delete one or more workflow approval."
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:211
-msgid "Failed to delete organization."
-msgstr "Failed to delete organization."
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:333
-msgid "Failed to delete project."
-msgstr "Failed to delete project."
-
-#: components/ResourceAccessList/ResourceAccessList.js:292
-msgid "Failed to delete role"
-msgstr "Failed to delete role"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:261
-#: screens/User/UserRoles/UserRolesList.js:257
-msgid "Failed to delete role."
-msgstr "Failed to delete role."
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:430
-msgid "Failed to delete schedule."
-msgstr "Failed to delete schedule."
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:188
-msgid "Failed to delete smart inventory."
-msgstr "Failed to delete smart inventory."
-
-#: screens/Team/TeamDetail/TeamDetail.js:81
-msgid "Failed to delete team."
-msgstr "Failed to delete team."
-
-#: screens/User/UserDetail/UserDetail.js:118
-msgid "Failed to delete user."
-msgstr "Failed to delete user."
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:376
-msgid "Failed to delete workflow approval."
-msgstr "Failed to delete workflow approval."
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:282
-msgid "Failed to delete workflow job template."
-msgstr "Failed to delete workflow job template."
-
-#: screens/Host/HostDetail/HostDetail.js:59
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:59
-msgid "Failed to delete {name}."
-msgstr "Failed to delete {name}."
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:313
-msgid "Failed to deny one or more workflow approval."
-msgstr "Failed to deny one or more workflow approval."
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:409
-msgid "Failed to deny workflow approval."
-msgstr "Failed to deny workflow approval."
-
-#: screens/Host/HostGroups/HostGroupsList.js:249
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:266
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:269
-msgid "Failed to disassociate one or more groups."
-msgstr "Failed to disassociate one or more groups."
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:299
-msgid "Failed to disassociate one or more hosts."
-msgstr "Failed to disassociate one or more hosts."
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:308
-#: screens/InstanceGroup/Instances/InstanceList.js:302
-#: screens/Instances/InstanceDetail/InstanceDetail.js:254
-msgid "Failed to disassociate one or more instances."
-msgstr "Failed to disassociate one or more instances."
-
-#: screens/User/UserTeams/UserTeamList.js:264
-msgid "Failed to disassociate one or more teams."
-msgstr "Failed to disassociate one or more teams."
-
-#: screens/Login/Login.js:221
-msgid "Failed to fetch custom login configuration settings. System defaults will be shown instead."
-msgstr "Failed to fetch custom login configuration settings. System defaults will be shown instead."
-
-#: screens/Project/ProjectList/ProjectList.js:306
-msgid "Failed to fetch the updated project data."
-msgstr "Failed to fetch the updated project data."
-
-#: components/AdHocCommands/AdHocCommands.js:112
-#: components/LaunchButton/LaunchButton.js:165
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:128
-msgid "Failed to launch job."
-msgstr "Failed to launch job."
-
-#: contexts/Config.js:98
-msgid "Failed to retrieve configuration."
-msgstr "Failed to retrieve configuration."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:349
-msgid "Failed to retrieve full node resource object."
-msgstr "Failed to retrieve full node resource object."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:360
-msgid "Failed to retrieve node credentials."
-msgstr "Failed to retrieve node credentials."
-
-#: screens/InstanceGroup/Instances/InstanceList.js:304
-#: screens/Instances/InstanceList/InstanceList.js:181
-msgid "Failed to run a health check on one or more instances."
-msgstr "Failed to run a health check on one or more instances."
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:172
-msgid "Failed to send test notification."
-msgstr "Failed to send test notification."
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:52
-msgid "Failed to sync inventory source."
-msgstr "Failed to sync inventory source."
-
-#: screens/Project/shared/ProjectSyncButton.js:62
-msgid "Failed to sync project."
-msgstr "Failed to sync project."
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:242
-msgid "Failed to sync some or all inventory sources."
-msgstr "Failed to sync some or all inventory sources."
-
-#: components/HostToggle/HostToggle.js:80
-msgid "Failed to toggle host."
-msgstr "Failed to toggle host."
-
-#: components/InstanceToggle/InstanceToggle.js:71
-msgid "Failed to toggle instance."
-msgstr "Failed to toggle instance."
-
-#: components/NotificationList/NotificationList.js:250
-msgid "Failed to toggle notification."
-msgstr "Failed to toggle notification."
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:77
-msgid "Failed to toggle schedule."
-msgstr "Failed to toggle schedule."
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:307
-#: screens/InstanceGroup/Instances/InstanceListItem.js:222
-#: screens/Instances/InstanceDetail/InstanceDetail.js:253
-#: screens/Instances/InstanceList/InstanceListItem.js:238
-msgid "Failed to update capacity adjustment."
-msgstr "Failed to update capacity adjustment."
-
-#: screens/Template/TemplateSurvey.js:133
-msgid "Failed to update survey."
-msgstr "Failed to update survey."
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:87
-msgid "Failed to user token."
-msgstr "Failed to user token."
-
-#: components/NotificationList/NotificationListItem.js:85
-#: components/NotificationList/NotificationListItem.js:86
-msgid "Failure"
-msgstr ""
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:65
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:205
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:235
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:265
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:310
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:368
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:80
-msgid "False"
-msgstr "False"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:118
-msgid "February"
-msgstr "February"
-
-#: components/Search/LookupTypeInput.js:52
-msgid "Field contains value."
-msgstr "Field contains value."
-
-#: components/Search/LookupTypeInput.js:80
-msgid "Field ends with value."
-msgstr "Field ends with value."
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:76
-msgid "Field for passing a custom Kubernetes or OpenShift Pod specification."
-msgstr "Field for passing a custom Kubernetes or OpenShift Pod specification."
-
-#: components/Search/LookupTypeInput.js:94
-msgid "Field matches the given regular expression."
-msgstr "Field matches the given regular expression."
-
-#: components/Search/LookupTypeInput.js:66
-msgid "Field starts with value."
-msgstr "Field starts with value."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:409
-msgid "Fifth"
-msgstr "Fifth"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:106
-msgid "File Difference"
-msgstr "File Difference"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:72
-msgid "File upload rejected. Please select a single .json file."
-msgstr "File upload rejected. Please select a single .json file."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:96
-msgid "File, directory or script"
-msgstr "File, directory or script"
-
-#: components/Search/Search.js:198
-#: components/Search/Search.js:222
-msgid "Filter By {name}"
-msgstr "Filter By {name}"
-
-#: components/JobList/JobList.js:248
-#: components/JobList/JobListItem.js:100
-msgid "Finish Time"
-msgstr "Finish Time"
-
-#: screens/Job/JobDetail/JobDetail.js:206
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:249
-msgid "Finished"
-msgstr "Finished"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:397
-msgid "First"
-msgstr ""
-
-#: components/AddRole/AddResourceRole.js:28
-#: components/AddRole/AddResourceRole.js:42
-#: components/ResourceAccessList/ResourceAccessList.js:178
-#: screens/User/UserDetail/UserDetail.js:64
-#: screens/User/UserList/UserList.js:124
-#: screens/User/UserList/UserList.js:161
-#: screens/User/UserList/UserListItem.js:53
-#: screens/User/shared/UserForm.js:63
-msgid "First Name"
-msgstr "First Name"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:255
-msgid "First Run"
-msgstr "First Run"
-
-#: components/ResourceAccessList/ResourceAccessList.js:227
-#: components/ResourceAccessList/ResourceAccessListItem.js:67
-msgid "First name"
-msgstr "First name"
-
-#: components/Search/AdvancedSearch.js:213
-#: components/Search/AdvancedSearch.js:227
-msgid "First, select a key"
-msgstr "First, select a key"
-
-#: components/Workflow/WorkflowTools.js:88
-msgid "Fit the graph to the available screen size"
-msgstr "Fit the graph to the available screen size"
-
-#: screens/TopologyView/Header.js:75
-#: screens/TopologyView/Header.js:78
-msgid "Fit to screen"
-msgstr "Fit to screen"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:94
-msgid "Float"
-msgstr "Float"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:184
-msgid "Follow"
-msgstr "Follow"
-
-#: screens/Template/shared/JobTemplateForm.js:253
-#~ msgid ""
-#~ "For job templates, select run to execute\n"
-#~ "the playbook. Select check to only check playbook syntax,\n"
-#~ "test environment setup, and report problems without\n"
-#~ "executing the playbook."
-#~ msgstr ""
-#~ "For job templates, select run to execute\n"
-#~ "the playbook. Select check to only check playbook syntax,\n"
-#~ "test environment setup, and report problems without\n"
-#~ "executing the playbook."
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:114
-msgid ""
-"For job templates, select run to execute the playbook.\n"
-"Select check to only check playbook syntax, test environment setup,\n"
-"and report problems without executing the playbook."
-msgstr ""
-"For job templates, select run to execute the playbook.\n"
-"Select check to only check playbook syntax, test environment setup,\n"
-"and report problems without executing the playbook."
-
-#: screens/Job/Job.helptext.js:5
-#: screens/Template/shared/JobTemplate.helptext.js:5
-msgid "For job templates, select run to execute the playbook. Select check to only check playbook syntax, test environment setup, and report problems without executing the playbook."
-msgstr "For job templates, select run to execute the playbook. Select check to only check playbook syntax, test environment setup, and report problems without executing the playbook."
-
-#: screens/Project/shared/Project.helptext.js:98
-msgid "For more information, refer to the"
-msgstr "For more information, refer to the"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:160
-#: components/AdHocCommands/AdHocDetailsStep.js:161
-#: components/PromptDetail/PromptJobTemplateDetail.js:147
-#: screens/Job/JobDetail/JobDetail.js:384
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:252
-#: screens/Template/shared/JobTemplateForm.js:403
-msgid "Forks"
-msgstr "Forks"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:407
-msgid "Fourth"
-msgstr "Fourth"
-
-#: components/Schedule/shared/ScheduleForm.js:177
-msgid "Frequency Details"
-msgstr "Frequency Details"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:201
-#: components/Schedule/shared/buildRuleObj.js:71
-msgid "Frequency did not match an expected value"
-msgstr "Frequency did not match an expected value"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:303
-msgid "Fri"
-msgstr "Fri"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:308
-#: components/Schedule/shared/FrequencyDetailSubform.js:446
-msgid "Friday"
-msgstr "Friday"
-
-#: components/Search/RelatedLookupTypeInput.js:45
-msgid "Fuzzy search on id, name or description fields."
-msgstr "Fuzzy search on id, name or description fields."
-
-#: components/Search/RelatedLookupTypeInput.js:32
-msgid "Fuzzy search on name field."
-msgstr "Fuzzy search on name field."
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:153
-#: screens/Organization/shared/OrganizationForm.js:101
-msgid "Galaxy Credentials"
-msgstr "Galaxy Credentials"
-
-#: screens/Credential/shared/CredentialForm.js:185
-msgid "Galaxy credentials must be owned by an Organization."
-msgstr "Galaxy credentials must be owned by an Organization."
-
-#: screens/Job/JobOutput/JobOutputSearch.js:107
-msgid "Gathering Facts"
-msgstr "Gathering Facts"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:222
-msgid "Get subscription"
-msgstr "Get subscription"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:216
-msgid "Get subscriptions"
-msgstr "Get subscriptions"
-
-#: components/Lookup/ProjectLookup.js:135
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:89
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:158
-#: screens/Job/JobDetail/JobDetail.js:74
-#: screens/Project/ProjectList/ProjectList.js:198
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:97
-msgid "Git"
-msgstr "Git"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:305
-#: screens/Template/shared/WebhookSubForm.js:105
-msgid "GitHub"
-msgstr "GitHub"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:85
-#: screens/Setting/Settings.js:50
-msgid "GitHub Default"
-msgstr "GitHub Default"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:100
-#: screens/Setting/Settings.js:59
-msgid "GitHub Enterprise"
-msgstr "GitHub Enterprise"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:105
-#: screens/Setting/Settings.js:62
-msgid "GitHub Enterprise Organization"
-msgstr "GitHub Enterprise Organization"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:110
-#: screens/Setting/Settings.js:65
-msgid "GitHub Enterprise Team"
-msgstr "GitHub Enterprise Team"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:90
-#: screens/Setting/Settings.js:53
-msgid "GitHub Organization"
-msgstr "GitHub Organization"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:95
-#: screens/Setting/Settings.js:56
-msgid "GitHub Team"
-msgstr "GitHub Team"
-
-#: screens/Setting/SettingList.js:61
-msgid "GitHub settings"
-msgstr "GitHub settings"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:305
-#: screens/Template/shared/WebhookSubForm.js:111
-msgid "GitLab"
-msgstr "GitLab"
-
-#: components/Lookup/ExecutionEnvironmentLookup.js:206
-#~ msgid "Global Default Execution Environment"
-#~ msgstr "Global Default Execution Environment"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:78
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:84
-msgid "Globally Available"
-msgstr "Globally Available"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:133
-msgid "Globally available execution environment can not be reassigned to a specific Organization"
-msgstr "Globally available execution environment can not be reassigned to a specific Organization"
-
-#: components/Pagination/Pagination.js:29
-msgid "Go to first page"
-msgstr ""
-
-#: components/Pagination/Pagination.js:31
-msgid "Go to last page"
-msgstr ""
-
-#: components/Pagination/Pagination.js:32
-msgid "Go to next page"
-msgstr ""
-
-#: components/Pagination/Pagination.js:30
-msgid "Go to previous page"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:99
-msgid "Google Compute Engine"
-msgstr "Google Compute Engine"
-
-#: screens/Setting/SettingList.js:65
-msgid "Google OAuth 2 settings"
-msgstr "Google OAuth 2 settings"
-
-#: screens/Setting/Settings.js:68
-msgid "Google OAuth2"
-msgstr "Google OAuth2"
-
-#: components/NotificationList/NotificationList.js:194
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:135
-msgid "Grafana"
-msgstr "Grafana"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:153
-msgid "Grafana API key"
-msgstr "Grafana API key"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:182
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:144
-msgid "Grafana URL"
-msgstr "Grafana URL"
-
-#: components/Search/LookupTypeInput.js:106
-msgid "Greater than comparison."
-msgstr "Greater than comparison."
-
-#: components/Search/LookupTypeInput.js:113
-msgid "Greater than or equal to comparison."
-msgstr "Greater than or equal to comparison."
-
-#: components/Lookup/HostFilterLookup.js:102
-msgid "Group"
-msgstr "Group"
-
-#: screens/Inventory/Inventories.js:78
-msgid "Group details"
-msgstr "Group details"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:124
-msgid "Group type"
-msgstr "Group type"
-
-#: screens/Host/Host.js:68
-#: screens/Host/HostGroups/HostGroupsList.js:231
-#: screens/Host/Hosts.js:29
-#: screens/Inventory/Inventories.js:72
-#: screens/Inventory/Inventories.js:74
-#: screens/Inventory/Inventory.js:66
-#: screens/Inventory/InventoryHost/InventoryHost.js:83
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:248
-#: screens/Inventory/InventoryList/InventoryListItem.js:127
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:251
-#: util/getRelatedResourceDeleteDetails.js:118
-msgid "Groups"
-msgstr "Groups"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:378
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:453
-msgid "HTTP Headers"
-msgstr "HTTP Headers"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:373
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:466
-msgid "HTTP Method"
-msgstr "HTTP Method"
-
-#: components/HealthCheckButton/HealthCheckButton.js:32
-#: components/HealthCheckButton/HealthCheckButton.js:45
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:273
-#: screens/Instances/InstanceDetail/InstanceDetail.js:228
-#~ msgid "Health Check"
-#~ msgstr "Health Check"
-
-#: components/StatusLabel/StatusLabel.js:34
-#: screens/TopologyView/Legend.js:118
-msgid "Healthy"
-msgstr "Healthy"
-
-#: components/AppContainer/PageHeaderToolbar.js:121
-msgid "Help"
-msgstr ""
-
-#: components/FormField/PasswordInput.js:35
-msgid "Hide"
-msgstr "Hide"
-
-#: components/LaunchPrompt/LaunchPrompt.js:105
-#: components/Schedule/shared/SchedulePromptableFields.js:109
-msgid "Hide description"
-msgstr "Hide description"
-
-#: components/NotificationList/NotificationList.js:195
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:136
-msgid "Hipchat"
-msgstr "Hipchat"
-
-#: screens/Instances/InstanceList/InstanceList.js:119
-msgid "Hop"
-msgstr "Hop"
-
-#: screens/TopologyView/Legend.js:103
-msgid "Hop node"
-msgstr "Hop node"
-
-#: screens/Job/JobOutput/HostEventModal.js:109
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:148
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:76
-msgid "Host"
-msgstr "Host"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:108
-msgid "Host Async Failure"
-msgstr "Host Async Failure"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:109
-msgid "Host Async OK"
-msgstr "Host Async OK"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:154
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:290
-#: screens/Template/shared/JobTemplateForm.js:575
-msgid "Host Config Key"
-msgstr "Host Config Key"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:96
-msgid "Host Count"
-msgstr "Host Count"
-
-#: screens/Job/JobOutput/HostEventModal.js:88
-msgid "Host Details"
-msgstr "Host Details"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:110
-msgid "Host Failed"
-msgstr "Host Failed"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:111
-msgid "Host Failure"
-msgstr "Host Failure"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:257
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:145
-msgid "Host Filter"
-msgstr "Host Filter"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:196
-#: screens/Instances/InstanceDetail/InstanceDetail.js:144
-msgid "Host Name"
-msgstr "Host Name"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:112
-msgid "Host OK"
-msgstr "Host OK"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:113
-msgid "Host Polling"
-msgstr "Host Polling"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:114
-msgid "Host Retry"
-msgstr "Host Retry"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:115
-msgid "Host Skipped"
-msgstr "Host Skipped"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:116
-msgid "Host Started"
-msgstr "Host Started"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:117
-msgid "Host Unreachable"
-msgstr "Host Unreachable"
-
-#: screens/Inventory/Inventories.js:69
-msgid "Host details"
-msgstr "Host details"
-
-#: screens/Job/JobOutput/HostEventModal.js:89
-msgid "Host details modal"
-msgstr "Host details modal"
-
-#: screens/Host/Host.js:96
-#: screens/Inventory/InventoryHost/InventoryHost.js:100
-msgid "Host not found."
-msgstr "Host not found."
-
-#: screens/Job/JobOutput/shared/HostStatusBar.js:76
-msgid "Host status information for this job is unavailable."
-msgstr "Host status information for this job is unavailable."
-
-#: routeConfig.js:85
-#: screens/ActivityStream/ActivityStream.js:176
-#: screens/Dashboard/Dashboard.js:81
-#: screens/Host/HostList/HostList.js:143
-#: screens/Host/HostList/HostList.js:191
-#: screens/Host/Hosts.js:14
-#: screens/Host/Hosts.js:23
-#: screens/Inventory/Inventories.js:65
-#: screens/Inventory/Inventories.js:79
-#: screens/Inventory/Inventory.js:67
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:67
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:189
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:272
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:112
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:172
-#: screens/Inventory/SmartInventory.js:68
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:71
-#: screens/Job/JobOutput/shared/OutputToolbar.js:97
-#: util/getRelatedResourceDeleteDetails.js:122
-msgid "Hosts"
-msgstr "Hosts"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:132
-msgid "Hosts automated"
-msgstr "Hosts automated"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:114
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:121
-msgid "Hosts available"
-msgstr "Hosts available"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:127
-msgid "Hosts imported"
-msgstr "Hosts imported"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:145
-msgid "Hosts remaining"
-msgstr "Hosts remaining"
-
-#: components/Schedule/shared/ScheduleForm.js:155
-msgid "Hour"
-msgstr "Hour"
-
-#: screens/TopologyView/Legend.js:92
-msgid "Hy"
-msgstr "Hy"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:198
-#: screens/Instances/InstanceList/InstanceList.js:118
-msgid "Hybrid"
-msgstr "Hybrid"
-
-#: screens/TopologyView/Legend.js:95
-msgid "Hybrid node"
-msgstr "Hybrid node"
-
-#: components/JobList/JobList.js:200
-#: components/Lookup/HostFilterLookup.js:98
-#: screens/Team/TeamRoles/TeamRolesList.js:155
-msgid "ID"
-msgstr "ID"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:188
-msgid "ID of the Dashboard"
-msgstr "ID of the Dashboard"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:193
-msgid "ID of the Panel"
-msgstr "ID of the Panel"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:160
-msgid "ID of the dashboard (optional)"
-msgstr "ID of the dashboard (optional)"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:166
-msgid "ID of the panel (optional)"
-msgstr "ID of the panel (optional)"
-
-#: components/NotificationList/NotificationList.js:196
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:137
-msgid "IRC"
-msgstr "IRC"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:223
-msgid "IRC Nick"
-msgstr "IRC Nick"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:218
-msgid "IRC Server Address"
-msgstr "IRC Server Address"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:213
-msgid "IRC Server Port"
-msgstr "IRC Server Port"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:214
-msgid "IRC nick"
-msgstr "IRC nick"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:206
-msgid "IRC server address"
-msgstr "IRC server address"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:192
-msgid "IRC server password"
-msgstr "IRC server password"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:197
-msgid "IRC server port"
-msgstr "IRC server port"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:258
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:303
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:263
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:334
-msgid "Icon URL"
-msgstr "Icon URL"
-
-#: screens/Inventory/shared/Inventory.helptext.js:100
-msgid ""
-"If checked, all variables for child groups\n"
-"and hosts will be removed and replaced by those found\n"
-"on the external source."
-msgstr ""
-"If checked, all variables for child groups\n"
-"and hosts will be removed and replaced by those found\n"
-"on the external source."
-
-#: screens/Inventory/shared/Inventory.helptext.js:84
-msgid ""
-"If checked, any hosts and groups that were\n"
-"previously present on the external source but are now removed\n"
-"will be removed from the inventory. Hosts and groups\n"
-"that were not managed by the inventory source will be promoted\n"
-"to the next manually created group or if there is no manually\n"
-"created group to promote them into, they will be left in the \"all\"\n"
-"default group for the inventory."
-msgstr ""
-"If checked, any hosts and groups that were\n"
-"previously present on the external source but are now removed\n"
-"will be removed from the inventory. Hosts and groups\n"
-"that were not managed by the inventory source will be promoted\n"
-"to the next manually created group or if there is no manually\n"
-"created group to promote them into, they will be left in the \"all\"\n"
-"default group for the inventory."
-
-#: screens/Template/shared/JobTemplateForm.js:558
-#~ msgid ""
-#~ "If enabled, run this playbook as an\n"
-#~ "administrator."
-#~ msgstr ""
-#~ "If enabled, run this playbook as an\n"
-#~ "administrator."
-
-#: screens/Template/shared/JobTemplate.helptext.js:29
-msgid "If enabled, run this playbook as an administrator."
-msgstr "If enabled, run this playbook as an administrator."
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:156
-msgid ""
-"If enabled, show the changes made\n"
-"by Ansible tasks, where supported. This is equivalent to Ansible’s\n"
-"--diff mode."
-msgstr ""
-"If enabled, show the changes made\n"
-"by Ansible tasks, where supported. This is equivalent to Ansible’s\n"
-"--diff mode."
-
-#: screens/Template/shared/JobTemplateForm.js:498
-#~ msgid ""
-#~ "If enabled, show the changes made by\n"
-#~ "Ansible tasks, where supported. This is equivalent\n"
-#~ "to Ansible's --diff mode."
-#~ msgstr ""
-#~ "If enabled, show the changes made by\n"
-#~ "Ansible tasks, where supported. This is equivalent\n"
-#~ "to Ansible's --diff mode."
-
-#: screens/Template/shared/JobTemplate.helptext.js:18
-msgid "If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible's --diff mode."
-msgstr "If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible's --diff mode."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:181
-msgid "If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible’s --diff mode."
-msgstr "If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible’s --diff mode."
-
-#: screens/Template/shared/JobTemplateForm.js:604
-#~ msgid ""
-#~ "If enabled, simultaneous runs of this job\n"
-#~ "template will be allowed."
-#~ msgstr ""
-#~ "If enabled, simultaneous runs of this job\n"
-#~ "template will be allowed."
-
-#: screens/Template/shared/JobTemplate.helptext.js:31
-msgid "If enabled, simultaneous runs of this job template will be allowed."
-msgstr "If enabled, simultaneous runs of this job template will be allowed."
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:16
-msgid "If enabled, simultaneous runs of this workflow job template will be allowed."
-msgstr "If enabled, simultaneous runs of this workflow job template will be allowed."
-
-#: screens/Template/shared/JobTemplateForm.js:611
-#~ msgid ""
-#~ "If enabled, this will store gathered facts so they can\n"
-#~ "be viewed at the host level. Facts are persisted and\n"
-#~ "injected into the fact cache at runtime."
-#~ msgstr ""
-#~ "If enabled, this will store gathered facts so they can\n"
-#~ "be viewed at the host level. Facts are persisted and\n"
-#~ "injected into the fact cache at runtime."
-
-#: screens/Template/shared/JobTemplate.helptext.js:32
-msgid "If enabled, this will store gathered facts so they can be viewed at the host level. Facts are persisted and injected into the fact cache at runtime."
-msgstr "If enabled, this will store gathered facts so they can be viewed at the host level. Facts are persisted and injected into the fact cache at runtime."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:275
-msgid "If specified, this field will be shown on the node instead of the resource name when viewing the workflow"
-msgstr "If specified, this field will be shown on the node instead of the resource name when viewing the workflow"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:150
-msgid "If you are ready to upgrade or renew, please <0>contact us.0>"
-msgstr "If you are ready to upgrade or renew, please <0>contact us.0>"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:63
-msgid ""
-"If you do not have a subscription, you can visit\n"
-"Red Hat to obtain a trial subscription."
-msgstr ""
-"If you do not have a subscription, you can visit\n"
-"Red Hat to obtain a trial subscription."
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:46
-msgid "If you only want to remove access for this particular user, please remove them from the team."
-msgstr "If you only want to remove access for this particular user, please remove them from the team."
-
-#: screens/Inventory/shared/Inventory.helptext.js:120
-#: screens/Inventory/shared/Inventory.helptext.js:139
-msgid ""
-"If you want the Inventory Source to update on\n"
-"launch and on project update, click on Update on launch, and also go to"
-msgstr ""
-"If you want the Inventory Source to update on\n"
-"launch and on project update, click on Update on launch, and also go to"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:53
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:141
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:147
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:166
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:75
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:97
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:89
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:108
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.js:21
-msgid "Image"
-msgstr "Image"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:118
-msgid "Including File"
-msgstr "Including File"
-
-#: components/HostToggle/HostToggle.js:16
-msgid ""
-"Indicates if a host is available and should be included in running\n"
-"jobs. For hosts that are part of an external inventory, this may be\n"
-"reset by the inventory sync process."
-msgstr ""
-"Indicates if a host is available and should be included in running\n"
-"jobs. For hosts that are part of an external inventory, this may be\n"
-"reset by the inventory sync process."
-
-#: components/AppContainer/PageHeaderToolbar.js:108
-msgid "Info"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStreamListItem.js:45
-msgid "Initiated By"
-msgstr "Initiated By"
-
-#: screens/ActivityStream/ActivityStream.js:253
-#: screens/ActivityStream/ActivityStream.js:263
-#: screens/ActivityStream/ActivityStreamDetailButton.js:44
-msgid "Initiated by"
-msgstr "Initiated by"
-
-#: screens/ActivityStream/ActivityStream.js:243
-msgid "Initiated by (username)"
-msgstr "Initiated by (username)"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:82
-#: screens/CredentialType/shared/CredentialTypeForm.js:46
-msgid "Injector configuration"
-msgstr "Injector configuration"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:74
-#: screens/CredentialType/shared/CredentialTypeForm.js:38
-msgid "Input configuration"
-msgstr "Input configuration"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:79
-msgid "Input schema which defines a set of ordered fields for that type."
-msgstr "Input schema which defines a set of ordered fields for that type."
-
-#: screens/Project/shared/ProjectSubForms/InsightsSubForm.js:31
-msgid "Insights Credential"
-msgstr "Insights Credential"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:71
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:72
-#~ msgid "Insights for Ansible Automation Platform"
-#~ msgstr "Insights for Ansible Automation Platform"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:111
-#~ msgid "Insights for Ansible Automation Platform dashboard"
-#~ msgstr "Insights for Ansible Automation Platform dashboard"
-
-#: components/Lookup/HostFilterLookup.js:123
-msgid "Insights system ID"
-msgstr "Insights system ID"
-
-#: screens/Metrics/Metrics.js:187
-msgid "Instance"
-msgstr "Instance"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:147
-msgid "Instance Filters"
-msgstr "Instance Filters"
-
-#: screens/Job/JobDetail/JobDetail.js:353
-msgid "Instance Group"
-msgstr "Instance Group"
-
-#: components/Lookup/InstanceGroupsLookup.js:69
-#: components/Lookup/InstanceGroupsLookup.js:75
-#: components/Lookup/InstanceGroupsLookup.js:121
-#: components/PromptDetail/PromptJobTemplateDetail.js:222
-#: routeConfig.js:132
-#: screens/ActivityStream/ActivityStream.js:205
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:111
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:193
-#: screens/InstanceGroup/InstanceGroups.js:45
-#: screens/InstanceGroup/InstanceGroups.js:55
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:85
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:127
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:407
-msgid "Instance Groups"
-msgstr ""
-
-#: components/Lookup/HostFilterLookup.js:115
-msgid "Instance ID"
-msgstr "Instance ID"
-
-#: screens/InstanceGroup/InstanceGroups.js:62
-msgid "Instance details"
-msgstr "Instance details"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:58
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:69
-msgid "Instance group"
-msgstr "Instance group"
-
-#: screens/InstanceGroup/InstanceGroup.js:92
-msgid "Instance group not found."
-msgstr "Instance group not found."
-
-#: screens/InstanceGroup/Instances/InstanceListItem.js:165
-#: screens/Instances/InstanceList/InstanceListItem.js:176
-msgid "Instance group used capacity"
-msgstr "Instance group used capacity"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:124
-msgid "Instance groups"
-msgstr "Instance groups"
-
-#: routeConfig.js:137
-#: screens/ActivityStream/ActivityStream.js:203
-#: screens/InstanceGroup/InstanceGroup.js:74
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:212
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:73
-#: screens/InstanceGroup/InstanceGroups.js:60
-#: screens/InstanceGroup/Instances/InstanceList.js:181
-#: screens/InstanceGroup/Instances/InstanceList.js:279
-#: screens/Instances/InstanceList/InstanceList.js:101
-#: screens/Instances/Instances.js:12
-#: screens/Instances/Instances.js:20
-msgid "Instances"
-msgstr "Instances"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:93
-msgid "Integer"
-msgstr "Integer"
-
-#: util/validators.js:94
-msgid "Invalid email address"
-msgstr "Invalid email address"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:116
-msgid "Invalid file format. Please upload a valid Red Hat Subscription Manifest."
-msgstr "Invalid file format. Please upload a valid Red Hat Subscription Manifest."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:151
-#~ msgid "Invalid link target. Unable to link to children or ancestor nodes. Graph cycles are not supported."
-#~ msgstr "Invalid link target. Unable to link to children or ancestor nodes. Graph cycles are not supported."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:151
-msgid "Invalid link target. Unable to link to children or ancestor nodes. Graph cycles are not supported."
-msgstr "Invalid link target. Unable to link to children or ancestor nodes. Graph cycles are not supported."
-
-#: util/validators.js:33
-msgid "Invalid time format"
-msgstr "Invalid time format"
-
-#: screens/Login/Login.js:142
-msgid "Invalid username or password. Please try again."
-msgstr ""
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:119
-#: routeConfig.js:80
-#: screens/ActivityStream/ActivityStream.js:173
-#: screens/Dashboard/Dashboard.js:92
-#: screens/Inventory/Inventories.js:17
-#: screens/Inventory/InventoryList/InventoryList.js:174
-#: screens/Inventory/InventoryList/InventoryList.js:237
-#: util/getRelatedResourceDeleteDetails.js:201
-#: util/getRelatedResourceDeleteDetails.js:269
-msgid "Inventories"
-msgstr ""
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:153
-msgid "Inventories with sources cannot be copied"
-msgstr "Inventories with sources cannot be copied"
-
-#: components/HostForm/HostForm.js:48
-#: components/JobList/JobListItem.js:223
-#: components/LaunchPrompt/steps/InventoryStep.js:105
-#: components/LaunchPrompt/steps/useInventoryStep.js:48
-#: components/Lookup/HostFilterLookup.js:423
-#: components/Lookup/HostListItem.js:10
-#: components/Lookup/InventoryLookup.js:129
-#: components/Lookup/InventoryLookup.js:138
-#: components/Lookup/InventoryLookup.js:178
-#: components/Lookup/InventoryLookup.js:193
-#: components/Lookup/InventoryLookup.js:233
-#: components/PromptDetail/PromptDetail.js:205
-#: components/PromptDetail/PromptInventorySourceDetail.js:87
-#: components/PromptDetail/PromptJobTemplateDetail.js:117
-#: components/PromptDetail/PromptJobTemplateDetail.js:127
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:77
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:290
-#: components/TemplateList/TemplateListItem.js:284
-#: components/TemplateList/TemplateListItem.js:294
-#: screens/Host/HostDetail/HostDetail.js:75
-#: screens/Host/HostList/HostList.js:171
-#: screens/Host/HostList/HostListItem.js:61
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:72
-#: screens/Inventory/InventoryList/InventoryList.js:186
-#: screens/Inventory/InventoryList/InventoryListItem.js:117
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:39
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:113
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:41
-#: screens/Job/JobDetail/JobDetail.js:106
-#: screens/Job/JobDetail/JobDetail.js:121
-#: screens/Job/JobDetail/JobDetail.js:128
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:207
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:217
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:142
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:33
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:293
-msgid "Inventory"
-msgstr "Inventory"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:104
-msgid "Inventory (Name)"
-msgstr "Inventory (Name)"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:110
-msgid "Inventory File"
-msgstr "Inventory File"
-
-#: components/Lookup/HostFilterLookup.js:106
-msgid "Inventory ID"
-msgstr "Inventory ID"
-
-#: screens/Job/JobDetail/JobDetail.js:262
-msgid "Inventory Source"
-msgstr "Inventory Source"
-
-#: screens/Job/JobDetail/JobDetail.js:285
-msgid "Inventory Source Project"
-msgstr "Inventory Source Project"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:73
-msgid "Inventory Source Sync"
-msgstr "Inventory Source Sync"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:315
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:110
-msgid "Inventory Source Sync Error"
-msgstr "Inventory Source Sync Error"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:176
-#: screens/Inventory/InventorySources/InventorySourceList.js:193
-#: util/getRelatedResourceDeleteDetails.js:66
-#: util/getRelatedResourceDeleteDetails.js:146
-msgid "Inventory Sources"
-msgstr "Inventory Sources"
-
-#: components/JobList/JobList.js:212
-#: components/JobList/JobListItem.js:43
-#: components/Schedule/ScheduleList/ScheduleListItem.js:36
-#: components/Workflow/WorkflowLegend.js:100
-#: screens/Job/JobDetail/JobDetail.js:65
-msgid "Inventory Sync"
-msgstr "Inventory Sync"
-
-#: screens/Inventory/InventoryList/InventoryList.js:183
-msgid "Inventory Type"
-msgstr "Inventory Type"
-
-#: components/Workflow/WorkflowNodeHelp.js:71
-msgid "Inventory Update"
-msgstr "Inventory Update"
-
-#: screens/Inventory/InventoryList/InventoryList.js:121
-msgid "Inventory copied successfully"
-msgstr "Inventory copied successfully"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:241
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:108
-msgid "Inventory file"
-msgstr "Inventory file"
-
-#: screens/Inventory/Inventory.js:94
-msgid "Inventory not found."
-msgstr "Inventory not found."
-
-#: screens/Dashboard/DashboardGraph.js:140
-msgid "Inventory sync"
-msgstr "Inventory sync"
-
-#: screens/Dashboard/Dashboard.js:98
-msgid "Inventory sync failures"
-msgstr "Inventory sync failures"
-
-#: components/DataListToolbar/DataListToolbar.js:110
-msgid "Is expanded"
-msgstr "Is expanded"
-
-#: components/DataListToolbar/DataListToolbar.js:112
-msgid "Is not expanded"
-msgstr "Is not expanded"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:119
-msgid "Item Failed"
-msgstr "Item Failed"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:120
-msgid "Item OK"
-msgstr "Item OK"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:121
-msgid "Item Skipped"
-msgstr "Item Skipped"
-
-#: components/AssociateModal/AssociateModal.js:20
-#: components/PaginatedTable/PaginatedTable.js:42
-msgid "Items"
-msgstr "Items"
-
-#: components/Pagination/Pagination.js:27
-msgid "Items per page"
-msgstr ""
-
-#: components/Sparkline/Sparkline.js:28
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:172
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:36
-#: screens/Project/ProjectDetail/ProjectDetail.js:132
-#: screens/Project/ProjectList/ProjectListItem.js:70
-msgid "JOB ID:"
-msgstr "JOB ID:"
-
-#: screens/Job/JobOutput/HostEventModal.js:136
-msgid "JSON"
-msgstr "JSON"
-
-#: screens/Job/JobOutput/HostEventModal.js:137
-msgid "JSON tab"
-msgstr "JSON tab"
-
-#: screens/Inventory/shared/Inventory.helptext.js:49
-msgid "JSON:"
-msgstr "JSON:"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:113
-msgid "January"
-msgstr "January"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:221
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:66
-#~ msgid "Job"
-#~ msgstr "Job"
-
-#: components/JobList/JobListItem.js:112
-#: screens/Job/JobDetail/JobDetail.js:581
-#: screens/Job/JobOutput/JobOutput.js:790
-#: screens/Job/JobOutput/JobOutput.js:791
-#: screens/Job/JobOutput/shared/OutputToolbar.js:136
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:88
-msgid "Job Cancel Error"
-msgstr "Job Cancel Error"
-
-#: screens/Job/JobDetail/JobDetail.js:603
-#: screens/Job/JobOutput/JobOutput.js:779
-#: screens/Job/JobOutput/JobOutput.js:780
-msgid "Job Delete Error"
-msgstr "Job Delete Error"
-
-#: screens/Job/JobDetail/JobDetail.js:184
-msgid "Job ID"
-msgstr "Job ID"
-
-#: screens/Dashboard/shared/LineChart.js:128
-msgid "Job Runs"
-msgstr "Job Runs"
-
-#: components/JobList/JobListItem.js:313
-#: screens/Job/JobDetail/JobDetail.js:369
-msgid "Job Slice"
-msgstr "Job Slice"
-
-#: components/JobList/JobListItem.js:318
-#: screens/Job/JobDetail/JobDetail.js:377
-msgid "Job Slice Parent"
-msgstr "Job Slice Parent"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:153
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:282
-#: screens/Template/shared/JobTemplateForm.js:435
-msgid "Job Slicing"
-msgstr "Job Slicing"
-
-#: components/Workflow/WorkflowNodeHelp.js:164
-msgid "Job Status"
-msgstr "Job Status"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:57
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:58
-#: components/PromptDetail/PromptDetail.js:228
-#: components/PromptDetail/PromptJobTemplateDetail.js:241
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:346
-#: screens/Job/JobDetail/JobDetail.js:458
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:434
-#: screens/Template/shared/JobTemplateForm.js:469
-msgid "Job Tags"
-msgstr "Job Tags"
-
-#: components/JobList/JobListItem.js:191
-#: components/TemplateList/TemplateList.js:217
-#: components/Workflow/WorkflowLegend.js:92
-#: components/Workflow/WorkflowNodeHelp.js:59
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:97
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:19
-#: screens/Job/JobDetail/JobDetail.js:213
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:79
-msgid "Job Template"
-msgstr "Job Template"
-
-#: components/LaunchPrompt/steps/credentialsValidator.js:38
-msgid "Job Template default credentials must be replaced with one of the same type. Please select a credential for the following types in order to proceed: {0}"
-msgstr "Job Template default credentials must be replaced with one of the same type. Please select a credential for the following types in order to proceed: {0}"
-
-#: screens/Credential/Credential.js:79
-#: screens/Credential/Credentials.js:30
-#: screens/Inventory/Inventories.js:62
-#: screens/Inventory/Inventory.js:74
-#: screens/Inventory/SmartInventory.js:74
-#: screens/Project/Project.js:107
-#: screens/Project/Projects.js:29
-#: util/getRelatedResourceDeleteDetails.js:55
-#: util/getRelatedResourceDeleteDetails.js:100
-#: util/getRelatedResourceDeleteDetails.js:132
-msgid "Job Templates"
-msgstr "Job Templates"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.js:25
-msgid "Job Templates with a missing inventory or project cannot be selected when creating or editing nodes. Select another template or fix the missing fields to proceed."
-msgstr "Job Templates with a missing inventory or project cannot be selected when creating or editing nodes. Select another template or fix the missing fields to proceed."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useWorkflowNodeSteps.js:316
-msgid "Job Templates with credentials that prompt for passwords cannot be selected when creating or editing nodes"
-msgstr "Job Templates with credentials that prompt for passwords cannot be selected when creating or editing nodes"
-
-#: components/JobList/JobList.js:208
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:111
-#: components/PromptDetail/PromptDetail.js:176
-#: components/PromptDetail/PromptJobTemplateDetail.js:100
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:286
-#: screens/Job/JobDetail/JobDetail.js:247
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:185
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:139
-#: screens/Template/shared/JobTemplateForm.js:252
-msgid "Job Type"
-msgstr "Job Type"
-
-#: screens/Dashboard/Dashboard.js:125
-msgid "Job status"
-msgstr "Job status"
-
-#: screens/Dashboard/Dashboard.js:123
-msgid "Job status graph tab"
-msgstr "Job status graph tab"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:156
-#: components/RelatedTemplateList/RelatedTemplateList.js:206
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:15
-msgid "Job templates"
-msgstr "Job templates"
-
-#: components/JobList/JobList.js:191
-#: components/JobList/JobList.js:274
-#: routeConfig.js:39
-#: screens/ActivityStream/ActivityStream.js:150
-#: screens/Dashboard/shared/LineChart.js:64
-#: screens/Host/Host.js:73
-#: screens/Host/Hosts.js:30
-#: screens/InstanceGroup/ContainerGroup.js:71
-#: screens/InstanceGroup/InstanceGroup.js:79
-#: screens/InstanceGroup/InstanceGroups.js:63
-#: screens/InstanceGroup/InstanceGroups.js:68
-#: screens/Inventory/Inventories.js:60
-#: screens/Inventory/Inventories.js:70
-#: screens/Inventory/Inventory.js:70
-#: screens/Inventory/InventoryHost/InventoryHost.js:88
-#: screens/Inventory/SmartInventory.js:70
-#: screens/Job/Jobs.js:22
-#: screens/Job/Jobs.js:32
-#: screens/Setting/SettingList.js:87
-#: screens/Setting/Settings.js:71
-#: screens/Template/Template.js:155
-#: screens/Template/Templates.js:47
-#: screens/Template/WorkflowJobTemplate.js:141
-msgid "Jobs"
-msgstr ""
-
-#: screens/Setting/SettingList.js:92
-msgid "Jobs settings"
-msgstr "Jobs settings"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:143
-msgid "July"
-msgstr "July"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:138
-msgid "June"
-msgstr "June"
-
-#: components/Search/AdvancedSearch.js:262
-msgid "Key"
-msgstr "Key"
-
-#: components/Search/AdvancedSearch.js:253
-msgid "Key select"
-msgstr "Key select"
-
-#: components/Search/AdvancedSearch.js:256
-msgid "Key typeahead"
-msgstr "Key typeahead"
-
-#: screens/ActivityStream/ActivityStream.js:238
-msgid "Keyword"
-msgstr "Keyword"
-
-#: screens/User/UserDetail/UserDetail.js:56
-#: screens/User/UserList/UserListItem.js:44
-msgid "LDAP"
-msgstr "LDAP"
-
-#: screens/Setting/Settings.js:76
-msgid "LDAP 1"
-msgstr "LDAP 1"
-
-#: screens/Setting/Settings.js:77
-msgid "LDAP 2"
-msgstr "LDAP 2"
-
-#: screens/Setting/Settings.js:78
-msgid "LDAP 3"
-msgstr "LDAP 3"
-
-#: screens/Setting/Settings.js:79
-msgid "LDAP 4"
-msgstr "LDAP 4"
-
-#: screens/Setting/Settings.js:80
-msgid "LDAP 5"
-msgstr "LDAP 5"
-
-#: screens/Setting/Settings.js:75
-msgid "LDAP Default"
-msgstr "LDAP Default"
-
-#: screens/Setting/SettingList.js:69
-msgid "LDAP settings"
-msgstr "LDAP settings"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:107
-msgid "LDAP1"
-msgstr "LDAP1"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:112
-msgid "LDAP2"
-msgstr "LDAP2"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:117
-msgid "LDAP3"
-msgstr "LDAP3"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:122
-msgid "LDAP4"
-msgstr "LDAP4"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:127
-msgid "LDAP5"
-msgstr "LDAP5"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:178
-#: components/TemplateList/TemplateList.js:234
-msgid "Label"
-msgstr "Label"
-
-#: components/JobList/JobList.js:204
-msgid "Label Name"
-msgstr "Label Name"
-
-#: components/JobList/JobListItem.js:283
-#: components/PromptDetail/PromptJobTemplateDetail.js:203
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:114
-#: components/TemplateList/TemplateListItem.js:345
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:110
-#: screens/Inventory/shared/InventoryForm.js:75
-#: screens/Job/JobDetail/JobDetail.js:437
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:386
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:208
-#: screens/Template/shared/JobTemplateForm.js:379
-#: screens/Template/shared/WorkflowJobTemplateForm.js:189
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:315
-msgid "Labels"
-msgstr "Labels"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:410
-msgid "Last"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:213
-#: screens/InstanceGroup/Instances/InstanceListItem.js:133
-#: screens/InstanceGroup/Instances/InstanceListItem.js:208
-#: screens/Instances/InstanceDetail/InstanceDetail.js:164
-#: screens/Instances/InstanceList/InstanceListItem.js:138
-#: screens/Instances/InstanceList/InstanceListItem.js:223
-msgid "Last Health Check"
-msgstr "Last Health Check"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:198
-#: screens/Project/ProjectDetail/ProjectDetail.js:160
-msgid "Last Job Status"
-msgstr "Last Job Status"
-
-#: screens/User/UserDetail/UserDetail.js:80
-msgid "Last Login"
-msgstr "Last Login"
-
-#: components/PromptDetail/PromptDetail.js:152
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:275
-#: components/TemplateList/TemplateListItem.js:315
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:105
-#: screens/Application/ApplicationsList/ApplicationListItem.js:45
-#: screens/Application/ApplicationsList/ApplicationsList.js:159
-#: screens/Credential/CredentialDetail/CredentialDetail.js:263
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:95
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:107
-#: screens/Host/HostDetail/HostDetail.js:87
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:72
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:98
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:139
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:45
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:85
-#: screens/Job/JobDetail/JobDetail.js:520
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:393
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:120
-#: screens/Project/ProjectDetail/ProjectDetail.js:279
-#: screens/Team/TeamDetail/TeamDetail.js:48
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:344
-#: screens/User/UserDetail/UserDetail.js:84
-#: screens/User/UserTokenDetail/UserTokenDetail.js:65
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:245
-msgid "Last Modified"
-msgstr ""
-
-#: components/AddRole/AddResourceRole.js:32
-#: components/AddRole/AddResourceRole.js:46
-#: components/ResourceAccessList/ResourceAccessList.js:182
-#: screens/User/UserDetail/UserDetail.js:65
-#: screens/User/UserList/UserList.js:128
-#: screens/User/UserList/UserList.js:162
-#: screens/User/UserList/UserListItem.js:54
-#: screens/User/shared/UserForm.js:69
-msgid "Last Name"
-msgstr ""
-
-#: components/TemplateList/TemplateList.js:245
-#: components/TemplateList/TemplateListItem.js:194
-msgid "Last Ran"
-msgstr "Last Ran"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:262
-msgid "Last Run"
-msgstr "Last Run"
-
-#: components/Lookup/HostFilterLookup.js:119
-msgid "Last job"
-msgstr "Last job"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:296
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:153
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:50
-#: screens/Project/ProjectList/ProjectListItem.js:308
-msgid "Last modified"
-msgstr "Last modified"
-
-#: components/ResourceAccessList/ResourceAccessList.js:228
-#: components/ResourceAccessList/ResourceAccessListItem.js:68
-msgid "Last name"
-msgstr "Last name"
-
-#: screens/Project/ProjectList/ProjectListItem.js:313
-msgid "Last used"
-msgstr "Last used"
-
-#: components/AdHocCommands/useAdHocPreviewStep.js:22
-#: components/LaunchPrompt/steps/usePreviewStep.js:35
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:54
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:57
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:503
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:512
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:247
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:256
-msgid "Launch"
-msgstr "Launch"
-
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:74
-#~ msgid "Launch Management Job"
-#~ msgstr "Launch Management Job"
-
-#: components/TemplateList/TemplateListItem.js:214
-msgid "Launch Template"
-msgstr "Launch Template"
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:32
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:34
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:46
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:47
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:87
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:90
-msgid "Launch management job"
-msgstr "Launch management job"
-
-#: components/TemplateList/TemplateListItem.js:222
-msgid "Launch template"
-msgstr "Launch template"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:119
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:120
-msgid "Launch workflow"
-msgstr "Launch workflow"
-
-#: components/LaunchPrompt/LaunchPrompt.js:100
-msgid "Launch | {0}"
-msgstr "Launch | {0}"
-
-#: components/DetailList/LaunchedByDetail.js:54
-msgid "Launched By"
-msgstr "Launched By"
-
-#: components/JobList/JobList.js:220
-msgid "Launched By (Username)"
-msgstr "Launched By (Username)"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:120
-msgid "Learn more about Automation Analytics"
-msgstr "Learn more about Automation Analytics"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:120
-#~ msgid "Learn more about Insights for Ansible Automation Platform"
-#~ msgstr "Learn more about Insights for Ansible Automation Platform"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:68
-msgid "Leave this field blank to make the execution environment globally available."
-msgstr "Leave this field blank to make the execution environment globally available."
-
-#: components/Workflow/WorkflowLegend.js:86
-#: screens/Metrics/LineChart.js:120
-#: screens/TopologyView/Header.js:102
-#: screens/TopologyView/Legend.js:65
-msgid "Legend"
-msgstr "Legend"
-
-#: components/Search/LookupTypeInput.js:120
-msgid "Less than comparison."
-msgstr "Less than comparison."
-
-#: components/Search/LookupTypeInput.js:127
-msgid "Less than or equal to comparison."
-msgstr "Less than or equal to comparison."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:140
-#: components/AdHocCommands/AdHocDetailsStep.js:141
-#: components/JobList/JobList.js:238
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:36
-#: components/PromptDetail/PromptDetail.js:216
-#: components/PromptDetail/PromptJobTemplateDetail.js:148
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:88
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:314
-#: screens/Job/JobDetail/JobDetail.js:320
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:258
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:152
-#: screens/Template/shared/JobTemplateForm.js:408
-#: screens/Template/shared/WorkflowJobTemplateForm.js:153
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:279
-msgid "Limit"
-msgstr "Limit"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:234
-msgid "Link to an available node"
-msgstr "Link to an available node"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:334
-msgid "Loading"
-msgstr "Loading"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:49
-msgid "Local"
-msgstr "Local"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:263
-msgid "Local Time Zone"
-msgstr "Local Time Zone"
-
-#: components/Schedule/shared/ScheduleForm.js:132
-msgid "Local time zone"
-msgstr "Local time zone"
-
-#: screens/Login/Login.js:195
-msgid "Log In"
-msgstr "Log In"
-
-#: screens/Setting/Settings.js:93
-msgid "Logging"
-msgstr "Logging"
-
-#: screens/Setting/SettingList.js:111
-msgid "Logging settings"
-msgstr "Logging settings"
-
-#: components/AppContainer/AppContainer.js:81
-#: components/AppContainer/AppContainer.js:150
-#: components/AppContainer/PageHeaderToolbar.js:174
-msgid "Logout"
-msgstr ""
-
-#: components/Lookup/HostFilterLookup.js:366
-#: components/Lookup/Lookup.js:181
-msgid "Lookup modal"
-msgstr "Lookup modal"
-
-#: components/Search/LookupTypeInput.js:22
-msgid "Lookup select"
-msgstr "Lookup select"
-
-#: components/Search/LookupTypeInput.js:31
-msgid "Lookup type"
-msgstr "Lookup type"
-
-#: components/Search/LookupTypeInput.js:25
-msgid "Lookup typeahead"
-msgstr "Lookup typeahead"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:170
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:34
-#: screens/Project/ProjectDetail/ProjectDetail.js:130
-#: screens/Project/ProjectList/ProjectListItem.js:68
-msgid "MOST RECENT SYNC"
-msgstr "MOST RECENT SYNC"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:97
-#: components/AdHocCommands/AdHocCredentialStep.js:98
-#: components/AdHocCommands/AdHocCredentialStep.js:112
-#: screens/Job/JobDetail/JobDetail.js:392
-msgid "Machine Credential"
-msgstr "Machine Credential"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:64
-msgid "Managed"
-msgstr "Managed"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:147
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:169
-msgid "Managed nodes"
-msgstr "Managed nodes"
-
-#: components/JobList/JobList.js:215
-#: components/JobList/JobListItem.js:46
-#: components/Schedule/ScheduleList/ScheduleListItem.js:39
-#: components/Workflow/WorkflowLegend.js:108
-#: components/Workflow/WorkflowNodeHelp.js:79
-#: screens/Job/JobDetail/JobDetail.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:102
-msgid "Management Job"
-msgstr "Management Job"
-
-#: routeConfig.js:127
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:84
-msgid "Management Jobs"
-msgstr ""
-
-#: screens/ManagementJob/ManagementJobs.js:20
-msgid "Management job"
-msgstr "Management job"
-
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:109
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:110
-msgid "Management job launch error"
-msgstr "Management job launch error"
-
-#: screens/ManagementJob/ManagementJob.js:133
-msgid "Management job not found."
-msgstr "Management job not found."
-
-#: screens/ManagementJob/ManagementJobs.js:13
-msgid "Management jobs"
-msgstr "Management jobs"
-
-#: components/Lookup/ProjectLookup.js:134
-#: components/PromptDetail/PromptProjectDetail.js:98
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:88
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:157
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:208
-#: screens/InstanceGroup/Instances/InstanceListItem.js:204
-#: screens/Instances/InstanceDetail/InstanceDetail.js:159
-#: screens/Instances/InstanceList/InstanceListItem.js:219
-#: screens/Job/JobDetail/JobDetail.js:73
-#: screens/Project/ProjectDetail/ProjectDetail.js:191
-#: screens/Project/ProjectList/ProjectList.js:197
-#: screens/Project/ProjectList/ProjectListItem.js:219
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:96
-msgid "Manual"
-msgstr "Manual"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:123
-msgid "March"
-msgstr "March"
-
-#: components/NotificationList/NotificationList.js:197
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:138
-msgid "Mattermost"
-msgstr "Mattermost"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:98
-#: screens/Organization/shared/OrganizationForm.js:71
-msgid "Max Hosts"
-msgstr "Max Hosts"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:220
-msgid "Maximum"
-msgstr "Maximum"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:204
-msgid "Maximum length"
-msgstr "Maximum length"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:133
-msgid "May"
-msgstr "May"
-
-#: screens/Organization/OrganizationList/OrganizationList.js:144
-#: screens/Organization/OrganizationList/OrganizationListItem.js:63
-msgid "Members"
-msgstr ""
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:47
-msgid "Metadata"
-msgstr "Metadata"
-
-#: screens/Metrics/Metrics.js:207
-msgid "Metric"
-msgstr "Metric"
-
-#: screens/Metrics/Metrics.js:179
-msgid "Metrics"
-msgstr "Metrics"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:100
-msgid "Microsoft Azure Resource Manager"
-msgstr "Microsoft Azure Resource Manager"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:214
-msgid "Minimum"
-msgstr "Minimum"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:198
-msgid "Minimum length"
-msgstr "Minimum length"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:65
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:31
-msgid ""
-"Minimum number of instances that will be automatically\n"
-"assigned to this group when new instances come online."
-msgstr ""
-"Minimum number of instances that will be automatically\n"
-"assigned to this group when new instances come online."
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:71
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:41
-msgid ""
-"Minimum percentage of all instances that will be automatically\n"
-"assigned to this group when new instances come online."
-msgstr ""
-"Minimum percentage of all instances that will be automatically\n"
-"assigned to this group when new instances come online."
-
-#: components/Schedule/shared/ScheduleForm.js:154
-msgid "Minute"
-msgstr "Minute"
-
-#: screens/Setting/Settings.js:96
-msgid "Miscellaneous Authentication"
-msgstr "Miscellaneous Authentication"
-
-#: screens/Setting/SettingList.js:107
-msgid "Miscellaneous Authentication settings"
-msgstr "Miscellaneous Authentication settings"
-
-#: screens/Setting/Settings.js:99
-msgid "Miscellaneous System"
-msgstr "Miscellaneous System"
-
-#: screens/Setting/SettingList.js:103
-msgid "Miscellaneous System settings"
-msgstr "Miscellaneous System settings"
-
-#: components/Workflow/WorkflowNodeHelp.js:120
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:86
-msgid "Missing"
-msgstr "Missing"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:66
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:109
-msgid "Missing resource"
-msgstr "Missing resource"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:193
-#: screens/User/UserTokenList/UserTokenList.js:154
-msgid "Modified"
-msgstr ""
-
-#: components/AdHocCommands/AdHocCredentialStep.js:126
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:116
-#: components/AddRole/AddResourceRole.js:61
-#: components/AssociateModal/AssociateModal.js:148
-#: components/LaunchPrompt/steps/CredentialsStep.js:177
-#: components/LaunchPrompt/steps/InventoryStep.js:93
-#: components/Lookup/CredentialLookup.js:197
-#: components/Lookup/InventoryLookup.js:165
-#: components/Lookup/InventoryLookup.js:220
-#: components/Lookup/MultiCredentialsLookup.js:197
-#: components/Lookup/OrganizationLookup.js:137
-#: components/Lookup/ProjectLookup.js:146
-#: components/NotificationList/NotificationList.js:210
-#: components/RelatedTemplateList/RelatedTemplateList.js:170
-#: components/Schedule/ScheduleList/ScheduleList.js:201
-#: components/TemplateList/TemplateList.js:230
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:31
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:62
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:100
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:131
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:169
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:200
-#: screens/Credential/CredentialList/CredentialList.js:154
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:100
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:136
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:106
-#: screens/Host/HostGroups/HostGroupsList.js:168
-#: screens/Host/HostList/HostList.js:161
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:203
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:133
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:178
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:132
-#: screens/Inventory/InventoryList/InventoryList.js:203
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:189
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:98
-#: screens/Organization/OrganizationList/OrganizationList.js:135
-#: screens/Project/ProjectList/ProjectList.js:209
-#: screens/Team/TeamList/TeamList.js:134
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:167
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:108
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:112
-msgid "Modified By (Username)"
-msgstr "Modified By (Username)"
-
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:85
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:151
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:77
-msgid "Modified by (username)"
-msgstr "Modified by (username)"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:59
-#: screens/Job/JobOutput/HostEventModal.js:125
-msgid "Module"
-msgstr "Module"
-
-#: screens/Job/JobDetail/JobDetail.js:512
-msgid "Module Arguments"
-msgstr "Module Arguments"
-
-#: screens/Job/JobDetail/JobDetail.js:506
-msgid "Module Name"
-msgstr "Module Name"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:259
-msgid "Mon"
-msgstr "Mon"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:264
-#: components/Schedule/shared/FrequencyDetailSubform.js:426
-msgid "Monday"
-msgstr "Monday"
-
-#: components/Schedule/shared/ScheduleForm.js:158
-msgid "Month"
-msgstr "Month"
-
-#: components/Popover/Popover.js:32
-msgid "More information"
-msgstr "More information"
-
-#: screens/Setting/shared/SharedFields.js:73
-msgid "More information for"
-msgstr "More information for"
-
-#: screens/Template/Survey/SurveyReorderModal.js:162
-#: screens/Template/Survey/SurveyReorderModal.js:163
-msgid "Multi-Select"
-msgstr "Multi-Select"
-
-#: screens/Template/Survey/SurveyReorderModal.js:146
-#: screens/Template/Survey/SurveyReorderModal.js:147
-msgid "Multiple Choice"
-msgstr "Multiple Choice"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:91
-msgid "Multiple Choice (multiple select)"
-msgstr "Multiple Choice (multiple select)"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:86
-msgid "Multiple Choice (single select)"
-msgstr "Multiple Choice (single select)"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:258
-msgid "Multiple Choice Options"
-msgstr "Multiple Choice Options"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:117
-#: components/AdHocCommands/AdHocCredentialStep.js:132
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:107
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:122
-#: components/AddRole/AddResourceRole.js:52
-#: components/AddRole/AddResourceRole.js:68
-#: components/AssociateModal/AssociateModal.js:139
-#: components/AssociateModal/AssociateModal.js:154
-#: components/HostForm/HostForm.js:96
-#: components/JobList/JobList.js:195
-#: components/JobList/JobList.js:244
-#: components/JobList/JobListItem.js:86
-#: components/LaunchPrompt/steps/CredentialsStep.js:168
-#: components/LaunchPrompt/steps/CredentialsStep.js:183
-#: components/LaunchPrompt/steps/InventoryStep.js:84
-#: components/LaunchPrompt/steps/InventoryStep.js:99
-#: components/Lookup/ApplicationLookup.js:99
-#: components/Lookup/ApplicationLookup.js:110
-#: components/Lookup/CredentialLookup.js:188
-#: components/Lookup/CredentialLookup.js:203
-#: components/Lookup/ExecutionEnvironmentLookup.js:172
-#: components/Lookup/ExecutionEnvironmentLookup.js:179
-#: components/Lookup/HostFilterLookup.js:93
-#: components/Lookup/HostFilterLookup.js:421
-#: components/Lookup/HostListItem.js:8
-#: components/Lookup/InstanceGroupsLookup.js:103
-#: components/Lookup/InstanceGroupsLookup.js:114
-#: components/Lookup/InventoryLookup.js:156
-#: components/Lookup/InventoryLookup.js:171
-#: components/Lookup/InventoryLookup.js:211
-#: components/Lookup/InventoryLookup.js:226
-#: components/Lookup/MultiCredentialsLookup.js:188
-#: components/Lookup/MultiCredentialsLookup.js:203
-#: components/Lookup/OrganizationLookup.js:128
-#: components/Lookup/OrganizationLookup.js:143
-#: components/Lookup/ProjectLookup.js:126
-#: components/Lookup/ProjectLookup.js:156
-#: components/NotificationList/NotificationList.js:181
-#: components/NotificationList/NotificationList.js:218
-#: components/NotificationList/NotificationListItem.js:28
-#: components/OptionsList/OptionsList.js:57
-#: components/PaginatedTable/PaginatedTable.js:72
-#: components/PromptDetail/PromptDetail.js:105
-#: components/RelatedTemplateList/RelatedTemplateList.js:161
-#: components/RelatedTemplateList/RelatedTemplateList.js:186
-#: components/ResourceAccessList/ResourceAccessListItem.js:58
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:252
-#: components/Schedule/ScheduleList/ScheduleList.js:168
-#: components/Schedule/ScheduleList/ScheduleList.js:188
-#: components/Schedule/ScheduleList/ScheduleListItem.js:80
-#: components/Schedule/shared/ScheduleForm.js:107
-#: components/TemplateList/TemplateList.js:205
-#: components/TemplateList/TemplateList.js:242
-#: components/TemplateList/TemplateListItem.js:142
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:18
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:37
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:49
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:68
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:80
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:110
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:122
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:137
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:149
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:179
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:191
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:206
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:59
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:109
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:135
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:28
-#: screens/Application/Applications.js:81
-#: screens/Application/ApplicationsList/ApplicationListItem.js:33
-#: screens/Application/ApplicationsList/ApplicationsList.js:118
-#: screens/Application/ApplicationsList/ApplicationsList.js:155
-#: screens/Application/shared/ApplicationForm.js:53
-#: screens/Credential/CredentialDetail/CredentialDetail.js:217
-#: screens/Credential/CredentialList/CredentialList.js:141
-#: screens/Credential/CredentialList/CredentialList.js:164
-#: screens/Credential/CredentialList/CredentialListItem.js:58
-#: screens/Credential/shared/CredentialForm.js:161
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:71
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:91
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:68
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:123
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:176
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:33
-#: screens/CredentialType/shared/CredentialTypeForm.js:21
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:48
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:136
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:165
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:69
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:89
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:115
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:12
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:88
-#: screens/Host/HostDetail/HostDetail.js:69
-#: screens/Host/HostGroups/HostGroupItem.js:28
-#: screens/Host/HostGroups/HostGroupsList.js:159
-#: screens/Host/HostGroups/HostGroupsList.js:176
-#: screens/Host/HostList/HostList.js:148
-#: screens/Host/HostList/HostList.js:169
-#: screens/Host/HostList/HostListItem.js:50
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:41
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:49
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:175
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:208
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:61
-#: screens/InstanceGroup/Instances/InstanceList.js:188
-#: screens/InstanceGroup/Instances/InstanceList.js:204
-#: screens/InstanceGroup/Instances/InstanceList.js:255
-#: screens/InstanceGroup/Instances/InstanceList.js:288
-#: screens/InstanceGroup/Instances/InstanceListItem.js:124
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:44
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:19
-#: screens/Instances/InstanceList/InstanceList.js:108
-#: screens/Instances/InstanceList/InstanceList.js:125
-#: screens/Instances/InstanceList/InstanceList.js:150
-#: screens/Instances/InstanceList/InstanceListItem.js:128
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:67
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:31
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:194
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:209
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:215
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:34
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:119
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:141
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:74
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:36
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:169
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:186
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:33
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:119
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:138
-#: screens/Inventory/InventoryList/InventoryList.js:178
-#: screens/Inventory/InventoryList/InventoryList.js:209
-#: screens/Inventory/InventoryList/InventoryList.js:218
-#: screens/Inventory/InventoryList/InventoryListItem.js:92
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:180
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:195
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:232
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:196
-#: screens/Inventory/InventorySources/InventorySourceList.js:211
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:71
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:98
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:30
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:76
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:111
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:33
-#: screens/Inventory/shared/InventoryForm.js:42
-#: screens/Inventory/shared/InventoryGroupForm.js:32
-#: screens/Inventory/shared/InventorySourceForm.js:101
-#: screens/Inventory/shared/SmartInventoryForm.js:47
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:90
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:100
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:67
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:106
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:122
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:178
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:112
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:41
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:91
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:84
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:107
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.js:16
-#: screens/Organization/OrganizationList/OrganizationList.js:122
-#: screens/Organization/OrganizationList/OrganizationList.js:143
-#: screens/Organization/OrganizationList/OrganizationListItem.js:45
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:68
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:85
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:14
-#: screens/Organization/shared/OrganizationForm.js:56
-#: screens/Project/ProjectDetail/ProjectDetail.js:175
-#: screens/Project/ProjectList/ProjectList.js:185
-#: screens/Project/ProjectList/ProjectList.js:221
-#: screens/Project/ProjectList/ProjectListItem.js:179
-#: screens/Project/shared/ProjectForm.js:170
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:146
-#: screens/Team/TeamDetail/TeamDetail.js:37
-#: screens/Team/TeamList/TeamList.js:117
-#: screens/Team/TeamList/TeamList.js:142
-#: screens/Team/TeamList/TeamListItem.js:33
-#: screens/Team/shared/TeamForm.js:29
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:178
-#: screens/Template/Survey/SurveyList.js:102
-#: screens/Template/Survey/SurveyList.js:102
-#: screens/Template/Survey/SurveyListItem.js:39
-#: screens/Template/Survey/SurveyReorderModal.js:218
-#: screens/Template/Survey/SurveyReorderModal.js:218
-#: screens/Template/Survey/SurveyReorderModal.js:238
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:111
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:69
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:88
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:122
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:154
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:178
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:88
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.js:74
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.js:94
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:75
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:95
-#: screens/Template/shared/JobTemplateForm.js:239
-#: screens/Template/shared/WorkflowJobTemplateForm.js:104
-#: screens/User/UserOrganizations/UserOrganizationList.js:75
-#: screens/User/UserOrganizations/UserOrganizationList.js:79
-#: screens/User/UserOrganizations/UserOrganizationListItem.js:13
-#: screens/User/UserRoles/UserRolesList.js:155
-#: screens/User/UserRoles/UserRolesListItem.js:12
-#: screens/User/UserTeams/UserTeamList.js:180
-#: screens/User/UserTeams/UserTeamList.js:232
-#: screens/User/UserTeams/UserTeamListItem.js:18
-#: screens/User/UserTokenList/UserTokenListItem.js:22
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:181
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:200
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:251
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:34
-msgid "Name"
-msgstr ""
-
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:21
-#~ msgid "Name cannot be changed on this Instance Group"
-#~ msgstr "Name cannot be changed on this Instance Group"
-
-#: components/AppContainer/AppContainer.js:95
-msgid "Navigation"
-msgstr "Navigation"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:505
-#: screens/Dashboard/shared/ChartTooltip.js:106
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:57
-msgid "Never"
-msgstr "Never"
-
-#: components/Workflow/WorkflowNodeHelp.js:114
-msgid "Never Updated"
-msgstr "Never Updated"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:47
-msgid "Never expires"
-msgstr "Never expires"
-
-#: components/JobList/JobList.js:227
-#: components/Workflow/WorkflowNodeHelp.js:90
-msgid "New"
-msgstr "New"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:52
-#: components/AdHocCommands/useAdHocCredentialStep.js:29
-#: components/AdHocCommands/useAdHocDetailsStep.js:40
-#: components/AdHocCommands/useAdHocExecutionEnvironmentStep.js:22
-#: components/AddRole/AddResourceRole.js:196
-#: components/AddRole/AddResourceRole.js:231
-#: components/LaunchPrompt/LaunchPrompt.js:130
-#: components/Schedule/shared/SchedulePromptableFields.js:134
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:66
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:59
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:130
-msgid "Next"
-msgstr ""
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:259
-#: components/Schedule/ScheduleList/ScheduleList.js:170
-#: components/Schedule/ScheduleList/ScheduleListItem.js:104
-#: components/Schedule/ScheduleList/ScheduleListItem.js:108
-msgid "Next Run"
-msgstr "Next Run"
-
-#: components/Search/Search.js:232
-msgid "No"
-msgstr "No"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:122
-msgid "No Hosts Matched"
-msgstr "No Hosts Matched"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:123
-#: screens/Job/JobOutput/JobOutputSearch.js:124
-msgid "No Hosts Remaining"
-msgstr "No Hosts Remaining"
-
-#: screens/Job/JobOutput/HostEventModal.js:150
-msgid "No JSON Available"
-msgstr "No JSON Available"
-
-#: screens/Dashboard/shared/ChartTooltip.js:82
-msgid "No Jobs"
-msgstr "No Jobs"
-
-#: screens/Job/JobOutput/HostEventModal.js:185
-#~ msgid "No Standard Error Available"
-#~ msgstr "No Standard Error Available"
-
-#: screens/Job/JobOutput/HostEventModal.js:166
-#~ msgid "No Standard Out Available"
-#~ msgstr "No Standard Out Available"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:72
-msgid "No inventory sync failures."
-msgstr "No inventory sync failures."
-
-#: components/ContentEmpty/ContentEmpty.js:20
-msgid "No items found."
-msgstr "No items found."
-
-#: screens/Host/HostList/HostListItem.js:100
-msgid "No job data available"
-msgstr "No job data available"
-
-#: screens/Job/JobOutput/EmptyOutput.js:25
-msgid "No output found for this job."
-msgstr "No output found for this job."
-
-#: screens/Job/JobOutput/HostEventModal.js:126
-msgid "No result found"
-msgstr "No result found"
-
-#: components/LabelSelect/LabelSelect.js:102
-#: components/LaunchPrompt/steps/SurveyStep.js:134
-#: components/LaunchPrompt/steps/SurveyStep.js:193
-#: components/MultiSelect/TagMultiSelect.js:60
-#: components/Search/AdvancedSearch.js:151
-#: components/Search/AdvancedSearch.js:266
-#: components/Search/LookupTypeInput.js:33
-#: components/Search/RelatedLookupTypeInput.js:26
-#: components/Search/Search.js:153
-#: components/Search/Search.js:202
-#: components/Search/Search.js:226
-#: screens/ActivityStream/ActivityStream.js:142
-#: screens/Credential/shared/CredentialForm.js:143
-#: screens/Credential/shared/CredentialFormFields/BecomeMethodField.js:65
-#: screens/Dashboard/DashboardGraph.js:106
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:136
-#: screens/Template/Survey/SurveyReorderModal.js:166
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:260
-#: screens/Template/shared/PlaybookSelect.js:72
-msgid "No results found"
-msgstr "No results found"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:116
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:137
-msgid "No subscriptions found"
-msgstr "No subscriptions found"
-
-#: screens/Template/Survey/SurveyList.js:147
-msgid "No survey questions found."
-msgstr "No survey questions found."
-
-#: components/PaginatedTable/PaginatedTable.js:80
-msgid "No {pluralizedItemName} Found"
-msgstr "No {pluralizedItemName} Found"
-
-#: components/Workflow/WorkflowNodeHelp.js:148
-#: components/Workflow/WorkflowNodeHelp.js:184
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:273
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:274
-msgid "Node Alias"
-msgstr "Node Alias"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:216
-#: screens/InstanceGroup/Instances/InstanceList.js:193
-#: screens/InstanceGroup/Instances/InstanceList.js:257
-#: screens/InstanceGroup/Instances/InstanceList.js:289
-#: screens/InstanceGroup/Instances/InstanceListItem.js:142
-#: screens/Instances/InstanceDetail/InstanceDetail.js:154
-#: screens/Instances/InstanceList/InstanceList.js:113
-#: screens/Instances/InstanceList/InstanceList.js:152
-#: screens/Instances/InstanceList/InstanceListItem.js:150
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:118
-msgid "Node Type"
-msgstr "Node Type"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.js:75
-msgid "Node type"
-msgstr "Node type"
-
-#: screens/TopologyView/Legend.js:68
-msgid "Node types"
-msgstr "Node types"
-
-#: components/Workflow/WorkflowNodeHelp.js:123
-msgid "None"
-msgstr "None"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:136
-msgid "None (Run Once)"
-msgstr "None (Run Once)"
-
-#: components/Schedule/shared/ScheduleForm.js:153
-msgid "None (run once)"
-msgstr "None (run once)"
-
-#: screens/User/UserDetail/UserDetail.js:51
-#: screens/User/UserList/UserListItem.js:23
-#: screens/User/shared/UserForm.js:29
-msgid "Normal User"
-msgstr "Normal User"
-
-#: components/ContentError/ContentError.js:37
-msgid "Not Found"
-msgstr "Not Found"
-
-#: screens/Setting/shared/SettingDetail.js:71
-#: screens/Setting/shared/SettingDetail.js:112
-msgid "Not configured"
-msgstr "Not configured"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:75
-msgid "Not configured for inventory sync."
-msgstr "Not configured for inventory sync."
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:248
-msgid ""
-"Note that only hosts directly in this group can\n"
-"be disassociated. Hosts in sub-groups must be disassociated\n"
-"directly from the sub-group level that they belong."
-msgstr ""
-"Note that only hosts directly in this group can\n"
-"be disassociated. Hosts in sub-groups must be disassociated\n"
-"directly from the sub-group level that they belong."
-
-#: screens/Host/HostGroups/HostGroupsList.js:212
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:230
-msgid ""
-"Note that you may still see the group in the list after\n"
-"disassociating if the host is also a member of that group’s\n"
-"children. This list shows all groups the host is associated\n"
-"with directly and indirectly."
-msgstr ""
-"Note that you may still see the group in the list after\n"
-"disassociating if the host is also a member of that group’s\n"
-"children. This list shows all groups the host is associated\n"
-"with directly and indirectly."
-
-#: components/Lookup/InstanceGroupsLookup.js:90
-msgid "Note: The order in which these are selected sets the execution precedence. Select more than one to enable drag."
-msgstr "Note: The order in which these are selected sets the execution precedence. Select more than one to enable drag."
-
-#: screens/Organization/shared/OrganizationForm.js:116
-msgid "Note: The order of these credentials sets precedence for the sync and lookup of the content. Select more than one to enable drag."
-msgstr "Note: The order of these credentials sets precedence for the sync and lookup of the content. Select more than one to enable drag."
-
-#: screens/Project/shared/Project.helptext.js:81
-msgid "Note: This field assumes the remote name is \"origin\"."
-msgstr "Note: This field assumes the remote name is \"origin\"."
-
-#: screens/Project/shared/Project.helptext.js:35
-msgid ""
-"Note: When using SSH protocol for GitHub or\n"
-"Bitbucket, enter an SSH key only, do not enter a username\n"
-"(other than git). Additionally, GitHub and Bitbucket do\n"
-"not support password authentication when using SSH. GIT\n"
-"read only protocol (git://) does not use username or\n"
-"password information."
-msgstr ""
-"Note: When using SSH protocol for GitHub or\n"
-"Bitbucket, enter an SSH key only, do not enter a username\n"
-"(other than git). Additionally, GitHub and Bitbucket do\n"
-"not support password authentication when using SSH. GIT\n"
-"read only protocol (git://) does not use username or\n"
-"password information."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:326
-msgid "Notification Color"
-msgstr "Notification Color"
-
-#: screens/NotificationTemplate/NotificationTemplate.js:58
-#: screens/NotificationTemplate/NotificationTemplateAdd.js:50
-msgid "Notification Template not found."
-msgstr "Notification Template not found."
-
-#: screens/ActivityStream/ActivityStream.js:198
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:117
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:171
-#: screens/NotificationTemplate/NotificationTemplates.js:14
-#: screens/NotificationTemplate/NotificationTemplates.js:21
-#: util/getRelatedResourceDeleteDetails.js:180
-msgid "Notification Templates"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:133
-msgid "Notification Type"
-msgstr "Notification Type"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:369
-msgid "Notification color"
-msgstr "Notification color"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:193
-msgid "Notification sent successfully"
-msgstr "Notification sent successfully"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:444
-msgid "Notification test failed."
-msgstr "Notification test failed."
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:197
-msgid "Notification timed out"
-msgstr "Notification timed out"
-
-#: components/NotificationList/NotificationList.js:190
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:131
-msgid "Notification type"
-msgstr "Notification type"
-
-#: components/NotificationList/NotificationList.js:177
-#: routeConfig.js:122
-#: screens/Inventory/Inventories.js:93
-#: screens/Inventory/InventorySource/InventorySource.js:99
-#: screens/ManagementJob/ManagementJob.js:116
-#: screens/ManagementJob/ManagementJobs.js:22
-#: screens/Organization/Organization.js:135
-#: screens/Organization/Organizations.js:33
-#: screens/Project/Project.js:114
-#: screens/Project/Projects.js:28
-#: screens/Template/Template.js:141
-#: screens/Template/Templates.js:46
-#: screens/Template/WorkflowJobTemplate.js:123
-msgid "Notifications"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:163
-msgid "November"
-msgstr "November"
-
-#: components/StatusLabel/StatusLabel.js:36
-#: components/Workflow/WorkflowNodeHelp.js:117
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:66
-#: screens/Job/JobOutput/shared/HostStatusBar.js:35
-msgid "OK"
-msgstr "OK"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:42
-#: components/Schedule/shared/FrequencyDetailSubform.js:542
-msgid "Occurrences"
-msgstr "Occurrences"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:158
-msgid "October"
-msgstr "October"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:189
-#: components/HostToggle/HostToggle.js:61
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:164
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:167
-#: components/PromptDetail/PromptDetail.js:284
-#: components/PromptDetail/PromptJobTemplateDetail.js:151
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:318
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:58
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:46
-#: screens/Setting/shared/SettingDetail.js:98
-#: screens/Setting/shared/SharedFields.js:150
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:277
-#: screens/Template/shared/JobTemplateForm.js:455
-msgid "Off"
-msgstr "Off"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:188
-#: components/HostToggle/HostToggle.js:60
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:164
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:166
-#: components/PromptDetail/PromptDetail.js:284
-#: components/PromptDetail/PromptJobTemplateDetail.js:151
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:318
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:57
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:46
-#: screens/Setting/shared/SettingDetail.js:98
-#: screens/Setting/shared/SharedFields.js:149
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:277
-#: screens/Template/shared/JobTemplateForm.js:455
-msgid "On"
-msgstr "On"
-
-#: components/Workflow/WorkflowLegend.js:126
-#: components/Workflow/WorkflowLinkHelp.js:30
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:39
-msgid "On Failure"
-msgstr "On Failure"
-
-#: components/Workflow/WorkflowLegend.js:122
-#: components/Workflow/WorkflowLinkHelp.js:27
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:63
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:32
-msgid "On Success"
-msgstr "On Success"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:529
-msgid "On date"
-msgstr "On date"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:244
-msgid "On days"
-msgstr "On days"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:18
-msgid ""
-"One Slack channel per line. The pound symbol (#)\n"
-"is required for channels. To respond to or start a thread to a specific message add the parent message Id to the channel where the parent message Id is 16 digits. A dot (.) must be manually inserted after the 10th digit. ie:#destination-channel, 1231257890.006423. See Slack"
-msgstr ""
-"One Slack channel per line. The pound symbol (#)\n"
-"is required for channels. To respond to or start a thread to a specific message add the parent message Id to the channel where the parent message Id is 16 digits. A dot (.) must be manually inserted after the 10th digit. ie:#destination-channel, 1231257890.006423. See Slack"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:166
-msgid "Only Group By"
-msgstr "Only Group By"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:103
-msgid "OpenStack"
-msgstr "OpenStack"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:107
-msgid "Option Details"
-msgstr "Option Details"
-
-#: screens/Inventory/shared/Inventory.helptext.js:25
-msgid ""
-"Optional labels that describe this inventory,\n"
-"such as 'dev' or 'test'. Labels can be used to group and filter\n"
-"inventories and completed jobs."
-msgstr ""
-"Optional labels that describe this inventory,\n"
-"such as 'dev' or 'test'. Labels can be used to group and filter\n"
-"inventories and completed jobs."
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:11
-msgid ""
-"Optional labels that describe this job template,\n"
-"such as 'dev' or 'test'. Labels can be used to group and filter\n"
-"job templates and completed jobs."
-msgstr ""
-"Optional labels that describe this job template,\n"
-"such as 'dev' or 'test'. Labels can be used to group and filter\n"
-"job templates and completed jobs."
-
-#: screens/Job/Job.helptext.js:11
-#: screens/Template/shared/JobTemplate.helptext.js:12
-msgid "Optional labels that describe this job template, such as 'dev' or 'test'. Labels can be used to group and filter job templates and completed jobs."
-msgstr "Optional labels that describe this job template, such as 'dev' or 'test'. Labels can be used to group and filter job templates and completed jobs."
-
-#: screens/Template/shared/JobTemplate.helptext.js:25
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:19
-msgid "Optionally select the credential to use to send status updates back to the webhook service."
-msgstr "Optionally select the credential to use to send status updates back to the webhook service."
-
-#: components/NotificationList/NotificationList.js:220
-#: components/NotificationList/NotificationListItem.js:34
-#: screens/Credential/shared/TypeInputsSubForm.js:47
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:61
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:65
-#: screens/Template/shared/JobTemplateForm.js:493
-#: screens/Template/shared/WorkflowJobTemplateForm.js:210
-msgid "Options"
-msgstr "Options"
-
-#: screens/Template/Survey/SurveyReorderModal.js:217
-#: screens/Template/Survey/SurveyReorderModal.js:217
-#: screens/Template/Survey/SurveyReorderModal.js:233
-msgid "Order"
-msgstr "Order"
-
-#: components/Lookup/ApplicationLookup.js:118
-#: components/Lookup/OrganizationLookup.js:101
-#: components/Lookup/OrganizationLookup.js:107
-#: components/Lookup/OrganizationLookup.js:123
-#: components/PromptDetail/PromptInventorySourceDetail.js:73
-#: components/PromptDetail/PromptInventorySourceDetail.js:83
-#: components/PromptDetail/PromptJobTemplateDetail.js:103
-#: components/PromptDetail/PromptJobTemplateDetail.js:113
-#: components/PromptDetail/PromptProjectDetail.js:77
-#: components/PromptDetail/PromptProjectDetail.js:88
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:65
-#: components/TemplateList/TemplateList.js:244
-#: components/TemplateList/TemplateListItem.js:185
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:69
-#: screens/Application/ApplicationsList/ApplicationListItem.js:38
-#: screens/Application/ApplicationsList/ApplicationsList.js:157
-#: screens/Credential/CredentialDetail/CredentialDetail.js:230
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:69
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:155
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:167
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:76
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:74
-#: screens/Inventory/InventoryList/InventoryList.js:191
-#: screens/Inventory/InventoryList/InventoryList.js:221
-#: screens/Inventory/InventoryList/InventoryListItem.js:119
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:217
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:108
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:120
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:130
-#: screens/Project/ProjectDetail/ProjectDetail.js:179
-#: screens/Project/ProjectList/ProjectListItem.js:287
-#: screens/Project/ProjectList/ProjectListItem.js:298
-#: screens/Team/TeamDetail/TeamDetail.js:40
-#: screens/Team/TeamList/TeamList.js:143
-#: screens/Team/TeamList/TeamListItem.js:38
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:192
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:203
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:121
-#: screens/User/UserTeams/UserTeamList.js:181
-#: screens/User/UserTeams/UserTeamList.js:237
-#: screens/User/UserTeams/UserTeamListItem.js:23
-msgid "Organization"
-msgstr "Organization"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:100
-msgid "Organization (Name)"
-msgstr "Organization (Name)"
-
-#: screens/Team/TeamList/TeamList.js:126
-msgid "Organization Name"
-msgstr "Organization Name"
-
-#: screens/Organization/Organization.js:154
-msgid "Organization not found."
-msgstr "Organization not found."
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:188
-#: routeConfig.js:96
-#: screens/ActivityStream/ActivityStream.js:181
-#: screens/Organization/OrganizationList/OrganizationList.js:117
-#: screens/Organization/OrganizationList/OrganizationList.js:163
-#: screens/Organization/Organizations.js:16
-#: screens/Organization/Organizations.js:26
-#: screens/User/User.js:66
-#: screens/User/UserOrganizations/UserOrganizationList.js:72
-#: screens/User/Users.js:33
-#: util/getRelatedResourceDeleteDetails.js:231
-#: util/getRelatedResourceDeleteDetails.js:265
-msgid "Organizations"
-msgstr ""
-
-#: components/LaunchPrompt/steps/useOtherPromptsStep.js:85
-msgid "Other prompts"
-msgstr "Other prompts"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:57
-msgid "Out of compliance"
-msgstr "Out of compliance"
-
-#: screens/Job/Job.js:118
-#: screens/Job/JobOutput/HostEventModal.js:156
-#: screens/Job/Jobs.js:34
-msgid "Output"
-msgstr "Output"
-
-#: screens/Job/JobOutput/HostEventModal.js:157
-msgid "Output tab"
-msgstr "Output tab"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:76
-msgid "Overwrite"
-msgstr "Overwrite"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:47
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:126
-msgid "Overwrite local groups and hosts from remote inventory source"
-msgstr "Overwrite local groups and hosts from remote inventory source"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:52
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:132
-msgid "Overwrite local variables from remote inventory source"
-msgstr "Overwrite local variables from remote inventory source"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:82
-msgid "Overwrite variables"
-msgstr "Overwrite variables"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:478
-msgid "POST"
-msgstr "POST"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:479
-msgid "PUT"
-msgstr "PUT"
-
-#: components/NotificationList/NotificationList.js:198
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:139
-msgid "Pagerduty"
-msgstr "Pagerduty"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:274
-msgid "Pagerduty Subdomain"
-msgstr "Pagerduty Subdomain"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:289
-msgid "Pagerduty subdomain"
-msgstr "Pagerduty subdomain"
-
-#: components/Pagination/Pagination.js:35
-msgid "Pagination"
-msgstr ""
-
-#: components/Workflow/WorkflowTools.js:165
-msgid "Pan Down"
-msgstr "Pan Down"
-
-#: components/Workflow/WorkflowTools.js:132
-msgid "Pan Left"
-msgstr "Pan Left"
-
-#: components/Workflow/WorkflowTools.js:176
-msgid "Pan Right"
-msgstr "Pan Right"
-
-#: components/Workflow/WorkflowTools.js:143
-msgid "Pan Up"
-msgstr "Pan Up"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:243
-msgid "Pass extra command line changes. There are two ansible command line parameters:"
-msgstr "Pass extra command line changes. There are two ansible command line parameters:"
-
-#: screens/Template/shared/JobTemplateForm.js:413
-#~ msgid ""
-#~ "Pass extra command line variables to the playbook. This is the\n"
-#~ "-e or --extra-vars command line parameter for ansible-playbook.\n"
-#~ "Provide key/value pairs using either YAML or JSON. Refer to the\n"
-#~ "documentation for example syntax."
-#~ msgstr ""
-#~ "Pass extra command line variables to the playbook. This is the\n"
-#~ "-e or --extra-vars command line parameter for ansible-playbook.\n"
-#~ "Provide key/value pairs using either YAML or JSON. Refer to the\n"
-#~ "documentation for example syntax."
-
-#: screens/Template/shared/WorkflowJobTemplateForm.js:215
-msgid "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. Refer to the Ansible Controller documentation for example syntax."
-msgstr "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. Refer to the Ansible Controller documentation for example syntax."
-
-#: screens/Job/Job.helptext.js:12
-#: screens/Template/shared/JobTemplate.helptext.js:13
-msgid "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. Refer to the documentation for example syntax."
-msgstr "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. Refer to the documentation for example syntax."
-
-#: screens/Login/Login.js:205
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:71
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:101
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:212
-#: screens/Template/Survey/SurveyQuestionForm.js:82
-#: screens/User/shared/UserForm.js:88
-msgid "Password"
-msgstr ""
-
-#: screens/Dashboard/DashboardGraph.js:119
-msgid "Past 24 hours"
-msgstr "Past 24 hours"
-
-#: screens/Dashboard/DashboardGraph.js:110
-msgid "Past month"
-msgstr "Past month"
-
-#: screens/Dashboard/DashboardGraph.js:113
-msgid "Past two weeks"
-msgstr "Past two weeks"
-
-#: screens/Dashboard/DashboardGraph.js:116
-msgid "Past week"
-msgstr "Past week"
-
-#: components/JobList/JobList.js:228
-#: components/StatusLabel/StatusLabel.js:41
-#: components/Workflow/WorkflowNodeHelp.js:93
-msgid "Pending"
-msgstr "Pending"
-
-#: components/AppContainer/PageHeaderToolbar.js:83
-msgid "Pending Workflow Approvals"
-msgstr "Pending Workflow Approvals"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:128
-msgid "Pending delete"
-msgstr "Pending delete"
-
-#: components/Lookup/HostFilterLookup.js:369
-msgid "Perform a search to define a host filter"
-msgstr "Perform a search to define a host filter"
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:72
-#: screens/User/UserTokenList/UserTokenList.js:105
-msgid "Personal Access Token"
-msgstr "Personal Access Token"
-
-#: screens/User/UserTokenList/UserTokenListItem.js:26
-msgid "Personal access token"
-msgstr "Personal access token"
-
-#: screens/Job/JobOutput/HostEventModal.js:122
-msgid "Play"
-msgstr "Play"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:84
-msgid "Play Count"
-msgstr "Play Count"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:125
-msgid "Play Started"
-msgstr "Play Started"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:146
-#: screens/Job/JobDetail/JobDetail.js:314
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:246
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:43
-#: screens/Template/shared/JobTemplateForm.js:350
-msgid "Playbook"
-msgstr "Playbook"
-
-#: components/JobList/JobListItem.js:44
-#: screens/Job/JobDetail/JobDetail.js:66
-msgid "Playbook Check"
-msgstr "Playbook Check"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:126
-msgid "Playbook Complete"
-msgstr "Playbook Complete"
-
-#: components/PromptDetail/PromptProjectDetail.js:150
-#: screens/Project/ProjectDetail/ProjectDetail.js:270
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:71
-msgid "Playbook Directory"
-msgstr "Playbook Directory"
-
-#: components/JobList/JobList.js:213
-#: components/JobList/JobListItem.js:44
-#: components/Schedule/ScheduleList/ScheduleListItem.js:37
-#: screens/Job/JobDetail/JobDetail.js:66
-msgid "Playbook Run"
-msgstr "Playbook Run"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:127
-msgid "Playbook Started"
-msgstr "Playbook Started"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:174
-#: components/TemplateList/TemplateList.js:222
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:23
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:54
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:159
-msgid "Playbook name"
-msgstr "Playbook name"
-
-#: screens/Dashboard/DashboardGraph.js:146
-msgid "Playbook run"
-msgstr "Playbook run"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:85
-msgid "Plays"
-msgstr "Plays"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:149
-msgid "Please add a Schedule to populate this list."
-msgstr "Please add a Schedule to populate this list."
-
-#: components/Schedule/ScheduleList/ScheduleList.js:152
-msgid "Please add a Schedule to populate this list. Schedules can be added to a Template, Project, or Inventory Source."
-msgstr "Please add a Schedule to populate this list. Schedules can be added to a Template, Project, or Inventory Source."
-
-#: screens/Template/Survey/SurveyList.js:146
-msgid "Please add survey questions."
-msgstr "Please add survey questions."
-
-#: components/PaginatedTable/PaginatedTable.js:93
-msgid "Please add {pluralizedItemName} to populate this list"
-msgstr "Please add {pluralizedItemName} to populate this list"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:43
-msgid "Please click the Start button to begin."
-msgstr "Please click the Start button to begin."
-
-#: util/validators.js:160
-msgid "Please enter a valid URL"
-msgstr "Please enter a valid URL"
-
-#: screens/User/shared/UserTokenForm.js:20
-msgid "Please enter a value."
-msgstr "Please enter a value."
-
-#: screens/Login/Login.js:169
-msgid "Please log in"
-msgstr "Please log in"
-
-#: components/JobList/JobList.js:190
-msgid "Please run a job to populate this list."
-msgstr "Please run a job to populate this list."
-
-#: components/Schedule/shared/ScheduleForm.js:622
-msgid "Please select a day number between 1 and 31."
-msgstr "Please select a day number between 1 and 31."
-
-#: screens/Template/shared/JobTemplateForm.js:170
-msgid "Please select an Inventory or check the Prompt on Launch option"
-msgstr "Please select an Inventory or check the Prompt on Launch option"
-
-#: components/Schedule/shared/ScheduleForm.js:614
-msgid "Please select an end date/time that comes after the start date/time."
-msgstr "Please select an end date/time that comes after the start date/time."
-
-#: components/Lookup/HostFilterLookup.js:358
-msgid "Please select an organization before editing the host filter"
-msgstr "Please select an organization before editing the host filter"
-
-#: screens/Job/JobOutput/EmptyOutput.js:20
-msgid "Please try another search using the filter above"
-msgstr "Please try another search using the filter above"
-
-#: screens/TopologyView/ContentLoading.js:40
-msgid "Please wait until the topology view is populated..."
-msgstr "Please wait until the topology view is populated..."
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:78
-msgid "Pod spec override"
-msgstr "Pod spec override"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:207
-#: screens/InstanceGroup/Instances/InstanceListItem.js:203
-#: screens/Instances/InstanceDetail/InstanceDetail.js:158
-#: screens/Instances/InstanceList/InstanceListItem.js:218
-msgid "Policy Type"
-msgstr "Policy Type"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:63
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:26
-msgid "Policy instance minimum"
-msgstr "Policy instance minimum"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:70
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:36
-msgid "Policy instance percentage"
-msgstr "Policy instance percentage"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginField.js:64
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginField.js:70
-msgid "Populate field from an external secret management system"
-msgstr "Populate field from an external secret management system"
-
-#: components/Lookup/HostFilterLookup.js:322
-#~ msgid ""
-#~ "Populate the hosts for this inventory by using a search\n"
-#~ "filter. Example: ansible_facts.ansible_distribution:\"RedHat\".\n"
-#~ "Refer to the documentation for further syntax and\n"
-#~ "examples. Refer to the Ansible Tower documentation for further syntax and\n"
-#~ "examples."
-#~ msgstr ""
-#~ "Populate the hosts for this inventory by using a search\n"
-#~ "filter. Example: ansible_facts.ansible_distribution:\"RedHat\".\n"
-#~ "Refer to the documentation for further syntax and\n"
-#~ "examples. Refer to the Ansible Tower documentation for further syntax and\n"
-#~ "examples."
-
-#: components/Lookup/HostFilterLookup.js:348
-msgid ""
-"Populate the hosts for this inventory by using a search\n"
-"filter. Example: ansible_facts__ansible_distribution:\"RedHat\".\n"
-"Refer to the documentation for further syntax and\n"
-"examples. Refer to the Ansible Controller documentation for further syntax and\n"
-"examples."
-msgstr ""
-"Populate the hosts for this inventory by using a search\n"
-"filter. Example: ansible_facts__ansible_distribution:\"RedHat\".\n"
-"Refer to the documentation for further syntax and\n"
-"examples. Refer to the Ansible Controller documentation for further syntax and\n"
-"examples."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:164
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:102
-msgid "Port"
-msgstr "Port"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:231
-msgid "Preconditions for running this node when there are multiple parents. Refer to the"
-msgstr "Preconditions for running this node when there are multiple parents. Refer to the"
-
-#: screens/Template/Survey/MultipleChoiceField.js:59
-msgid ""
-"Press 'Enter' to add more answer choices. One answer\n"
-"choice per line."
-msgstr ""
-"Press 'Enter' to add more answer choices. One answer\n"
-"choice per line."
-
-#: components/CodeEditor/CodeEditor.js:181
-msgid "Press Enter to edit. Press ESC to stop editing."
-msgstr "Press Enter to edit. Press ESC to stop editing."
-
-#: components/SelectedList/DraggableSelectedList.js:85
-msgid ""
-"Press space or enter to begin dragging,\n"
-"and use the arrow keys to navigate up or down.\n"
-"Press enter to confirm the drag, or any other key to\n"
-"cancel the drag operation."
-msgstr ""
-"Press space or enter to begin dragging,\n"
-"and use the arrow keys to navigate up or down.\n"
-"Press enter to confirm the drag, or any other key to\n"
-"cancel the drag operation."
-
-#: components/AdHocCommands/useAdHocPreviewStep.js:17
-#: components/LaunchPrompt/steps/usePreviewStep.js:23
-msgid "Preview"
-msgstr "Preview"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:103
-msgid "Private key passphrase"
-msgstr "Private key passphrase"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:58
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:120
-#: screens/Template/shared/JobTemplateForm.js:499
-msgid "Privilege Escalation"
-msgstr "Privilege Escalation"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:111
-msgid "Privilege escalation password"
-msgstr "Privilege escalation password"
-
-#: screens/Template/shared/JobTemplate.helptext.js:37
-msgid "Privilege escalation: If enabled, run this playbook as an administrator."
-msgstr "Privilege escalation: If enabled, run this playbook as an administrator."
-
-#: components/JobList/JobListItem.js:239
-#: components/Lookup/ProjectLookup.js:104
-#: components/Lookup/ProjectLookup.js:109
-#: components/Lookup/ProjectLookup.js:165
-#: components/PromptDetail/PromptInventorySourceDetail.js:98
-#: components/PromptDetail/PromptJobTemplateDetail.js:131
-#: components/PromptDetail/PromptJobTemplateDetail.js:139
-#: components/TemplateList/TemplateListItem.js:299
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:231
-#: screens/Job/JobDetail/JobDetail.js:158
-#: screens/Job/JobDetail/JobDetail.js:176
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:222
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:232
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:38
-msgid "Project"
-msgstr "Project"
-
-#: components/PromptDetail/PromptProjectDetail.js:143
-#: screens/Project/ProjectDetail/ProjectDetail.js:263
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:60
-msgid "Project Base Path"
-msgstr "Project Base Path"
-
-#: screens/Job/JobDetail/JobDetail.js:227
-#~ msgid "Project Status"
-#~ msgstr "Project Status"
-
-#: components/Workflow/WorkflowLegend.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:85
-msgid "Project Sync"
-msgstr "Project Sync"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:302
-#: screens/Project/ProjectList/ProjectListItem.js:229
-msgid "Project Sync Error"
-msgstr "Project Sync Error"
-
-#: components/Workflow/WorkflowNodeHelp.js:67
-msgid "Project Update"
-msgstr "Project Update"
-
-#: screens/Job/JobDetail/JobDetail.js:164
-msgid "Project Update Status"
-msgstr "Project Update Status"
-
-#: screens/Job/Job.helptext.js:21
-msgid "Project checkout results"
-msgstr "Project checkout results"
-
-#: screens/Project/ProjectList/ProjectList.js:132
-msgid "Project copied successfully"
-msgstr "Project copied successfully"
-
-#: screens/Project/Project.js:136
-msgid "Project not found."
-msgstr "Project not found."
-
-#: screens/Dashboard/Dashboard.js:109
-msgid "Project sync failures"
-msgstr "Project sync failures"
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:146
-#: routeConfig.js:75
-#: screens/ActivityStream/ActivityStream.js:170
-#: screens/Dashboard/Dashboard.js:103
-#: screens/Project/ProjectList/ProjectList.js:180
-#: screens/Project/ProjectList/ProjectList.js:249
-#: screens/Project/Projects.js:12
-#: screens/Project/Projects.js:22
-#: util/getRelatedResourceDeleteDetails.js:59
-#: util/getRelatedResourceDeleteDetails.js:194
-#: util/getRelatedResourceDeleteDetails.js:224
-msgid "Projects"
-msgstr ""
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:139
-msgid "Promote Child Groups and Hosts"
-msgstr "Promote Child Groups and Hosts"
-
-#: components/Schedule/shared/ScheduleForm.js:670
-#: components/Schedule/shared/ScheduleForm.js:673
-msgid "Prompt"
-msgstr "Prompt"
-
-#: components/PromptDetail/PromptDetail.js:173
-msgid "Prompt Overrides"
-msgstr "Prompt Overrides"
-
-#: components/CodeEditor/VariablesField.js:241
-#: components/FieldWithPrompt/FieldWithPrompt.js:46
-#: screens/Credential/CredentialDetail/CredentialDetail.js:175
-msgid "Prompt on launch"
-msgstr "Prompt on launch"
-
-#: components/Schedule/shared/SchedulePromptableFields.js:104
-msgid "Prompt | {0}"
-msgstr "Prompt | {0}"
-
-#: components/PromptDetail/PromptDetail.js:171
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:282
-msgid "Prompted Values"
-msgstr "Prompted Values"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:6
-msgid ""
-"Provide a host pattern to further constrain\n"
-"the list of hosts that will be managed or affected by the\n"
-"playbook. Multiple patterns are allowed. Refer to Ansible\n"
-"documentation for more information and examples on patterns."
-msgstr ""
-"Provide a host pattern to further constrain\n"
-"the list of hosts that will be managed or affected by the\n"
-"playbook. Multiple patterns are allowed. Refer to Ansible\n"
-"documentation for more information and examples on patterns."
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:37
-msgid ""
-"Provide a host pattern to further constrain the list\n"
-"of hosts that will be managed or affected by the playbook. Multiple\n"
-"patterns are allowed. Refer to Ansible documentation for more\n"
-"information and examples on patterns."
-msgstr ""
-"Provide a host pattern to further constrain the list\n"
-"of hosts that will be managed or affected by the playbook. Multiple\n"
-"patterns are allowed. Refer to Ansible documentation for more\n"
-"information and examples on patterns."
-
-#: screens/Job/Job.helptext.js:13
-#: screens/Template/shared/JobTemplate.helptext.js:14
-msgid "Provide a host pattern to further constrain the list of hosts that will be managed or affected by the playbook. Multiple patterns are allowed. Refer to Ansible documentation for more information and examples on patterns."
-msgstr "Provide a host pattern to further constrain the list of hosts that will be managed or affected by the playbook. Multiple patterns are allowed. Refer to Ansible documentation for more information and examples on patterns."
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:179
-msgid "Provide a value for this field or select the Prompt on launch option."
-msgstr "Provide a value for this field or select the Prompt on launch option."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:247
-msgid ""
-"Provide key/value pairs using either\n"
-"YAML or JSON."
-msgstr ""
-"Provide key/value pairs using either\n"
-"YAML or JSON."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:191
-msgid ""
-"Provide your Red Hat or Red Hat Satellite credentials\n"
-"below and you can choose from a list of your available subscriptions.\n"
-"The credentials you use will be stored for future use in\n"
-"retrieving renewal or expanded subscriptions."
-msgstr ""
-"Provide your Red Hat or Red Hat Satellite credentials\n"
-"below and you can choose from a list of your available subscriptions.\n"
-"The credentials you use will be stored for future use in\n"
-"retrieving renewal or expanded subscriptions."
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:83
-msgid "Provide your Red Hat or Red Hat Satellite credentials to enable Automation Analytics."
-msgstr "Provide your Red Hat or Red Hat Satellite credentials to enable Automation Analytics."
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:83
-#~ msgid "Provide your Red Hat or Red Hat Satellite credentials to enable Insights for Ansible Automation Platform."
-#~ msgstr "Provide your Red Hat or Red Hat Satellite credentials to enable Insights for Ansible Automation Platform."
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:157
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:295
-#: screens/Template/shared/JobTemplateForm.js:562
-msgid "Provisioning Callback URL"
-msgstr "Provisioning Callback URL"
-
-#: screens/Template/shared/JobTemplateForm.js:557
-msgid "Provisioning Callback details"
-msgstr "Provisioning Callback details"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:63
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:125
-#: screens/Template/shared/JobTemplateForm.js:503
-#: screens/Template/shared/JobTemplateForm.js:506
-msgid "Provisioning Callbacks"
-msgstr "Provisioning Callbacks"
-
-#: screens/Template/shared/JobTemplate.helptext.js:38
-msgid "Provisioning callbacks: Enables creation of a provisioning callback URL. Using the URL a host can contact Ansible AWX and request a configuration update using this job template."
-msgstr "Provisioning callbacks: Enables creation of a provisioning callback URL. Using the URL a host can contact Ansible AWX and request a configuration update using this job template."
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:85
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:113
-msgid "Pull"
-msgstr "Pull"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:163
-msgid "Question"
-msgstr "Question"
-
-#: screens/Setting/Settings.js:102
-msgid "RADIUS"
-msgstr "RADIUS"
-
-#: screens/Setting/SettingList.js:73
-msgid "RADIUS settings"
-msgstr "RADIUS settings"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:237
-#: screens/InstanceGroup/Instances/InstanceListItem.js:161
-#: screens/Instances/InstanceDetail/InstanceDetail.js:187
-#: screens/Instances/InstanceList/InstanceListItem.js:171
-msgid "RAM {0}"
-msgstr "RAM {0}"
-
-#: screens/User/shared/UserTokenForm.js:76
-msgid "Read"
-msgstr "Read"
-
-#: screens/Dashboard/Dashboard.js:133
-msgid "Recent Jobs"
-msgstr "Recent Jobs"
-
-#: screens/Dashboard/Dashboard.js:131
-msgid "Recent Jobs list tab"
-msgstr "Recent Jobs list tab"
-
-#: screens/Dashboard/Dashboard.js:145
-msgid "Recent Templates"
-msgstr "Recent Templates"
-
-#: screens/Dashboard/Dashboard.js:143
-msgid "Recent Templates list tab"
-msgstr "Recent Templates list tab"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:188
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:112
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:38
-msgid "Recent jobs"
-msgstr "Recent jobs"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:153
-msgid "Recipient List"
-msgstr "Recipient List"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:84
-msgid "Recipient list"
-msgstr "Recipient list"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:105
-msgid "Red Hat Ansible Automation Platform"
-msgstr "Red Hat Ansible Automation Platform"
-
-#: components/Lookup/ProjectLookup.js:138
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:92
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:161
-#: screens/Job/JobDetail/JobDetail.js:76
-#: screens/Project/ProjectList/ProjectList.js:201
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:100
-msgid "Red Hat Insights"
-msgstr "Red Hat Insights"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:102
-msgid "Red Hat Satellite 6"
-msgstr "Red Hat Satellite 6"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:104
-msgid "Red Hat Virtualization"
-msgstr "Red Hat Virtualization"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:117
-msgid "Red Hat subscription manifest"
-msgstr "Red Hat subscription manifest"
-
-#: components/About/About.js:36
-msgid "Red Hat, Inc."
-msgstr "Red Hat, Inc."
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:93
-#: screens/Application/shared/ApplicationForm.js:106
-msgid "Redirect URIs"
-msgstr "Redirect URIs"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:91
-#~ msgid "Redirect uris"
-#~ msgstr "Redirect uris"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:259
-msgid "Redirecting to dashboard"
-msgstr "Redirecting to dashboard"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:263
-msgid "Redirecting to subscription detail"
-msgstr "Redirecting to subscription detail"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:261
-msgid "Refer to the"
-msgstr "Refer to the"
-
-#: screens/Template/shared/JobTemplateForm.js:433
-#~ msgid ""
-#~ "Refer to the Ansible documentation for details\n"
-#~ "about the configuration file."
-#~ msgstr ""
-#~ "Refer to the Ansible documentation for details\n"
-#~ "about the configuration file."
-
-#: screens/Job/Job.helptext.js:26
-#: screens/Template/shared/JobTemplate.helptext.js:46
-msgid "Refer to the Ansible documentation for details about the configuration file."
-msgstr "Refer to the Ansible documentation for details about the configuration file."
-
-#: screens/User/UserTokens/UserTokens.js:77
-msgid "Refresh Token"
-msgstr "Refresh Token"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:81
-msgid "Refresh Token Expiration"
-msgstr "Refresh Token Expiration"
-
-#: screens/Project/ProjectList/ProjectListItem.js:132
-msgid "Refresh for revision"
-msgstr "Refresh for revision"
-
-#: screens/Project/ProjectList/ProjectListItem.js:134
-msgid "Refresh project revision"
-msgstr "Refresh project revision"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:128
-msgid "Regions"
-msgstr "Regions"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:91
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:142
-msgid "Registry credential"
-msgstr "Registry credential"
-
-#: screens/Inventory/shared/Inventory.helptext.js:156
-msgid "Regular expression where only matching host names will be imported. The filter is applied as a post-processing step after any inventory plugin filters are applied."
-msgstr "Regular expression where only matching host names will be imported. The filter is applied as a post-processing step after any inventory plugin filters are applied."
-
-#: screens/Inventory/Inventories.js:81
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:62
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:175
-msgid "Related Groups"
-msgstr "Related Groups"
-
-#: components/Search/AdvancedSearch.js:287
-msgid "Related Keys"
-msgstr "Related Keys"
-
-#: components/Search/RelatedLookupTypeInput.js:16
-#: components/Search/RelatedLookupTypeInput.js:24
-msgid "Related search type"
-msgstr "Related search type"
-
-#: components/Search/RelatedLookupTypeInput.js:19
-msgid "Related search type typeahead"
-msgstr "Related search type typeahead"
-
-#: components/JobList/JobListItem.js:146
-#: components/LaunchButton/ReLaunchDropDown.js:82
-#: screens/Job/JobDetail/JobDetail.js:562
-#: screens/Job/JobDetail/JobDetail.js:570
-#: screens/Job/JobOutput/shared/OutputToolbar.js:167
-msgid "Relaunch"
-msgstr "Relaunch"
-
-#: components/JobList/JobListItem.js:126
-#: screens/Job/JobOutput/shared/OutputToolbar.js:147
-msgid "Relaunch Job"
-msgstr "Relaunch Job"
-
-#: components/LaunchButton/ReLaunchDropDown.js:41
-msgid "Relaunch all hosts"
-msgstr "Relaunch all hosts"
-
-#: components/LaunchButton/ReLaunchDropDown.js:54
-msgid "Relaunch failed hosts"
-msgstr "Relaunch failed hosts"
-
-#: components/LaunchButton/ReLaunchDropDown.js:30
-#: components/LaunchButton/ReLaunchDropDown.js:35
-msgid "Relaunch on"
-msgstr "Relaunch on"
-
-#: components/JobList/JobListItem.js:125
-#: screens/Job/JobOutput/shared/OutputToolbar.js:146
-msgid "Relaunch using host parameters"
-msgstr "Relaunch using host parameters"
-
-#: components/Lookup/ProjectLookup.js:137
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:91
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:160
-#: screens/Job/JobDetail/JobDetail.js:77
-#: screens/Project/ProjectList/ProjectList.js:200
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:99
-msgid "Remote Archive"
-msgstr "Remote Archive"
-
-#: components/SelectedList/DraggableSelectedList.js:105
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:21
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:29
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:40
-msgid "Remove"
-msgstr "Remove"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:36
-msgid "Remove All Nodes"
-msgstr "Remove All Nodes"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:17
-msgid "Remove Link"
-msgstr "Remove Link"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:18
-#~ msgid "Remove Node"
-#~ msgstr "Remove Node"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:28
-msgid "Remove Node {nodeName}"
-msgstr "Remove Node {nodeName}"
-
-#: screens/Project/shared/Project.helptext.js:109
-msgid "Remove any local modifications prior to performing an update."
-msgstr "Remove any local modifications prior to performing an update."
-
-#: components/Search/AdvancedSearch.js:206
-msgid "Remove the current search related to ansible facts to enable another search using this key."
-msgstr "Remove the current search related to ansible facts to enable another search using this key."
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:14
-msgid "Remove {0} Access"
-msgstr ""
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:45
-msgid "Remove {0} chip"
-msgstr "Remove {0} chip"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:48
-msgid "Removing this link will orphan the rest of the branch and cause it to be executed immediately on launch."
-msgstr "Removing this link will orphan the rest of the branch and cause it to be executed immediately on launch."
-
-#: components/SelectedList/DraggableSelectedList.js:83
-msgid "Reorder"
-msgstr "Reorder"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:264
-msgid "Repeat Frequency"
-msgstr "Repeat Frequency"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:52
-msgid "Replace"
-msgstr "Replace"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:60
-msgid "Replace field with new value"
-msgstr "Replace field with new value"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:67
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:74
-msgid "Request subscription"
-msgstr "Request subscription"
-
-#: screens/Template/Survey/SurveyListItem.js:51
-#: screens/Template/Survey/SurveyQuestionForm.js:188
-msgid "Required"
-msgstr "Required"
-
-#: screens/TopologyView/Header.js:87
-#: screens/TopologyView/Header.js:90
-msgid "Reset zoom"
-msgstr "Reset zoom"
-
-#: components/Workflow/WorkflowNodeHelp.js:154
-#: components/Workflow/WorkflowNodeHelp.js:190
-#: screens/Team/TeamRoles/TeamRoleListItem.js:12
-#: screens/Team/TeamRoles/TeamRolesList.js:180
-msgid "Resource Name"
-msgstr "Resource Name"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:227
-msgid "Resource deleted"
-msgstr "Resource deleted"
-
-#: routeConfig.js:61
-#: screens/ActivityStream/ActivityStream.js:159
-msgid "Resources"
-msgstr ""
-
-#: components/TemplateList/TemplateListItem.js:149
-msgid "Resources are missing from this template."
-msgstr "Resources are missing from this template."
-
-#: screens/Setting/shared/RevertButton.js:43
-msgid "Restore initial value."
-msgstr "Restore initial value."
-
-#: screens/Inventory/shared/Inventory.helptext.js:153
-msgid ""
-"Retrieve the enabled state from the given dict of host variables.\n"
-"The enabled variable may be specified using dot notation, e.g: 'foo.bar'"
-msgstr ""
-"Retrieve the enabled state from the given dict of host variables.\n"
-"The enabled variable may be specified using dot notation, e.g: 'foo.bar'"
-
-#: components/JobCancelButton/JobCancelButton.js:81
-#: components/JobCancelButton/JobCancelButton.js:85
-#: components/JobList/JobListCancelButton.js:160
-#: components/JobList/JobListCancelButton.js:163
-#: screens/Job/JobOutput/JobOutput.js:764
-#: screens/Job/JobOutput/JobOutput.js:767
-msgid "Return"
-msgstr "Return"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:129
-msgid "Return to subscription management."
-msgstr "Return to subscription management."
-
-#: components/Search/AdvancedSearch.js:171
-msgid "Returns results that have values other than this one as well as other filters."
-msgstr "Returns results that have values other than this one as well as other filters."
-
-#: components/Search/AdvancedSearch.js:158
-msgid "Returns results that satisfy this one as well as other filters. This is the default set type if nothing is selected."
-msgstr "Returns results that satisfy this one as well as other filters. This is the default set type if nothing is selected."
-
-#: components/Search/AdvancedSearch.js:164
-msgid "Returns results that satisfy this one or any other filters."
-msgstr "Returns results that satisfy this one or any other filters."
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:52
-#: screens/Setting/shared/RevertButton.js:53
-#: screens/Setting/shared/RevertButton.js:62
-msgid "Revert"
-msgstr "Revert"
-
-#: screens/Setting/shared/RevertAllAlert.js:23
-msgid "Revert all"
-msgstr "Revert all"
-
-#: screens/Setting/shared/RevertFormActionGroup.js:21
-#: screens/Setting/shared/RevertFormActionGroup.js:27
-msgid "Revert all to default"
-msgstr "Revert all to default"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:59
-msgid "Revert field to previously saved value"
-msgstr "Revert field to previously saved value"
-
-#: screens/Setting/shared/RevertAllAlert.js:11
-msgid "Revert settings"
-msgstr "Revert settings"
-
-#: screens/Setting/shared/RevertButton.js:42
-msgid "Revert to factory default."
-msgstr "Revert to factory default."
-
-#: screens/Job/JobDetail/JobDetail.js:309
-#: screens/Project/ProjectList/ProjectList.js:224
-#: screens/Project/ProjectList/ProjectListItem.js:221
-msgid "Revision"
-msgstr "Revision"
-
-#: screens/Project/shared/ProjectSubForms/SvnSubForm.js:20
-msgid "Revision #"
-msgstr "Revision #"
-
-#: components/NotificationList/NotificationList.js:199
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:140
-msgid "Rocket.Chat"
-msgstr "Rocket.Chat"
-
-#: screens/Team/TeamRoles/TeamRoleListItem.js:20
-#: screens/Team/TeamRoles/TeamRolesList.js:148
-#: screens/Team/TeamRoles/TeamRolesList.js:182
-#: screens/User/UserList/UserList.js:163
-#: screens/User/UserList/UserListItem.js:55
-#: screens/User/UserRoles/UserRolesList.js:146
-#: screens/User/UserRoles/UserRolesList.js:157
-#: screens/User/UserRoles/UserRolesListItem.js:26
-msgid "Role"
-msgstr "Role"
-
-#: components/ResourceAccessList/ResourceAccessList.js:189
-#: components/ResourceAccessList/ResourceAccessList.js:202
-#: components/ResourceAccessList/ResourceAccessList.js:229
-#: components/ResourceAccessList/ResourceAccessListItem.js:69
-#: screens/Team/Team.js:59
-#: screens/Team/Teams.js:32
-#: screens/User/User.js:71
-#: screens/User/Users.js:31
-msgid "Roles"
-msgstr "Roles"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:99
-#: components/Workflow/WorkflowLinkHelp.js:39
-#: screens/Credential/shared/ExternalTestModal.js:89
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:49
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:23
-#: screens/Template/shared/JobTemplateForm.js:210
-msgid "Run"
-msgstr "Run"
-
-#: components/AdHocCommands/AdHocCommands.js:131
-#: components/AdHocCommands/AdHocCommands.js:135
-#: components/AdHocCommands/AdHocCommands.js:141
-#: components/AdHocCommands/AdHocCommands.js:145
-#: screens/Job/JobDetail/JobDetail.js:67
-msgid "Run Command"
-msgstr "Run Command"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:270
-#: screens/Instances/InstanceDetail/InstanceDetail.js:225
-msgid "Run a health check on the instance"
-msgstr "Run a health check on the instance"
-
-#: components/AdHocCommands/AdHocCommands.js:125
-msgid "Run ad hoc command"
-msgstr "Run ad hoc command"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:49
-msgid "Run command"
-msgstr "Run command"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:216
-msgid "Run every"
-msgstr "Run every"
-
-#: components/Schedule/shared/ScheduleForm.js:148
-msgid "Run frequency"
-msgstr "Run frequency"
-
-#: components/HealthCheckButton/HealthCheckButton.js:32
-#: components/HealthCheckButton/HealthCheckButton.js:45
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:279
-#: screens/Instances/InstanceDetail/InstanceDetail.js:234
-msgid "Run health check"
-msgstr "Run health check"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:337
-msgid "Run on"
-msgstr "Run on"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useRunTypeStep.js:32
-msgid "Run type"
-msgstr "Run type"
-
-#: components/JobList/JobList.js:230
-#: components/StatusLabel/StatusLabel.js:40
-#: components/TemplateList/TemplateListItem.js:118
-#: components/Workflow/WorkflowNodeHelp.js:99
-msgid "Running"
-msgstr "Running"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:128
-msgid "Running Handlers"
-msgstr "Running Handlers"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:210
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:210
-#: screens/InstanceGroup/Instances/InstanceListItem.js:194
-#: screens/Instances/InstanceDetail/InstanceDetail.js:161
-#: screens/Instances/InstanceList/InstanceListItem.js:209
-msgid "Running Jobs"
-msgstr "Running Jobs"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:277
-#: screens/Instances/InstanceDetail/InstanceDetail.js:232
-msgid "Running health check"
-msgstr "Running health check"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:71
-msgid "Running jobs"
-msgstr "Running jobs"
-
-#: screens/Setting/Settings.js:105
-msgid "SAML"
-msgstr "SAML"
-
-#: screens/Setting/SettingList.js:77
-msgid "SAML settings"
-msgstr "SAML settings"
-
-#: screens/Dashboard/DashboardGraph.js:143
-msgid "SCM update"
-msgstr "SCM update"
-
-#: screens/User/UserDetail/UserDetail.js:58
-#: screens/User/UserList/UserListItem.js:49
-msgid "SOCIAL"
-msgstr "SOCIAL"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:95
-msgid "SSH password"
-msgstr "SSH password"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:234
-msgid "SSL Connection"
-msgstr "SSL Connection"
-
-#: components/Workflow/WorkflowStartNode.js:60
-#: components/Workflow/workflowReducer.js:413
-msgid "START"
-msgstr "START"
-
-#: components/Sparkline/Sparkline.js:31
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:175
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:39
-#: screens/Project/ProjectDetail/ProjectDetail.js:135
-#: screens/Project/ProjectList/ProjectListItem.js:73
-msgid "STATUS:"
-msgstr "STATUS:"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:314
-msgid "Sat"
-msgstr "Sat"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:319
-#: components/Schedule/shared/FrequencyDetailSubform.js:451
-msgid "Saturday"
-msgstr "Saturday"
-
-#: components/AddRole/AddResourceRole.js:247
-#: components/AssociateModal/AssociateModal.js:104
-#: components/AssociateModal/AssociateModal.js:110
-#: components/FormActionGroup/FormActionGroup.js:13
-#: components/FormActionGroup/FormActionGroup.js:19
-#: components/Schedule/shared/ScheduleForm.js:656
-#: components/Schedule/shared/ScheduleForm.js:662
-#: components/Schedule/shared/useSchedulePromptSteps.js:45
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:130
-#: screens/Credential/shared/CredentialForm.js:318
-#: screens/Credential/shared/CredentialForm.js:323
-#: screens/Setting/shared/RevertFormActionGroup.js:12
-#: screens/Setting/shared/RevertFormActionGroup.js:18
-#: screens/Template/Survey/SurveyReorderModal.js:205
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:35
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:129
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:158
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:162
-msgid "Save"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:33
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:36
-msgid "Save & Exit"
-msgstr "Save & Exit"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:32
-msgid "Save link changes"
-msgstr "Save link changes"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:254
-msgid "Save successful!"
-msgstr "Save successful!"
-
-#: components/JobList/JobListItem.js:181
-#: components/JobList/JobListItem.js:187
-msgid "Schedule"
-msgstr "Schedule"
-
-#: screens/Project/Projects.js:34
-#: screens/Template/Templates.js:54
-msgid "Schedule Details"
-msgstr "Schedule Details"
-
-#: components/Schedule/shared/ScheduleForm.js:455
-msgid "Schedule Rules"
-msgstr "Schedule Rules"
-
-#: screens/Inventory/Inventories.js:92
-msgid "Schedule details"
-msgstr "Schedule details"
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:49
-msgid "Schedule is active"
-msgstr "Schedule is active"
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:49
-msgid "Schedule is inactive"
-msgstr "Schedule is inactive"
-
-#: components/Schedule/shared/ScheduleForm.js:566
-msgid "Schedule is missing rrule"
-msgstr "Schedule is missing rrule"
-
-#: components/Schedule/Schedule.js:82
-msgid "Schedule not found."
-msgstr "Schedule not found."
-
-#: components/Schedule/ScheduleList/ScheduleList.js:163
-#: components/Schedule/ScheduleList/ScheduleList.js:228
-#: routeConfig.js:44
-#: screens/ActivityStream/ActivityStream.js:153
-#: screens/Inventory/Inventories.js:89
-#: screens/Inventory/InventorySource/InventorySource.js:88
-#: screens/ManagementJob/ManagementJob.js:108
-#: screens/ManagementJob/ManagementJobs.js:23
-#: screens/Project/Project.js:120
-#: screens/Project/Projects.js:31
-#: screens/Schedule/AllSchedules.js:21
-#: screens/Template/Template.js:148
-#: screens/Template/Templates.js:51
-#: screens/Template/WorkflowJobTemplate.js:130
-msgid "Schedules"
-msgstr ""
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:136
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:33
-#: screens/User/UserTokenDetail/UserTokenDetail.js:49
-#: screens/User/UserTokenList/UserTokenList.js:142
-#: screens/User/UserTokenList/UserTokenList.js:189
-#: screens/User/UserTokenList/UserTokenListItem.js:32
-#: screens/User/shared/UserTokenForm.js:68
-msgid "Scope"
-msgstr "Scope"
-
-#: screens/User/shared/User.helptext.js:5
-msgid "Scope for the token's access"
-msgstr "Scope for the token's access"
-
-#: screens/Job/JobOutput/PageControls.js:79
-msgid "Scroll first"
-msgstr "Scroll first"
-
-#: screens/Job/JobOutput/PageControls.js:87
-msgid "Scroll last"
-msgstr "Scroll last"
-
-#: screens/Job/JobOutput/PageControls.js:71
-msgid "Scroll next"
-msgstr "Scroll next"
-
-#: screens/Job/JobOutput/PageControls.js:63
-msgid "Scroll previous"
-msgstr "Scroll previous"
-
-#: components/Lookup/HostFilterLookup.js:289
-#: components/Lookup/Lookup.js:137
-msgid "Search"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:152
-msgid "Search is disabled while the job is running"
-msgstr "Search is disabled while the job is running"
-
-#: components/Search/AdvancedSearch.js:311
-#: components/Search/Search.js:259
-msgid "Search submit button"
-msgstr "Search submit button"
-
-#: components/Search/Search.js:248
-msgid "Search text input"
-msgstr ""
-
-#: components/Lookup/HostFilterLookup.js:397
-msgid "Searching by ansible_facts requires special syntax. Refer to the"
-msgstr "Searching by ansible_facts requires special syntax. Refer to the"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:401
-msgid "Second"
-msgstr "Second"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:114
-#: components/PromptDetail/PromptProjectDetail.js:138
-#: screens/Project/ProjectDetail/ProjectDetail.js:251
-msgid "Seconds"
-msgstr "Seconds"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:35
-#: components/LaunchPrompt/steps/PreviewStep.js:63
-msgid "See errors on the left"
-msgstr "See errors on the left"
-
-#: components/JobList/JobListItem.js:84
-#: components/Lookup/HostFilterLookup.js:379
-#: components/Lookup/Lookup.js:194
-#: components/Pagination/Pagination.js:33
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:98
-msgid "Select"
-msgstr ""
-
-#: screens/Credential/shared/CredentialForm.js:129
-msgid "Select Credential Type"
-msgstr "Select Credential Type"
-
-#: screens/Host/HostGroups/HostGroupsList.js:237
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:254
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:257
-msgid "Select Groups"
-msgstr "Select Groups"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:278
-msgid "Select Hosts"
-msgstr "Select Hosts"
-
-#: components/AnsibleSelect/AnsibleSelect.js:38
-msgid "Select Input"
-msgstr ""
-
-#: screens/InstanceGroup/Instances/InstanceList.js:284
-msgid "Select Instances"
-msgstr "Select Instances"
-
-#: components/AssociateModal/AssociateModal.js:22
-msgid "Select Items"
-msgstr "Select Items"
-
-#: components/AddRole/AddResourceRole.js:201
-msgid "Select Items from List"
-msgstr "Select Items from List"
-
-#: components/LabelSelect/LabelSelect.js:99
-msgid "Select Labels"
-msgstr "Select Labels"
-
-#: components/AddRole/AddResourceRole.js:236
-msgid "Select Roles to Apply"
-msgstr "Select Roles to Apply"
-
-#: screens/User/UserTeams/UserTeamList.js:251
-msgid "Select Teams"
-msgstr "Select Teams"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:25
-msgid "Select a JSON formatted service account key to autopopulate the following fields."
-msgstr "Select a JSON formatted service account key to autopopulate the following fields."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:122
-msgid "Select a Node Type"
-msgstr "Select a Node Type"
-
-#: components/AddRole/AddResourceRole.js:170
-msgid "Select a Resource Type"
-msgstr "Select a Resource Type"
-
-#: screens/Template/shared/JobTemplateForm.js:334
-#~ msgid ""
-#~ "Select a branch for the job template. This branch is applied to\n"
-#~ "all job template nodes that prompt for a branch."
-#~ msgstr ""
-#~ "Select a branch for the job template. This branch is applied to\n"
-#~ "all job template nodes that prompt for a branch."
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:48
-msgid "Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch"
-msgstr "Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch"
-
-#: screens/Job/Job.helptext.js:20
-#: screens/Template/shared/JobTemplate.helptext.js:26
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:10
-msgid "Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch."
-msgstr "Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch."
-
-#: screens/Credential/shared/CredentialForm.js:139
-msgid "Select a credential Type"
-msgstr "Select a credential Type"
-
-#: screens/Metrics/Metrics.js:193
-#~ msgid "Select a instance"
-#~ msgstr "Select a instance"
-
-#: components/JobList/JobListCancelButton.js:98
-msgid "Select a job to cancel"
-msgstr "Select a job to cancel"
-
-#: screens/Metrics/Metrics.js:211
-msgid "Select a metric"
-msgstr "Select a metric"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:75
-msgid "Select a module"
-msgstr "Select a module"
-
-#: screens/Template/shared/PlaybookSelect.js:60
-#: screens/Template/shared/PlaybookSelect.js:61
-msgid "Select a playbook"
-msgstr "Select a playbook"
-
-#: screens/Template/shared/JobTemplateForm.js:319
-msgid "Select a project before editing the execution environment."
-msgstr "Select a project before editing the execution environment."
-
-#: screens/Template/Survey/SurveyToolbar.js:82
-msgid "Select a question to delete"
-msgstr "Select a question to delete"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListApproveButton.js:19
-#~ msgid "Select a row to approve"
-#~ msgstr "Select a row to approve"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:160
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:103
-msgid "Select a row to delete"
-msgstr ""
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListDenyButton.js:19
-#~ msgid "Select a row to deny"
-#~ msgstr "Select a row to deny"
-
-#: components/DisassociateButton/DisassociateButton.js:75
-msgid "Select a row to disassociate"
-msgstr "Select a row to disassociate"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:87
-msgid "Select a subscription"
-msgstr "Select a subscription"
-
-#: components/HostForm/HostForm.js:39
-#: components/Schedule/shared/FrequencyDetailSubform.js:59
-#: components/Schedule/shared/FrequencyDetailSubform.js:87
-#: components/Schedule/shared/FrequencyDetailSubform.js:91
-#: components/Schedule/shared/FrequencyDetailSubform.js:99
-#: components/Schedule/shared/ScheduleForm.js:95
-#: components/Schedule/shared/ScheduleForm.js:99
-#: screens/Credential/shared/CredentialForm.js:44
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:78
-#: screens/Inventory/shared/InventoryForm.js:64
-#: screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.js:44
-#: screens/Inventory/shared/InventorySourceSubForms/GCESubForm.js:44
-#: screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.js:44
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:36
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:96
-#: screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.js:43
-#: screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.js:45
-#: screens/Inventory/shared/SmartInventoryForm.js:67
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:24
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:61
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:421
-#: screens/Project/shared/ProjectForm.js:190
-#: screens/Project/shared/ProjectSubForms/InsightsSubForm.js:39
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:36
-#: screens/Team/shared/TeamForm.js:49
-#: screens/Template/Survey/SurveyQuestionForm.js:30
-#: screens/Template/shared/WorkflowJobTemplateForm.js:125
-#: screens/User/shared/UserForm.js:139
-msgid "Select a value for this field"
-msgstr "Select a value for this field"
-
-#: screens/Template/shared/JobTemplate.helptext.js:22
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:20
-msgid "Select a webhook service."
-msgstr "Select a webhook service."
-
-#: components/DataListToolbar/DataListToolbar.js:121
-#: components/DataListToolbar/DataListToolbar.js:125
-#: screens/Template/Survey/SurveyToolbar.js:49
-msgid "Select all"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStream.js:129
-msgid "Select an activity type"
-msgstr "Select an activity type"
-
-#: screens/Metrics/Metrics.js:200
-msgid "Select an instance"
-msgstr "Select an instance"
-
-#: screens/Metrics/Metrics.js:242
-msgid "Select an instance and a metric to show chart"
-msgstr "Select an instance and a metric to show chart"
-
-#: components/HealthCheckButton/HealthCheckButton.js:19
-msgid "Select an instance to run a health check."
-msgstr "Select an instance to run a health check."
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:5
-msgid "Select an inventory for the workflow. This inventory is applied to all workflow nodes that prompt for an inventory."
-msgstr "Select an inventory for the workflow. This inventory is applied to all workflow nodes that prompt for an inventory."
-
-#: components/LaunchPrompt/steps/SurveyStep.js:129
-msgid "Select an option"
-msgstr "Select an option"
-
-#: screens/Project/shared/ProjectForm.js:201
-msgid "Select an organization before editing the default execution environment."
-msgstr "Select an organization before editing the default execution environment."
-
-#: screens/Template/shared/JobTemplateForm.js:376
-#~ msgid ""
-#~ "Select credentials for accessing the nodes this job will be ran\n"
-#~ "against. You can only select one credential of each type. For machine credentials (SSH),\n"
-#~ "checking \"Prompt on launch\" without selecting credentials will require you to select a machine\n"
-#~ "credential at run time. If you select credentials and check \"Prompt on launch\", the selected\n"
-#~ "credential(s) become the defaults that can be updated at run time."
-#~ msgstr ""
-#~ "Select credentials for accessing the nodes this job will be ran\n"
-#~ "against. You can only select one credential of each type. For machine credentials (SSH),\n"
-#~ "checking \"Prompt on launch\" without selecting credentials will require you to select a machine\n"
-#~ "credential at run time. If you select credentials and check \"Prompt on launch\", the selected\n"
-#~ "credential(s) become the defaults that can be updated at run time."
-
-#: screens/Job/Job.helptext.js:10
-#: screens/Template/shared/JobTemplate.helptext.js:11
-msgid "Select credentials for accessing the nodes this job will be ran against. You can only select one credential of each type. For machine credentials (SSH), checking \"Prompt on launch\" without selecting credentials will require you to select a machine credential at run time. If you select credentials and check \"Prompt on launch\", the selected credential(s) become the defaults that can be updated at run time."
-msgstr "Select credentials for accessing the nodes this job will be ran against. You can only select one credential of each type. For machine credentials (SSH), checking \"Prompt on launch\" without selecting credentials will require you to select a machine credential at run time. If you select credentials and check \"Prompt on launch\", the selected credential(s) become the defaults that can be updated at run time."
-
-#: screens/Project/shared/Project.helptext.js:18
-msgid ""
-"Select from the list of directories found in\n"
-"the Project Base Path. Together the base path and the playbook\n"
-"directory provide the full path used to locate playbooks."
-msgstr ""
-"Select from the list of directories found in\n"
-"the Project Base Path. Together the base path and the playbook\n"
-"directory provide the full path used to locate playbooks."
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:98
-msgid "Select items from list"
-msgstr ""
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:55
-msgid "Select items to approve, deny, or cancel"
-msgstr "Select items to approve, deny, or cancel"
-
-#: screens/Dashboard/DashboardGraph.js:124
-#: screens/Dashboard/DashboardGraph.js:125
-msgid "Select job type"
-msgstr "Select job type"
-
-#: components/LaunchPrompt/steps/SurveyStep.js:177
-msgid "Select option(s)"
-msgstr "Select option(s)"
-
-#: screens/Dashboard/DashboardGraph.js:95
-#: screens/Dashboard/DashboardGraph.js:96
-#: screens/Dashboard/DashboardGraph.js:97
-msgid "Select period"
-msgstr "Select period"
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:117
-msgid "Select roles to apply"
-msgstr "Select roles to apply"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:127
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:128
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:129
-msgid "Select source path"
-msgstr "Select source path"
-
-#: screens/Dashboard/DashboardGraph.js:151
-#: screens/Dashboard/DashboardGraph.js:152
-msgid "Select status"
-msgstr "Select status"
-
-#: components/MultiSelect/TagMultiSelect.js:59
-msgid "Select tags"
-msgstr "Select tags"
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:94
-msgid "Select the Execution Environment you want this command to run inside."
-msgstr "Select the Execution Environment you want this command to run inside."
-
-#: screens/Inventory/shared/SmartInventoryForm.js:87
-msgid "Select the Instance Groups for this Inventory to run on."
-msgstr "Select the Instance Groups for this Inventory to run on."
-
-#: screens/Template/shared/JobTemplateForm.js:513
-#~ msgid ""
-#~ "Select the Instance Groups for this Job Template\n"
-#~ "to run on."
-#~ msgstr ""
-#~ "Select the Instance Groups for this Job Template\n"
-#~ "to run on."
-
-#: screens/Job/Job.helptext.js:17
-#: screens/Template/shared/JobTemplate.helptext.js:19
-msgid "Select the Instance Groups for this Job Template to run on."
-msgstr "Select the Instance Groups for this Job Template to run on."
-
-#: screens/Template/shared/JobTemplateForm.js:513
-#~ msgid ""
-#~ "Select the Instance Groups for this Organization\n"
-#~ "to run on."
-#~ msgstr ""
-#~ "Select the Instance Groups for this Organization\n"
-#~ "to run on."
-
-#: screens/Organization/shared/OrganizationForm.js:83
-msgid "Select the Instance Groups for this Organization to run on."
-msgstr ""
-
-#: screens/User/shared/UserTokenForm.js:49
-#~ msgid "Select the application that this token will belong to, or leave this field empty to create a Personal Access Token."
-#~ msgstr "Select the application that this token will belong to, or leave this field empty to create a Personal Access Token."
-
-#: screens/User/shared/UserTokenForm.js:49
-#~ msgid "Select the application that this token will belong to."
-#~ msgstr "Select the application that this token will belong to."
-
-#: components/AdHocCommands/AdHocCredentialStep.js:104
-msgid "Select the credential you want to use when accessing the remote hosts to run the command. Choose the credential containing the username and SSH key or password that Ansible will need to log into the remote hosts."
-msgstr "Select the credential you want to use when accessing the remote hosts to run the command. Choose the credential containing the username and SSH key or password that Ansible will need to log into the remote hosts."
-
-#: screens/Template/shared/JobTemplate.helptext.js:8
-msgid "Select the execution environment for this job template."
-msgstr "Select the execution environment for this job template."
-
-#: components/Lookup/InventoryLookup.js:133
-msgid ""
-"Select the inventory containing the hosts\n"
-"you want this job to manage."
-msgstr ""
-"Select the inventory containing the hosts\n"
-"you want this job to manage."
-
-#: screens/Job/Job.helptext.js:6
-#: screens/Template/shared/JobTemplate.helptext.js:6
-msgid "Select the inventory containing the hosts you want this job to manage."
-msgstr "Select the inventory containing the hosts you want this job to manage."
-
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:107
-#~ msgid ""
-#~ "Select the inventory file\n"
-#~ "to be synced by this source. You can select from\n"
-#~ "the dropdown or enter a file within the input."
-#~ msgstr ""
-#~ "Select the inventory file\n"
-#~ "to be synced by this source. You can select from\n"
-#~ "the dropdown or enter a file within the input."
-
-#: components/HostForm/HostForm.js:32
-#: components/HostForm/HostForm.js:51
-msgid "Select the inventory that this host will belong to."
-msgstr "Select the inventory that this host will belong to."
-
-#: screens/Job/Job.helptext.js:9
-#: screens/Template/shared/JobTemplate.helptext.js:10
-msgid "Select the playbook to be executed by this job."
-msgstr "Select the playbook to be executed by this job."
-
-#: screens/Template/shared/JobTemplateForm.js:300
-#~ msgid ""
-#~ "Select the project containing the playbook\n"
-#~ "you want this job to execute."
-#~ msgstr ""
-#~ "Select the project containing the playbook\n"
-#~ "you want this job to execute."
-
-#: screens/Job/Job.helptext.js:7
-#: screens/Template/shared/JobTemplate.helptext.js:7
-msgid "Select the project containing the playbook you want this job to execute."
-msgstr "Select the project containing the playbook you want this job to execute."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:79
-msgid "Select your Ansible Automation Platform subscription to use."
-msgstr "Select your Ansible Automation Platform subscription to use."
-
-#: components/Lookup/Lookup.js:180
-msgid "Select {0}"
-msgstr "Select {0}"
-
-#: components/AddRole/AddResourceRole.js:212
-#: components/AddRole/AddResourceRole.js:224
-#: components/AddRole/AddResourceRole.js:242
-#: components/AddRole/SelectRoleStep.js:27
-#: components/CheckboxListItem/CheckboxListItem.js:44
-#: components/Lookup/InstanceGroupsLookup.js:87
-#: components/OptionsList/OptionsList.js:74
-#: components/Schedule/ScheduleList/ScheduleListItem.js:78
-#: components/TemplateList/TemplateListItem.js:140
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:107
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:125
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:26
-#: screens/Application/ApplicationsList/ApplicationListItem.js:31
-#: screens/Credential/CredentialList/CredentialListItem.js:56
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:31
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:65
-#: screens/Host/HostGroups/HostGroupItem.js:26
-#: screens/Host/HostList/HostListItem.js:48
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:59
-#: screens/InstanceGroup/Instances/InstanceListItem.js:122
-#: screens/Instances/InstanceList/InstanceListItem.js:126
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:42
-#: screens/Inventory/InventoryList/InventoryListItem.js:90
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:37
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:110
-#: screens/Organization/OrganizationList/OrganizationListItem.js:43
-#: screens/Organization/shared/OrganizationForm.js:113
-#: screens/Project/ProjectList/ProjectListItem.js:177
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:242
-#: screens/Team/TeamList/TeamListItem.js:31
-#: screens/Template/Survey/SurveyListItem.js:34
-#: screens/User/UserTokenList/UserTokenListItem.js:19
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:32
-msgid "Selected"
-msgstr ""
-
-#: components/LaunchPrompt/steps/CredentialsStep.js:142
-#: components/LaunchPrompt/steps/CredentialsStep.js:147
-#: components/Lookup/MultiCredentialsLookup.js:161
-#: components/Lookup/MultiCredentialsLookup.js:166
-msgid "Selected Category"
-msgstr "Selected Category"
-
-#: components/Schedule/shared/ScheduleForm.js:605
-#: components/Schedule/shared/ScheduleForm.js:606
-msgid "Selected date range must have at least 1 schedule occurrence."
-msgstr "Selected date range must have at least 1 schedule occurrence."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:159
-msgid "Sender Email"
-msgstr "Sender Email"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:94
-msgid "Sender e-mail"
-msgstr "Sender e-mail"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:153
-msgid "September"
-msgstr "September"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:24
-msgid "Service account JSON file"
-msgstr "Service account JSON file"
-
-#: screens/Inventory/shared/InventorySourceForm.js:46
-#: screens/Project/shared/ProjectForm.js:94
-msgid "Set a value for this field"
-msgstr "Set a value for this field"
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:70
-msgid "Set how many days of data should be retained."
-msgstr "Set how many days of data should be retained."
-
-#: screens/Setting/SettingList.js:118
-msgid "Set preferences for data collection, logos, and logins"
-msgstr "Set preferences for data collection, logos, and logins"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:130
-msgid "Set source path to"
-msgstr "Set source path to"
-
-#: components/InstanceToggle/InstanceToggle.js:48
-msgid "Set the instance enabled or disabled. If disabled, jobs will not be assigned to this instance."
-msgstr "Set the instance enabled or disabled. If disabled, jobs will not be assigned to this instance."
-
-#: components/InstanceToggle/InstanceToggle.js:48
-#~ msgid "Set the instance online or offline. If offline, jobs will not be assigned to this instance."
-#~ msgstr "Set the instance online or offline. If offline, jobs will not be assigned to this instance."
-
-#: screens/Application/shared/Application.helptext.js:5
-msgid "Set to Public or Confidential depending on how secure the client device is."
-msgstr "Set to Public or Confidential depending on how secure the client device is."
-
-#: components/Search/AdvancedSearch.js:149
-msgid "Set type"
-msgstr "Set type"
-
-#: components/Search/AdvancedSearch.js:239
-msgid "Set type disabled for related search field fuzzy searches"
-msgstr "Set type disabled for related search field fuzzy searches"
-
-#: components/Search/AdvancedSearch.js:140
-msgid "Set type select"
-msgstr "Set type select"
-
-#: components/Search/AdvancedSearch.js:143
-msgid "Set type typeahead"
-msgstr "Set type typeahead"
-
-#: components/Workflow/WorkflowTools.js:154
-msgid "Set zoom to 100% and center graph"
-msgstr "Set zoom to 100% and center graph"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:46
-msgid "Setting category"
-msgstr "Setting category"
-
-#: screens/Setting/shared/RevertButton.js:46
-msgid "Setting matches factory default."
-msgstr "Setting matches factory default."
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:49
-msgid "Setting name"
-msgstr "Setting name"
-
-#: routeConfig.js:159
-#: routeConfig.js:163
-#: screens/ActivityStream/ActivityStream.js:220
-#: screens/ActivityStream/ActivityStream.js:222
-#: screens/Setting/Settings.js:42
-msgid "Settings"
-msgstr ""
-
-#: components/FormField/PasswordInput.js:35
-msgid "Show"
-msgstr "Show"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:154
-#: components/PromptDetail/PromptDetail.js:283
-#: components/PromptDetail/PromptJobTemplateDetail.js:151
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:317
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:276
-#: screens/Template/shared/JobTemplateForm.js:448
-msgid "Show Changes"
-msgstr "Show Changes"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:131
-#~ msgid "Show all groups"
-#~ msgstr "Show all groups"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:177
-#: components/AdHocCommands/AdHocDetailsStep.js:178
-msgid "Show changes"
-msgstr "Show changes"
-
-#: components/LaunchPrompt/LaunchPrompt.js:105
-#: components/Schedule/shared/SchedulePromptableFields.js:109
-msgid "Show description"
-msgstr "Show description"
-
-#: components/ChipGroup/ChipGroup.js:12
-msgid "Show less"
-msgstr "Show less"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:126
-msgid "Show only root groups"
-msgstr "Show only root groups"
-
-#: screens/Login/Login.js:240
-msgid "Sign in with Azure AD"
-msgstr "Sign in with Azure AD"
-
-#: screens/Login/Login.js:254
-msgid "Sign in with GitHub"
-msgstr "Sign in with GitHub"
-
-#: screens/Login/Login.js:296
-msgid "Sign in with GitHub Enterprise"
-msgstr "Sign in with GitHub Enterprise"
-
-#: screens/Login/Login.js:311
-msgid "Sign in with GitHub Enterprise Organizations"
-msgstr "Sign in with GitHub Enterprise Organizations"
-
-#: screens/Login/Login.js:327
-msgid "Sign in with GitHub Enterprise Teams"
-msgstr "Sign in with GitHub Enterprise Teams"
-
-#: screens/Login/Login.js:268
-msgid "Sign in with GitHub Organizations"
-msgstr "Sign in with GitHub Organizations"
-
-#: screens/Login/Login.js:282
-msgid "Sign in with GitHub Teams"
-msgstr "Sign in with GitHub Teams"
-
-#: screens/Login/Login.js:342
-msgid "Sign in with Google"
-msgstr "Sign in with Google"
-
-#: screens/Login/Login.js:361
-msgid "Sign in with SAML"
-msgstr "Sign in with SAML"
-
-#: screens/Login/Login.js:360
-msgid "Sign in with SAML {samlIDP}"
-msgstr "Sign in with SAML {samlIDP}"
-
-#: components/Search/Search.js:145
-#: components/Search/Search.js:146
-msgid "Simple key select"
-msgstr "Simple key select"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:69
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:70
-#: components/PromptDetail/PromptDetail.js:256
-#: components/PromptDetail/PromptJobTemplateDetail.js:260
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:369
-#: screens/Job/JobDetail/JobDetail.js:483
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:459
-#: screens/Template/shared/JobTemplateForm.js:481
-msgid "Skip Tags"
-msgstr "Skip Tags"
-
-#: screens/Template/shared/JobTemplateForm.js:538
-#~ msgid ""
-#~ "Skip tags are useful when you have a\n"
-#~ "large playbook, and you want to skip specific parts of a\n"
-#~ "play or task. Use commas to separate multiple tags. Refer\n"
-#~ "to the documentation for details on the usage\n"
-#~ "of tags."
-#~ msgstr ""
-#~ "Skip tags are useful when you have a\n"
-#~ "large playbook, and you want to skip specific parts of a\n"
-#~ "play or task. Use commas to separate multiple tags. Refer\n"
-#~ "to the documentation for details on the usage\n"
-#~ "of tags."
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:71
-msgid ""
-"Skip tags are useful when you have a large\n"
-"playbook, and you want to skip specific parts of a play or task.\n"
-"Use commas to separate multiple tags. Refer to Ansible Controller\n"
-"documentation for details on the usage of tags."
-msgstr ""
-"Skip tags are useful when you have a large\n"
-"playbook, and you want to skip specific parts of a play or task.\n"
-"Use commas to separate multiple tags. Refer to Ansible Controller\n"
-"documentation for details on the usage of tags."
-
-#: screens/Job/Job.helptext.js:19
-#: screens/Template/shared/JobTemplate.helptext.js:21
-msgid "Skip tags are useful when you have a large playbook, and you want to skip specific parts of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags."
-msgstr "Skip tags are useful when you have a large playbook, and you want to skip specific parts of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags."
-
-#: screens/Job/JobOutput/shared/HostStatusBar.js:39
-msgid "Skipped"
-msgstr "Skipped"
-
-#: components/StatusLabel/StatusLabel.js:42
-msgid "Skipped'"
-msgstr "Skipped'"
-
-#: components/NotificationList/NotificationList.js:200
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:141
-msgid "Slack"
-msgstr "Slack"
-
-#: screens/Host/HostList/SmartInventoryButton.js:39
-#: screens/Host/HostList/SmartInventoryButton.js:48
-#: screens/Host/HostList/SmartInventoryButton.js:52
-#: screens/Inventory/InventoryList/InventoryList.js:187
-#: screens/Inventory/InventoryList/InventoryListItem.js:117
-msgid "Smart Inventory"
-msgstr "Smart Inventory"
-
-#: screens/Inventory/SmartInventory.js:94
-msgid "Smart Inventory not found."
-msgstr "Smart Inventory not found."
-
-#: components/Lookup/HostFilterLookup.js:344
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:117
-msgid "Smart host filter"
-msgstr "Smart host filter"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:106
-msgid "Smart inventory"
-msgstr "Smart inventory"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:32
-#: components/LaunchPrompt/steps/PreviewStep.js:60
-msgid "Some of the previous step(s) have errors"
-msgstr "Some of the previous step(s) have errors"
-
-#: screens/Host/HostList/SmartInventoryButton.js:17
-msgid "Some search modifiers like not__ and __search are not supported in Smart Inventory host filters. Remove these to create a new Smart Inventory with this filter."
-msgstr "Some search modifiers like not__ and __search are not supported in Smart Inventory host filters. Remove these to create a new Smart Inventory with this filter."
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.js:39
-msgid "Something went wrong with the request to test this credential and metadata."
-msgstr "Something went wrong with the request to test this credential and metadata."
-
-#: components/ContentError/ContentError.js:37
-msgid "Something went wrong..."
-msgstr "Something went wrong..."
-
-#: components/Sort/Sort.js:139
-msgid "Sort"
-msgstr ""
-
-#: screens/Template/Survey/SurveyListItem.js:72
-#: screens/Template/Survey/SurveyListItem.js:73
-#~ msgid "Sort question order"
-#~ msgstr "Sort question order"
-
-#: components/JobList/JobListItem.js:170
-#: components/PromptDetail/PromptInventorySourceDetail.js:95
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:214
-#: screens/Inventory/shared/InventorySourceForm.js:131
-#: screens/Job/JobDetail/JobDetail.js:274
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:93
-msgid "Source"
-msgstr "Source"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:47
-#: components/PromptDetail/PromptDetail.js:211
-#: components/PromptDetail/PromptJobTemplateDetail.js:145
-#: components/PromptDetail/PromptProjectDetail.js:106
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:87
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:312
-#: screens/Job/JobDetail/JobDetail.js:302
-#: screens/Project/ProjectDetail/ProjectDetail.js:229
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:241
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:134
-#: screens/Template/shared/JobTemplateForm.js:328
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:286
-msgid "Source Control Branch"
-msgstr "Source Control Branch"
-
-#: screens/Project/shared/ProjectSubForms/GitSubForm.js:29
-msgid "Source Control Branch/Tag/Commit"
-msgstr "Source Control Branch/Tag/Commit"
-
-#: components/PromptDetail/PromptProjectDetail.js:117
-#: screens/Project/ProjectDetail/ProjectDetail.js:239
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:53
-msgid "Source Control Credential"
-msgstr "Source Control Credential"
-
-#: screens/Project/shared/ProjectForm.js:214
-#~ msgid "Source Control Credential Type"
-#~ msgstr "Source Control Credential Type"
-
-#: components/PromptDetail/PromptProjectDetail.js:111
-#: screens/Project/ProjectDetail/ProjectDetail.js:234
-#: screens/Project/shared/ProjectSubForms/GitSubForm.js:32
-msgid "Source Control Refspec"
-msgstr "Source Control Refspec"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:194
-msgid "Source Control Revision"
-msgstr "Source Control Revision"
-
-#: components/PromptDetail/PromptProjectDetail.js:96
-#: screens/Job/JobDetail/JobDetail.js:253
-#: screens/Project/ProjectDetail/ProjectDetail.js:190
-#: screens/Project/shared/ProjectForm.js:215
-msgid "Source Control Type"
-msgstr "Source Control Type"
-
-#: components/Lookup/ProjectLookup.js:142
-#: components/PromptDetail/PromptProjectDetail.js:101
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:96
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:165
-#: screens/Project/ProjectDetail/ProjectDetail.js:224
-#: screens/Project/ProjectList/ProjectList.js:205
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:16
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:104
-msgid "Source Control URL"
-msgstr "Source Control URL"
-
-#: components/JobList/JobList.js:211
-#: components/JobList/JobListItem.js:42
-#: components/Schedule/ScheduleList/ScheduleListItem.js:38
-#: screens/Job/JobDetail/JobDetail.js:64
-msgid "Source Control Update"
-msgstr "Source Control Update"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:335
-msgid "Source Phone Number"
-msgstr "Source Phone Number"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:188
-msgid "Source Variables"
-msgstr "Source Variables"
-
-#: components/JobList/JobListItem.js:213
-#: screens/Job/JobDetail/JobDetail.js:237
-msgid "Source Workflow Job"
-msgstr "Source Workflow Job"
-
-#: screens/Template/shared/WorkflowJobTemplateForm.js:172
-msgid "Source control branch"
-msgstr "Source control branch"
-
-#: screens/Inventory/shared/InventorySourceForm.js:153
-msgid "Source details"
-msgstr "Source details"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:390
-msgid "Source phone number"
-msgstr "Source phone number"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:285
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:19
-msgid "Source variables"
-msgstr "Source variables"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:97
-msgid "Sourced from a project"
-msgstr "Sourced from a project"
-
-#: screens/Inventory/Inventories.js:84
-#: screens/Inventory/Inventory.js:68
-msgid "Sources"
-msgstr "Sources"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:30
-msgid ""
-"Specify HTTP Headers in JSON format. Refer to\n"
-"the Ansible Controller documentation for example syntax."
-msgstr ""
-"Specify HTTP Headers in JSON format. Refer to\n"
-"the Ansible Controller documentation for example syntax."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:24
-msgid ""
-"Specify a notification color. Acceptable colors are hex\n"
-"color code (example: #3af or #789abc)."
-msgstr ""
-"Specify a notification color. Acceptable colors are hex\n"
-"color code (example: #3af or #789abc)."
-
-#: screens/User/shared/UserTokenForm.js:71
-#~ msgid "Specify a scope for the token's access"
-#~ msgstr "Specify a scope for the token's access"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:26
-msgid "Specify the conditions under which this node should be executed"
-msgstr "Specify the conditions under which this node should be executed"
-
-#: screens/Job/JobOutput/HostEventModal.js:173
-msgid "Standard Error"
-msgstr "Standard Error"
-
-#: screens/Job/JobOutput/HostEventModal.js:152
-#~ msgid "Standard Out"
-#~ msgstr "Standard Out"
-
-#: screens/Job/JobOutput/HostEventModal.js:174
-msgid "Standard error tab"
-msgstr "Standard error tab"
-
-#: screens/Job/JobOutput/HostEventModal.js:153
-#~ msgid "Standard out tab"
-#~ msgstr "Standard out tab"
-
-#: components/NotificationList/NotificationListItem.js:57
-#: components/NotificationList/NotificationListItem.js:58
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:47
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:53
-msgid "Start"
-msgstr "Start"
-
-#: components/JobList/JobList.js:247
-#: components/JobList/JobListItem.js:99
-msgid "Start Time"
-msgstr "Start Time"
-
-#: components/Schedule/shared/DateTimePicker.js:51
-msgid "Start date"
-msgstr "Start date"
-
-#: components/Schedule/shared/ScheduleForm.js:122
-msgid "Start date/time"
-msgstr "Start date/time"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:460
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:105
-msgid "Start message"
-msgstr "Start message"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:469
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:114
-msgid "Start message body"
-msgstr "Start message body"
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:33
-msgid "Start sync process"
-msgstr "Start sync process"
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:37
-msgid "Start sync source"
-msgstr "Start sync source"
-
-#: components/Schedule/shared/DateTimePicker.js:61
-msgid "Start time"
-msgstr "Start time"
-
-#: screens/Job/JobDetail/JobDetail.js:200
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:253
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:54
-msgid "Started"
-msgstr "Started"
-
-#: components/JobList/JobList.js:224
-#: components/JobList/JobList.js:245
-#: components/JobList/JobListItem.js:95
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:201
-#: screens/InstanceGroup/Instances/InstanceList.js:256
-#: screens/InstanceGroup/Instances/InstanceListItem.js:129
-#: screens/Instances/InstanceDetail/InstanceDetail.js:149
-#: screens/Instances/InstanceList/InstanceList.js:151
-#: screens/Instances/InstanceList/InstanceListItem.js:134
-#: screens/Inventory/InventoryList/InventoryList.js:219
-#: screens/Inventory/InventoryList/InventoryListItem.js:101
-#: screens/Inventory/InventorySources/InventorySourceList.js:212
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:87
-#: screens/Job/JobDetail/JobDetail.js:188
-#: screens/Job/JobOutput/HostEventModal.js:118
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:114
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:179
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:117
-#: screens/Project/ProjectList/ProjectList.js:222
-#: screens/Project/ProjectList/ProjectListItem.js:197
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:45
-#: screens/TopologyView/Tooltip.js:98
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:203
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:254
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:57
-msgid "Status"
-msgstr "Status"
-
-#: screens/TopologyView/Legend.js:107
-msgid "Status types"
-msgstr "Status types"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:92
-msgid "Stdout"
-msgstr "Stdout"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:37
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:49
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:212
-msgid "Submit"
-msgstr "Submit"
-
-#: screens/Project/shared/Project.helptext.js:114
-msgid ""
-"Submodules will track the latest commit on\n"
-"their master branch (or other branch specified in\n"
-".gitmodules). If no, submodules will be kept at\n"
-"the revision specified by the main project.\n"
-"This is equivalent to specifying the --remote\n"
-"flag to git submodule update."
-msgstr ""
-"Submodules will track the latest commit on\n"
-"their master branch (or other branch specified in\n"
-".gitmodules). If no, submodules will be kept at\n"
-"the revision specified by the main project.\n"
-"This is equivalent to specifying the --remote\n"
-"flag to git submodule update."
-
-#: screens/Setting/SettingList.js:128
-#: screens/Setting/Settings.js:108
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:74
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:195
-msgid "Subscription"
-msgstr "Subscription"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:32
-msgid "Subscription Details"
-msgstr "Subscription Details"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:194
-msgid "Subscription Management"
-msgstr "Subscription Management"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:82
-msgid "Subscription manifest"
-msgstr "Subscription manifest"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:84
-msgid "Subscription selection modal"
-msgstr "Subscription selection modal"
-
-#: screens/Setting/SettingList.js:133
-msgid "Subscription settings"
-msgstr "Subscription settings"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:69
-msgid "Subscription type"
-msgstr "Subscription type"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:142
-msgid "Subscriptions table"
-msgstr "Subscriptions table"
-
-#: components/Lookup/ProjectLookup.js:136
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:90
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:159
-#: screens/Job/JobDetail/JobDetail.js:75
-#: screens/Project/ProjectList/ProjectList.js:199
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:98
-msgid "Subversion"
-msgstr "Subversion"
-
-#: components/NotificationList/NotificationListItem.js:71
-#: components/NotificationList/NotificationListItem.js:72
-#: components/StatusLabel/StatusLabel.js:33
-msgid "Success"
-msgstr "Success"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:478
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:123
-msgid "Success message"
-msgstr "Success message"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:487
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:132
-msgid "Success message body"
-msgstr "Success message body"
-
-#: components/JobList/JobList.js:231
-#: components/StatusLabel/StatusLabel.js:35
-#: components/Workflow/WorkflowNodeHelp.js:102
-#: screens/Dashboard/shared/ChartTooltip.js:59
-msgid "Successful"
-msgstr ""
-
-#: screens/Dashboard/DashboardGraph.js:166
-msgid "Successful jobs"
-msgstr "Successful jobs"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:200
-#: screens/Project/ProjectList/ProjectListItem.js:97
-msgid "Successfully copied to clipboard!"
-msgstr "Successfully copied to clipboard!"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:248
-msgid "Sun"
-msgstr "Sun"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:253
-#: components/Schedule/shared/FrequencyDetailSubform.js:421
-msgid "Sunday"
-msgstr "Sunday"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:26
-#: screens/Template/Template.js:159
-#: screens/Template/Templates.js:48
-#: screens/Template/WorkflowJobTemplate.js:145
-msgid "Survey"
-msgstr "Survey"
-
-#: screens/Template/Survey/SurveyToolbar.js:105
-msgid "Survey Disabled"
-msgstr "Survey Disabled"
-
-#: screens/Template/Survey/SurveyToolbar.js:104
-msgid "Survey Enabled"
-msgstr "Survey Enabled"
-
-#: screens/Template/Survey/SurveyList.js:132
-#~ msgid "Survey List"
-#~ msgstr "Survey List"
-
-#: screens/Template/Survey/SurveyPreviewModal.js:31
-#~ msgid "Survey Preview"
-#~ msgstr "Survey Preview"
-
-#: screens/Template/Survey/SurveyReorderModal.js:191
-msgid "Survey Question Order"
-msgstr "Survey Question Order"
-
-#: screens/Template/Survey/SurveyToolbar.js:102
-msgid "Survey Toggle"
-msgstr "Survey Toggle"
-
-#: screens/Template/Survey/SurveyReorderModal.js:192
-msgid "Survey preview modal"
-msgstr "Survey preview modal"
-
-#: screens/Template/Survey/SurveyListItem.js:66
-#~ msgid "Survey questions"
-#~ msgstr "Survey questions"
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:120
-#: screens/Inventory/shared/InventorySourceSyncButton.js:41
-#: screens/Project/shared/ProjectSyncButton.js:40
-#: screens/Project/shared/ProjectSyncButton.js:52
-msgid "Sync"
-msgstr "Sync"
-
-#: screens/Project/ProjectList/ProjectListItem.js:238
-#: screens/Project/shared/ProjectSyncButton.js:36
-#: screens/Project/shared/ProjectSyncButton.js:47
-msgid "Sync Project"
-msgstr "Sync Project"
-
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:19
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:29
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:32
-msgid "Sync all"
-msgstr "Sync all"
-
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:25
-msgid "Sync all sources"
-msgstr "Sync all sources"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:236
-msgid "Sync error"
-msgstr "Sync error"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:212
-#: screens/Project/ProjectList/ProjectListItem.js:109
-msgid "Sync for revision"
-msgstr "Sync for revision"
-
-#: screens/Project/ProjectList/ProjectListItem.js:122
-msgid "Syncing"
-msgstr "Syncing"
-
-#: screens/Setting/SettingList.js:98
-#: screens/User/UserRoles/UserRolesListItem.js:18
-msgid "System"
-msgstr ""
-
-#: screens/Team/TeamRoles/TeamRolesList.js:128
-#: screens/User/UserDetail/UserDetail.js:47
-#: screens/User/UserList/UserListItem.js:19
-#: screens/User/UserRoles/UserRolesList.js:127
-#: screens/User/shared/UserForm.js:41
-msgid "System Administrator"
-msgstr "System Administrator"
-
-#: screens/User/UserDetail/UserDetail.js:49
-#: screens/User/UserList/UserListItem.js:21
-#: screens/User/shared/UserForm.js:35
-msgid "System Auditor"
-msgstr "System Auditor"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:129
-msgid "System Warning"
-msgstr "System Warning"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:131
-#: screens/User/UserRoles/UserRolesList.js:130
-msgid "System administrators have unrestricted access to all resources."
-msgstr "System administrators have unrestricted access to all resources."
-
-#: screens/Setting/Settings.js:111
-msgid "TACACS+"
-msgstr "TACACS+"
-
-#: screens/Setting/SettingList.js:81
-msgid "TACACS+ settings"
-msgstr "TACACS+ settings"
-
-#: screens/Dashboard/Dashboard.js:117
-#: screens/Job/JobOutput/HostEventModal.js:94
-msgid "Tabs"
-msgstr "Tabs"
-
-#: screens/Template/shared/JobTemplateForm.js:522
-#~ msgid ""
-#~ "Tags are useful when you have a large\n"
-#~ "playbook, and you want to run a specific part of a\n"
-#~ "play or task. Use commas to separate multiple tags.\n"
-#~ "Refer to the documentation for details on\n"
-#~ "the usage of tags."
-#~ msgstr ""
-#~ "Tags are useful when you have a large\n"
-#~ "playbook, and you want to run a specific part of a\n"
-#~ "play or task. Use commas to separate multiple tags.\n"
-#~ "Refer to the documentation for details on\n"
-#~ "the usage of tags."
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:59
-msgid ""
-"Tags are useful when you have a large\n"
-"playbook, and you want to run a specific part of a play or task.\n"
-"Use commas to separate multiple tags. Refer to Ansible Controller\n"
-"documentation for details on the usage of tags."
-msgstr ""
-"Tags are useful when you have a large\n"
-"playbook, and you want to run a specific part of a play or task.\n"
-"Use commas to separate multiple tags. Refer to Ansible Controller\n"
-"documentation for details on the usage of tags."
-
-#: screens/Job/Job.helptext.js:18
-#: screens/Template/shared/JobTemplate.helptext.js:20
-msgid "Tags are useful when you have a large playbook, and you want to run a specific part of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags."
-msgstr "Tags are useful when you have a large playbook, and you want to run a specific part of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:198
-msgid "Tags for the Annotation"
-msgstr "Tags for the Annotation"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:172
-msgid "Tags for the annotation (optional)"
-msgstr "Tags for the annotation (optional)"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:243
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:293
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:361
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:243
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:320
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:438
-msgid "Target URL"
-msgstr "Target URL"
-
-#: screens/Job/JobOutput/HostEventModal.js:123
-msgid "Task"
-msgstr "Task"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:90
-msgid "Task Count"
-msgstr "Task Count"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:130
-msgid "Task Started"
-msgstr "Task Started"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:91
-msgid "Tasks"
-msgstr "Tasks"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:13
-msgid "Team"
-msgstr ""
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:87
-#: screens/Team/TeamRoles/TeamRolesList.js:144
-msgid "Team Roles"
-msgstr ""
-
-#: screens/Team/Team.js:75
-msgid "Team not found."
-msgstr "Team not found."
-
-#: components/AddRole/AddResourceRole.js:188
-#: components/AddRole/AddResourceRole.js:189
-#: routeConfig.js:106
-#: screens/ActivityStream/ActivityStream.js:187
-#: screens/Organization/Organization.js:125
-#: screens/Organization/OrganizationList/OrganizationList.js:145
-#: screens/Organization/OrganizationList/OrganizationListItem.js:66
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:64
-#: screens/Organization/Organizations.js:32
-#: screens/Team/TeamList/TeamList.js:112
-#: screens/Team/TeamList/TeamList.js:166
-#: screens/Team/Teams.js:15
-#: screens/Team/Teams.js:25
-#: screens/User/User.js:70
-#: screens/User/UserTeams/UserTeamList.js:175
-#: screens/User/UserTeams/UserTeamList.js:246
-#: screens/User/Users.js:32
-#: util/getRelatedResourceDeleteDetails.js:173
-msgid "Teams"
-msgstr ""
-
-#: screens/Setting/Jobs/JobsEdit/JobsEdit.js:130
-msgid "Template"
-msgstr "Template"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:115
-#: components/TemplateList/TemplateList.js:133
-msgid "Template copied successfully"
-msgstr "Template copied successfully"
-
-#: screens/Template/Template.js:175
-#: screens/Template/WorkflowJobTemplate.js:175
-msgid "Template not found."
-msgstr "Template not found."
-
-#: components/TemplateList/TemplateList.js:200
-#: components/TemplateList/TemplateList.js:263
-#: routeConfig.js:65
-#: screens/ActivityStream/ActivityStream.js:164
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:70
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:83
-#: screens/Template/Templates.js:17
-#: util/getRelatedResourceDeleteDetails.js:217
-#: util/getRelatedResourceDeleteDetails.js:274
-msgid "Templates"
-msgstr ""
-
-#: screens/Credential/shared/CredentialForm.js:331
-#: screens/Credential/shared/CredentialForm.js:337
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:80
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:421
-msgid "Test"
-msgstr "Test"
-
-#: screens/Credential/shared/ExternalTestModal.js:77
-msgid "Test External Credential"
-msgstr "Test External Credential"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:128
-msgid "Test Notification"
-msgstr "Test Notification"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:125
-msgid "Test notification"
-msgstr "Test notification"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.js:44
-msgid "Test passed"
-msgstr "Test passed"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:80
-#: screens/Template/Survey/SurveyReorderModal.js:181
-msgid "Text"
-msgstr "Text"
-
-#: screens/Template/Survey/SurveyReorderModal.js:135
-msgid "Text Area"
-msgstr "Text Area"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:81
-msgid "Textarea"
-msgstr "Textarea"
-
-#: components/Lookup/Lookup.js:62
-msgid "That value was not found. Please enter or select a valid value."
-msgstr "That value was not found. Please enter or select a valid value."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:391
-msgid "The"
-msgstr "The"
-
-#: screens/Setting/MiscSystem/MiscSystemEdit/MiscSystemEdit.js:200
-#~ msgid "The Execution Environment to be used when one has not been configured for a job template."
-#~ msgstr "The Execution Environment to be used when one has not been configured for a job template."
-
-#: screens/Application/shared/ApplicationForm.js:86
-#~ msgid "The Grant type the user must use for acquire tokens for this application"
-#~ msgstr "The Grant type the user must use for acquire tokens for this application"
-
-#: screens/Application/shared/Application.helptext.js:4
-msgid "The Grant type the user must use to acquire tokens for this application"
-msgstr "The Grant type the user must use to acquire tokens for this application"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:128
-msgid "The Instance Groups for this Organization to run on."
-msgstr "The Instance Groups for this Organization to run on."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:6
-msgid ""
-"The amount of time (in seconds) before the email\n"
-"notification stops trying to reach the host and times out. Ranges\n"
-"from 1 to 120 seconds."
-msgstr ""
-"The amount of time (in seconds) before the email\n"
-"notification stops trying to reach the host and times out. Ranges\n"
-"from 1 to 120 seconds."
-
-#: screens/Template/shared/JobTemplateForm.js:489
-#~ msgid ""
-#~ "The amount of time (in seconds) to run\n"
-#~ "before the job is canceled. Defaults to 0 for no job\n"
-#~ "timeout."
-#~ msgstr ""
-#~ "The amount of time (in seconds) to run\n"
-#~ "before the job is canceled. Defaults to 0 for no job\n"
-#~ "timeout."
-
-#: screens/Job/Job.helptext.js:16
-#: screens/Template/shared/JobTemplate.helptext.js:17
-msgid "The amount of time (in seconds) to run before the job is canceled. Defaults to 0 for no job timeout."
-msgstr "The amount of time (in seconds) to run before the job is canceled. Defaults to 0 for no job timeout."
-
-#: screens/User/shared/User.helptext.js:4
-msgid "The application that this token belongs to, or leave this field empty to create a Personal Access Token."
-msgstr "The application that this token belongs to, or leave this field empty to create a Personal Access Token."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:9
-msgid ""
-"The base URL of the Grafana server - the\n"
-"/api/annotations endpoint will be added automatically to the base\n"
-"Grafana URL."
-msgstr ""
-"The base URL of the Grafana server - the\n"
-"/api/annotations endpoint will be added automatically to the base\n"
-"Grafana URL."
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:109
-msgid ""
-"The execution environment that will be used for jobs\n"
-"inside of this organization. This will be used a fallback when\n"
-"an execution environment has not been explicitly assigned at the\n"
-"project, job template or workflow level."
-msgstr ""
-"The execution environment that will be used for jobs\n"
-"inside of this organization. This will be used a fallback when\n"
-"an execution environment has not been explicitly assigned at the\n"
-"project, job template or workflow level."
-
-#: screens/Organization/shared/OrganizationForm.js:93
-msgid "The execution environment that will be used for jobs inside of this organization. This will be used a fallback when an execution environment has not been explicitly assigned at the project, job template or workflow level."
-msgstr "The execution environment that will be used for jobs inside of this organization. This will be used a fallback when an execution environment has not been explicitly assigned at the project, job template or workflow level."
-
-#: screens/Project/shared/Project.helptext.js:5
-msgid "The execution environment that will be used for jobs that use this project. This will be used as fallback when an execution environment has not been explicitly assigned at the job template or workflow level."
-msgstr "The execution environment that will be used for jobs that use this project. This will be used as fallback when an execution environment has not been explicitly assigned at the job template or workflow level."
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:239
-#~ msgid ""
-#~ "The execution environment that will be used when launching\n"
-#~ "this job template. The resolved execution environment can be overridden by\n"
-#~ "explicitly assigning a different one to this job template."
-#~ msgstr ""
-#~ "The execution environment that will be used when launching\n"
-#~ "this job template. The resolved execution environment can be overridden by\n"
-#~ "explicitly assigning a different one to this job template."
-
-#: screens/Job/Job.helptext.js:8
-#: screens/Template/shared/JobTemplate.helptext.js:9
-msgid "The execution environment that will be used when launching this job template. The resolved execution environment can be overridden by explicitly assigning a different one to this job template."
-msgstr "The execution environment that will be used when launching this job template. The resolved execution environment can be overridden by explicitly assigning a different one to this job template."
-
-#: screens/Project/shared/Project.helptext.js:93
-msgid ""
-"The first fetches all references. The second\n"
-"fetches the Github pull request number 62, in this example\n"
-"the branch needs to be \"pull/62/head\"."
-msgstr ""
-"The first fetches all references. The second\n"
-"fetches the Github pull request number 62, in this example\n"
-"the branch needs to be \"pull/62/head\"."
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:66
-msgid "The following selected items are complete and cannot be acted on: {completedItems}"
-msgstr "The following selected items are complete and cannot be acted on: {completedItems}"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:7
-msgid "The full image location, including the container registry, image name, and version tag."
-msgstr "The full image location, including the container registry, image name, and version tag."
-
-#: screens/Inventory/shared/Inventory.helptext.js:191
-msgid ""
-"The inventory file\n"
-"to be synced by this source. You can select from\n"
-"the dropdown or enter a file within the input."
-msgstr ""
-"The inventory file\n"
-"to be synced by this source. You can select from\n"
-"the dropdown or enter a file within the input."
-
-#: screens/Host/HostDetail/HostDetail.js:77
-msgid "The inventory that this host belongs to."
-msgstr "The inventory that this host belongs to."
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:100
-msgid ""
-"The maximum number of hosts allowed to be managed by\n"
-"this organization. Value defaults to 0 which means no limit.\n"
-"Refer to the Ansible documentation for more details."
-msgstr ""
-"The maximum number of hosts allowed to be managed by\n"
-"this organization. Value defaults to 0 which means no limit.\n"
-"Refer to the Ansible documentation for more details."
-
-#: screens/Organization/shared/OrganizationForm.js:72
-msgid ""
-"The maximum number of hosts allowed to be managed by this organization.\n"
-"Value defaults to 0 which means no limit. Refer to the Ansible\n"
-"documentation for more details."
-msgstr ""
-"The maximum number of hosts allowed to be managed by this organization.\n"
-"Value defaults to 0 which means no limit. Refer to the Ansible\n"
-"documentation for more details."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:26
-msgid ""
-"The number associated with the \"Messaging\n"
-"Service\" in Twilio with the format +18005550199."
-msgstr ""
-"The number associated with the \"Messaging\n"
-"Service\" in Twilio with the format +18005550199."
-
-#: screens/Template/shared/JobTemplateForm.js:427
-#~ msgid ""
-#~ "The number of parallel or simultaneous\n"
-#~ "processes to use while executing the playbook. An empty value,\n"
-#~ "or a value less than 1 will use the Ansible default which is\n"
-#~ "usually 5. The default number of forks can be overwritten\n"
-#~ "with a change to"
-#~ msgstr ""
-#~ "The number of parallel or simultaneous\n"
-#~ "processes to use while executing the playbook. An empty value,\n"
-#~ "or a value less than 1 will use the Ansible default which is\n"
-#~ "usually 5. The default number of forks can be overwritten\n"
-#~ "with a change to"
-
-#: screens/Job/Job.helptext.js:24
-#: screens/Template/shared/JobTemplate.helptext.js:44
-msgid "The number of parallel or simultaneous processes to use while executing the playbook. An empty value, or a value less than 1 will use the Ansible default which is usually 5. The default number of forks can be overwritten with a change to"
-msgstr "The number of parallel or simultaneous processes to use while executing the playbook. An empty value, or a value less than 1 will use the Ansible default which is usually 5. The default number of forks can be overwritten with a change to"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:164
-msgid "The number of parallel or simultaneous processes to use while executing the playbook. Inputting no value will use the default value from the ansible configuration file. You can find more information"
-msgstr "The number of parallel or simultaneous processes to use while executing the playbook. Inputting no value will use the default value from the ansible configuration file. You can find more information"
-
-#: components/ContentError/ContentError.js:41
-#: screens/Job/Job.js:138
-msgid "The page you requested could not be found."
-msgstr "The page you requested could not be found."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:144
-msgid "The pattern used to target hosts in the inventory. Leaving the field blank, all, and * will all target all hosts in the inventory. You can find more information about Ansible's host patterns"
-msgstr "The pattern used to target hosts in the inventory. Leaving the field blank, all, and * will all target all hosts in the inventory. You can find more information about Ansible's host patterns"
-
-#: screens/Project/ProjectList/ProjectListItem.js:120
-msgid "The project is currently syncing and the revision will be available after the sync is complete."
-msgstr "The project is currently syncing and the revision will be available after the sync is complete."
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:210
-#: screens/Project/ProjectList/ProjectListItem.js:107
-msgid "The project must be synced before a revision is available."
-msgstr "The project must be synced before a revision is available."
-
-#: screens/Project/ProjectList/ProjectListItem.js:130
-msgid "The project revision is currently out of date. Please refresh to fetch the most recent revision."
-msgstr "The project revision is currently out of date. Please refresh to fetch the most recent revision."
-
-#: components/Workflow/WorkflowNodeHelp.js:138
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:131
-msgid "The resource associated with this node has been deleted."
-msgstr "The resource associated with this node has been deleted."
-
-#: screens/Job/JobOutput/EmptyOutput.js:19
-msgid "The search filter did not produce any results…"
-msgstr "The search filter did not produce any results…"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:180
-msgid ""
-"The suggested format for variable names is lowercase and\n"
-"underscore-separated (for example, foo_bar, user_id, host_name,\n"
-"etc.). Variable names with spaces are not allowed."
-msgstr ""
-"The suggested format for variable names is lowercase and\n"
-"underscore-separated (for example, foo_bar, user_id, host_name,\n"
-"etc.). Variable names with spaces are not allowed."
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:47
-#~ msgid ""
-#~ "There are no available playbook directories in {project_base_dir}.\n"
-#~ "Either that directory is empty, or all of the contents are already\n"
-#~ "assigned to other projects. Create a new directory there and make\n"
-#~ "sure the playbook files can be read by the \"awx\" system user,\n"
-#~ "or have {0} directly retrieve your playbooks from\n"
-#~ "source control using the Source Control Type option above."
-#~ msgstr ""
-#~ "There are no available playbook directories in {project_base_dir}.\n"
-#~ "Either that directory is empty, or all of the contents are already\n"
-#~ "assigned to other projects. Create a new directory there and make\n"
-#~ "sure the playbook files can be read by the \"awx\" system user,\n"
-#~ "or have {0} directly retrieve your playbooks from\n"
-#~ "source control using the Source Control Type option above."
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:49
-msgid ""
-"There are no available playbook directories in {project_base_dir}.\n"
-"Either that directory is empty, or all of the contents are already\n"
-"assigned to other projects. Create a new directory there and make\n"
-"sure the playbook files can be read by the \"awx\" system user,\n"
-"or have {brandName} directly retrieve your playbooks from\n"
-"source control using the Source Control Type option above."
-msgstr ""
-"There are no available playbook directories in {project_base_dir}.\n"
-"Either that directory is empty, or all of the contents are already\n"
-"assigned to other projects. Create a new directory there and make\n"
-"sure the playbook files can be read by the \"awx\" system user,\n"
-"or have {brandName} directly retrieve your playbooks from\n"
-"source control using the Source Control Type option above."
-
-#: screens/Template/Survey/MultipleChoiceField.js:34
-msgid "There must be a value in at least one input"
-msgstr "There must be a value in at least one input"
-
-#: screens/Login/Login.js:144
-msgid "There was a problem logging in. Please try again."
-msgstr "There was a problem logging in. Please try again."
-
-#: components/ContentError/ContentError.js:42
-msgid "There was an error loading this content. Please reload the page."
-msgstr "There was an error loading this content. Please reload the page."
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:56
-msgid "There was an error parsing the file. Please check the file formatting and try again."
-msgstr "There was an error parsing the file. Please check the file formatting and try again."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:621
-msgid "There was an error saving the workflow."
-msgstr "There was an error saving the workflow."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:68
-#~ msgid "These are the modules that {0} supports running commands against."
-#~ msgstr "These are the modules that {0} supports running commands against."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:69
-msgid "These are the modules that {brandName} supports running commands against."
-msgstr "These are the modules that {brandName} supports running commands against."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:129
-msgid "These are the verbosity levels for standard out of the command run that are supported."
-msgstr "These are the verbosity levels for standard out of the command run that are supported."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:122
-#: screens/Job/Job.helptext.js:42
-msgid "These arguments are used with the specified module."
-msgstr "These arguments are used with the specified module."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:111
-msgid "These arguments are used with the specified module. You can find information about {0} by clicking"
-msgstr "These arguments are used with the specified module. You can find information about {0} by clicking"
-
-#: screens/Job/Job.helptext.js:32
-msgid "These arguments are used with the specified module. You can find information about {moduleName} by clicking"
-msgstr "These arguments are used with the specified module. You can find information about {moduleName} by clicking"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:403
-msgid "Third"
-msgstr "Third"
-
-#: screens/Template/shared/JobTemplateForm.js:153
-msgid "This Project needs to be updated"
-msgstr "This Project needs to be updated"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:286
-#: screens/Template/Survey/SurveyList.js:82
-msgid "This action will delete the following:"
-msgstr "This action will delete the following:"
-
-#: screens/User/UserTeams/UserTeamList.js:217
-msgid "This action will disassociate all roles for this user from the selected teams."
-msgstr "This action will disassociate all roles for this user from the selected teams."
-
-#: screens/Team/TeamRoles/TeamRolesList.js:236
-#: screens/User/UserRoles/UserRolesList.js:232
-msgid "This action will disassociate the following role from {0}:"
-msgstr "This action will disassociate the following role from {0}:"
-
-#: components/DisassociateButton/DisassociateButton.js:148
-msgid "This action will disassociate the following:"
-msgstr "This action will disassociate the following:"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:113
-msgid "This container group is currently being by other resources. Are you sure you want to delete it?"
-msgstr "This container group is currently being by other resources. Are you sure you want to delete it?"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:305
-msgid "This credential is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "This credential is currently being used by other resources. Are you sure you want to delete it?"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:121
-msgid "This credential type is currently being used by some credentials and cannot be deleted"
-msgstr "This credential type is currently being used by some credentials and cannot be deleted"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:74
-msgid ""
-"This data is used to enhance\n"
-"future releases of the Software and to provide\n"
-"Automation Analytics."
-msgstr ""
-"This data is used to enhance\n"
-"future releases of the Software and to provide\n"
-"Automation Analytics."
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:74
-#~ msgid ""
-#~ "This data is used to enhance\n"
-#~ "future releases of the Software and to provide\n"
-#~ "Insights for Ansible Automation Platform."
-#~ msgstr ""
-#~ "This data is used to enhance\n"
-#~ "future releases of the Software and to provide\n"
-#~ "Insights for Ansible Automation Platform."
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:62
-msgid ""
-"This data is used to enhance\n"
-"future releases of the Tower Software and help\n"
-"streamline customer experience and success."
-msgstr ""
-"This data is used to enhance\n"
-"future releases of the Tower Software and help\n"
-"streamline customer experience and success."
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:131
-msgid "This execution environment is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "This execution environment is currently being used by other resources. Are you sure you want to delete it?"
-
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:74
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:79
-msgid "This feature is deprecated and will be removed in a future release."
-msgstr "This feature is deprecated and will be removed in a future release."
-
-#: screens/Inventory/shared/Inventory.helptext.js:155
-msgid "This field is ignored unless an Enabled Variable is set. If the enabled variable matches this value, the host will be enabled on import."
-msgstr "This field is ignored unless an Enabled Variable is set. If the enabled variable matches this value, the host will be enabled on import."
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:61
-#~ msgid "This field is must not be blank"
-#~ msgstr "This field is must not be blank"
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:55
-#~ msgid "This field is must not be blank."
-#~ msgstr "This field is must not be blank."
-
-#: components/AdHocCommands/useAdHocCredentialPasswordStep.js:44
-#: components/LaunchPrompt/steps/useCredentialPasswordsStep.js:50
-msgid "This field may not be blank"
-msgstr "This field may not be blank"
-
-#: util/validators.js:127
-msgid "This field must be a number"
-msgstr "This field must be a number"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:107
-msgid "This field must be a number and have a value between {0} and {1}"
-msgstr "This field must be a number and have a value between {0} and {1}"
-
-#: util/validators.js:67
-msgid "This field must be a number and have a value between {min} and {max}"
-msgstr "This field must be a number and have a value between {min} and {max}"
-
-#: util/validators.js:64
-msgid "This field must be a number and have a value greater than {min}"
-msgstr "This field must be a number and have a value greater than {min}"
-
-#: util/validators.js:61
-msgid "This field must be a number and have a value less than {max}"
-msgstr "This field must be a number and have a value less than {max}"
-
-#: util/validators.js:184
-msgid "This field must be a regular expression"
-msgstr "This field must be a regular expression"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:52
-#: util/validators.js:111
-msgid "This field must be an integer"
-msgstr "This field must be an integer"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:99
-msgid "This field must be at least {0} characters"
-msgstr "This field must be at least {0} characters"
-
-#: util/validators.js:52
-msgid "This field must be at least {min} characters"
-msgstr "This field must be at least {min} characters"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:55
-msgid "This field must be greater than 0"
-msgstr "This field must be greater than 0"
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:52
-#: components/LaunchPrompt/steps/useSurveyStep.js:111
-#: screens/Template/shared/JobTemplateForm.js:150
-#: screens/User/shared/UserForm.js:92
-#: screens/User/shared/UserForm.js:103
-#: util/validators.js:5
-#: util/validators.js:76
-msgid "This field must not be blank"
-msgstr ""
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:46
-msgid "This field must not be blank."
-msgstr "This field must not be blank."
-
-#: util/validators.js:101
-msgid "This field must not contain spaces"
-msgstr "This field must not contain spaces"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:102
-msgid "This field must not exceed {0} characters"
-msgstr "This field must not exceed {0} characters"
-
-#: util/validators.js:43
-msgid "This field must not exceed {max} characters"
-msgstr ""
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:51
-msgid "This field will be retrieved from an external secret management system using the specified credential."
-msgstr "This field will be retrieved from an external secret management system using the specified credential."
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:125
-msgid "This instance group is currently being by other resources. Are you sure you want to delete it?"
-msgstr "This instance group is currently being by other resources. Are you sure you want to delete it?"
-
-#: components/LaunchPrompt/steps/useInventoryStep.js:59
-msgid "This inventory is applied to all workflow nodes within this workflow ({0}) that prompt for an inventory."
-msgstr "This inventory is applied to all workflow nodes within this workflow ({0}) that prompt for an inventory."
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:160
-msgid "This inventory is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "This inventory is currently being used by other resources. Are you sure you want to delete it?"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:329
-msgid "This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?"
-msgstr "This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?"
-
-#: screens/Application/Applications.js:77
-msgid "This is the only time the client secret will be shown."
-msgstr "This is the only time the client secret will be shown."
-
-#: screens/User/UserTokens/UserTokens.js:59
-msgid "This is the only time the token value and associated refresh token value will be shown."
-msgstr "This is the only time the token value and associated refresh token value will be shown."
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:526
-msgid "This job template is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "This job template is currently being used by other resources. Are you sure you want to delete it?"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:197
-msgid "This organization is currently being by other resources. Are you sure you want to delete it?"
-msgstr "This organization is currently being by other resources. Are you sure you want to delete it?"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:320
-msgid "This project is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "This project is currently being used by other resources. Are you sure you want to delete it?"
-
-#: screens/Project/shared/Project.helptext.js:59
-msgid "This project is currently on sync and cannot be clicked until sync process completed"
-msgstr "This project is currently on sync and cannot be clicked until sync process completed"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:122
-msgid "This schedule is missing an Inventory"
-msgstr "This schedule is missing an Inventory"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:147
-msgid "This schedule is missing required survey values"
-msgstr "This schedule is missing required survey values"
-
-#: components/LaunchPrompt/steps/StepName.js:26
-msgid "This step contains errors"
-msgstr "This step contains errors"
-
-#: screens/User/shared/UserForm.js:150
-msgid "This value does not match the password you entered previously. Please confirm that password."
-msgstr "This value does not match the password you entered previously. Please confirm that password."
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:90
-msgid "This will cancel the workflow and no subsequent nodes will execute."
-msgstr "This will cancel the workflow and no subsequent nodes will execute."
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:105
-msgid "This will continue the workflow"
-msgstr "This will continue the workflow"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:78
-msgid "This will continue the workflow along failure and always paths."
-msgstr "This will continue the workflow along failure and always paths."
-
-#: screens/Setting/shared/RevertAllAlert.js:36
-msgid ""
-"This will revert all configuration values on this page to\n"
-"their factory defaults. Are you sure you want to proceed?"
-msgstr ""
-"This will revert all configuration values on this page to\n"
-"their factory defaults. Are you sure you want to proceed?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:40
-msgid "This workflow does not have any nodes configured."
-msgstr "This workflow does not have any nodes configured."
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:269
-msgid "This workflow job template is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "This workflow job template is currently being used by other resources. Are you sure you want to delete it?"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:292
-msgid "Thu"
-msgstr "Thu"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:297
-#: components/Schedule/shared/FrequencyDetailSubform.js:441
-msgid "Thursday"
-msgstr "Thursday"
-
-#: screens/ActivityStream/ActivityStream.js:249
-#: screens/ActivityStream/ActivityStream.js:261
-#: screens/ActivityStream/ActivityStreamDetailButton.js:41
-#: screens/ActivityStream/ActivityStreamListItem.js:42
-msgid "Time"
-msgstr "Time"
-
-#: screens/Project/shared/Project.helptext.js:124
-msgid ""
-"Time in seconds to consider a project\n"
-"to be current. During job runs and callbacks the task\n"
-"system will evaluate the timestamp of the latest project\n"
-"update. If it is older than Cache Timeout, it is not\n"
-"considered current, and a new project update will be\n"
-"performed."
-msgstr ""
-"Time in seconds to consider a project\n"
-"to be current. During job runs and callbacks the task\n"
-"system will evaluate the timestamp of the latest project\n"
-"update. If it is older than Cache Timeout, it is not\n"
-"considered current, and a new project update will be\n"
-"performed."
-
-#: screens/Inventory/shared/Inventory.helptext.js:147
-msgid ""
-"Time in seconds to consider an inventory sync\n"
-"to be current. During job runs and callbacks the task system will\n"
-"evaluate the timestamp of the latest sync. If it is older than\n"
-"Cache Timeout, it is not considered current, and a new\n"
-"inventory sync will be performed."
-msgstr ""
-"Time in seconds to consider an inventory sync\n"
-"to be current. During job runs and callbacks the task system will\n"
-"evaluate the timestamp of the latest sync. If it is older than\n"
-"Cache Timeout, it is not considered current, and a new\n"
-"inventory sync will be performed."
-
-#: components/StatusLabel/StatusLabel.js:43
-msgid "Timed out"
-msgstr "Timed out"
-
-#: components/PromptDetail/PromptDetail.js:127
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:169
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:112
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:270
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:186
-#: screens/Template/shared/JobTemplateForm.js:443
-msgid "Timeout"
-msgstr "Timeout"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:193
-msgid "Timeout minutes"
-msgstr "Timeout minutes"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:207
-msgid "Timeout seconds"
-msgstr "Timeout seconds"
-
-#: screens/Host/HostList/SmartInventoryButton.js:20
-msgid "To create a smart inventory using ansible facts, go to the smart inventory screen."
-msgstr "To create a smart inventory using ansible facts, go to the smart inventory screen."
-
-#: screens/Template/Survey/SurveyReorderModal.js:182
-#~ msgid "To reoder the survey questions drag and drop them in the desired location."
-#~ msgstr "To reoder the survey questions drag and drop them in the desired location."
-
-#: screens/Template/Survey/SurveyReorderModal.js:194
-msgid "To reorder the survey questions drag and drop them in the desired location."
-msgstr "To reorder the survey questions drag and drop them in the desired location."
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:106
-msgid "Toggle Legend"
-msgstr "Toggle Legend"
-
-#: components/FormField/PasswordInput.js:39
-msgid "Toggle Password"
-msgstr "Toggle Password"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:116
-msgid "Toggle Tools"
-msgstr "Toggle Tools"
-
-#: components/HostToggle/HostToggle.js:70
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:56
-msgid "Toggle host"
-msgstr "Toggle host"
-
-#: components/InstanceToggle/InstanceToggle.js:61
-msgid "Toggle instance"
-msgstr "Toggle instance"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:80
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:82
-#: screens/TopologyView/Header.js:99
-msgid "Toggle legend"
-msgstr "Toggle legend"
-
-#: components/NotificationList/NotificationListItem.js:50
-msgid "Toggle notification approvals"
-msgstr "Toggle notification approvals"
-
-#: components/NotificationList/NotificationListItem.js:92
-msgid "Toggle notification failure"
-msgstr ""
-
-#: components/NotificationList/NotificationListItem.js:64
-msgid "Toggle notification start"
-msgstr "Toggle notification start"
-
-#: components/NotificationList/NotificationListItem.js:78
-msgid "Toggle notification success"
-msgstr ""
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:66
-msgid "Toggle schedule"
-msgstr "Toggle schedule"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:92
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:94
-msgid "Toggle tools"
-msgstr "Toggle tools"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:362
-#: screens/User/UserTokens/UserTokens.js:64
-msgid "Token"
-msgstr "Token"
-
-#: screens/User/UserTokens/UserTokens.js:50
-#: screens/User/UserTokens/UserTokens.js:53
-msgid "Token information"
-msgstr "Token information"
-
-#: screens/User/UserToken/UserToken.js:73
-msgid "Token not found."
-msgstr "Token not found."
-
-#: screens/Application/Application/Application.js:80
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:105
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:128
-#: screens/Application/Applications.js:40
-#: screens/User/User.js:76
-#: screens/User/UserTokenList/UserTokenList.js:118
-#: screens/User/Users.js:34
-msgid "Tokens"
-msgstr "Tokens"
-
-#: components/Workflow/WorkflowTools.js:83
-msgid "Tools"
-msgstr "Tools"
-
-#: components/PaginatedTable/PaginatedTable.js:133
-#~ msgid "Top Pagination"
-#~ msgstr "Top Pagination"
-
-#: routeConfig.js:152
-#: screens/TopologyView/TopologyView.js:40
-msgid "Topology View"
-msgstr "Topology View"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:211
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:211
-#: screens/InstanceGroup/Instances/InstanceListItem.js:199
-#: screens/Instances/InstanceDetail/InstanceDetail.js:162
-#: screens/Instances/InstanceList/InstanceListItem.js:214
-msgid "Total Jobs"
-msgstr "Total Jobs"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:76
-msgid "Total Nodes"
-msgstr "Total Nodes"
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:81
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:120
-msgid "Total hosts"
-msgstr "Total hosts"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:72
-msgid "Total jobs"
-msgstr "Total jobs"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:83
-msgid "Track submodules"
-msgstr "Track submodules"
-
-#: components/PromptDetail/PromptProjectDetail.js:56
-#: screens/Project/ProjectDetail/ProjectDetail.js:108
-msgid "Track submodules latest commit on branch"
-msgstr "Track submodules latest commit on branch"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:79
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:168
-msgid "Trial"
-msgstr "Trial"
-
-#: components/JobList/JobListItem.js:318
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:65
-#: screens/Job/JobDetail/JobDetail.js:378
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:205
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:235
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:265
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:310
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:368
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:80
-msgid "True"
-msgstr "True"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:270
-msgid "Tue"
-msgstr "Tue"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:275
-#: components/Schedule/shared/FrequencyDetailSubform.js:431
-msgid "Tuesday"
-msgstr "Tuesday"
-
-#: components/NotificationList/NotificationList.js:201
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:142
-msgid "Twilio"
-msgstr "Twilio"
-
-#: components/JobList/JobList.js:246
-#: components/JobList/JobListItem.js:98
-#: components/Lookup/ProjectLookup.js:131
-#: components/NotificationList/NotificationList.js:219
-#: components/NotificationList/NotificationListItem.js:33
-#: components/PromptDetail/PromptDetail.js:115
-#: components/RelatedTemplateList/RelatedTemplateList.js:187
-#: components/Schedule/ScheduleList/ScheduleList.js:169
-#: components/Schedule/ScheduleList/ScheduleListItem.js:97
-#: components/TemplateList/TemplateList.js:214
-#: components/TemplateList/TemplateList.js:243
-#: components/TemplateList/TemplateListItem.js:184
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:85
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:154
-#: components/Workflow/WorkflowNodeHelp.js:160
-#: components/Workflow/WorkflowNodeHelp.js:196
-#: screens/Credential/CredentialList/CredentialList.js:165
-#: screens/Credential/CredentialList/CredentialListItem.js:63
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:94
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:116
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:17
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:46
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:54
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:209
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:66
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:72
-#: screens/Inventory/InventoryList/InventoryList.js:220
-#: screens/Inventory/InventoryList/InventoryListItem.js:116
-#: screens/Inventory/InventorySources/InventorySourceList.js:213
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:100
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:106
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:180
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:120
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:68
-#: screens/Project/ProjectList/ProjectList.js:194
-#: screens/Project/ProjectList/ProjectList.js:223
-#: screens/Project/ProjectList/ProjectListItem.js:218
-#: screens/Team/TeamRoles/TeamRoleListItem.js:17
-#: screens/Team/TeamRoles/TeamRolesList.js:181
-#: screens/Template/Survey/SurveyList.js:103
-#: screens/Template/Survey/SurveyList.js:103
-#: screens/Template/Survey/SurveyListItem.js:60
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:93
-#: screens/TopologyView/Tooltip.js:92
-#: screens/User/UserDetail/UserDetail.js:75
-#: screens/User/UserRoles/UserRolesList.js:156
-#: screens/User/UserRoles/UserRolesListItem.js:21
-msgid "Type"
-msgstr "Type"
-
-#: screens/Credential/shared/TypeInputsSubForm.js:25
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:46
-#: screens/Project/shared/ProjectForm.js:247
-msgid "Type Details"
-msgstr "Type Details"
-
-#: screens/Template/Survey/MultipleChoiceField.js:56
-msgid ""
-"Type answer then click checkbox on right to select answer as\n"
-"default."
-msgstr ""
-"Type answer then click checkbox on right to select answer as\n"
-"default."
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:50
-msgid "UTC"
-msgstr "UTC"
-
-#: components/HostForm/HostForm.js:62
-msgid "Unable to change inventory on a host"
-msgstr "Unable to change inventory on a host"
-
-#: screens/Project/ProjectList/ProjectListItem.js:211
-msgid "Unable to load last job update"
-msgstr "Unable to load last job update"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:253
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:87
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:46
-#: screens/InstanceGroup/Instances/InstanceListItem.js:78
-#: screens/Instances/InstanceDetail/InstanceDetail.js:205
-#: screens/Instances/InstanceList/InstanceListItem.js:77
-msgid "Unavailable"
-msgstr "Unavailable"
-
-#: components/StatusLabel/StatusLabel.js:67
-#~ msgid "Undefined"
-#~ msgstr "Undefined"
-
-#: screens/Setting/shared/RevertButton.js:53
-#: screens/Setting/shared/RevertButton.js:62
-msgid "Undo"
-msgstr "Undo"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:184
-msgid "Unfollow"
-msgstr "Unfollow"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:122
-msgid "Unlimited"
-msgstr "Unlimited"
-
-#: components/StatusLabel/StatusLabel.js:39
-#: screens/Job/JobOutput/shared/HostStatusBar.js:51
-#: screens/Job/JobOutput/shared/OutputToolbar.js:103
-msgid "Unreachable"
-msgstr "Unreachable"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:102
-msgid "Unreachable Host Count"
-msgstr "Unreachable Host Count"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:104
-msgid "Unreachable Hosts"
-msgstr "Unreachable Hosts"
-
-#: util/dates.js:74
-msgid "Unrecognized day string"
-msgstr "Unrecognized day string"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:15
-msgid "Unsaved changes modal"
-msgstr "Unsaved changes modal"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:90
-msgid "Update Revision on Launch"
-msgstr "Update Revision on Launch"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:57
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:138
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:89
-msgid "Update on launch"
-msgstr "Update on launch"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:62
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:148
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:97
-msgid "Update on project update"
-msgstr "Update on project update"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:71
-msgid "Update options"
-msgstr "Update options"
-
-#: components/PromptDetail/PromptProjectDetail.js:61
-#: screens/Project/ProjectDetail/ProjectDetail.js:114
-msgid "Update revision on job launch"
-msgstr "Update revision on job launch"
-
-#: screens/Setting/SettingList.js:87
-#~ msgid "Update settings pertaining to Jobs within {0}"
-#~ msgstr "Update settings pertaining to Jobs within {0}"
-
-#: screens/Setting/SettingList.js:88
-msgid "Update settings pertaining to Jobs within {brandName}"
-msgstr "Update settings pertaining to Jobs within {brandName}"
-
-#: screens/Template/shared/WebhookSubForm.js:187
-msgid "Update webhook key"
-msgstr "Update webhook key"
-
-#: components/Workflow/WorkflowNodeHelp.js:126
-msgid "Updating"
-msgstr "Updating"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:118
-msgid "Upload a .zip file"
-msgstr "Upload a .zip file"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:97
-msgid "Upload a Red Hat Subscription Manifest containing your subscription. To generate your subscription manifest, go to <0>subscription allocations0> on the Red Hat Customer Portal."
-msgstr "Upload a Red Hat Subscription Manifest containing your subscription. To generate your subscription manifest, go to <0>subscription allocations0> on the Red Hat Customer Portal."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:52
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:126
-msgid "Use SSL"
-msgstr "Use SSL"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:57
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:131
-msgid "Use TLS"
-msgstr "Use TLS"
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:72
-msgid ""
-"Use custom messages to change the content of\n"
-"notifications sent when a job starts, succeeds, or fails. Use\n"
-"curly braces to access information about the job:"
-msgstr ""
-"Use custom messages to change the content of\n"
-"notifications sent when a job starts, succeeds, or fails. Use\n"
-"curly braces to access information about the job:"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:12
-msgid "Use one Annotation Tag per line, without commas."
-msgstr "Use one Annotation Tag per line, without commas."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:13
-msgid ""
-"Use one IRC channel or username per line. The pound\n"
-"symbol (#) for channels, and the at (@) symbol for users, are not\n"
-"required."
-msgstr ""
-"Use one IRC channel or username per line. The pound\n"
-"symbol (#) for channels, and the at (@) symbol for users, are not\n"
-"required."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:5
-msgid "Use one email address per line to create a recipient list for this type of notification."
-msgstr "Use one email address per line to create a recipient list for this type of notification."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:28
-msgid ""
-"Use one phone number per line to specify where to\n"
-"route SMS messages. Phone numbers should be formatted +11231231234. For more information see Twilio documentation"
-msgstr ""
-"Use one phone number per line to specify where to\n"
-"route SMS messages. Phone numbers should be formatted +11231231234. For more information see Twilio documentation"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:242
-#: screens/InstanceGroup/Instances/InstanceList.js:259
-#: screens/Instances/InstanceDetail/InstanceDetail.js:192
-#: screens/Instances/InstanceList/InstanceList.js:154
-msgid "Used Capacity"
-msgstr "Used Capacity"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:246
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:250
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:78
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:86
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:42
-#: screens/InstanceGroup/Instances/InstanceListItem.js:74
-#: screens/Instances/InstanceDetail/InstanceDetail.js:196
-#: screens/Instances/InstanceDetail/InstanceDetail.js:202
-#: screens/Instances/InstanceList/InstanceListItem.js:73
-msgid "Used capacity"
-msgstr "Used capacity"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:13
-msgid "User"
-msgstr ""
-
-#: components/AppContainer/PageHeaderToolbar.js:165
-msgid "User Details"
-msgstr ""
-
-#: screens/Setting/SettingList.js:117
-#: screens/Setting/Settings.js:114
-msgid "User Interface"
-msgstr ""
-
-#: screens/Setting/SettingList.js:122
-msgid "User Interface settings"
-msgstr "User Interface settings"
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:73
-#: screens/User/UserRoles/UserRolesList.js:142
-msgid "User Roles"
-msgstr ""
-
-#: screens/User/UserDetail/UserDetail.js:72
-#: screens/User/shared/UserForm.js:119
-msgid "User Type"
-msgstr "User Type"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:59
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:60
-msgid "User analytics"
-msgstr "User analytics"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:34
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:202
-msgid "User and Automation Analytics"
-msgstr "User and Automation Analytics"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:34
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:202
-#~ msgid "User and Insights analytics"
-#~ msgstr "User and Insights analytics"
-
-#: components/AppContainer/PageHeaderToolbar.js:159
-msgid "User details"
-msgstr "User details"
-
-#: screens/User/User.js:96
-msgid "User not found."
-msgstr "User not found."
-
-#: screens/User/UserTokenList/UserTokenList.js:180
-msgid "User tokens"
-msgstr "User tokens"
-
-#: components/AddRole/AddResourceRole.js:23
-#: components/AddRole/AddResourceRole.js:38
-#: components/ResourceAccessList/ResourceAccessList.js:173
-#: components/ResourceAccessList/ResourceAccessList.js:226
-#: screens/Login/Login.js:208
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:143
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:248
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:298
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:356
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:65
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:251
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:328
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:427
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:92
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:204
-#: screens/User/UserDetail/UserDetail.js:68
-#: screens/User/UserList/UserList.js:120
-#: screens/User/UserList/UserList.js:160
-#: screens/User/UserList/UserListItem.js:38
-#: screens/User/shared/UserForm.js:76
-msgid "Username"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:88
-msgid "Username / password"
-msgstr "Username / password"
-
-#: components/AddRole/AddResourceRole.js:178
-#: components/AddRole/AddResourceRole.js:179
-#: routeConfig.js:101
-#: screens/ActivityStream/ActivityStream.js:184
-#: screens/Team/Teams.js:30
-#: screens/User/UserList/UserList.js:110
-#: screens/User/UserList/UserList.js:153
-#: screens/User/Users.js:15
-#: screens/User/Users.js:26
-msgid "Users"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:101
-msgid "VMware vCenter"
-msgstr "VMware vCenter"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:69
-#: components/HostForm/HostForm.js:113
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:81
-#: components/PromptDetail/PromptDetail.js:159
-#: components/PromptDetail/PromptDetail.js:291
-#: components/PromptDetail/PromptJobTemplateDetail.js:278
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:132
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:393
-#: screens/Host/HostDetail/HostDetail.js:91
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:126
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:37
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:89
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:145
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:54
-#: screens/Inventory/shared/InventoryForm.js:90
-#: screens/Inventory/shared/InventoryGroupForm.js:46
-#: screens/Inventory/shared/SmartInventoryForm.js:93
-#: screens/Job/JobDetail/JobDetail.js:528
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:484
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:228
-#: screens/Template/shared/JobTemplateForm.js:393
-#: screens/Template/shared/WorkflowJobTemplateForm.js:205
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:335
-msgid "Variables"
-msgstr "Variables"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:131
-msgid "Variables Prompted"
-msgstr "Variables Prompted"
-
-#: screens/Inventory/shared/Inventory.helptext.js:43
-msgid "Variables must be in JSON or YAML syntax. Use the radio button to toggle between the two."
-msgstr "Variables must be in JSON or YAML syntax. Use the radio button to toggle between the two."
-
-#: screens/Inventory/shared/Inventory.helptext.js:166
-msgid "Variables used to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>{sourceType}1> plugin configuration guide."
-msgstr "Variables used to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>{sourceType}1> plugin configuration guide."
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:121
-msgid "Vault password"
-msgstr "Vault password"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:121
-msgid "Vault password | {credId}"
-msgstr "Vault password | {credId}"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:132
-msgid "Verbose"
-msgstr "Verbose"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:63
-#: components/PromptDetail/PromptDetail.js:221
-#: components/PromptDetail/PromptInventorySourceDetail.js:111
-#: components/PromptDetail/PromptJobTemplateDetail.js:149
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:309
-#: components/VerbositySelectField/VerbositySelectField.js:47
-#: components/VerbositySelectField/VerbositySelectField.js:58
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:247
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:43
-#: screens/Job/JobDetail/JobDetail.js:326
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:264
-msgid "Verbosity"
-msgstr "Verbosity"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:64
-msgid "Version"
-msgstr "Version"
-
-#: screens/Setting/AzureAD/AzureAD.js:25
-msgid "View Azure AD settings"
-msgstr "View Azure AD settings"
-
-#: screens/Credential/Credential.js:143
-#: screens/Credential/Credential.js:155
-msgid "View Credential Details"
-msgstr "View Credential Details"
-
-#: components/Schedule/Schedule.js:151
-msgid "View Details"
-msgstr "View Details"
-
-#: screens/Setting/GitHub/GitHub.js:58
-msgid "View GitHub Settings"
-msgstr "View GitHub Settings"
-
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2.js:26
-msgid "View Google OAuth 2.0 settings"
-msgstr "View Google OAuth 2.0 settings"
-
-#: screens/Host/Host.js:137
-msgid "View Host Details"
-msgstr "View Host Details"
-
-#: screens/Instances/Instance.js:41
-msgid "View Instance Details"
-msgstr "View Instance Details"
-
-#: screens/Inventory/Inventory.js:192
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:142
-#: screens/Inventory/SmartInventory.js:175
-msgid "View Inventory Details"
-msgstr "View Inventory Details"
-
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:92
-msgid "View Inventory Groups"
-msgstr "View Inventory Groups"
-
-#: screens/Inventory/InventoryHost/InventoryHost.js:160
-msgid "View Inventory Host Details"
-msgstr "View Inventory Host Details"
-
-#: screens/Inventory/shared/Inventory.helptext.js:55
-msgid "View JSON examples at <0>www.json.org0>"
-msgstr "View JSON examples at <0>www.json.org0>"
-
-#: screens/Job/Job.js:183
-msgid "View Job Details"
-msgstr "View Job Details"
-
-#: screens/Setting/Jobs/Jobs.js:25
-msgid "View Jobs settings"
-msgstr "View Jobs settings"
-
-#: screens/Setting/LDAP/LDAP.js:38
-msgid "View LDAP Settings"
-msgstr "View LDAP Settings"
-
-#: screens/Setting/Logging/Logging.js:32
-msgid "View Logging settings"
-msgstr "View Logging settings"
-
-#: screens/Setting/MiscAuthentication/MiscAuthentication.js:32
-msgid "View Miscellaneous Authentication settings"
-msgstr "View Miscellaneous Authentication settings"
-
-#: screens/Setting/MiscSystem/MiscSystem.js:32
-msgid "View Miscellaneous System settings"
-msgstr "View Miscellaneous System settings"
-
-#: screens/Organization/Organization.js:225
-msgid "View Organization Details"
-msgstr "View Organization Details"
-
-#: screens/Project/Project.js:200
-msgid "View Project Details"
-msgstr "View Project Details"
-
-#: screens/Setting/RADIUS/RADIUS.js:25
-msgid "View RADIUS settings"
-msgstr "View RADIUS settings"
-
-#: screens/Setting/SAML/SAML.js:25
-msgid "View SAML settings"
-msgstr "View SAML settings"
-
-#: components/Schedule/Schedule.js:83
-#: components/Schedule/Schedule.js:101
-msgid "View Schedules"
-msgstr "View Schedules"
-
-#: screens/Setting/Subscription/Subscription.js:30
-msgid "View Settings"
-msgstr "View Settings"
-
-#: screens/Template/Template.js:159
-#: screens/Template/WorkflowJobTemplate.js:145
-msgid "View Survey"
-msgstr "View Survey"
-
-#: screens/Setting/TACACS/TACACS.js:25
-msgid "View TACACS+ settings"
-msgstr "View TACACS+ settings"
-
-#: screens/Team/Team.js:118
-msgid "View Team Details"
-msgstr "View Team Details"
-
-#: screens/Template/Template.js:260
-#: screens/Template/WorkflowJobTemplate.js:275
-msgid "View Template Details"
-msgstr "View Template Details"
-
-#: screens/User/UserToken/UserToken.js:100
-msgid "View Tokens"
-msgstr "View Tokens"
-
-#: screens/User/User.js:141
-msgid "View User Details"
-msgstr "View User Details"
-
-#: screens/Setting/UI/UI.js:26
-msgid "View User Interface settings"
-msgstr "View User Interface settings"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:102
-msgid "View Workflow Approval Details"
-msgstr "View Workflow Approval Details"
-
-#: screens/Inventory/shared/Inventory.helptext.js:66
-msgid "View YAML examples at <0>docs.ansible.com0>"
-msgstr "View YAML examples at <0>docs.ansible.com0>"
-
-#: components/ScreenHeader/ScreenHeader.js:65
-#: components/ScreenHeader/ScreenHeader.js:68
-msgid "View activity stream"
-msgstr "View activity stream"
-
-#: screens/Credential/Credential.js:99
-msgid "View all Credentials."
-msgstr "View all Credentials."
-
-#: screens/Host/Host.js:97
-msgid "View all Hosts."
-msgstr "View all Hosts."
-
-#: screens/Inventory/Inventory.js:95
-#: screens/Inventory/SmartInventory.js:95
-msgid "View all Inventories."
-msgstr "View all Inventories."
-
-#: screens/Inventory/InventoryHost/InventoryHost.js:101
-msgid "View all Inventory Hosts."
-msgstr "View all Inventory Hosts."
-
-#: screens/Job/JobTypeRedirect.js:40
-msgid "View all Jobs"
-msgstr "View all Jobs"
-
-#: screens/Job/Job.js:139
-msgid "View all Jobs."
-msgstr "View all Jobs."
-
-#: screens/NotificationTemplate/NotificationTemplate.js:60
-#: screens/NotificationTemplate/NotificationTemplateAdd.js:52
-msgid "View all Notification Templates."
-msgstr "View all Notification Templates."
-
-#: screens/Organization/Organization.js:155
-msgid "View all Organizations."
-msgstr "View all Organizations."
-
-#: screens/Project/Project.js:137
-msgid "View all Projects."
-msgstr "View all Projects."
-
-#: screens/Team/Team.js:76
-msgid "View all Teams."
-msgstr "View all Teams."
-
-#: screens/Template/Template.js:176
-#: screens/Template/WorkflowJobTemplate.js:176
-msgid "View all Templates."
-msgstr "View all Templates."
-
-#: screens/User/User.js:97
-msgid "View all Users."
-msgstr "View all Users."
-
-#: screens/WorkflowApproval/WorkflowApproval.js:54
-msgid "View all Workflow Approvals."
-msgstr "View all Workflow Approvals."
-
-#: screens/Application/Application/Application.js:96
-msgid "View all applications."
-msgstr "View all applications."
-
-#: screens/CredentialType/CredentialType.js:78
-msgid "View all credential types"
-msgstr "View all credential types"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:85
-msgid "View all execution environments"
-msgstr "View all execution environments"
-
-#: screens/InstanceGroup/ContainerGroup.js:86
-#: screens/InstanceGroup/InstanceGroup.js:94
-msgid "View all instance groups"
-msgstr "View all instance groups"
-
-#: screens/ManagementJob/ManagementJob.js:135
-msgid "View all management jobs"
-msgstr "View all management jobs"
-
-#: screens/Setting/Settings.js:197
-msgid "View all settings"
-msgstr "View all settings"
-
-#: screens/User/UserToken/UserToken.js:74
-msgid "View all tokens."
-msgstr "View all tokens."
-
-#: screens/Setting/SettingList.js:129
-msgid "View and edit your subscription information"
-msgstr "View and edit your subscription information"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:25
-#: screens/ActivityStream/ActivityStreamListItem.js:50
-msgid "View event details"
-msgstr "View event details"
-
-#: screens/Inventory/InventorySource/InventorySource.js:167
-msgid "View inventory source details"
-msgstr "View inventory source details"
-
-#: components/Sparkline/Sparkline.js:44
-msgid "View job {0}"
-msgstr "View job {0}"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:193
-msgid "View node details"
-msgstr "View node details"
-
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:85
-msgid "View smart inventory host details"
-msgstr "View smart inventory host details"
-
-#: routeConfig.js:30
-#: screens/ActivityStream/ActivityStream.js:145
-msgid "Views"
-msgstr ""
-
-#: components/TemplateList/TemplateListItem.js:198
-#: components/TemplateList/TemplateListItem.js:204
-#: screens/Template/WorkflowJobTemplate.js:137
-msgid "Visualizer"
-msgstr "Visualizer"
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:43
-msgid "WARNING:"
-msgstr "WARNING:"
-
-#: components/JobList/JobList.js:229
-#: components/StatusLabel/StatusLabel.js:44
-#: components/Workflow/WorkflowNodeHelp.js:96
-msgid "Waiting"
-msgstr "Waiting"
-
-#: screens/Job/JobOutput/EmptyOutput.js:23
-msgid "Waiting for job output…"
-msgstr "Waiting for job output…"
-
-#: components/Workflow/WorkflowLegend.js:118
-#: screens/Job/JobOutput/JobOutputSearch.js:133
-msgid "Warning"
-msgstr "Warning"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:14
-msgid "Warning: Unsaved Changes"
-msgstr "Warning: Unsaved Changes"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:119
-msgid "We were unable to locate licenses associated with this account."
-msgstr "We were unable to locate licenses associated with this account."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:138
-msgid "We were unable to locate subscriptions associated with this account."
-msgstr "We were unable to locate subscriptions associated with this account."
-
-#: components/DetailList/LaunchedByDetail.js:24
-#: components/NotificationList/NotificationList.js:202
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:143
-msgid "Webhook"
-msgstr "Webhook"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:172
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:101
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:326
-#: screens/Template/shared/WebhookSubForm.js:198
-msgid "Webhook Credential"
-msgstr "Webhook Credential"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:176
-msgid "Webhook Credentials"
-msgstr "Webhook Credentials"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:168
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:90
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:319
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:169
-#: screens/Template/shared/WebhookSubForm.js:172
-msgid "Webhook Key"
-msgstr "Webhook Key"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:161
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:89
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:304
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:157
-#: screens/Template/shared/WebhookSubForm.js:128
-msgid "Webhook Service"
-msgstr "Webhook Service"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:164
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:93
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:312
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:163
-#: screens/Template/shared/WebhookSubForm.js:160
-#: screens/Template/shared/WebhookSubForm.js:166
-msgid "Webhook URL"
-msgstr "Webhook URL"
-
-#: screens/Template/shared/JobTemplateForm.js:588
-#: screens/Template/shared/WorkflowJobTemplateForm.js:240
-msgid "Webhook details"
-msgstr "Webhook details"
-
-#: screens/Template/shared/JobTemplate.helptext.js:23
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:17
-msgid "Webhook services can launch jobs with this workflow job template by making a POST request to this URL."
-msgstr "Webhook services can launch jobs with this workflow job template by making a POST request to this URL."
-
-#: screens/Template/shared/JobTemplate.helptext.js:24
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:18
-msgid "Webhook services can use this as a shared secret."
-msgstr "Webhook services can use this as a shared secret."
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:78
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:41
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:140
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:63
-msgid "Webhooks"
-msgstr "Webhooks"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:24
-msgid "Webhooks: Enable Webhook for this workflow job template."
-msgstr "Webhooks: Enable Webhook for this workflow job template."
-
-#: screens/Template/shared/JobTemplate.helptext.js:39
-msgid "Webhooks: Enable webhook for this template."
-msgstr "Webhooks: Enable webhook for this template."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:281
-msgid "Wed"
-msgstr "Wed"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:286
-#: components/Schedule/shared/FrequencyDetailSubform.js:436
-msgid "Wednesday"
-msgstr "Wednesday"
-
-#: components/Schedule/shared/ScheduleForm.js:157
-msgid "Week"
-msgstr "Week"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:457
-msgid "Weekday"
-msgstr "Weekday"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:462
-msgid "Weekend day"
-msgstr "Weekend day"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:59
-msgid ""
-"Welcome to Red Hat Ansible Automation Platform!\n"
-"Please complete the steps below to activate your subscription."
-msgstr ""
-"Welcome to Red Hat Ansible Automation Platform!\n"
-"Please complete the steps below to activate your subscription."
-
-#: screens/Login/Login.js:168
-msgid "Welcome to {brandName}!"
-msgstr "Welcome to {brandName}!"
-
-#: screens/Inventory/shared/Inventory.helptext.js:105
-msgid ""
-"When not checked, a merge will be performed,\n"
-"combining local variables with those found on the\n"
-"external source."
-msgstr ""
-"When not checked, a merge will be performed,\n"
-"combining local variables with those found on the\n"
-"external source."
-
-#: screens/Inventory/shared/Inventory.helptext.js:93
-msgid ""
-"When not checked, local child\n"
-"hosts and groups not found on the external source will remain\n"
-"untouched by the inventory update process."
-msgstr ""
-"When not checked, local child\n"
-"hosts and groups not found on the external source will remain\n"
-"untouched by the inventory update process."
-
-#: components/Workflow/WorkflowLegend.js:96
-msgid "Workflow"
-msgstr "Workflow"
-
-#: components/Workflow/WorkflowNodeHelp.js:75
-msgid "Workflow Approval"
-msgstr "Workflow Approval"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:52
-msgid "Workflow Approval not found."
-msgstr "Workflow Approval not found."
-
-#: routeConfig.js:54
-#: screens/ActivityStream/ActivityStream.js:156
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:195
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:233
-#: screens/WorkflowApproval/WorkflowApprovals.js:13
-#: screens/WorkflowApproval/WorkflowApprovals.js:22
-msgid "Workflow Approvals"
-msgstr "Workflow Approvals"
-
-#: components/JobList/JobList.js:216
-#: components/JobList/JobListItem.js:47
-#: components/Schedule/ScheduleList/ScheduleListItem.js:40
-#: screens/Job/JobDetail/JobDetail.js:69
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:265
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:252
-msgid "Workflow Job"
-msgstr "Workflow Job"
-
-#: components/JobList/JobListItem.js:201
-#: components/Workflow/WorkflowNodeHelp.js:63
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:20
-#: screens/Job/JobDetail/JobDetail.js:224
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:91
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:227
-#: util/getRelatedResourceDeleteDetails.js:104
-msgid "Workflow Job Template"
-msgstr "Workflow Job Template"
-
-#: util/getRelatedResourceDeleteDetails.js:114
-#: util/getRelatedResourceDeleteDetails.js:156
-#: util/getRelatedResourceDeleteDetails.js:259
-msgid "Workflow Job Template Nodes"
-msgstr "Workflow Job Template Nodes"
-
-#: util/getRelatedResourceDeleteDetails.js:139
-msgid "Workflow Job Templates"
-msgstr "Workflow Job Templates"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:23
-msgid "Workflow Link"
-msgstr "Workflow Link"
-
-#: components/TemplateList/TemplateList.js:218
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:98
-msgid "Workflow Template"
-msgstr "Workflow Template"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:514
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:159
-msgid "Workflow approved message"
-msgstr "Workflow approved message"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:526
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:168
-msgid "Workflow approved message body"
-msgstr "Workflow approved message body"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:538
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:177
-msgid "Workflow denied message"
-msgstr "Workflow denied message"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:550
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:186
-msgid "Workflow denied message body"
-msgstr "Workflow denied message body"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:106
-msgid "Workflow documentation"
-msgstr "Workflow documentation"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:261
-msgid "Workflow job details"
-msgstr "Workflow job details"
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:46
-msgid "Workflow job templates"
-msgstr "Workflow job templates"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:24
-msgid "Workflow link modal"
-msgstr "Workflow link modal"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:228
-msgid "Workflow node view modal"
-msgstr "Workflow node view modal"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:562
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:195
-msgid "Workflow pending message"
-msgstr "Workflow pending message"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:574
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:204
-msgid "Workflow pending message body"
-msgstr "Workflow pending message body"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:586
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:213
-msgid "Workflow timed out message"
-msgstr "Workflow timed out message"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:598
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:222
-msgid "Workflow timed out message body"
-msgstr "Workflow timed out message body"
-
-#: screens/User/shared/UserTokenForm.js:77
-msgid "Write"
-msgstr "Write"
-
-#: screens/Inventory/shared/Inventory.helptext.js:52
-msgid "YAML:"
-msgstr "YAML:"
-
-#: components/Schedule/shared/ScheduleForm.js:159
-msgid "Year"
-msgstr "Year"
-
-#: components/Search/Search.js:229
-msgid "Yes"
-msgstr "Yes"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListApproveButton.js:28
-#~ msgid "You are unable to act on the following workflow approvals: {itemsUnableToApprove}"
-#~ msgstr "You are unable to act on the following workflow approvals: {itemsUnableToApprove}"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListDenyButton.js:28
-#~ msgid "You are unable to act on the following workflow approvals: {itemsUnableToDeny}"
-#~ msgstr "You are unable to act on the following workflow approvals: {itemsUnableToDeny}"
-
-#: components/Lookup/MultiCredentialsLookup.js:154
-msgid "You cannot select multiple vault credentials with the same vault ID. Doing so will automatically deselect the other with the same vault ID."
-msgstr "You cannot select multiple vault credentials with the same vault ID. Doing so will automatically deselect the other with the same vault ID."
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:96
-msgid "You do not have permission to delete the following Groups: {itemsUnableToDelete}"
-msgstr "You do not have permission to delete the following Groups: {itemsUnableToDelete}"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:152
-msgid "You do not have permission to delete {pluralizedItemName}: {itemsUnableToDelete}"
-msgstr "You do not have permission to delete {pluralizedItemName}: {itemsUnableToDelete}"
-
-#: components/DisassociateButton/DisassociateButton.js:66
-msgid "You do not have permission to disassociate the following: {itemsUnableToDisassociate}"
-msgstr "You do not have permission to disassociate the following: {itemsUnableToDisassociate}"
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:87
-msgid ""
-"You may apply a number of possible variables in the\n"
-"message. For more information, refer to the"
-msgstr ""
-"You may apply a number of possible variables in the\n"
-"message. For more information, refer to the"
-
-#: screens/Login/Login.js:176
-msgid "Your session has expired. Please log in to continue where you left off."
-msgstr "Your session has expired. Please log in to continue where you left off."
-
-#: components/AppContainer/AppContainer.js:130
-msgid "Your session is about to expire"
-msgstr "Your session is about to expire"
-
-#: components/Workflow/WorkflowTools.js:121
-msgid "Zoom In"
-msgstr "Zoom In"
-
-#: components/Workflow/WorkflowTools.js:100
-msgid "Zoom Out"
-msgstr "Zoom Out"
-
-#: screens/TopologyView/Header.js:51
-#: screens/TopologyView/Header.js:54
-msgid "Zoom in"
-msgstr "Zoom in"
-
-#: screens/TopologyView/Header.js:63
-#: screens/TopologyView/Header.js:66
-msgid "Zoom out"
-msgstr "Zoom out"
-
-#: screens/Template/shared/JobTemplateForm.js:685
-#: screens/Template/shared/WebhookSubForm.js:149
-msgid "a new webhook key will be generated on save."
-msgstr "a new webhook key will be generated on save."
-
-#: screens/Template/shared/JobTemplateForm.js:682
-#: screens/Template/shared/WebhookSubForm.js:139
-msgid "a new webhook url will be generated on save."
-msgstr "a new webhook url will be generated on save."
-
-#: screens/Template/Survey/SurveyListItem.js:157
-#~ msgid "actions"
-#~ msgstr "actions"
-
-#: screens/Inventory/shared/Inventory.helptext.js:123
-#: screens/Inventory/shared/Inventory.helptext.js:142
-msgid "and click on Update Revision on Launch"
-msgstr "and click on Update Revision on Launch"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:505
-msgid "approved"
-msgstr "approved"
-
-#: components/AppContainer/AppContainer.js:55
-msgid "brand logo"
-msgstr "brand logo"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:279
-#: screens/Template/Survey/SurveyList.js:72
-msgid "cancel delete"
-msgstr ""
-
-#: screens/Setting/shared/SharedFields.js:341
-msgid "cancel edit login redirect"
-msgstr "cancel edit login redirect"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:217
-msgid "command"
-msgstr "command"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:267
-#: screens/Template/Survey/SurveyList.js:63
-msgid "confirm delete"
-msgstr ""
-
-#: components/DisassociateButton/DisassociateButton.js:130
-#: screens/Team/TeamRoles/TeamRolesList.js:219
-msgid "confirm disassociate"
-msgstr "confirm disassociate"
-
-#: screens/Setting/shared/SharedFields.js:330
-msgid "confirm edit login redirect"
-msgstr "confirm edit login redirect"
-
-#: screens/TopologyView/ContentLoading.js:32
-msgid "content-loading-in-progress"
-msgstr "content-loading-in-progress"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:151
-msgid "deletion error"
-msgstr "deletion error"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:513
-msgid "denied"
-msgstr "denied"
-
-#: components/DisassociateButton/DisassociateButton.js:91
-msgid "disassociate"
-msgstr "disassociate"
-
-#: components/Lookup/HostFilterLookup.js:405
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:20
-#: screens/Template/Survey/SurveyQuestionForm.js:269
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:239
-msgid "documentation"
-msgstr "documentation"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:105
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:116
-#: screens/Host/HostDetail/HostDetail.js:102
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:97
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:109
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:100
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:305
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:163
-#: screens/Project/ProjectDetail/ProjectDetail.js:291
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:165
-#: screens/User/UserDetail/UserDetail.js:92
-msgid "edit"
-msgstr "edit"
-
-#: screens/Template/Survey/SurveyListItem.js:163
-#~ msgid "edit survey"
-#~ msgstr "edit survey"
-
-#: screens/Template/Survey/SurveyListItem.js:65
-#: screens/Template/Survey/SurveyReorderModal.js:125
-msgid "encrypted"
-msgstr "encrypted"
-
-#: components/Lookup/HostFilterLookup.js:407
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:241
-msgid "for more info."
-msgstr "for more info."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:21
-#: screens/Template/Survey/SurveyQuestionForm.js:271
-msgid "for more information."
-msgstr "for more information."
-
-#: screens/TopologyView/Legend.js:100
-msgid "h"
-msgstr "h"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:150
-msgid "here"
-msgstr "here"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:118
-#: components/AdHocCommands/AdHocDetailsStep.js:170
-#: screens/Job/Job.helptext.js:38
-msgid "here."
-msgstr "here."
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:49
-msgid "host-description-{0}"
-msgstr "host-description-{0}"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:44
-msgid "host-name-{0}"
-msgstr "host-name-{0}"
-
-#: components/Lookup/HostFilterLookup.js:417
-msgid "hosts"
-msgstr "hosts"
-
-#: components/Pagination/Pagination.js:24
-msgid "items"
-msgstr ""
-
-#: screens/User/UserList/UserListItem.js:44
-msgid "ldap user"
-msgstr "ldap user"
-
-#: screens/User/UserDetail/UserDetail.js:76
-msgid "login type"
-msgstr "login type"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:203
-msgid "min"
-msgstr "min"
-
-#: screens/Template/Survey/SurveyListItem.js:91
-#~ msgid "move down"
-#~ msgstr "move down"
-
-#: screens/Template/Survey/SurveyListItem.js:80
-#~ msgid "move up"
-#~ msgstr "move up"
-
-#: screens/Template/Survey/MultipleChoiceField.js:76
-msgid "new choice"
-msgstr "new choice"
-
-#: screens/TopologyView/Tooltip.js:94
-msgid "node"
-msgstr "node"
-
-#: components/Pagination/Pagination.js:36
-#: components/Schedule/shared/FrequencyDetailSubform.js:473
-msgid "of"
-msgstr "of"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:215
-msgid "option to the"
-msgstr "option to the"
-
-#: components/Pagination/Pagination.js:25
-msgid "page"
-msgstr "page"
-
-#: components/Pagination/Pagination.js:26
-msgid "pages"
-msgstr ""
-
-#: components/Pagination/Pagination.js:28
-msgid "per page"
-msgstr ""
-
-#: components/LaunchButton/ReLaunchDropDown.js:77
-#: components/LaunchButton/ReLaunchDropDown.js:100
-msgid "relaunch jobs"
-msgstr "relaunch jobs"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:217
-msgid "sec"
-msgstr "sec"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:253
-msgid "seconds"
-msgstr "seconds"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:58
-msgid "select module"
-msgstr "select module"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:127
-#~ msgid "select verbosity"
-#~ msgstr "select verbosity"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:135
-msgid "since"
-msgstr "since"
-
-#: screens/User/UserList/UserListItem.js:49
-msgid "social login"
-msgstr "social login"
-
-#: screens/Template/shared/JobTemplateForm.js:339
-#: screens/Template/shared/WorkflowJobTemplateForm.js:183
-msgid "source control branch"
-msgstr "source control branch"
-
-#: screens/ActivityStream/ActivityStreamListItem.js:30
-msgid "system"
-msgstr "system"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:511
-msgid "timed out"
-msgstr "timed out"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:195
-msgid "toggle changes"
-msgstr "toggle changes"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:516
-msgid "updated"
-msgstr "updated"
-
-#: screens/Template/shared/WebhookSubForm.js:180
-msgid "workflow job template webhook key"
-msgstr "workflow job template webhook key"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:65
-msgid "{0, plural, one {# source with sync failures.} other {# sources with sync failures.}}"
-msgstr "{0, plural, one {# source with sync failures.} other {# sources with sync failures.}}"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:115
-msgid "{0, plural, one {Are you sure you want delete the group below?} other {Are you sure you want delete the groups below?}}"
-msgstr "{0, plural, one {Are you sure you want delete the group below?} other {Are you sure you want delete the groups below?}}"
-
-#: components/HealthCheckButton/HealthCheckButton.js:23
-#~ msgid "{0, plural, one {Click to run a health check on the selected instance.} other {Click to run a health check on the selected instances.}}"
-#~ msgstr "{0, plural, one {Click to run a health check on the selected instance.} other {Click to run a health check on the selected instances.}}"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:86
-msgid "{0, plural, one {Delete Group?} other {Delete Groups?}}"
-msgstr "{0, plural, one {Delete Group?} other {Delete Groups?}}"
-
-#: util/validators.js:138
-msgid "{0, plural, one {Please enter a valid phone number.} other {Please enter valid phone numbers.}}"
-msgstr "{0, plural, one {Please enter a valid phone number.} other {Please enter valid phone numbers.}}"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:175
-#~ msgid "{0, plural, one {The following Instance Group cannot be deleted} other {The following Instance Groups cannot be deleted}}"
-#~ msgstr "{0, plural, one {The following Instance Group cannot be deleted} other {The following Instance Groups cannot be deleted}}"
-
-#: screens/Inventory/InventoryList/InventoryList.js:247
-msgid "{0, plural, one {The inventory will be in a pending status until the final delete is processed.} other {The inventories will be in a pending status until the final delete is processed.}}"
-msgstr "{0, plural, one {The inventory will be in a pending status until the final delete is processed.} other {The inventories will be in a pending status until the final delete is processed.}}"
-
-#: components/JobList/JobList.js:280
-msgid "{0, plural, one {The selected job cannot be deleted due to insufficient permission or a running job status} other {The selected jobs cannot be deleted due to insufficient permissions or a running job status}}"
-msgstr "{0, plural, one {The selected job cannot be deleted due to insufficient permission or a running job status} other {The selected jobs cannot be deleted due to insufficient permissions or a running job status}}"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:239
-msgid "{0, plural, one {This approval cannot be deleted due to insufficient permissions or a pending job status} other {These approvals cannot be deleted due to insufficient permissions or a pending job status}}"
-msgstr "{0, plural, one {This approval cannot be deleted due to insufficient permissions or a pending job status} other {These approvals cannot be deleted due to insufficient permissions or a pending job status}}"
-
-#: screens/Credential/CredentialList/CredentialList.js:198
-msgid "{0, plural, one {This credential is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these credentials could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This credential is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these credentials could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:164
-msgid "{0, plural, one {This credential type is currently being used by some credentials and cannot be deleted.} other {Credential types that are being used by credentials cannot be deleted. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This credential type is currently being used by some credentials and cannot be deleted.} other {Credential types that are being used by credentials cannot be deleted. Are you sure you want to delete anyway?}}"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:194
-msgid "{0, plural, one {This execution environment is currently being used by other resources. Are you sure you want to delete it?} other {These execution environments could be in use by other resources that rely on them. Are you sure you want to delete them anyway?}}"
-msgstr "{0, plural, one {This execution environment is currently being used by other resources. Are you sure you want to delete it?} other {These execution environments could be in use by other resources that rely on them. Are you sure you want to delete them anyway?}}"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:196
-msgid "{0, plural, one {This instance group is currently being by other resources. Are you sure you want to delete it?} other {Deleting these instance groups could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This instance group is currently being by other resources. Are you sure you want to delete it?} other {Deleting these instance groups could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Inventory/InventoryList/InventoryList.js:240
-msgid "{0, plural, one {This inventory is currently being used by some templates. Are you sure you want to delete it?} other {Deleting these inventories could impact some templates that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This inventory is currently being used by some templates. Are you sure you want to delete it?} other {Deleting these inventories could impact some templates that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:196
-msgid "{0, plural, one {This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?} other {Deleting these inventory sources could impact other resources that rely on them. Are you sure you want to delete anyway}}"
-msgstr "{0, plural, one {This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?} other {Deleting these inventory sources could impact other resources that rely on them. Are you sure you want to delete anyway}}"
-
-#: screens/Organization/OrganizationList/OrganizationList.js:166
-#~ msgid "{0, plural, one {This organization is currently being by other resources. Are you sure you want to delete it?} other {Deleting these organizations could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-#~ msgstr "{0, plural, one {This organization is currently being by other resources. Are you sure you want to delete it?} other {Deleting these organizations could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Organization/OrganizationList/OrganizationList.js:166
-msgid "{0, plural, one {This organization is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these organizations could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This organization is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these organizations could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Project/ProjectList/ProjectList.js:252
-msgid "{0, plural, one {This project is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these projects could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This project is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these projects could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:209
-#: components/TemplateList/TemplateList.js:266
-msgid "{0, plural, one {This template is currently being used by some workflow nodes. Are you sure you want to delete it?} other {Deleting these templates could impact some workflow nodes that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This template is currently being used by some workflow nodes. Are you sure you want to delete it?} other {Deleting these templates could impact some workflow nodes that rely on them. Are you sure you want to delete anyway?}}"
-
-#: components/JobList/JobListCancelButton.js:72
-msgid "{0, plural, one {You cannot cancel the following job because it is not running:} other {You cannot cancel the following jobs because they are not running:}}"
-msgstr "{0, plural, one {You cannot cancel the following job because it is not running:} other {You cannot cancel the following jobs because they are not running:}}"
-
-#: components/JobList/JobListCancelButton.js:56
-msgid "{0, plural, one {You do not have permission to cancel the following job:} other {You do not have permission to cancel the following jobs:}}"
-msgstr "{0, plural, one {You do not have permission to cancel the following job:} other {You do not have permission to cancel the following jobs:}}"
-
-#: screens/ActivityStream/ActivityStreamListItem.js:28
-msgid "{0} (deleted)"
-msgstr "{0} (deleted)"
-
-#: components/ChipGroup/ChipGroup.js:13
-msgid "{0} more"
-msgstr "{0} more"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:138
-#~ msgid "{0} since {1}"
-#~ msgstr "{0} since {1}"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:61
-#~ msgid "{0} sources with sync failures."
-#~ msgstr "{0} sources with sync failures."
-
-#: components/AppContainer/AppContainer.js:55
-msgid "{brandName} logo"
-msgstr "{brandName} logo"
-
-#: components/DetailList/UserDateDetail.js:23
-msgid "{dateStr} by <0>{username}0>"
-msgstr "{dateStr} by <0>{username}0>"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:224
-#: screens/InstanceGroup/Instances/InstanceListItem.js:148
-#: screens/Instances/InstanceDetail/InstanceDetail.js:174
-#: screens/Instances/InstanceList/InstanceListItem.js:158
-msgid "{forks, plural, one {# fork} other {# forks}}"
-msgstr "{forks, plural, one {# fork} other {# forks}}"
-
-#: components/HealthCheckButton/HealthCheckButton.js:15
-#~ msgid "{hopNodeSelected, plural, one {Cannot run health check on a hop node. Deselect the hop node to run a health check.} other {Cannot run health check on hop nodes. Deselect the hop nodes to run health checks.}}"
-#~ msgstr "{hopNodeSelected, plural, one {Cannot run health check on a hop node. Deselect the hop node to run a health check.} other {Cannot run health check on hop nodes. Deselect the hop nodes to run health checks.}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:193
-msgid "{intervalValue, plural, one {day} other {days}}"
-msgstr "{intervalValue, plural, one {day} other {days}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:191
-msgid "{intervalValue, plural, one {hour} other {hours}}"
-msgstr "{intervalValue, plural, one {hour} other {hours}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:189
-msgid "{intervalValue, plural, one {minute} other {minutes}}"
-msgstr "{intervalValue, plural, one {minute} other {minutes}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:197
-msgid "{intervalValue, plural, one {month} other {months}}"
-msgstr "{intervalValue, plural, one {month} other {months}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:195
-msgid "{intervalValue, plural, one {week} other {weeks}}"
-msgstr "{intervalValue, plural, one {week} other {weeks}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:199
-msgid "{intervalValue, plural, one {year} other {years}}"
-msgstr "{intervalValue, plural, one {year} other {years}}"
-
-#: components/Schedule/shared/DateTimePicker.js:49
-#~ msgid "{label} date"
-#~ msgstr "{label} date"
-
-#: components/Schedule/shared/DateTimePicker.js:57
-#~ msgid "{label} time"
-#~ msgstr "{label} time"
-
-#: components/PromptDetail/PromptDetail.js:41
-msgid "{minutes} min {seconds} sec"
-msgstr "{minutes} min {seconds} sec"
-
-#: components/JobList/JobListCancelButton.js:106
-msgid "{numJobsToCancel, plural, one {Cancel job} other {Cancel jobs}}"
-msgstr "{numJobsToCancel, plural, one {Cancel job} other {Cancel jobs}}"
-
-#: components/JobList/JobListCancelButton.js:168
-msgid "{numJobsToCancel, plural, one {This action will cancel the following job:} other {This action will cancel the following jobs:}}"
-msgstr "{numJobsToCancel, plural, one {This action will cancel the following job:} other {This action will cancel the following jobs:}}"
-
-#: components/JobList/JobListCancelButton.js:91
-msgid "{numJobsToCancel, plural, one {{0}} other {{1}}}"
-msgstr "{numJobsToCancel, plural, one {{0}} other {{1}}}"
-
-#: components/DetailList/NumberSinceDetail.js:19
-#~ msgid "{number} since {dateStr}"
-#~ msgstr "{number} since {dateStr}"
-
-#: components/PaginatedTable/PaginatedTable.js:79
-msgid "{pluralizedItemName} List"
-msgstr "{pluralizedItemName} List"
-
-#: components/HealthCheckButton/HealthCheckButton.js:13
-msgid "{selectedItemsCount, plural, one {Click to run a health check on the selected instance.} other {Click to run a health check on the selected instances.}}"
-msgstr "{selectedItemsCount, plural, one {Click to run a health check on the selected instance.} other {Click to run a health check on the selected instances.}}"
-
-#: components/AppContainer/AppContainer.js:154
-msgid "{sessionCountdown, plural, one {You will be logged out in # second due to inactivity} other {You will be logged out in # seconds due to inactivity}}"
-msgstr "{sessionCountdown, plural, one {You will be logged out in # second due to inactivity} other {You will be logged out in # seconds due to inactivity}}"
diff --git a/awx/ui/src/locales/es/messages.po b/awx/ui/src/locales/es/messages.po
deleted file mode 100644
index 3521e3b94d2c..000000000000
--- a/awx/ui/src/locales/es/messages.po
+++ /dev/null
@@ -1,10832 +0,0 @@
-msgid ""
-msgstr ""
-"POT-Creation-Date: 2018-12-10 10:08-0500\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: @lingui/cli\n"
-"Language: en\n"
-"Project-Id-Version: \n"
-"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: \n"
-"Last-Translator: \n"
-"Language-Team: \n"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:43
-msgid "(Limited to first 10)"
-msgstr "(Limitado a los primeros 10)"
-
-#: components/TemplateList/TemplateListItem.js:103
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:161
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:90
-msgid "(Prompt on launch)"
-msgstr "(Preguntar al ejecutar)"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:283
-msgid "* This field will be retrieved from an external secret management system using the specified credential."
-msgstr "* Este campo se recuperará de un sistema de gestión de claves secretas externo con la credencial especificada."
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:228
-msgid "/ (project root)"
-msgstr "/ (raíz del proyecto)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:10
-msgid "0 (Normal)"
-msgstr "0 (Normal)"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:38
-msgid "0 (Warning)"
-msgstr "0 (Advertencia)"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:39
-msgid "1 (Info)"
-msgstr "1 (Información)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:11
-msgid "1 (Verbose)"
-msgstr "1 (Nivel de detalle)"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:40
-msgid "2 (Debug)"
-msgstr "2 (Depurar)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:12
-msgid "2 (More Verbose)"
-msgstr "2 (Más nivel de detalle)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:13
-msgid "3 (Debug)"
-msgstr "3 (Depurar)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:14
-msgid "4 (Connection Debug)"
-msgstr "4 (Depuración de la conexión)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:15
-msgid "5 (WinRM Debug)"
-msgstr "5 (Depuración de WinRM)"
-
-#: screens/Project/shared/Project.helptext.js:76
-msgid ""
-"A refspec to fetch (passed to the Ansible git\n"
-"module). This parameter allows access to references via\n"
-"the branch field not otherwise available."
-msgstr "Un refspec para extraer (pasado al módulo git de Ansible). Este parámetro permite el acceso a las referencias a través del campo de rama no disponible de otra manera."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:122
-msgid "A subscription manifest is an export of a Red Hat Subscription. To generate a subscription manifest, go to <0>access.redhat.com0>. For more information, see the <1>User Guide1>."
-msgstr "Un manifiesto de suscripción es una exportación de una suscripción de Red Hat. Para generar un manifiesto de suscripción, vaya a <0>access.redhat.com0>. Para más información, consulte el <1>Manual del usuario1>."
-
-#: screens/Job/WorkflowOutput/WorkflowOutputNode.js:143
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:326
-msgid "ALL"
-msgstr "TODOS"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:284
-msgid "API Service/Integration Key"
-msgstr "Servicio API/Clave de integración"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:291
-msgid "API Token"
-msgstr "Token API"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:306
-msgid "API service/integration key"
-msgstr "Servicio API/clave de integración"
-
-#: components/AppContainer/PageHeaderToolbar.js:125
-msgid "About"
-msgstr "Acerca de"
-
-#: routeConfig.js:92
-#: screens/ActivityStream/ActivityStream.js:179
-#: screens/Credential/Credential.js:74
-#: screens/Credential/Credentials.js:29
-#: screens/Inventory/Inventories.js:59
-#: screens/Inventory/Inventory.js:65
-#: screens/Inventory/SmartInventory.js:67
-#: screens/Organization/Organization.js:124
-#: screens/Organization/Organizations.js:31
-#: screens/Project/Project.js:105
-#: screens/Project/Projects.js:27
-#: screens/Team/Team.js:58
-#: screens/Team/Teams.js:31
-#: screens/Template/Template.js:136
-#: screens/Template/Templates.js:45
-#: screens/Template/WorkflowJobTemplate.js:118
-msgid "Access"
-msgstr "Acceso"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:75
-msgid "Access Token Expiration"
-msgstr "Expiración del token de acceso"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:352
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:421
-msgid "Account SID"
-msgstr "Cuenta SID"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:396
-msgid "Account token"
-msgstr "Cuenta token"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:50
-msgid "Action"
-msgstr "Acción"
-
-#: components/JobList/JobList.js:249
-#: components/JobList/JobListItem.js:103
-#: components/RelatedTemplateList/RelatedTemplateList.js:189
-#: components/Schedule/ScheduleList/ScheduleList.js:172
-#: components/Schedule/ScheduleList/ScheduleListItem.js:128
-#: components/SelectedList/DraggableSelectedList.js:101
-#: components/TemplateList/TemplateList.js:246
-#: components/TemplateList/TemplateListItem.js:195
-#: screens/ActivityStream/ActivityStream.js:266
-#: screens/ActivityStream/ActivityStreamListItem.js:49
-#: screens/Application/ApplicationsList/ApplicationListItem.js:48
-#: screens/Application/ApplicationsList/ApplicationsList.js:160
-#: screens/Credential/CredentialList/CredentialList.js:166
-#: screens/Credential/CredentialList/CredentialListItem.js:66
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:177
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:38
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:168
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:87
-#: screens/Host/HostGroups/HostGroupItem.js:34
-#: screens/Host/HostGroups/HostGroupsList.js:177
-#: screens/Host/HostList/HostList.js:172
-#: screens/Host/HostList/HostListItem.js:70
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:200
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:75
-#: screens/InstanceGroup/Instances/InstanceList.js:271
-#: screens/InstanceGroup/Instances/InstanceListItem.js:171
-#: screens/Instances/InstanceList/InstanceList.js:206
-#: screens/Instances/InstanceList/InstanceListItem.js:183
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:218
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:53
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:39
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:142
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:41
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:187
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:44
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:140
-#: screens/Inventory/InventoryList/InventoryList.js:222
-#: screens/Inventory/InventoryList/InventoryListItem.js:131
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:233
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:44
-#: screens/Inventory/InventorySources/InventorySourceList.js:214
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:101
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:102
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:73
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:181
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:124
-#: screens/Organization/OrganizationList/OrganizationList.js:146
-#: screens/Organization/OrganizationList/OrganizationListItem.js:69
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:86
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:19
-#: screens/Project/ProjectList/ProjectList.js:225
-#: screens/Project/ProjectList/ProjectListItem.js:222
-#: screens/Team/TeamList/TeamList.js:144
-#: screens/Team/TeamList/TeamListItem.js:47
-#: screens/Template/Survey/SurveyList.js:105
-#: screens/Template/Survey/SurveyList.js:105
-#: screens/Template/Survey/SurveyListItem.js:90
-#: screens/User/UserList/UserList.js:164
-#: screens/User/UserList/UserListItem.js:56
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:167
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:78
-msgid "Actions"
-msgstr "Acciones"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:98
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:61
-#: components/TemplateList/TemplateListItem.js:277
-#: screens/Host/HostDetail/HostDetail.js:71
-#: screens/Host/HostList/HostListItem.js:95
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:217
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:50
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:76
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:100
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:32
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:115
-msgid "Activity"
-msgstr "Actividad"
-
-#: routeConfig.js:49
-#: screens/ActivityStream/ActivityStream.js:43
-#: screens/ActivityStream/ActivityStream.js:121
-#: screens/Setting/Settings.js:44
-msgid "Activity Stream"
-msgstr "Flujo de actividad"
-
-#: screens/ActivityStream/ActivityStream.js:124
-msgid "Activity Stream type selector"
-msgstr "Selector de tipo de flujo de actividad"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:169
-msgid "Actor"
-msgstr "Actor"
-
-#: components/AddDropDownButton/AddDropDownButton.js:40
-#: components/PaginatedTable/ToolbarAddButton.js:14
-msgid "Add"
-msgstr "Añadir"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkAddModal.js:14
-msgid "Add Link"
-msgstr "Agregar enlace"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.js:78
-msgid "Add Node"
-msgstr "Agregar nodo"
-
-#: screens/Template/Templates.js:49
-msgid "Add Question"
-msgstr "Agregar pregunta"
-
-#: components/AddRole/AddResourceRole.js:164
-msgid "Add Roles"
-msgstr "Agregar roles"
-
-#: components/AddRole/AddResourceRole.js:161
-msgid "Add Team Roles"
-msgstr "Agregar roles de equipo"
-
-#: components/AddRole/AddResourceRole.js:158
-msgid "Add User Roles"
-msgstr "Agregar roles de usuario"
-
-#: components/Workflow/WorkflowStartNode.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:238
-msgid "Add a new node"
-msgstr "Agregar un nuevo nodo"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:49
-msgid "Add a new node between these two nodes"
-msgstr "Agregar un nuevo nodo entre estos dos nodos"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:108
-msgid "Add container group"
-msgstr "Agregar grupo de contenedores"
-
-#: components/Schedule/shared/ScheduleFormFields.js:171
-msgid "Add exceptions"
-msgstr "Añadir excepciones"
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:140
-msgid "Add existing group"
-msgstr "Agregar grupo existente"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:152
-msgid "Add existing host"
-msgstr "Agregar host existente"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:109
-msgid "Add instance group"
-msgstr "Agregar grupo de instancias"
-
-#: screens/Inventory/InventoryList/InventoryList.js:136
-msgid "Add inventory"
-msgstr "Agregar inventario"
-
-#: components/TemplateList/TemplateList.js:151
-msgid "Add job template"
-msgstr "Agregar plantilla de trabajo"
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:141
-msgid "Add new group"
-msgstr "Agregar nuevo grupo"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:153
-msgid "Add new host"
-msgstr "Agregar nuevo host"
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:77
-msgid "Add resource type"
-msgstr "Agregar tipo de recurso"
-
-#: screens/Inventory/InventoryList/InventoryList.js:137
-msgid "Add smart inventory"
-msgstr "Agregar inventario inteligente"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:202
-msgid "Add team permissions"
-msgstr "Agregar permisos de equipo"
-
-#: screens/User/UserRoles/UserRolesList.js:198
-msgid "Add user permissions"
-msgstr "Agregar permisos de usuario"
-
-#: components/TemplateList/TemplateList.js:152
-msgid "Add workflow template"
-msgstr "Agregar plantilla de flujo de trabajo"
-
-#: screens/TopologyView/Legend.js:269
-msgid "Adding"
-msgstr "Añadiendo"
-
-#: routeConfig.js:113
-#: screens/ActivityStream/ActivityStream.js:190
-msgid "Administration"
-msgstr "Administración"
-
-#: components/DataListToolbar/DataListToolbar.js:139
-#: screens/Job/JobOutput/JobOutputSearch.js:136
-msgid "Advanced"
-msgstr "Avanzado"
-
-#: components/Search/AdvancedSearch.js:318
-msgid "Advanced search documentation"
-msgstr "Documentación de búsqueda avanzada"
-
-#: components/Search/AdvancedSearch.js:211
-#: components/Search/AdvancedSearch.js:225
-msgid "Advanced search value input"
-msgstr "Entrada de valores de búsqueda avanzada"
-
-#: screens/Inventory/shared/Inventory.helptext.js:131
-msgid ""
-"After every project update where the SCM revision\n"
-"changes, refresh the inventory from the selected source\n"
-"before executing job tasks. This is intended for static content,\n"
-"like the Ansible inventory .ini file format."
-msgstr "Después de cada actualización del proyecto en el que se modifique la revisión SCM, actualice el inventario del origen seleccionado antes de llevar a cabo tareas. Esto está orientado a contenido estático, como el formato de archivo .ini del inventario Ansible."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:524
-msgid "After number of occurrences"
-msgstr "Después del número de ocurrencias"
-
-#: components/AlertModal/AlertModal.js:75
-msgid "Alert modal"
-msgstr "Modal de alerta"
-
-#: components/LaunchButton/ReLaunchDropDown.js:48
-#: components/PromptDetail/PromptDetail.js:132
-#: screens/Metrics/Metrics.js:82
-#: screens/Metrics/Metrics.js:82
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:266
-msgid "All"
-msgstr "Todos"
-
-#: screens/Dashboard/DashboardGraph.js:137
-msgid "All job types"
-msgstr "Todos los tipos de tarea"
-
-#: screens/Dashboard/DashboardGraph.js:162
-msgid "All jobs"
-msgstr "Todas las tareas"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:101
-msgid "Allow Branch Override"
-msgstr "Permitir la anulación de la rama"
-
-#: components/PromptDetail/PromptProjectDetail.js:66
-#: screens/Project/ProjectDetail/ProjectDetail.js:121
-msgid "Allow branch override"
-msgstr "Permitir la invalidación de la rama"
-
-#: screens/Project/shared/Project.helptext.js:126
-msgid ""
-"Allow changing the Source Control branch or revision in a job\n"
-"template that uses this project."
-msgstr "Permitir el cambio de la rama o revisión de la fuente de control\n"
-"en una plantilla de trabajo que utilice este proyecto."
-
-#: screens/Application/shared/Application.helptext.js:6
-msgid "Allowed URIs list, space separated"
-msgstr "Lista de URI permitidos, separados por espacios"
-
-#: components/Workflow/WorkflowLegend.js:130
-#: components/Workflow/WorkflowLinkHelp.js:24
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:58
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:46
-msgid "Always"
-msgstr "Siempre"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:98
-msgid "Amazon EC2"
-msgstr "Amazon EC2"
-
-#: components/Lookup/shared/LookupErrorMessage.js:12
-msgid "An error occurred"
-msgstr "Se ha producido un error"
-
-#: components/LaunchPrompt/steps/useInventoryStep.js:35
-msgid "An inventory must be selected"
-msgstr "Debe seleccionar un inventario"
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:96
-msgid "Ansible Controller Documentation."
-msgstr "Documentación del controlador Ansible."
-
-#: screens/Template/Survey/SurveyQuestionForm.js:43
-msgid "Answer type"
-msgstr "Tipo de respuesta"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:177
-msgid "Answer variable name"
-msgstr "Nombre de la variable de respuesta"
-
-#: components/PromptDetail/PromptDetail.js:132
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:263
-msgid "Any"
-msgstr "Cualquiera"
-
-#: components/Lookup/ApplicationLookup.js:83
-#: screens/User/UserTokenDetail/UserTokenDetail.js:39
-#: screens/User/shared/UserTokenForm.js:48
-msgid "Application"
-msgstr "Aplicación"
-
-#: screens/User/UserTokenList/UserTokenList.js:187
-msgid "Application Name"
-msgstr "Nombre de la aplicación"
-
-#: screens/Application/Applications.js:67
-#: screens/Application/Applications.js:70
-msgid "Application information"
-msgstr "Información de la aplicación"
-
-#: screens/User/UserTokenList/UserTokenList.js:123
-#: screens/User/UserTokenList/UserTokenList.js:134
-msgid "Application name"
-msgstr "Nombre de la aplicación"
-
-#: screens/Application/Application/Application.js:95
-msgid "Application not found."
-msgstr "No se encontró la aplicación."
-
-#: components/Lookup/ApplicationLookup.js:96
-#: routeConfig.js:142
-#: screens/Application/Applications.js:26
-#: screens/Application/Applications.js:35
-#: screens/Application/ApplicationsList/ApplicationsList.js:113
-#: screens/Application/ApplicationsList/ApplicationsList.js:148
-#: util/getRelatedResourceDeleteDetails.js:209
-msgid "Applications"
-msgstr "Aplicaciones"
-
-#: screens/ActivityStream/ActivityStream.js:211
-msgid "Applications & Tokens"
-msgstr "Aplicaciones y tokens"
-
-#: components/NotificationList/NotificationListItem.js:39
-#: components/NotificationList/NotificationListItem.js:40
-#: components/Workflow/WorkflowLegend.js:114
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:67
-msgid "Approval"
-msgstr "Aprobación"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:82
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:44
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:47
-msgid "Approve"
-msgstr "Aprobar"
-
-#: components/StatusLabel/StatusLabel.js:39
-msgid "Approved"
-msgstr "Aprobado"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:11
-msgid "Approved - {0}. See the Activity Stream for more information."
-msgstr "Aprobado - {0}. Consulte el flujo de actividades para obtener más información."
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:7
-msgid "Approved by {0} - {1}"
-msgstr "Aprobado por {0} - {1}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:162
-#: components/Schedule/shared/FrequencyDetailSubform.js:113
-msgid "April"
-msgstr "Abril"
-
-#: components/JobCancelButton/JobCancelButton.js:104
-msgid "Are you sure you want to cancel this job?"
-msgstr "¿Está seguro de que desea cancelar esta tarea?"
-
-#: components/DeleteButton/DeleteButton.js:127
-msgid "Are you sure you want to delete:"
-msgstr "¿Está seguro de que desea eliminar:"
-
-#: screens/Setting/shared/SharedFields.js:142
-msgid "Are you sure you want to disable local authentication? Doing so could impact users' ability to log in and the system administrator's ability to reverse this change."
-msgstr "¿Está seguro de que desea deshabilitar la autenticación local? Esto podría afectar la capacidad de los usuarios para iniciar sesión y la capacidad del administrador del sistema para revertir este cambio."
-
-#: screens/Setting/shared/SharedFields.js:350
-msgid "Are you sure you want to edit login redirect override URL? Doing so could impact users' ability to log in to the system once local authentication is also disabled."
-msgstr "¿Está seguro de que quiere editar la URL de redirección de inicio de sesión? Hacerlo podría afectar a la capacidad de los usuarios para iniciar sesión en el sistema una vez que la autenticación local también esté desactivada."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:41
-msgid "Are you sure you want to exit the Workflow Creator without saving your changes?"
-msgstr "¿Está seguro de que desea salir del Creador de flujo de trabajo sin guardar los cambios?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:40
-msgid "Are you sure you want to remove all the nodes in this workflow?"
-msgstr "¿Está seguro de que desea eliminar todos los nodos de este flujo de trabajo?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:56
-msgid "Are you sure you want to remove the node below:"
-msgstr "¿Está seguro de que desea eliminar el siguiente nodo:"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:43
-msgid "Are you sure you want to remove this link?"
-msgstr "¿Está seguro de que desea eliminar este enlace?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:63
-msgid "Are you sure you want to remove this node?"
-msgstr "¿Está seguro de que desea eliminar este nodo?"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:43
-msgid "Are you sure you want to remove {0} access from {1}? Doing so affects all members of the team."
-msgstr "¿Está seguro de que desea eliminar el acceso de {0} a {1}? Esto afecta a todos los miembros del equipo."
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:50
-msgid "Are you sure you want to remove {0} access from {username}?"
-msgstr "¿Está seguro de que quiere eliminar el acceso de {0} a {username}?"
-
-#: screens/Job/JobOutput/JobOutput.js:826
-msgid "Are you sure you want to submit the request to cancel this job?"
-msgstr "¿Está seguro de que desea enviar la solicitud para cancelar este trabajo?"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:102
-#: components/AdHocCommands/AdHocDetailsStep.js:104
-msgid "Arguments"
-msgstr "Argumentos"
-
-#: screens/Job/JobDetail/JobDetail.js:559
-msgid "Artifacts"
-msgstr "Artefactos"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:233
-#: screens/User/UserTeams/UserTeamList.js:208
-msgid "Associate"
-msgstr "Asociar"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:244
-#: screens/User/UserRoles/UserRolesList.js:240
-msgid "Associate role error"
-msgstr "Asociar error del rol"
-
-#: components/AssociateModal/AssociateModal.js:98
-msgid "Association modal"
-msgstr "Modal de asociación"
-
-#: components/LaunchPrompt/steps/SurveyStep.js:168
-msgid "At least one value must be selected for this field."
-msgstr "Debe seleccionar al menos un valor para este campo."
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:166
-#: components/Schedule/shared/FrequencyDetailSubform.js:133
-msgid "August"
-msgstr "Agosto"
-
-#: screens/Setting/SettingList.js:52
-msgid "Authentication"
-msgstr "Identificación"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:88
-msgid "Authorization Code Expiration"
-msgstr "Expiración del código de autorización"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:81
-#: screens/Application/shared/ApplicationForm.js:85
-msgid "Authorization grant type"
-msgstr "Tipo de autorización"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:215
-#: screens/InstanceGroup/Instances/InstanceListItem.js:204
-#: screens/Instances/InstanceDetail/InstanceDetail.js:209
-#: screens/Instances/InstanceList/InstanceListItem.js:219
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:83
-msgid "Auto"
-msgstr "Auto"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:71
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:72
-msgid "Automation Analytics"
-msgstr "Automation Analytics"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:111
-msgid "Automation Analytics dashboard"
-msgstr "Panel de control de Automation Analytics"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:175
-msgid "Automation controller version"
-msgstr "Versión del controlador de automatización"
-
-#: screens/Setting/Settings.js:47
-msgid "Azure AD"
-msgstr "Azure AD"
-
-#: screens/Setting/SettingList.js:57
-msgid "Azure AD settings"
-msgstr "Configuración de Azure AD"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:49
-#: components/AddRole/AddResourceRole.js:267
-#: components/LaunchPrompt/LaunchPrompt.js:158
-#: components/Schedule/shared/SchedulePromptableFields.js:125
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:90
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:70
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:154
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:157
-msgid "Back"
-msgstr "Volver"
-
-#: screens/Credential/Credential.js:65
-msgid "Back to Credentials"
-msgstr "Volver a Credenciales"
-
-#: components/ContentError/ContentError.js:43
-msgid "Back to Dashboard."
-msgstr "Volver al panel de control."
-
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:49
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:50
-msgid "Back to Groups"
-msgstr "Volver a Grupos"
-
-#: screens/Host/Host.js:50
-#: screens/Inventory/InventoryHost/InventoryHost.js:66
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:53
-msgid "Back to Hosts"
-msgstr "Volver a Hosts"
-
-#: screens/InstanceGroup/InstanceGroup.js:61
-msgid "Back to Instance Groups"
-msgstr "Volver a los grupos de instancias"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:173
-#: screens/Instances/Instance.js:22
-msgid "Back to Instances"
-msgstr "Volver a las instancias"
-
-#: screens/Inventory/Inventory.js:57
-#: screens/Inventory/SmartInventory.js:60
-msgid "Back to Inventories"
-msgstr "Volver a Inventarios"
-
-#: screens/Job/Job.js:123
-msgid "Back to Jobs"
-msgstr "Volver a Tareas"
-
-#: screens/NotificationTemplate/NotificationTemplate.js:76
-msgid "Back to Notifications"
-msgstr "Volver a Notificaciones"
-
-#: screens/Organization/Organization.js:116
-msgid "Back to Organizations"
-msgstr "Volver a Organizaciones"
-
-#: screens/Project/Project.js:97
-msgid "Back to Projects"
-msgstr "Volver a Proyectos"
-
-#: components/Schedule/Schedule.js:64
-msgid "Back to Schedules"
-msgstr "Volver a Programaciones"
-
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:44
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:78
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:44
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:58
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:95
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:69
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:43
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:90
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:44
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:49
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:45
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:34
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:49
-#: screens/Setting/UI/UIDetail/UIDetail.js:59
-msgid "Back to Settings"
-msgstr "Volver a Configuración"
-
-#: screens/Inventory/InventorySource/InventorySource.js:76
-msgid "Back to Sources"
-msgstr "Volver a Fuentes"
-
-#: screens/Team/Team.js:50
-msgid "Back to Teams"
-msgstr "Volver a Equipos"
-
-#: screens/Template/Template.js:128
-#: screens/Template/WorkflowJobTemplate.js:110
-msgid "Back to Templates"
-msgstr "Volver a Plantillas"
-
-#: screens/User/UserToken/UserToken.js:47
-msgid "Back to Tokens"
-msgstr "Volver a Tokens"
-
-#: screens/User/User.js:57
-msgid "Back to Users"
-msgstr "Volver a Usuarios"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:69
-msgid "Back to Workflow Approvals"
-msgstr "Volver a Aprobaciones del flujo de trabajo"
-
-#: screens/Application/Application/Application.js:72
-msgid "Back to applications"
-msgstr "Volver a las aplicaciones"
-
-#: screens/CredentialType/CredentialType.js:55
-msgid "Back to credential types"
-msgstr "Volver a los tipos de credenciales"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:57
-msgid "Back to execution environments"
-msgstr "Volver a los entornos de ejecución"
-
-#: screens/InstanceGroup/ContainerGroup.js:59
-msgid "Back to instance groups"
-msgstr "Volver a los grupos de instancias"
-
-#: screens/ManagementJob/ManagementJob.js:98
-msgid "Back to management jobs"
-msgstr "Volver a las tareas de gestión"
-
-#: screens/Project/shared/Project.helptext.js:8
-msgid ""
-"Base path used for locating playbooks. Directories\n"
-"found inside this path will be listed in the playbook directory drop-down.\n"
-"Together the base path and selected playbook directory provide the full\n"
-"path used to locate playbooks."
-msgstr "Directorio base utilizado para encontrar playbooks. Los directorios encontrados dentro de esta ruta se mostrarán en el menú desplegable del directorio de playbooks. Junto a la ruta base y el directorio de playbooks seleccionado, se creará la ruta completa utilizada para encontrar los playbooks."
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:448
-msgid "Basic auth password"
-msgstr "Contraseña de autenticación básica"
-
-#: screens/Project/shared/Project.helptext.js:104
-msgid ""
-"Branch to checkout. In addition to branches,\n"
-"you can input tags, commit hashes, and arbitrary refs. Some\n"
-"commit hashes and refs may not be available unless you also\n"
-"provide a custom refspec."
-msgstr "Rama para realizar la comprobación. Además de las ramas, puede\n"
-"introducir etiquetas, hashes de commit y referencias arbitrarias. Es posible\n"
-"que algunos hashes y referencias de commit no estén disponibles,\n"
-"a menos que usted también proporcione un refspec personalizado."
-
-#: screens/Template/shared/JobTemplate.helptext.js:27
-msgid "Branch to use in job run. Project default used if blank. Only allowed if project allow_override field is set to true."
-msgstr "Rama para usar en la ejecución del trabajo. Se utiliza el proyecto predeterminado si está en blanco. Solo se permite si el campo allow_override del proyecto se establece en true."
-
-#: components/About/About.js:45
-msgid "Brand Image"
-msgstr "Imagen de marca"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:158
-msgid "Browse"
-msgstr "Navegar"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:94
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:115
-msgid "Browse…"
-msgstr "Navegar"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:36
-msgid "By default, we collect and transmit analytics data on the service usage to Red Hat. There are two categories of data collected by the service. For more information, see <0>this Tower documentation page0>. Uncheck the following boxes to disable this feature."
-msgstr "Por defecto, recopilamos y transmitimos a Red Hat datos analíticos sobre el uso del servicio. Hay dos categorías de datos recogidos por el servicio. Para más información, consulte <0>esta página de documentación de Tower0>. Desmarque las siguientes casillas para desactivar esta función."
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:228
-#: screens/InstanceGroup/Instances/InstanceListItem.js:145
-#: screens/Instances/InstanceDetail/InstanceDetail.js:271
-#: screens/Instances/InstanceList/InstanceListItem.js:155
-#: screens/TopologyView/Tooltip.js:285
-msgid "CPU {0}"
-msgstr "CPU {0}"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:102
-#: components/PromptDetail/PromptProjectDetail.js:151
-#: screens/Project/ProjectDetail/ProjectDetail.js:268
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:118
-msgid "Cache Timeout"
-msgstr "Tiempo de espera de la caché"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:237
-msgid "Cache timeout"
-msgstr "Tiempo de espera de la caché"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:106
-msgid "Cache timeout (seconds)"
-msgstr "Tiempo de espera de la caché (segundos)"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:50
-#: components/AddRole/AddResourceRole.js:268
-#: components/AssociateModal/AssociateModal.js:114
-#: components/AssociateModal/AssociateModal.js:119
-#: components/DeleteButton/DeleteButton.js:120
-#: components/DeleteButton/DeleteButton.js:123
-#: components/DisassociateButton/DisassociateButton.js:139
-#: components/DisassociateButton/DisassociateButton.js:142
-#: components/FormActionGroup/FormActionGroup.js:23
-#: components/FormActionGroup/FormActionGroup.js:29
-#: components/LaunchPrompt/LaunchPrompt.js:159
-#: components/Lookup/HostFilterLookup.js:388
-#: components/Lookup/Lookup.js:209
-#: components/PaginatedTable/ToolbarDeleteButton.js:282
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:37
-#: components/Schedule/shared/ScheduleForm.js:548
-#: components/Schedule/shared/ScheduleForm.js:553
-#: components/Schedule/shared/SchedulePromptableFields.js:126
-#: components/Schedule/shared/UnsupportedScheduleForm.js:22
-#: components/Schedule/shared/UnsupportedScheduleForm.js:27
-#: screens/Credential/shared/CredentialForm.js:343
-#: screens/Credential/shared/CredentialForm.js:348
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:100
-#: screens/Credential/shared/ExternalTestModal.js:98
-#: screens/Instances/Shared/RemoveInstanceButton.js:174
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:111
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:63
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:66
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:80
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:101
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:107
-#: screens/Setting/shared/RevertAllAlert.js:32
-#: screens/Setting/shared/RevertFormActionGroup.js:31
-#: screens/Setting/shared/RevertFormActionGroup.js:37
-#: screens/Setting/shared/SharedFields.js:133
-#: screens/Setting/shared/SharedFields.js:139
-#: screens/Setting/shared/SharedFields.js:346
-#: screens/Team/TeamRoles/TeamRolesList.js:228
-#: screens/Team/TeamRoles/TeamRolesList.js:231
-#: screens/Template/Survey/SurveyList.js:78
-#: screens/Template/Survey/SurveyReorderModal.js:211
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:31
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:39
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:45
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:50
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:164
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:167
-#: screens/User/UserRoles/UserRolesList.js:224
-#: screens/User/UserRoles/UserRolesList.js:227
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:104
-msgid "Cancel"
-msgstr "Cancelar"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:300
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:112
-msgid "Cancel Inventory Source Sync"
-msgstr "Cancelar sincronización de la fuente del inventario"
-
-#: components/JobCancelButton/JobCancelButton.js:69
-#: screens/Job/JobOutput/JobOutput.js:802
-#: screens/Job/JobOutput/JobOutput.js:803
-msgid "Cancel Job"
-msgstr "Cancelar tarea"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:321
-#: screens/Project/ProjectList/ProjectListItem.js:230
-msgid "Cancel Project Sync"
-msgstr "Cancelar sincronización del proyecto"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:302
-#: screens/Project/ProjectDetail/ProjectDetail.js:323
-msgid "Cancel Sync"
-msgstr "Cancelar sincronización"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:322
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:327
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:95
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:101
-msgid "Cancel Workflow"
-msgstr "Cancelar el flujo de trabajo"
-
-#: screens/Job/JobOutput/JobOutput.js:810
-#: screens/Job/JobOutput/JobOutput.js:813
-msgid "Cancel job"
-msgstr "Cancelar tarea"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:42
-msgid "Cancel link changes"
-msgstr "Cancelar cambios de enlace"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:34
-msgid "Cancel link removal"
-msgstr "Cancelar eliminación del enlace"
-
-#: components/Lookup/Lookup.js:207
-msgid "Cancel lookup"
-msgstr "Cancelar búsqueda"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:28
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:47
-msgid "Cancel node removal"
-msgstr "Cancelar eliminación del nodo"
-
-#: screens/Setting/shared/RevertAllAlert.js:29
-msgid "Cancel revert"
-msgstr "Cancelar reversión"
-
-#: components/JobList/JobListCancelButton.js:93
-msgid "Cancel selected job"
-msgstr "Cancelar la tarea seleccionada"
-
-#: components/JobList/JobListCancelButton.js:94
-msgid "Cancel selected jobs"
-msgstr "Cancelar las tareas seleccionadas"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:77
-msgid "Cancel subscription edit"
-msgstr "Cancelar modificación de la suscripción"
-
-#: components/JobList/JobListItem.js:113
-#: screens/Job/JobDetail/JobDetail.js:600
-#: screens/Job/JobOutput/shared/OutputToolbar.js:137
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:89
-msgid "Cancel {0}"
-msgstr "Cancelar {0}"
-
-#: components/JobList/JobList.js:234
-#: components/StatusLabel/StatusLabel.js:54
-#: components/Workflow/WorkflowNodeHelp.js:111
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:212
-msgid "Canceled"
-msgstr "Cancelado"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:129
-msgid ""
-"Cannot enable log aggregator without providing\n"
-"logging aggregator host and logging aggregator type."
-msgstr "No se puede habilitar la agregación de registros sin proporcionar\n"
-"el host y el tipo de agregación de registros."
-
-#: screens/Instances/InstanceList/InstanceList.js:199
-#: screens/Instances/InstancePeers/InstancePeerList.js:94
-msgid "Cannot run health check on hop nodes."
-msgstr "No se puede ejecutar la comprobación de estado en los nodos de salto."
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:199
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:74
-#: screens/TopologyView/Tooltip.js:312
-msgid "Capacity"
-msgstr "Capacidad"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:225
-#: screens/InstanceGroup/Instances/InstanceList.js:269
-#: screens/InstanceGroup/Instances/InstanceListItem.js:143
-#: screens/Instances/InstanceDetail/InstanceDetail.js:267
-#: screens/Instances/InstanceList/InstanceList.js:204
-#: screens/Instances/InstanceList/InstanceListItem.js:153
-msgid "Capacity Adjustment"
-msgstr "Ajuste de la capacidad"
-
-#: components/Search/LookupTypeInput.js:59
-msgid "Case-insensitive version of contains"
-msgstr "Versión de contains que no distingue mayúsculas de minúsculas"
-
-#: components/Search/LookupTypeInput.js:87
-msgid "Case-insensitive version of endswith."
-msgstr "Versión de endswith que no distingue mayúsculas de minúsculas."
-
-#: components/Search/LookupTypeInput.js:45
-msgid "Case-insensitive version of exact."
-msgstr "Versión de exact que no distingue mayúsculas de minúsculas."
-
-#: components/Search/LookupTypeInput.js:100
-msgid "Case-insensitive version of regex."
-msgstr "Versión de regex que no distingue mayúsculas de minúsculas."
-
-#: components/Search/LookupTypeInput.js:73
-msgid "Case-insensitive version of startswith."
-msgstr "Versión de startswith que no distingue mayúsculas de minúsculas."
-
-#: screens/Project/shared/Project.helptext.js:14
-msgid ""
-"Change PROJECTS_ROOT when deploying\n"
-"{brandName} to change this location."
-msgstr "Cambie PROJECTS_ROOT al implementar {brandName}\n"
-"para cambiar esta ubicación."
-
-#: components/StatusLabel/StatusLabel.js:55
-#: screens/Job/JobOutput/shared/HostStatusBar.js:43
-msgid "Changed"
-msgstr "Cambiado"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:53
-msgid "Changes"
-msgstr "Cambios"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:258
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:266
-msgid "Channel"
-msgstr "Canal"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:138
-#: screens/Template/shared/JobTemplateForm.js:218
-msgid "Check"
-msgstr "Comprobar"
-
-#: components/Search/LookupTypeInput.js:134
-msgid "Check whether the given field or related object is null; expects a boolean value."
-msgstr "Comprobar si el campo dado o el objeto relacionado son nulos; se espera un valor booleano."
-
-#: components/Search/LookupTypeInput.js:140
-msgid "Check whether the given field's value is present in the list provided; expects a comma-separated list of items."
-msgstr "Comprobar si el valor del campo dado está presente en la lista proporcionada; se espera una lista de elementos separada por comas."
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:32
-msgid "Choose a .json file"
-msgstr "Elegir un archivo .json"
-
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:78
-msgid "Choose a Notification Type"
-msgstr "Elegir un tipo de notificación"
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:25
-msgid "Choose a Playbook Directory"
-msgstr "Elegir un directorio de playbook"
-
-#: screens/Project/shared/ProjectForm.js:268
-msgid "Choose a Source Control Type"
-msgstr "Elegir un tipo de fuente de control"
-
-#: screens/Template/shared/WebhookSubForm.js:100
-msgid "Choose a Webhook Service"
-msgstr "Elegir un servicio de Webhook"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:131
-#: screens/Template/shared/JobTemplateForm.js:211
-msgid "Choose a job type"
-msgstr "Seleccionar un tipo de tarea"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:82
-msgid "Choose a module"
-msgstr "Elegir un módulo"
-
-#: screens/Inventory/shared/InventorySourceForm.js:139
-msgid "Choose a source"
-msgstr "Elegir una fuente"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:490
-msgid "Choose an HTTP method"
-msgstr "Elegir un método HTTP"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:46
-msgid ""
-"Choose an answer type or format you want as the prompt for the user.\n"
-"Refer to the Ansible Controller Documentation for more additional\n"
-"information about each option."
-msgstr "Elija el tipo o formato de respuesta que desee como indicador para el usuario. Consulte la documentación de Ansible Tower para obtener más información sobre cada una de las opciones."
-
-#: components/AddRole/SelectRoleStep.js:20
-msgid "Choose roles to apply to the selected resources. Note that all selected roles will be applied to all selected resources."
-msgstr "Elija los roles que se aplicarán a los recursos seleccionados. Tenga en cuenta que todos los roles seleccionados se aplicarán a todos los recursos seleccionados."
-
-#: components/AddRole/SelectResourceStep.js:81
-msgid "Choose the resources that will be receiving new roles. You'll be able to select the roles to apply in the next step. Note that the resources chosen here will receive all roles chosen in the next step."
-msgstr "Elija los recursos que recibirán nuevos roles. Podrá seleccionar los roles que se aplicarán en el siguiente paso. Tenga en cuenta que los recursos elegidos aquí recibirán todos los roles elegidos en el siguiente paso."
-
-#: components/AddRole/AddResourceRole.js:174
-msgid "Choose the type of resource that will be receiving new roles. For example, if you'd like to add new roles to a set of users please choose Users and click Next. You'll be able to select the specific resources in the next step."
-msgstr "Elija el tipo de recurso que recibirá los nuevos roles. Por ejemplo, si desea agregar nuevos roles a un conjunto de usuarios, elija Users (Usuarios) y haga clic en Next (Siguiente). Podrá seleccionar los recursos específicos en el siguiente paso."
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:74
-msgid "Clean"
-msgstr "Limpiar"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:95
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:116
-msgid "Clear"
-msgstr "Borrar"
-
-#: components/DataListToolbar/DataListToolbar.js:95
-#: screens/Job/JobOutput/JobOutputSearch.js:144
-msgid "Clear all filters"
-msgstr "Borrar todos los filtros"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:247
-msgid "Clear subscription"
-msgstr "Borrar suscripción"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:252
-msgid "Clear subscription selection"
-msgstr "Borrar selección de la suscripción"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerGraph.js:245
-msgid "Click an available node to create a new link. Click outside the graph to cancel."
-msgstr "Haga clic en un nodo disponible para crear un nuevo enlace. Haga clic fuera del gráfico para cancelar."
-
-#: screens/TopologyView/Tooltip.js:191
-msgid "Click on a node icon to display the details."
-msgstr "Haga clic en el icono de un nodo para mostrar los detalles."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:134
-msgid "Click the Edit button below to reconfigure the node."
-msgstr "Haga clic en el botón Edit (Modificar) para volver a configurar el nodo."
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:71
-msgid "Click this button to verify connection to the secret management system using the selected credential and specified inputs."
-msgstr "Haga clic en este botón para verificar la conexión con el sistema de gestión de claves secretas con la credencial seleccionada y las entradas especificadas."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:179
-msgid "Click to create a new link to this node."
-msgstr "Haga clic para crear un nuevo enlace a este nodo."
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:251
-msgid "Click to download bundle"
-msgstr "Haga clic para descargar el paquete"
-
-#: screens/Template/Survey/SurveyToolbar.js:64
-msgid "Click to rearrange the order of the survey questions"
-msgstr "Haga clic para cambiar el orden de las preguntas de la encuesta"
-
-#: screens/Template/Survey/MultipleChoiceField.js:117
-msgid "Click to toggle default value"
-msgstr "Haga clic para alternar el valor predeterminado"
-
-#: components/Workflow/WorkflowNodeHelp.js:202
-msgid "Click to view job details"
-msgstr "Haga clic para ver los detalles de la tarea"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:89
-#: screens/Application/Applications.js:84
-msgid "Client ID"
-msgstr "ID del cliente"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:289
-msgid "Client Identifier"
-msgstr "Identificador del cliente"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:314
-msgid "Client identifier"
-msgstr "Identificador del cliente"
-
-#: screens/Application/Applications.js:97
-msgid "Client secret"
-msgstr "Clave secreta del cliente"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:100
-#: screens/Application/shared/ApplicationForm.js:127
-msgid "Client type"
-msgstr "Tipo de cliente"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:105
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:169
-msgid "Close"
-msgstr "Cerrar"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:123
-msgid "Close subscription modal"
-msgstr "Cerrar modal de suscripción"
-
-#: components/CredentialChip/CredentialChip.js:11
-msgid "Cloud"
-msgstr "Nube"
-
-#: components/ExpandCollapse/ExpandCollapse.js:41
-msgid "Collapse"
-msgstr "Contraer"
-
-#: screens/Job/JobOutput/PageControls.js:50
-msgid "Collapse all job events"
-msgstr "Contraer todos los eventos de trabajos"
-
-#: screens/Job/JobOutput/shared/JobEventLineToggle.js:39
-msgid "Collapse section"
-msgstr "Contraer sección"
-
-#: components/JobList/JobList.js:214
-#: components/JobList/JobListItem.js:45
-#: screens/Job/JobOutput/HostEventModal.js:129
-msgid "Command"
-msgstr "Comando"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:66
-msgid "Compliant"
-msgstr "Compatible"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:68
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:36
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:132
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:58
-#: screens/Template/shared/JobTemplateForm.js:591
-msgid "Concurrent Jobs"
-msgstr "Tareas concurrentes"
-
-#: screens/Template/shared/JobTemplate.helptext.js:38
-msgid "Concurrent jobs: If enabled, simultaneous runs of this job template will be allowed."
-msgstr "Si se habilita esta opción, se permitirá la ejecución\n"
-"simultánea de esta plantilla de trabajo."
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:25
-msgid "Concurrent jobs: If enabled, simultaneous runs of this workflow job template will be allowed."
-msgstr "Si se habilita esta opción, se permitirá la ejecución de esta plantilla de flujo de trabajo en paralelo."
-
-#: screens/Setting/shared/SharedFields.js:121
-#: screens/Setting/shared/SharedFields.js:127
-#: screens/Setting/shared/SharedFields.js:336
-msgid "Confirm"
-msgstr "Confirmar"
-
-#: components/DeleteButton/DeleteButton.js:107
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:95
-msgid "Confirm Delete"
-msgstr "Confirmar eliminación"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:191
-msgid "Confirm Disable Local Authorization"
-msgstr "Confirmar deshabilitación de la autorización local"
-
-#: screens/User/shared/UserForm.js:99
-msgid "Confirm Password"
-msgstr "Confirmar la contraseña"
-
-#: components/JobCancelButton/JobCancelButton.js:86
-msgid "Confirm cancel job"
-msgstr "Confirmar cancelación de la tarea"
-
-#: components/JobCancelButton/JobCancelButton.js:90
-msgid "Confirm cancellation"
-msgstr "Confirmar cancelación"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:26
-msgid "Confirm delete"
-msgstr "Confirmar eliminación"
-
-#: screens/User/UserRoles/UserRolesList.js:215
-msgid "Confirm disassociate"
-msgstr "Confirmar disociación"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:24
-msgid "Confirm link removal"
-msgstr "Confirmar eliminación de enlace"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:37
-msgid "Confirm node removal"
-msgstr "Confirmar eliminación de nodo"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:18
-msgid "Confirm removal of all nodes"
-msgstr "Confirmar eliminación de todos los nodos"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:160
-msgid "Confirm remove"
-msgstr "Confirmar la reinicialización"
-
-#: screens/Setting/shared/RevertAllAlert.js:20
-msgid "Confirm revert all"
-msgstr "Confirmar la reversión de todo"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:91
-msgid "Confirm selection"
-msgstr "Confirmar selección"
-
-#: screens/Job/JobDetail/JobDetail.js:366
-msgid "Container Group"
-msgstr "Grupo de contenedores"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:47
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:57
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:68
-msgid "Container group"
-msgstr "Grupo de contenedores"
-
-#: screens/InstanceGroup/ContainerGroup.js:84
-msgid "Container group not found."
-msgstr "No se encontró el grupo de contenedores."
-
-#: components/LaunchPrompt/LaunchPrompt.js:153
-#: components/Schedule/shared/SchedulePromptableFields.js:120
-msgid "Content Loading"
-msgstr "Carga de contenido"
-
-#: components/PromptDetail/PromptProjectDetail.js:130
-#: screens/Project/ProjectDetail/ProjectDetail.js:240
-#: screens/Project/shared/ProjectForm.js:290
-msgid "Content Signature Validation Credential"
-msgstr "Credencial de validación de la firma del contenido"
-
-#: components/AppContainer/AppContainer.js:142
-msgid "Continue"
-msgstr "Continuar"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:207
-#: screens/Instances/InstanceList/InstanceList.js:151
-msgid "Control"
-msgstr "Control"
-
-#: screens/TopologyView/Legend.js:77
-msgid "Control node"
-msgstr "Nodo de control"
-
-#: screens/Inventory/shared/Inventory.helptext.js:79
-msgid ""
-"Control the level of output Ansible\n"
-"will produce for inventory source update jobs."
-msgstr "Controlar el nivel de salida que producirá Ansible para las tareas de actualización de fuentes de inventario."
-
-#: screens/Job/Job.helptext.js:15
-#: screens/Template/shared/JobTemplate.helptext.js:16
-msgid "Control the level of output ansible will produce as the playbook executes."
-msgstr "Controlar el nivel de salida que ansible producirá al ejecutar playbooks."
-
-#: screens/Job/JobDetail/JobDetail.js:351
-msgid "Controller Node"
-msgstr "Nombre del controlador"
-
-#: components/PromptDetail/PromptDetail.js:130
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:225
-msgid "Convergence"
-msgstr "Convergencia"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:256
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:257
-msgid "Convergence select"
-msgstr "Selección de convergencia"
-
-#: components/CopyButton/CopyButton.js:40
-msgid "Copy"
-msgstr "Copiar"
-
-#: screens/Credential/CredentialList/CredentialListItem.js:80
-msgid "Copy Credential"
-msgstr "Copiar credencial"
-
-#: components/CopyButton/CopyButton.js:48
-msgid "Copy Error"
-msgstr "Copiar error"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:104
-msgid "Copy Execution Environment"
-msgstr "Copiar entorno de ejecución"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:154
-msgid "Copy Inventory"
-msgstr "Copiar inventario"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:152
-msgid "Copy Notification Template"
-msgstr "Copiar plantilla de notificaciones"
-
-#: screens/Project/ProjectList/ProjectListItem.js:262
-msgid "Copy Project"
-msgstr "Copiar proyecto"
-
-#: components/TemplateList/TemplateListItem.js:248
-msgid "Copy Template"
-msgstr "Copiar plantilla"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:202
-#: screens/Project/ProjectList/ProjectListItem.js:98
-msgid "Copy full revision to clipboard."
-msgstr "Copie la revisión completa al portapapeles."
-
-#: components/About/About.js:35
-msgid "Copyright"
-msgstr "Copyright"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:231
-#: components/MultiSelect/TagMultiSelect.js:62
-#: screens/Credential/shared/CredentialFormFields/BecomeMethodField.js:66
-#: screens/Inventory/shared/InventoryForm.js:91
-#: screens/Template/shared/JobTemplateForm.js:396
-#: screens/Template/shared/WorkflowJobTemplateForm.js:204
-msgid "Create"
-msgstr "Crear"
-
-#: screens/Application/Applications.js:27
-#: screens/Application/Applications.js:36
-msgid "Create New Application"
-msgstr "Crear una nueva aplicación"
-
-#: screens/Credential/Credentials.js:15
-#: screens/Credential/Credentials.js:25
-msgid "Create New Credential"
-msgstr "Crear nueva credencial"
-
-#: screens/Host/Hosts.js:15
-#: screens/Host/Hosts.js:24
-msgid "Create New Host"
-msgstr "Crear nuevo host"
-
-#: screens/Template/Templates.js:18
-msgid "Create New Job Template"
-msgstr "Crear nueva plantilla de trabajo"
-
-#: screens/NotificationTemplate/NotificationTemplates.js:15
-#: screens/NotificationTemplate/NotificationTemplates.js:22
-msgid "Create New Notification Template"
-msgstr "Crear nueva plantilla de notificación"
-
-#: screens/Organization/Organizations.js:17
-#: screens/Organization/Organizations.js:27
-msgid "Create New Organization"
-msgstr "Crear nueva organización"
-
-#: screens/Project/Projects.js:13
-#: screens/Project/Projects.js:23
-msgid "Create New Project"
-msgstr "Crear nuevo proyecto"
-
-#: screens/Inventory/Inventories.js:91
-#: screens/ManagementJob/ManagementJobs.js:24
-#: screens/Project/Projects.js:32
-#: screens/Template/Templates.js:52
-msgid "Create New Schedule"
-msgstr "Crear nuevo planificador"
-
-#: screens/Team/Teams.js:16
-#: screens/Team/Teams.js:26
-msgid "Create New Team"
-msgstr "Crear nuevo equipo"
-
-#: screens/User/Users.js:16
-#: screens/User/Users.js:27
-msgid "Create New User"
-msgstr "Crear nuevo usuario"
-
-#: screens/Template/Templates.js:19
-msgid "Create New Workflow Template"
-msgstr "Crear plantilla de flujo de trabajo"
-
-#: screens/Host/HostList/SmartInventoryButton.js:26
-msgid "Create a new Smart Inventory with the applied filter"
-msgstr "Crear un nuevo inventario inteligente con el filtro aplicado"
-
-#: screens/Instances/Instances.js:14
-msgid "Create new Instance"
-msgstr "Crear nuevo grupo de instancias"
-
-#: screens/InstanceGroup/InstanceGroups.js:18
-#: screens/InstanceGroup/InstanceGroups.js:28
-msgid "Create new container group"
-msgstr "Crear nuevo grupo de contenedores"
-
-#: screens/CredentialType/CredentialTypes.js:23
-msgid "Create new credential Type"
-msgstr "Crear un nuevo tipo de credencial"
-
-#: screens/CredentialType/CredentialTypes.js:14
-msgid "Create new credential type"
-msgstr "Crear un nuevo tipo de credencial"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:14
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:23
-msgid "Create new execution environment"
-msgstr "Crear un nuevo entorno de ejecución"
-
-#: screens/Inventory/Inventories.js:75
-#: screens/Inventory/Inventories.js:82
-msgid "Create new group"
-msgstr "Crear nuevo grupo"
-
-#: screens/Inventory/Inventories.js:66
-#: screens/Inventory/Inventories.js:80
-msgid "Create new host"
-msgstr "Crear nuevo host"
-
-#: screens/InstanceGroup/InstanceGroups.js:17
-#: screens/InstanceGroup/InstanceGroups.js:27
-msgid "Create new instance group"
-msgstr "Crear nuevo grupo de instancias"
-
-#: screens/Inventory/Inventories.js:18
-msgid "Create new inventory"
-msgstr "Crear nuevo inventario"
-
-#: screens/Inventory/Inventories.js:19
-msgid "Create new smart inventory"
-msgstr "Crear nuevo inventario inteligente"
-
-#: screens/Inventory/Inventories.js:85
-msgid "Create new source"
-msgstr "Crear nueva fuente"
-
-#: screens/User/Users.js:35
-msgid "Create user token"
-msgstr "Crear token de usuario"
-
-#: components/Lookup/ApplicationLookup.js:115
-#: components/PromptDetail/PromptDetail.js:154
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:406
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:105
-#: screens/Credential/CredentialDetail/CredentialDetail.js:257
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:90
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:103
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:151
-#: screens/Host/HostDetail/HostDetail.js:86
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:67
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:93
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:173
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:43
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:81
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:277
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:149
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:46
-#: screens/Job/JobDetail/JobDetail.js:534
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:393
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:115
-#: screens/Project/ProjectDetail/ProjectDetail.js:292
-#: screens/Team/TeamDetail/TeamDetail.js:47
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:353
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:187
-#: screens/User/UserDetail/UserDetail.js:82
-#: screens/User/UserTokenDetail/UserTokenDetail.js:61
-#: screens/User/UserTokenList/UserTokenList.js:150
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:199
-msgid "Created"
-msgstr "Creado"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:122
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:112
-#: components/AddRole/AddResourceRole.js:57
-#: components/AssociateModal/AssociateModal.js:144
-#: components/LaunchPrompt/steps/CredentialsStep.js:173
-#: components/LaunchPrompt/steps/InventoryStep.js:89
-#: components/Lookup/CredentialLookup.js:194
-#: components/Lookup/InventoryLookup.js:152
-#: components/Lookup/InventoryLookup.js:207
-#: components/Lookup/MultiCredentialsLookup.js:194
-#: components/Lookup/OrganizationLookup.js:134
-#: components/Lookup/ProjectLookup.js:151
-#: components/NotificationList/NotificationList.js:206
-#: components/RelatedTemplateList/RelatedTemplateList.js:166
-#: components/Schedule/ScheduleList/ScheduleList.js:198
-#: components/TemplateList/TemplateList.js:226
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:27
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:58
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:104
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:127
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:173
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:196
-#: screens/Credential/CredentialList/CredentialList.js:150
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:96
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:132
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:102
-#: screens/Host/HostGroups/HostGroupsList.js:164
-#: screens/Host/HostList/HostList.js:157
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:199
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:129
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:174
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:128
-#: screens/Inventory/InventoryList/InventoryList.js:199
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:185
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:94
-#: screens/Organization/OrganizationList/OrganizationList.js:131
-#: screens/Project/ProjectList/ProjectList.js:213
-#: screens/Team/TeamList/TeamList.js:130
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:161
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:112
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:108
-msgid "Created By (Username)"
-msgstr "Creado por (nombre de usuario)"
-
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:81
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:147
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:73
-msgid "Created by (username)"
-msgstr "Creado por (nombre de usuario)"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:54
-#: components/AdHocCommands/useAdHocCredentialStep.js:24
-#: components/PromptDetail/PromptInventorySourceDetail.js:107
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:40
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:52
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:50
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:258
-#: screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.js:38
-#: screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/GCESubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.js:38
-#: screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:84
-#: screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.js:36
-#: screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.js:38
-#: screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.js:38
-#: util/getRelatedResourceDeleteDetails.js:167
-msgid "Credential"
-msgstr "Credencial"
-
-#: util/getRelatedResourceDeleteDetails.js:74
-msgid "Credential Input Sources"
-msgstr "Fuentes de entrada de la credencial"
-
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:83
-#: components/Lookup/InstanceGroupsLookup.js:108
-msgid "Credential Name"
-msgstr "Nombre de la credencial"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:241
-#: screens/Credential/CredentialList/CredentialList.js:158
-#: screens/Credential/shared/CredentialForm.js:128
-#: screens/Credential/shared/CredentialForm.js:196
-msgid "Credential Type"
-msgstr "Tipo de credencial"
-
-#: routeConfig.js:117
-#: screens/ActivityStream/ActivityStream.js:192
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:118
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:161
-#: screens/CredentialType/CredentialTypes.js:13
-#: screens/CredentialType/CredentialTypes.js:22
-msgid "Credential Types"
-msgstr "Tipos de credencial"
-
-#: screens/Credential/CredentialList/CredentialList.js:113
-msgid "Credential copied successfully"
-msgstr "La credencial se copió correctamente"
-
-#: screens/Credential/Credential.js:98
-msgid "Credential not found."
-msgstr "No se encontró la credencial."
-
-#: components/AdHocCommands/useAdHocCredentialPasswordStep.js:23
-#: components/LaunchPrompt/steps/useCredentialPasswordsStep.js:28
-msgid "Credential passwords"
-msgstr "Contraseñas de credenciales"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:53
-msgid "Credential to authenticate with Kubernetes or OpenShift"
-msgstr "Credencial para autenticarse con Kubernetes u OpenShift"
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:57
-msgid "Credential to authenticate with Kubernetes or OpenShift. Must be of type \"Kubernetes/OpenShift API Bearer Token\". If left blank, the underlying Pod's service account will be used."
-msgstr "Credencial para autenticarse con Kubernetes u OpenShift. Debe ser del tipo \"Kubernetes/OpenShift API Bearer Token\". Si se deja en blanco, se usará la cuenta de servicio del Pod subyacente."
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:21
-msgid "Credential to authenticate with a protected container registry."
-msgstr "Credencial para autenticarse con un registro de contenedores protegido."
-
-#: screens/CredentialType/CredentialType.js:76
-msgid "Credential type not found."
-msgstr "No se encontró el tipo de credencial."
-
-#: components/JobList/JobListItem.js:260
-#: components/LaunchPrompt/steps/CredentialsStep.js:190
-#: components/LaunchPrompt/steps/useCredentialsStep.js:62
-#: components/Lookup/MultiCredentialsLookup.js:138
-#: components/Lookup/MultiCredentialsLookup.js:211
-#: components/PromptDetail/PromptDetail.js:192
-#: components/PromptDetail/PromptJobTemplateDetail.js:191
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:528
-#: components/TemplateList/TemplateListItem.js:323
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:77
-#: routeConfig.js:70
-#: screens/ActivityStream/ActivityStream.js:167
-#: screens/Credential/CredentialList/CredentialList.js:195
-#: screens/Credential/Credentials.js:14
-#: screens/Credential/Credentials.js:24
-#: screens/Job/JobDetail/JobDetail.js:429
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:374
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:49
-#: screens/Template/shared/JobTemplateForm.js:372
-#: util/getRelatedResourceDeleteDetails.js:91
-msgid "Credentials"
-msgstr "Credenciales"
-
-#: components/LaunchPrompt/steps/credentialsValidator.js:52
-msgid "Credentials that require passwords on launch are not permitted. Please remove or replace the following credentials with a credential of the same type in order to proceed: {0}"
-msgstr "No se permiten las credenciales que requieran contraseñas al iniciarse. Por favor, elimine o reemplace las siguientes credenciales con una credencial del mismo tipo para poder proceder: {0}"
-
-#: components/Pagination/Pagination.js:34
-msgid "Current page"
-msgstr "Página actual"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:85
-msgid "Custom Kubernetes or OpenShift Pod specification."
-msgstr "Campo para pasar una especificación personalizada de Kubernetes u OpenShift Pod."
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:79
-msgid "Custom pod spec"
-msgstr "Especificaciones del pod personalizado"
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:79
-#: screens/Organization/OrganizationList/OrganizationListItem.js:55
-#: screens/Project/ProjectList/ProjectListItem.js:188
-msgid "Custom virtual environment {0} must be replaced by an execution environment."
-msgstr "El entorno virtual personalizado {0} debe ser sustituido por un entorno de ejecución."
-
-#: components/TemplateList/TemplateListItem.js:163
-msgid "Custom virtual environment {0} must be replaced by an execution environment. For more information about migrating to execution environments see <0>the documentation.0>"
-msgstr "El entorno virtual personalizado {0} debe ser sustituido por un entorno de ejecución. Para más información sobre la migración a entornos de ejecución, consulte la <0>documentación.0>"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:73
-msgid "Custom virtual environment {virtualEnvironment} must be replaced by an execution environment. For more information about migrating to execution environments see <0>the documentation.0>"
-msgstr "El entorno virtual personalizado {virtualEnvironment} debe ser sustituido por un entorno de ejecución. Para más información sobre la migración a entornos de ejecución, consulte la <0>documentación.0>"
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:61
-msgid "Customize messages…"
-msgstr "Personalizar mensajes."
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:65
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:66
-msgid "Customize pod specification"
-msgstr "Personalizar especificaciones del pod"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputNode.js:109
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:212
-msgid "DELETED"
-msgstr "ELIMINADO"
-
-#: routeConfig.js:34
-#: screens/Dashboard/Dashboard.js:74
-msgid "Dashboard"
-msgstr "Panel de control"
-
-#: screens/ActivityStream/ActivityStream.js:147
-msgid "Dashboard (all activity)"
-msgstr "Panel de control (toda la actividad)"
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:75
-msgid "Data retention period"
-msgstr "Período de conservación de datos"
-
-#: screens/Dashboard/shared/LineChart.js:168
-msgid "Date"
-msgstr "Fecha"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:184
-#: components/Schedule/shared/FrequencyDetailSubform.js:177
-#: components/Schedule/shared/FrequencyDetailSubform.js:356
-#: components/Schedule/shared/FrequencyDetailSubform.js:460
-#: components/Schedule/shared/ScheduleFormFields.js:127
-#: components/Schedule/shared/ScheduleFormFields.js:187
-msgid "Day"
-msgstr "Día"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:130
-msgid "Day {0}"
-msgstr "Día {0}"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:399
-#: components/Schedule/shared/ScheduleFormFields.js:136
-msgid "Days of Data to Keep"
-msgstr "Días de datos para mantener"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/DaysToKeepStep.js:28
-msgid "Days of data to be retained"
-msgstr "Días de datos a conservar"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:167
-msgid "Days remaining"
-msgstr "Días restantes"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useDaysToKeepStep.js:35
-msgid "Days to keep"
-msgstr "Días para guardar"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:102
-msgid "Debug"
-msgstr "Debug"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:170
-#: components/Schedule/shared/FrequencyDetailSubform.js:153
-msgid "December"
-msgstr "Diciembre"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:102
-#: screens/Template/Survey/SurveyList.js:104
-#: screens/Template/Survey/SurveyList.js:104
-#: screens/Template/Survey/SurveyListItem.js:63
-msgid "Default"
-msgstr "Predeterminado"
-
-#: screens/Template/Survey/SurveyReorderModal.js:219
-#: screens/Template/Survey/SurveyReorderModal.js:219
-#: screens/Template/Survey/SurveyReorderModal.js:241
-msgid "Default Answer(s)"
-msgstr "Respuesta(s) por defecto"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:40
-msgid "Default Execution Environment"
-msgstr "Entorno de ejecución predeterminado"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:238
-#: screens/Template/Survey/SurveyQuestionForm.js:246
-#: screens/Template/Survey/SurveyQuestionForm.js:253
-msgid "Default answer"
-msgstr "Respuesta predeterminada"
-
-#: screens/Setting/SettingList.js:103
-msgid "Define system-level features and functions"
-msgstr "Defina características y funciones a nivel del sistema"
-
-#: components/DeleteButton/DeleteButton.js:75
-#: components/DeleteButton/DeleteButton.js:80
-#: components/DeleteButton/DeleteButton.js:90
-#: components/DeleteButton/DeleteButton.js:94
-#: components/DeleteButton/DeleteButton.js:114
-#: components/PaginatedTable/ToolbarDeleteButton.js:158
-#: components/PaginatedTable/ToolbarDeleteButton.js:235
-#: components/PaginatedTable/ToolbarDeleteButton.js:246
-#: components/PaginatedTable/ToolbarDeleteButton.js:250
-#: components/PaginatedTable/ToolbarDeleteButton.js:273
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:29
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:646
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:128
-#: screens/Credential/CredentialDetail/CredentialDetail.js:306
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:124
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:134
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:115
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:127
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:201
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:101
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:316
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:174
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:64
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:68
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:73
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:78
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:102
-#: screens/Job/JobDetail/JobDetail.js:612
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:436
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:199
-#: screens/Project/ProjectDetail/ProjectDetail.js:340
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:80
-#: screens/Team/TeamDetail/TeamDetail.js:70
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:545
-#: screens/Template/Survey/SurveyList.js:66
-#: screens/Template/Survey/SurveyToolbar.js:93
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:269
-#: screens/User/UserDetail/UserDetail.js:107
-#: screens/User/UserTokenDetail/UserTokenDetail.js:78
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:340
-msgid "Delete"
-msgstr "ELIMINAR"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:130
-msgid "Delete All Groups and Hosts"
-msgstr "Eliminar todos los grupos y hosts"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:300
-msgid "Delete Credential"
-msgstr "Eliminar credencial"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:127
-msgid "Delete Execution Environment"
-msgstr "Eliminar entorno de ejecución"
-
-#: screens/Host/HostDetail/HostDetail.js:114
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:109
-msgid "Delete Host"
-msgstr "Borrar un host"
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:196
-msgid "Delete Inventory"
-msgstr "Eliminar inventario"
-
-#: screens/Job/JobDetail/JobDetail.js:608
-#: screens/Job/JobOutput/shared/OutputToolbar.js:195
-#: screens/Job/JobOutput/shared/OutputToolbar.js:199
-msgid "Delete Job"
-msgstr "Eliminar tarea"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:539
-msgid "Delete Job Template"
-msgstr "Eliminar plantilla de trabajo"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:432
-msgid "Delete Notification"
-msgstr "Eliminar notificación"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:193
-msgid "Delete Organization"
-msgstr "Eliminar organización"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:334
-msgid "Delete Project"
-msgstr "Eliminar proyecto"
-
-#: screens/Template/Survey/SurveyList.js:52
-msgid "Delete Questions"
-msgstr "Eliminar pregunta"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:642
-msgid "Delete Schedule"
-msgstr "Eliminar planificación"
-
-#: screens/Template/Survey/SurveyList.js:52
-msgid "Delete Survey"
-msgstr "Eliminar encuesta"
-
-#: screens/Team/TeamDetail/TeamDetail.js:66
-msgid "Delete Team"
-msgstr "Eliminar equipo"
-
-#: screens/User/UserDetail/UserDetail.js:103
-msgid "Delete User"
-msgstr "Eliminar usuario"
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:74
-msgid "Delete User Token"
-msgstr "Eliminar token de usuario"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:336
-msgid "Delete Workflow Approval"
-msgstr "Eliminar la aprobación del flujo de trabajo"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:263
-msgid "Delete Workflow Job Template"
-msgstr "Eliminar plantilla de trabajo del flujo de trabajo"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:138
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:141
-msgid "Delete all nodes"
-msgstr "Eliminar todos los nodos"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:124
-msgid "Delete application"
-msgstr "Eliminar aplicación"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:116
-msgid "Delete credential type"
-msgstr "Eliminar tipo de credencial"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:249
-msgid "Delete error"
-msgstr "Eliminar el error"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:109
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:121
-msgid "Delete instance group"
-msgstr "Eliminar grupo de instancias"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:310
-msgid "Delete inventory source"
-msgstr "Eliminar fuente de inventario"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:170
-msgid "Delete smart inventory"
-msgstr "Eliminar inventario inteligente"
-
-#: screens/Template/Survey/SurveyToolbar.js:83
-msgid "Delete survey question"
-msgstr "Eliminar la pregunta de la encuesta"
-
-#: screens/Project/shared/Project.helptext.js:114
-msgid ""
-"Delete the local repository in its entirety prior to\n"
-"performing an update. Depending on the size of the\n"
-"repository this may significantly increase the amount\n"
-"of time required to complete an update."
-msgstr "Elimine el repositorio local por completo antes de realizar\n"
-"una actualización. Según el tamaño del repositorio, esto\n"
-"podría incrementar significativamente el tiempo necesario\n"
-"para completar una actualización."
-
-#: components/PromptDetail/PromptProjectDetail.js:51
-#: screens/Project/ProjectDetail/ProjectDetail.js:100
-msgid "Delete the project before syncing"
-msgstr "Eliminar el proyecto antes de la sincronización."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:83
-msgid "Delete this link"
-msgstr "Eliminar este enlace"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:274
-msgid "Delete this node"
-msgstr "Eliminar este nodo"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:163
-msgid "Delete {pluralizedItemName}?"
-msgstr "¿Eliminar {pluralizedItemName}?"
-
-#: components/DetailList/DeletedDetail.js:19
-#: components/Workflow/WorkflowNodeHelp.js:157
-#: components/Workflow/WorkflowNodeHelp.js:193
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:231
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:49
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:60
-msgid "Deleted"
-msgstr "Eliminado"
-
-#: components/TemplateList/TemplateList.js:296
-#: screens/Credential/CredentialList/CredentialList.js:211
-#: screens/Inventory/InventoryList/InventoryList.js:284
-#: screens/Project/ProjectList/ProjectList.js:290
-msgid "Deletion Error"
-msgstr "Error de eliminación"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:202
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:227
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:219
-msgid "Deletion error"
-msgstr "Error de eliminación"
-
-#: components/StatusLabel/StatusLabel.js:40
-msgid "Denied"
-msgstr "Denegado"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:21
-msgid "Denied - {0}. See the Activity Stream for more information."
-msgstr "Denegado: {0}. Consulte el flujo de actividad para obtener más información."
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:17
-msgid "Denied by {0} - {1}"
-msgstr "Denegado por {0} - {1}"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:89
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:35
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:42
-msgid "Deny"
-msgstr "Denegar"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:103
-msgid "Deprecated"
-msgstr "Obsoleto"
-
-#: components/StatusLabel/StatusLabel.js:60
-#: screens/TopologyView/Legend.js:164
-msgid "Deprovisioning"
-msgstr "Desaprovisionamiento"
-
-#: components/StatusLabel/StatusLabel.js:63
-msgid "Deprovisioning fail"
-msgstr "Fallo de desaprovisionamiento"
-
-#: components/HostForm/HostForm.js:104
-#: components/Lookup/ApplicationLookup.js:105
-#: components/Lookup/ApplicationLookup.js:123
-#: components/Lookup/HostFilterLookup.js:423
-#: components/Lookup/HostListItem.js:9
-#: components/NotificationList/NotificationList.js:186
-#: components/PromptDetail/PromptDetail.js:119
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:327
-#: components/Schedule/ScheduleList/ScheduleList.js:194
-#: components/Schedule/shared/ScheduleFormFields.js:80
-#: components/TemplateList/TemplateList.js:210
-#: components/TemplateList/TemplateListItem.js:271
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:65
-#: screens/Application/ApplicationsList/ApplicationsList.js:123
-#: screens/Application/shared/ApplicationForm.js:62
-#: screens/Credential/CredentialDetail/CredentialDetail.js:223
-#: screens/Credential/CredentialList/CredentialList.js:146
-#: screens/Credential/shared/CredentialForm.js:169
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:72
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:128
-#: screens/CredentialType/shared/CredentialTypeForm.js:29
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:60
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:159
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:127
-#: screens/Host/HostDetail/HostDetail.js:75
-#: screens/Host/HostList/HostList.js:153
-#: screens/Host/HostList/HostList.js:170
-#: screens/Host/HostList/HostListItem.js:57
-#: screens/Instances/Shared/InstanceForm.js:26
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:93
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:35
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:216
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:80
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:40
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:124
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:139
-#: screens/Inventory/InventoryList/InventoryList.js:195
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:198
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:104
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:36
-#: screens/Inventory/shared/InventoryForm.js:58
-#: screens/Inventory/shared/InventoryGroupForm.js:40
-#: screens/Inventory/shared/InventorySourceForm.js:108
-#: screens/Inventory/shared/SmartInventoryForm.js:55
-#: screens/Job/JobOutput/HostEventModal.js:112
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:101
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:72
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:109
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:127
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:49
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:95
-#: screens/Organization/OrganizationList/OrganizationList.js:127
-#: screens/Organization/shared/OrganizationForm.js:64
-#: screens/Project/ProjectDetail/ProjectDetail.js:177
-#: screens/Project/ProjectList/ProjectList.js:190
-#: screens/Project/ProjectList/ProjectListItem.js:281
-#: screens/Project/shared/ProjectForm.js:222
-#: screens/Team/TeamDetail/TeamDetail.js:38
-#: screens/Team/TeamList/TeamList.js:122
-#: screens/Team/shared/TeamForm.js:37
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:187
-#: screens/Template/Survey/SurveyQuestionForm.js:171
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:112
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:183
-#: screens/Template/shared/JobTemplateForm.js:251
-#: screens/Template/shared/WorkflowJobTemplateForm.js:117
-#: screens/User/UserOrganizations/UserOrganizationList.js:80
-#: screens/User/UserOrganizations/UserOrganizationListItem.js:18
-#: screens/User/UserTeams/UserTeamList.js:182
-#: screens/User/UserTeams/UserTeamListItem.js:32
-#: screens/User/UserTokenDetail/UserTokenDetail.js:45
-#: screens/User/UserTokenList/UserTokenList.js:128
-#: screens/User/UserTokenList/UserTokenList.js:138
-#: screens/User/UserTokenList/UserTokenList.js:188
-#: screens/User/UserTokenList/UserTokenListItem.js:29
-#: screens/User/shared/UserTokenForm.js:59
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:145
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:128
-msgid "Description"
-msgstr "Descripción"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:325
-msgid "Destination Channels"
-msgstr "Canales destinatarios"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:233
-msgid "Destination Channels or Users"
-msgstr "Canales destinatarios o usuarios"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:346
-msgid "Destination SMS Number(s)"
-msgstr "Números SMS del destinatario"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:412
-msgid "Destination SMS number(s)"
-msgstr "Números SMS del destinatario"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:364
-msgid "Destination channels"
-msgstr "Canales destinatarios"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:231
-msgid "Destination channels or users"
-msgstr "Usuarios o canales destinatarios"
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:35
-#: components/ErrorDetail/ErrorDetail.js:88
-#: components/Schedule/Schedule.js:71
-#: screens/Application/Application/Application.js:79
-#: screens/Application/Applications.js:39
-#: screens/Credential/Credential.js:72
-#: screens/Credential/Credentials.js:28
-#: screens/CredentialType/CredentialType.js:63
-#: screens/CredentialType/CredentialTypes.js:26
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:65
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:26
-#: screens/Host/Host.js:58
-#: screens/Host/Hosts.js:27
-#: screens/InstanceGroup/ContainerGroup.js:66
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:180
-#: screens/InstanceGroup/InstanceGroup.js:69
-#: screens/InstanceGroup/InstanceGroups.js:30
-#: screens/InstanceGroup/InstanceGroups.js:38
-#: screens/Instances/Instance.js:29
-#: screens/Instances/Instances.js:24
-#: screens/Inventory/Inventories.js:61
-#: screens/Inventory/Inventories.js:87
-#: screens/Inventory/Inventory.js:64
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:57
-#: screens/Inventory/InventoryHost/InventoryHost.js:73
-#: screens/Inventory/InventorySource/InventorySource.js:83
-#: screens/Inventory/SmartInventory.js:66
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:60
-#: screens/Job/Job.js:130
-#: screens/Job/JobOutput/HostEventModal.js:103
-#: screens/Job/Jobs.js:35
-#: screens/ManagementJob/ManagementJobs.js:26
-#: screens/NotificationTemplate/NotificationTemplate.js:84
-#: screens/NotificationTemplate/NotificationTemplates.js:25
-#: screens/Organization/Organization.js:123
-#: screens/Organization/Organizations.js:30
-#: screens/Project/Project.js:104
-#: screens/Project/Projects.js:26
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:51
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:51
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:65
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:76
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:50
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:97
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:51
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:56
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:52
-#: screens/Setting/Settings.js:45
-#: screens/Setting/Settings.js:48
-#: screens/Setting/Settings.js:52
-#: screens/Setting/Settings.js:55
-#: screens/Setting/Settings.js:58
-#: screens/Setting/Settings.js:61
-#: screens/Setting/Settings.js:64
-#: screens/Setting/Settings.js:67
-#: screens/Setting/Settings.js:70
-#: screens/Setting/Settings.js:73
-#: screens/Setting/Settings.js:76
-#: screens/Setting/Settings.js:85
-#: screens/Setting/Settings.js:86
-#: screens/Setting/Settings.js:87
-#: screens/Setting/Settings.js:88
-#: screens/Setting/Settings.js:89
-#: screens/Setting/Settings.js:90
-#: screens/Setting/Settings.js:98
-#: screens/Setting/Settings.js:101
-#: screens/Setting/Settings.js:104
-#: screens/Setting/Settings.js:107
-#: screens/Setting/Settings.js:110
-#: screens/Setting/Settings.js:113
-#: screens/Setting/Settings.js:116
-#: screens/Setting/Settings.js:119
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:56
-#: screens/Setting/UI/UIDetail/UIDetail.js:66
-#: screens/Team/Team.js:57
-#: screens/Team/Teams.js:29
-#: screens/Template/Template.js:135
-#: screens/Template/Templates.js:43
-#: screens/Template/WorkflowJobTemplate.js:117
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:138
-#: screens/TopologyView/Tooltip.js:187
-#: screens/TopologyView/Tooltip.js:213
-#: screens/User/User.js:64
-#: screens/User/UserToken/UserToken.js:54
-#: screens/User/Users.js:30
-#: screens/User/Users.js:36
-#: screens/WorkflowApproval/WorkflowApproval.js:77
-#: screens/WorkflowApproval/WorkflowApprovals.js:24
-msgid "Details"
-msgstr "Detalles"
-
-#: screens/Job/JobOutput/HostEventModal.js:100
-msgid "Details tab"
-msgstr "Pestaña de detalles"
-
-#: components/Search/AdvancedSearch.js:271
-msgid "Direct Keys"
-msgstr "Teclas directas"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:209
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:268
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:313
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:371
-msgid "Disable SSL Verification"
-msgstr "Deshabilite la verificación de SSL"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:187
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:240
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:279
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:350
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:461
-msgid "Disable SSL verification"
-msgstr "Deshabilitar verificación SSL"
-
-#: components/InstanceToggle/InstanceToggle.js:56
-#: components/StatusLabel/StatusLabel.js:53
-#: screens/TopologyView/Legend.js:233
-msgid "Disabled"
-msgstr "Deshabilitados"
-
-#: components/DisassociateButton/DisassociateButton.js:73
-#: components/DisassociateButton/DisassociateButton.js:97
-#: components/DisassociateButton/DisassociateButton.js:109
-#: components/DisassociateButton/DisassociateButton.js:113
-#: components/DisassociateButton/DisassociateButton.js:133
-#: screens/Team/TeamRoles/TeamRolesList.js:222
-#: screens/User/UserRoles/UserRolesList.js:218
-msgid "Disassociate"
-msgstr "Disociar"
-
-#: screens/Host/HostGroups/HostGroupsList.js:211
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:229
-msgid "Disassociate group from host?"
-msgstr "¿Disociar grupo del host?"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:247
-msgid "Disassociate host from group?"
-msgstr "¿Disociar host del grupo?"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:296
-#: screens/InstanceGroup/Instances/InstanceList.js:245
-msgid "Disassociate instance from instance group?"
-msgstr "¿Disociar instancia del grupo de instancias?"
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:225
-msgid "Disassociate related group(s)?"
-msgstr "¿Disociar grupos relacionados?"
-
-#: screens/User/UserTeams/UserTeamList.js:216
-msgid "Disassociate related team(s)?"
-msgstr "¿Disociar equipos relacionados?"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:209
-#: screens/User/UserRoles/UserRolesList.js:205
-msgid "Disassociate role"
-msgstr "Disociar rol"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:212
-#: screens/User/UserRoles/UserRolesList.js:208
-msgid "Disassociate role!"
-msgstr "Disociar rol"
-
-#: components/DisassociateButton/DisassociateButton.js:18
-msgid "Disassociate?"
-msgstr "¿Disociar?"
-
-#: components/PromptDetail/PromptProjectDetail.js:46
-#: screens/Project/ProjectDetail/ProjectDetail.js:94
-msgid "Discard local changes before syncing"
-msgstr "Descartar los cambios locales antes de la sincronización"
-
-#: screens/Job/Job.helptext.js:16
-#: screens/Template/shared/JobTemplate.helptext.js:17
-msgid "Divide the work done by this job template into the specified number of job slices, each running the same tasks against a portion of the inventory."
-msgstr "Divida el trabajo realizado por esta plantilla de trabajo en la cantidad especificada de fracciones de trabajo, cada una ejecutando las mismas tareas en una fracción de inventario."
-
-#: screens/Project/shared/Project.helptext.js:100
-msgid "Documentation."
-msgstr "Documentación."
-
-#: components/CodeEditor/VariablesDetail.js:117
-#: components/CodeEditor/VariablesDetail.js:123
-#: components/CodeEditor/VariablesField.js:139
-#: components/CodeEditor/VariablesField.js:145
-msgid "Done"
-msgstr "Finalizado"
-
-#: screens/TopologyView/Tooltip.js:251
-msgid "Download Bundle"
-msgstr "Descargar paquete"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:179
-#: screens/Job/JobOutput/shared/OutputToolbar.js:184
-msgid "Download Output"
-msgstr "Descargar salida"
-
-#: screens/TopologyView/Tooltip.js:247
-msgid "Download bundle"
-msgstr "Descargar el paquete"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:93
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:114
-msgid "Drag a file here or browse to upload"
-msgstr "Arrastre un archivo aquí o navegue para cargarlo"
-
-#: components/SelectedList/DraggableSelectedList.js:68
-msgid "Draggable list to reorder and remove selected items."
-msgstr "Lista arrastrada para reordenar y eliminar los elementos seleccionados."
-
-#: components/SelectedList/DraggableSelectedList.js:43
-msgid "Dragging cancelled. List is unchanged."
-msgstr "Arrastre cancelado. La lista no se modifica."
-
-#: components/SelectedList/DraggableSelectedList.js:38
-msgid "Dragging item {id}. Item with index {oldIndex} in now {newIndex}."
-msgstr "Arrastrar elemento {id}. Elemento con índice {oldIndex} en ahora {newIndex}."
-
-#: components/SelectedList/DraggableSelectedList.js:32
-msgid "Dragging started for item id: {newId}."
-msgstr "Arrastre iniciado para el id de artículo: {newId}."
-
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:81
-msgid "E-mail"
-msgstr "Correo electrónico"
-
-#: screens/Inventory/shared/Inventory.helptext.js:113
-msgid ""
-"Each time a job runs using this inventory,\n"
-"refresh the inventory from the selected source before\n"
-"executing job tasks."
-msgstr "Cada vez que se ejecute una tarea con este inventario,\n"
-"actualice el inventario de la fuente seleccionada antes\n"
-"de ejecutar tareas de trabajo."
-
-#: screens/Project/shared/Project.helptext.js:124
-msgid ""
-"Each time a job runs using this project, update the\n"
-"revision of the project prior to starting the job."
-msgstr "Cada vez que una tarea se ejecute con este proyecto,\n"
-"actualice la revisión del proyecto antes de iniciar dicha tarea."
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:632
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:636
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:115
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:117
-#: screens/Credential/CredentialDetail/CredentialDetail.js:293
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:109
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:121
-#: screens/Host/HostDetail/HostDetail.js:108
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:101
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:113
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:190
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:55
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:62
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:103
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:292
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:127
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:164
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:418
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:420
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:138
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:182
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:186
-#: screens/Project/ProjectDetail/ProjectDetail.js:313
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:85
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:89
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:148
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:152
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:85
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:89
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:96
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:100
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:164
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:168
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:107
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:111
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:84
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:88
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:152
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:156
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:85
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:89
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:99
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:103
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:86
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:90
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:199
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:103
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:108
-#: screens/Setting/UI/UIDetail/UIDetail.js:105
-#: screens/Setting/UI/UIDetail/UIDetail.js:110
-#: screens/Team/TeamDetail/TeamDetail.js:55
-#: screens/Team/TeamDetail/TeamDetail.js:59
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:514
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:516
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:239
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:241
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:260
-#: screens/User/UserDetail/UserDetail.js:96
-msgid "Edit"
-msgstr "Editar"
-
-#: screens/Credential/CredentialList/CredentialListItem.js:67
-#: screens/Credential/CredentialList/CredentialListItem.js:71
-msgid "Edit Credential"
-msgstr "Modificar credencial"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:37
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:42
-msgid "Edit Credential Plugin Configuration"
-msgstr "Modificar configuración del complemento de credenciales"
-
-#: screens/Application/Applications.js:38
-#: screens/Credential/Credentials.js:27
-#: screens/Host/Hosts.js:26
-#: screens/ManagementJob/ManagementJobs.js:27
-#: screens/NotificationTemplate/NotificationTemplates.js:24
-#: screens/Organization/Organizations.js:29
-#: screens/Project/Projects.js:25
-#: screens/Project/Projects.js:35
-#: screens/Setting/Settings.js:46
-#: screens/Setting/Settings.js:49
-#: screens/Setting/Settings.js:53
-#: screens/Setting/Settings.js:56
-#: screens/Setting/Settings.js:59
-#: screens/Setting/Settings.js:62
-#: screens/Setting/Settings.js:65
-#: screens/Setting/Settings.js:68
-#: screens/Setting/Settings.js:71
-#: screens/Setting/Settings.js:74
-#: screens/Setting/Settings.js:77
-#: screens/Setting/Settings.js:91
-#: screens/Setting/Settings.js:92
-#: screens/Setting/Settings.js:93
-#: screens/Setting/Settings.js:94
-#: screens/Setting/Settings.js:95
-#: screens/Setting/Settings.js:96
-#: screens/Setting/Settings.js:99
-#: screens/Setting/Settings.js:102
-#: screens/Setting/Settings.js:105
-#: screens/Setting/Settings.js:108
-#: screens/Setting/Settings.js:111
-#: screens/Setting/Settings.js:114
-#: screens/Setting/Settings.js:117
-#: screens/Setting/Settings.js:120
-#: screens/Team/Teams.js:28
-#: screens/Template/Templates.js:44
-#: screens/User/Users.js:29
-msgid "Edit Details"
-msgstr "Modificar detalles"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:90
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:94
-msgid "Edit Execution Environment"
-msgstr "Modificar entorno de ejecución"
-
-#: screens/Host/HostGroups/HostGroupItem.js:37
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:46
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:46
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:51
-msgid "Edit Group"
-msgstr "Modificar grupo"
-
-#: screens/Host/HostList/HostListItem.js:74
-#: screens/Host/HostList/HostListItem.js:78
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:61
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:64
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:67
-msgid "Edit Host"
-msgstr "Modificar host"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:134
-#: screens/Inventory/InventoryList/InventoryListItem.js:139
-msgid "Edit Inventory"
-msgstr "Editar inventario"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkEditModal.js:14
-msgid "Edit Link"
-msgstr "Modificar enlace"
-
-#: screens/Setting/shared/SharedFields.js:290
-msgid "Edit Login redirect override URL"
-msgstr "Editar la URL de redirección de inicio de sesión"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeEditModal.js:64
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:257
-msgid "Edit Node"
-msgstr "Modificar nodo"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:142
-msgid "Edit Notification Template"
-msgstr "Modificar plantilla de notificación"
-
-#: screens/Template/Survey/SurveyToolbar.js:73
-msgid "Edit Order"
-msgstr "Orden de edición"
-
-#: screens/Organization/OrganizationList/OrganizationListItem.js:72
-#: screens/Organization/OrganizationList/OrganizationListItem.js:76
-msgid "Edit Organization"
-msgstr "Editar organización"
-
-#: screens/Project/ProjectList/ProjectListItem.js:248
-#: screens/Project/ProjectList/ProjectListItem.js:253
-msgid "Edit Project"
-msgstr "Modificar proyecto"
-
-#: screens/Template/Templates.js:50
-msgid "Edit Question"
-msgstr "Editar pregunta"
-
-#: components/Schedule/ScheduleList/ScheduleListItem.js:132
-#: components/Schedule/ScheduleList/ScheduleListItem.js:136
-#: screens/Template/Templates.js:55
-msgid "Edit Schedule"
-msgstr "Modificar programación"
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:131
-msgid "Edit Source"
-msgstr "Modificar fuente"
-
-#: screens/Template/Survey/SurveyListItem.js:92
-msgid "Edit Survey"
-msgstr "Editar el cuestionario"
-
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:22
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:26
-#: screens/Team/TeamList/TeamListItem.js:50
-#: screens/Team/TeamList/TeamListItem.js:54
-msgid "Edit Team"
-msgstr "Modificar equipo"
-
-#: components/TemplateList/TemplateListItem.js:233
-#: components/TemplateList/TemplateListItem.js:239
-msgid "Edit Template"
-msgstr "Modificar plantilla"
-
-#: screens/User/UserList/UserListItem.js:59
-#: screens/User/UserList/UserListItem.js:63
-msgid "Edit User"
-msgstr "Modificar usuario"
-
-#: screens/Application/ApplicationsList/ApplicationListItem.js:51
-#: screens/Application/ApplicationsList/ApplicationListItem.js:55
-msgid "Edit application"
-msgstr "Modificar aplicación"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:41
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:45
-msgid "Edit credential type"
-msgstr "Editar el tipo de credencial"
-
-#: screens/CredentialType/CredentialTypes.js:25
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:25
-#: screens/InstanceGroup/InstanceGroups.js:35
-#: screens/InstanceGroup/InstanceGroups.js:40
-#: screens/Inventory/Inventories.js:63
-#: screens/Inventory/Inventories.js:68
-#: screens/Inventory/Inventories.js:77
-#: screens/Inventory/Inventories.js:88
-msgid "Edit details"
-msgstr "Modificar detalles"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:42
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:44
-msgid "Edit group"
-msgstr "Editar grupo"
-
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:48
-msgid "Edit host"
-msgstr "Editar el servidor"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:78
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:82
-msgid "Edit instance group"
-msgstr "Modificar grupo de instancias"
-
-#: screens/Setting/shared/SharedFields.js:320
-#: screens/Setting/shared/SharedFields.js:322
-msgid "Edit login redirect override URL"
-msgstr "Editar la URL de redirección de inicio de sesión"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:70
-msgid "Edit this link"
-msgstr "Modificar este enlace"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:248
-msgid "Edit this node"
-msgstr "Modificar este nodo"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:97
-msgid "Edit workflow"
-msgstr "Editar el flujo de trabajo"
-
-#: components/Workflow/WorkflowNodeHelp.js:170
-#: screens/Job/JobOutput/shared/OutputToolbar.js:125
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:216
-msgid "Elapsed"
-msgstr "Tiempo transcurrido"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:124
-msgid "Elapsed Time"
-msgstr "Tiempo transcurrido"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:126
-msgid "Elapsed time that the job ran"
-msgstr "Tiempo transcurrido de la ejecución de la tarea "
-
-#: components/NotificationList/NotificationList.js:193
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:134
-#: screens/User/UserDetail/UserDetail.js:66
-#: screens/User/UserList/UserList.js:115
-#: screens/User/shared/UserForm.js:73
-msgid "Email"
-msgstr "Correo electrónico"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:177
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:125
-msgid "Email Options"
-msgstr "Opciones de correo electrónico"
-
-#: screens/Template/shared/WorkflowJobTemplateForm.js:263
-msgid "Enable Concurrent Jobs"
-msgstr "Activar los trabajos concurrentes"
-
-#: screens/Template/shared/JobTemplateForm.js:597
-msgid "Enable Fact Storage"
-msgstr "Habilitar almacenamiento de eventos"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:192
-msgid "Enable HTTPS certificate verification"
-msgstr "Habilitar verificación del certificado HTTPS"
-
-#: screens/Instances/Shared/InstanceForm.js:58
-msgid "Enable Instance"
-msgstr "Alternar instancia"
-
-#: screens/Template/shared/JobTemplateForm.js:573
-#: screens/Template/shared/JobTemplateForm.js:576
-#: screens/Template/shared/WorkflowJobTemplateForm.js:244
-#: screens/Template/shared/WorkflowJobTemplateForm.js:247
-msgid "Enable Webhook"
-msgstr "Habilitar Webhook"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:15
-msgid "Enable Webhook for this workflow job template."
-msgstr "Habilitar Webhook para esta plantilla de trabajo del flujo de trabajo."
-
-#: screens/Project/shared/Project.helptext.js:108
-msgid ""
-"Enable content signing to verify that the content \n"
-"has remained secure when a project is synced. \n"
-"If the content has been tampered with, the \n"
-"job will not run."
-msgstr "Habilitar la firma de contenidos para verificar que el contenido \n"
-"ha permanecido seguro cuando se sincroniza un proyecto. \n"
-"Si el contenido ha sido manipulado, el trabajo \n"
-"trabajo no se ejecutará."
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:136
-msgid "Enable external logging"
-msgstr "Habilitar registro externo"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:168
-msgid "Enable log system tracking facts individually"
-msgstr "Habilitar eventos de seguimiento del sistema de registro de forma individual"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:201
-#: components/AdHocCommands/AdHocDetailsStep.js:204
-msgid "Enable privilege escalation"
-msgstr "Habilitar elevación de privilegios"
-
-#: screens/Setting/SettingList.js:53
-msgid "Enable simplified login for your {brandName} applications"
-msgstr "Habilite el inicio de sesión simplificado para sus aplicaciones {brandName}"
-
-#: screens/Template/shared/JobTemplate.helptext.js:31
-msgid "Enable webhook for this template."
-msgstr "Habilitar webhook para esta plantilla."
-
-#: components/InstanceToggle/InstanceToggle.js:55
-#: components/Lookup/HostFilterLookup.js:110
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:45
-#: screens/TopologyView/Legend.js:205
-msgid "Enabled"
-msgstr "Habilitado"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:171
-#: components/PromptDetail/PromptJobTemplateDetail.js:187
-#: components/PromptDetail/PromptProjectDetail.js:145
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:99
-#: screens/Credential/CredentialDetail/CredentialDetail.js:268
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:138
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:265
-#: screens/Project/ProjectDetail/ProjectDetail.js:302
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:365
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:199
-msgid "Enabled Options"
-msgstr "Opciones habilitadas"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:252
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:132
-msgid "Enabled Value"
-msgstr "Valor habilitado"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:247
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:119
-msgid "Enabled Variable"
-msgstr "Variable habilitada"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:209
-msgid ""
-"Enables creation of a provisioning\n"
-"callback URL. Using the URL a host can contact {brandName}\n"
-"and request a configuration update using this job\n"
-"template"
-msgstr "Permite la creación de una URL de\n"
-"de aprovisionamiento. A través de esta URL, un host puede ponerse en contacto con {brandName} y solicitar una actualización de la configuración utilizando esta plantilla"
-
-#: screens/Template/shared/JobTemplate.helptext.js:29
-msgid "Enables creation of a provisioning callback URL. Using the URL a host can contact {brandName} and request a configuration update using this job template."
-msgstr "Permite la creación de una URL de\n"
-"de aprovisionamiento. A través de esta URL, un host puede ponerse en contacto con {brandName} y solicitar una actualización de la configuración utilizando esta plantilla de trabajo."
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:160
-#: screens/Setting/shared/SettingDetail.js:87
-msgid "Encrypted"
-msgstr "Cifrado"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:109
-#: components/Schedule/shared/FrequencyDetailSubform.js:507
-msgid "End"
-msgstr "Fin"
-
-#: screens/Setting/Subscription/SubscriptionEdit/EulaStep.js:14
-msgid "End User License Agreement"
-msgstr "Acuerdo de licencia de usuario final"
-
-#: components/Schedule/shared/DateTimePicker.js:51
-msgid "End date"
-msgstr "Fecha de terminación"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:561
-msgid "End date/time"
-msgstr "Fecha/hora de finalización"
-
-#: components/Schedule/shared/buildRuleObj.js:110
-msgid "End did not match an expected value ({0})"
-msgstr "La finalización no coincide con un valor esperado ({0})"
-
-#: components/Schedule/shared/DateTimePicker.js:61
-msgid "End time"
-msgstr "Hora de terminación"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:209
-msgid "End user license agreement"
-msgstr "Acuerdo de licencia de usuario final"
-
-#: screens/Host/HostList/SmartInventoryButton.js:23
-msgid "Enter at least one search filter to create a new Smart Inventory"
-msgstr "Ingresar al menos un filtro de búsqueda para crear un nuevo inventario inteligente"
-
-#: screens/CredentialType/shared/CredentialTypeForm.js:43
-msgid "Enter injectors using either JSON or YAML syntax. Refer to the Ansible Controller documentation for example syntax."
-msgstr "Ingrese inyectores a través de la sintaxis JSON o YAML. Consulte la documentación de Ansible Tower para ver la sintaxis de ejemplo."
-
-#: screens/CredentialType/shared/CredentialTypeForm.js:35
-msgid "Enter inputs using either JSON or YAML syntax. Refer to the Ansible Controller documentation for example syntax."
-msgstr "Ingrese entradas a través de la sintaxis JSON o YAML. Consulte la documentación de Ansible Tower para ver la sintaxis de ejemplo."
-
-#: screens/Inventory/shared/SmartInventoryForm.js:94
-msgid ""
-"Enter inventory variables using either JSON or YAML syntax.\n"
-"Use the radio button to toggle between the two. Refer to the\n"
-"Ansible Controller documentation for example syntax."
-msgstr "Ingrese variables de inventario mediante el uso de la sintaxis JSON o YAML.\n"
-"Utilice el botón de selección para alternar entre las dos opciones. Consulte la\n"
-"documentación de Ansible Tower para acceder a ejemplos de sintaxis."
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:87
-msgid "Environment variables or extra variables that specify the values a credential type can inject."
-msgstr "Variables de entorno o variables extra que especifican los valores que un tipo de credencial puede inyectar."
-
-#: components/JobList/JobList.js:233
-#: components/StatusLabel/StatusLabel.js:46
-#: components/Workflow/WorkflowNodeHelp.js:108
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:133
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:205
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:143
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:230
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:123
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:135
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:222
-#: screens/Job/JobOutput/JobOutputSearch.js:104
-#: screens/TopologyView/Legend.js:178
-msgid "Error"
-msgstr "Error"
-
-#: screens/Project/ProjectList/ProjectList.js:302
-msgid "Error fetching updated project"
-msgstr "Error al recuperar el proyecto actualizado"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:501
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:141
-msgid "Error message"
-msgstr "Mensaje de error"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:510
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:150
-msgid "Error message body"
-msgstr "Cuerpo del mensaje de error"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:709
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:711
-msgid "Error saving the workflow!"
-msgstr "Error al guardar el flujo de trabajo"
-
-#: components/AdHocCommands/AdHocCommands.js:104
-#: components/CopyButton/CopyButton.js:51
-#: components/DeleteButton/DeleteButton.js:56
-#: components/HostToggle/HostToggle.js:76
-#: components/InstanceToggle/InstanceToggle.js:67
-#: components/JobList/JobList.js:315
-#: components/JobList/JobList.js:326
-#: components/LaunchButton/LaunchButton.js:185
-#: components/LaunchPrompt/LaunchPrompt.js:96
-#: components/NotificationList/NotificationList.js:246
-#: components/PaginatedTable/ToolbarDeleteButton.js:205
-#: components/RelatedTemplateList/RelatedTemplateList.js:241
-#: components/ResourceAccessList/ResourceAccessList.js:277
-#: components/ResourceAccessList/ResourceAccessList.js:289
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:654
-#: components/Schedule/ScheduleList/ScheduleList.js:239
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:73
-#: components/Schedule/shared/SchedulePromptableFields.js:63
-#: components/TemplateList/TemplateList.js:299
-#: contexts/Config.js:94
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:136
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:155
-#: screens/Application/ApplicationsList/ApplicationsList.js:185
-#: screens/Credential/CredentialDetail/CredentialDetail.js:314
-#: screens/Credential/CredentialList/CredentialList.js:214
-#: screens/Host/HostDetail/HostDetail.js:56
-#: screens/Host/HostDetail/HostDetail.js:123
-#: screens/Host/HostGroups/HostGroupsList.js:244
-#: screens/Host/HostList/HostList.js:233
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:310
-#: screens/InstanceGroup/Instances/InstanceList.js:308
-#: screens/InstanceGroup/Instances/InstanceListItem.js:218
-#: screens/Instances/InstanceDetail/InstanceDetail.js:360
-#: screens/Instances/InstanceDetail/InstanceDetail.js:375
-#: screens/Instances/InstanceList/InstanceList.js:231
-#: screens/Instances/InstanceList/InstanceList.js:243
-#: screens/Instances/InstanceList/InstanceListItem.js:234
-#: screens/Instances/Shared/RemoveInstanceButton.js:104
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:210
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:78
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:285
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:296
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:56
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:118
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:261
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:201
-#: screens/Inventory/InventoryList/InventoryList.js:285
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:264
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:323
-#: screens/Inventory/InventorySources/InventorySourceList.js:239
-#: screens/Inventory/InventorySources/InventorySourceList.js:252
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:183
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:152
-#: screens/Inventory/shared/InventorySourceSyncButton.js:49
-#: screens/Login/Login.js:239
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:125
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:444
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:233
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:169
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:208
-#: screens/Organization/OrganizationList/OrganizationList.js:195
-#: screens/Project/ProjectDetail/ProjectDetail.js:348
-#: screens/Project/ProjectList/ProjectList.js:291
-#: screens/Project/ProjectList/ProjectList.js:303
-#: screens/Project/shared/ProjectSyncButton.js:60
-#: screens/Team/TeamDetail/TeamDetail.js:78
-#: screens/Team/TeamList/TeamList.js:192
-#: screens/Team/TeamRoles/TeamRolesList.js:247
-#: screens/Team/TeamRoles/TeamRolesList.js:258
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:554
-#: screens/Template/TemplateSurvey.js:130
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:277
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:180
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:195
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:337
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:373
-#: screens/TopologyView/MeshGraph.js:405
-#: screens/TopologyView/Tooltip.js:199
-#: screens/User/UserDetail/UserDetail.js:115
-#: screens/User/UserList/UserList.js:189
-#: screens/User/UserRoles/UserRolesList.js:243
-#: screens/User/UserRoles/UserRolesList.js:254
-#: screens/User/UserTeams/UserTeamList.js:259
-#: screens/User/UserTokenDetail/UserTokenDetail.js:85
-#: screens/User/UserTokenList/UserTokenList.js:214
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:348
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:189
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:53
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:48
-msgid "Error!"
-msgstr "¡Error!"
-
-#: components/CodeEditor/VariablesDetail.js:105
-msgid "Error:"
-msgstr "Error:"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:267
-#: screens/Instances/InstanceDetail/InstanceDetail.js:315
-msgid "Errors"
-msgstr "Errores"
-
-#: screens/TopologyView/Legend.js:253
-msgid "Established"
-msgstr "Establecido"
-
-#: screens/ActivityStream/ActivityStream.js:265
-#: screens/ActivityStream/ActivityStreamListItem.js:46
-#: screens/Job/JobOutput/JobOutputSearch.js:99
-msgid "Event"
-msgstr "Evento"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:35
-msgid "Event detail"
-msgstr "Detalles del evento"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:36
-msgid "Event detail modal"
-msgstr "Modal de detalles del evento"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:555
-msgid "Event summary not available"
-msgstr "Resumen del evento no disponible."
-
-#: screens/ActivityStream/ActivityStream.js:234
-msgid "Events"
-msgstr "Eventos"
-
-#: screens/Job/JobOutput/JobOutput.js:713
-msgid "Events processing complete."
-msgstr "Procesamiento de eventos completo."
-
-#: components/Search/LookupTypeInput.js:39
-msgid "Exact match (default lookup if not specified)."
-msgstr "Coincidencia exacta (búsqueda predeterminada si no se especifica)."
-
-#: components/Search/RelatedLookupTypeInput.js:38
-msgid "Exact search on id field."
-msgstr "Búsqueda exacta en el campo de identificación."
-
-#: screens/Project/shared/Project.helptext.js:23
-msgid "Example URLs for GIT Source Control include:"
-msgstr "A continuación, se incluyen algunos ejemplos de URL para la fuente de control de GIT:"
-
-#: screens/Project/shared/Project.helptext.js:62
-msgid "Example URLs for Remote Archive Source Control include:"
-msgstr "A continuación, se incluyen ejemplos de URL para la fuente de control de archivo remoto:"
-
-#: screens/Project/shared/Project.helptext.js:45
-msgid "Example URLs for Subversion Source Control include:"
-msgstr "A continuación, se incluyen algunos ejemplos de URL para la fuente de control de subversión:"
-
-#: screens/Project/shared/Project.helptext.js:84
-msgid "Examples include:"
-msgstr "Los ejemplos incluyen:"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:10
-msgid "Examples:"
-msgstr "Ejemplos:"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:354
-msgid "Exception Frequency"
-msgstr "Frecuencia de las excepciones"
-
-#: components/Schedule/shared/ScheduleFormFields.js:160
-msgid "Exceptions"
-msgstr "Excepciones"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:47
-msgid "Execute regardless of the parent node's final state."
-msgstr "Ejecutar independientemente del estado final del nodo primario."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:40
-msgid "Execute when the parent node results in a failure state."
-msgstr "Ejecutar cuando el nodo primario se encuentre en estado de error."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:33
-msgid "Execute when the parent node results in a successful state."
-msgstr "Ejecutar cuando el nodo primario se encuentre en estado correcto."
-
-#: screens/InstanceGroup/Instances/InstanceList.js:208
-#: screens/Instances/InstanceList/InstanceList.js:152
-msgid "Execution"
-msgstr "Ejecución"
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:90
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:91
-#: components/AdHocCommands/AdHocPreviewStep.js:58
-#: components/AdHocCommands/useAdHocExecutionEnvironmentStep.js:15
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:41
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:105
-#: components/LaunchPrompt/steps/useExecutionEnvironmentStep.js:29
-#: components/Lookup/ExecutionEnvironmentLookup.js:159
-#: components/Lookup/ExecutionEnvironmentLookup.js:191
-#: components/Lookup/ExecutionEnvironmentLookup.js:208
-#: components/PromptDetail/PromptDetail.js:220
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:451
-msgid "Execution Environment"
-msgstr "Entorno de ejecución"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:70
-#: components/TemplateList/TemplateListItem.js:160
-msgid "Execution Environment Missing"
-msgstr "Falta el entorno de ejecución"
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:103
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:107
-#: routeConfig.js:147
-#: screens/ActivityStream/ActivityStream.js:217
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:129
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:191
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:13
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:22
-#: screens/Organization/Organization.js:127
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:78
-#: screens/Organization/Organizations.js:34
-#: util/getRelatedResourceDeleteDetails.js:81
-#: util/getRelatedResourceDeleteDetails.js:188
-msgid "Execution Environments"
-msgstr "Entornos de ejecución"
-
-#: screens/Job/JobDetail/JobDetail.js:345
-msgid "Execution Node"
-msgstr "Nodo de ejecución"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:103
-msgid "Execution environment copied successfully"
-msgstr "El entorno de ejecución se copió correctamente"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:112
-msgid "Execution environment is missing or deleted."
-msgstr "Falta el entorno de ejecución o se ha eliminado."
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:83
-msgid "Execution environment not found."
-msgstr "No se encontró el entorno de ejecución."
-
-#: screens/TopologyView/Legend.js:86
-msgid "Execution node"
-msgstr "Nodo de ejecución"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:23
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:26
-msgid "Exit Without Saving"
-msgstr "Salir sin guardar"
-
-#: components/ExpandCollapse/ExpandCollapse.js:52
-msgid "Expand"
-msgstr "Expandir"
-
-#: components/DataListToolbar/DataListToolbar.js:105
-msgid "Expand all rows"
-msgstr "Desplegar todas las filas"
-
-#: components/CodeEditor/VariablesDetail.js:212
-#: components/CodeEditor/VariablesField.js:248
-msgid "Expand input"
-msgstr "Expandir la entrada"
-
-#: screens/Job/JobOutput/PageControls.js:50
-msgid "Expand job events"
-msgstr "Expandir eventos de trabajo"
-
-#: screens/Job/JobOutput/shared/JobEventLineToggle.js:37
-msgid "Expand section"
-msgstr "Expandir sección"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:46
-msgid "Expected at least one of client_email, project_id or private_key to be present in the file."
-msgstr "Se esperaba que al menos uno de client_email, project_id o private_key estuviera presente en el archivo."
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:137
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:34
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:148
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:172
-#: screens/User/UserTokenDetail/UserTokenDetail.js:56
-#: screens/User/UserTokenList/UserTokenList.js:146
-#: screens/User/UserTokenList/UserTokenList.js:190
-#: screens/User/UserTokenList/UserTokenListItem.js:35
-#: screens/User/UserTokens/UserTokens.js:89
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:151
-msgid "Expires"
-msgstr "Expira"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:146
-msgid "Expires on"
-msgstr "Fecha de expiración"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:156
-msgid "Expires on UTC"
-msgstr "Fecha de expiración (UTC):"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:50
-msgid "Expires on {0}"
-msgstr "Expira el {0}"
-
-#: components/JobList/JobListItem.js:307
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:181
-msgid "Explanation"
-msgstr "Explicación"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:113
-msgid "External Secret Management System"
-msgstr "Sistema externo de gestión de claves secretas"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:272
-#: components/AdHocCommands/AdHocDetailsStep.js:273
-msgid "Extra variables"
-msgstr "Variables adicionales"
-
-#: components/Sparkline/Sparkline.js:35
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:164
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:43
-#: screens/Project/ProjectDetail/ProjectDetail.js:139
-#: screens/Project/ProjectList/ProjectListItem.js:77
-msgid "FINISHED:"
-msgstr "FINALIZADO:"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:73
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:137
-msgid "Fact Storage"
-msgstr "Almacenamiento de datos"
-
-#: screens/Template/shared/JobTemplate.helptext.js:39
-msgid "Fact storage: If enabled, this will store gathered facts so they can be viewed at the host level. Facts are persisted and injected into the fact cache at runtime.."
-msgstr "Si se habilita esta opción, se almacenarán los eventos recopilados para que estén visibles en el nivel de host. Los eventos persisten y\n"
-"se insertan en la caché de eventos en tiempo de ejecución."
-
-#: screens/Host/Host.js:63
-#: screens/Host/HostFacts/HostFacts.js:45
-#: screens/Host/Hosts.js:28
-#: screens/Inventory/Inventories.js:71
-#: screens/Inventory/InventoryHost/InventoryHost.js:78
-#: screens/Inventory/InventoryHostFacts/InventoryHostFacts.js:39
-msgid "Facts"
-msgstr "Eventos"
-
-#: components/JobList/JobList.js:232
-#: components/StatusLabel/StatusLabel.js:45
-#: components/Workflow/WorkflowNodeHelp.js:105
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:90
-#: screens/Dashboard/shared/ChartTooltip.js:66
-#: screens/Job/JobOutput/shared/HostStatusBar.js:47
-#: screens/Job/JobOutput/shared/OutputToolbar.js:113
-msgid "Failed"
-msgstr "Fallido"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:112
-msgid "Failed Host Count"
-msgstr "Recuento de hosts fallidos"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:114
-msgid "Failed Hosts"
-msgstr "Servidores fallidos"
-
-#: components/LaunchButton/ReLaunchDropDown.js:61
-#: screens/Dashboard/Dashboard.js:87
-msgid "Failed hosts"
-msgstr "Hosts fallidos"
-
-#: screens/Dashboard/DashboardGraph.js:170
-msgid "Failed jobs"
-msgstr "Tareas fallidas"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:56
-msgid "Failed to approve {0}."
-msgstr "No se aprueba {0}."
-
-#: components/ResourceAccessList/ResourceAccessList.js:281
-msgid "Failed to assign roles properly"
-msgstr "No se pudieron asignar correctamente los roles"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:250
-#: screens/User/UserRoles/UserRolesList.js:246
-msgid "Failed to associate role"
-msgstr "No se pudo asociar el rol"
-
-#: screens/Host/HostGroups/HostGroupsList.js:248
-#: screens/InstanceGroup/Instances/InstanceList.js:311
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:288
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:265
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:268
-#: screens/User/UserTeams/UserTeamList.js:263
-msgid "Failed to associate."
-msgstr "No se pudo asociar."
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:301
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:111
-msgid "Failed to cancel Inventory Source Sync"
-msgstr "No se pudo cancelar la sincronización de fuentes de inventario"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:322
-#: screens/Project/ProjectList/ProjectListItem.js:232
-msgid "Failed to cancel Project Sync"
-msgstr "No se pudo cancelar la sincronización de proyectos"
-
-#: components/JobList/JobList.js:329
-msgid "Failed to cancel one or more jobs."
-msgstr "No se pudo cancelar una o varias tareas."
-
-#: components/JobList/JobListItem.js:114
-#: screens/Job/JobDetail/JobDetail.js:601
-#: screens/Job/JobOutput/shared/OutputToolbar.js:138
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:90
-msgid "Failed to cancel {0}"
-msgstr "No se ha podido cancelar {0}"
-
-#: screens/Credential/CredentialList/CredentialListItem.js:88
-msgid "Failed to copy credential."
-msgstr "No se pudo copiar la credencial."
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:112
-msgid "Failed to copy execution environment"
-msgstr "No se pudo copiar el entorno de ejecución"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:162
-msgid "Failed to copy inventory."
-msgstr "No se pudo copiar el inventario."
-
-#: screens/Project/ProjectList/ProjectListItem.js:270
-msgid "Failed to copy project."
-msgstr "No se pudo copiar el proyecto."
-
-#: components/TemplateList/TemplateListItem.js:253
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:160
-msgid "Failed to copy template."
-msgstr "No se pudo copiar la plantilla."
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:139
-msgid "Failed to delete application."
-msgstr "No se pudo eliminar la aplicación."
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:317
-msgid "Failed to delete credential."
-msgstr "No se pudo eliminar la credencial."
-
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:82
-msgid "Failed to delete group {0}."
-msgstr "No se pudo eliminar el grupo {0}."
-
-#: screens/Host/HostDetail/HostDetail.js:126
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:121
-msgid "Failed to delete host."
-msgstr "No se pudo eliminar el host."
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:327
-msgid "Failed to delete inventory source {name}."
-msgstr "No se pudo eliminar la fuente del inventario {name}."
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:213
-msgid "Failed to delete inventory."
-msgstr "No se pudo eliminar el inventario."
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:557
-msgid "Failed to delete job template."
-msgstr "No se pudo eliminar la plantilla de trabajo."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:448
-msgid "Failed to delete notification."
-msgstr "No se pudo eliminar la notificación."
-
-#: screens/Application/ApplicationsList/ApplicationsList.js:188
-msgid "Failed to delete one or more applications."
-msgstr "No se pudo eliminar una o más aplicaciones."
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:208
-msgid "Failed to delete one or more credential types."
-msgstr "No se pudo eliminar uno o más tipos de credenciales."
-
-#: screens/Credential/CredentialList/CredentialList.js:217
-msgid "Failed to delete one or more credentials."
-msgstr "No se pudo eliminar una o más credenciales."
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:233
-msgid "Failed to delete one or more execution environments"
-msgstr "No se pudo eliminar uno o más entornos de ejecución"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:155
-msgid "Failed to delete one or more groups."
-msgstr "No se pudo eliminar uno o varios grupos."
-
-#: screens/Host/HostList/HostList.js:236
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:204
-msgid "Failed to delete one or more hosts."
-msgstr "No se pudo eliminar uno o más hosts."
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:225
-msgid "Failed to delete one or more instance groups."
-msgstr "No se pudo eliminar uno o más grupos de instancias."
-
-#: screens/Inventory/InventoryList/InventoryList.js:288
-msgid "Failed to delete one or more inventories."
-msgstr "No se pudo eliminar uno o más inventarios."
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:255
-msgid "Failed to delete one or more inventory sources."
-msgstr "No se pudo eliminar una o más fuentes de inventario."
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:244
-msgid "Failed to delete one or more job templates."
-msgstr "No se pudo eliminar una o más plantillas de trabajo."
-
-#: components/JobList/JobList.js:318
-msgid "Failed to delete one or more jobs."
-msgstr "No se pudo eliminar una o más tareas."
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:236
-msgid "Failed to delete one or more notification template."
-msgstr "No se pudo eliminar una o más plantillas de notificación."
-
-#: screens/Organization/OrganizationList/OrganizationList.js:198
-msgid "Failed to delete one or more organizations."
-msgstr "No se pudo eliminar una o más organizaciones."
-
-#: screens/Project/ProjectList/ProjectList.js:294
-msgid "Failed to delete one or more projects."
-msgstr "No se pudo eliminar uno o más proyectos."
-
-#: components/Schedule/ScheduleList/ScheduleList.js:242
-msgid "Failed to delete one or more schedules."
-msgstr "No se pudo eliminar una o más programaciones."
-
-#: screens/Team/TeamList/TeamList.js:195
-msgid "Failed to delete one or more teams."
-msgstr "No se pudo eliminar uno o más equipos."
-
-#: components/TemplateList/TemplateList.js:302
-msgid "Failed to delete one or more templates."
-msgstr "No se pudo eliminar una o más plantillas."
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:158
-msgid "Failed to delete one or more tokens."
-msgstr "No se pudo eliminar uno o más tokens."
-
-#: screens/User/UserTokenList/UserTokenList.js:217
-msgid "Failed to delete one or more user tokens."
-msgstr "No se pudo eliminar uno o más tokens de usuario."
-
-#: screens/User/UserList/UserList.js:192
-msgid "Failed to delete one or more users."
-msgstr "No se pudo eliminar uno o más usuarios."
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:192
-msgid "Failed to delete one or more workflow approval."
-msgstr "No se pudo eliminar una o más aprobaciones del flujo de trabajo."
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:211
-msgid "Failed to delete organization."
-msgstr "No se pudo eliminar la organización."
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:351
-msgid "Failed to delete project."
-msgstr "No se pudo eliminar el proyecto."
-
-#: components/ResourceAccessList/ResourceAccessList.js:292
-msgid "Failed to delete role"
-msgstr "No se pudo eliminar el rol"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:261
-#: screens/User/UserRoles/UserRolesList.js:257
-msgid "Failed to delete role."
-msgstr "No se pudo eliminar el rol."
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:657
-msgid "Failed to delete schedule."
-msgstr "No se pudo eliminar la programación."
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:186
-msgid "Failed to delete smart inventory."
-msgstr "No se pudo eliminar el inventario inteligente."
-
-#: screens/Team/TeamDetail/TeamDetail.js:81
-msgid "Failed to delete team."
-msgstr "No se pudo eliminar el equipo."
-
-#: screens/User/UserDetail/UserDetail.js:118
-msgid "Failed to delete user."
-msgstr "No se pudo eliminar el usuario."
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:351
-msgid "Failed to delete workflow approval."
-msgstr "No se pudo eliminar la aprobación del flujo de trabajo."
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:280
-msgid "Failed to delete workflow job template."
-msgstr "No se pudo eliminar la plantilla de trabajo del flujo de trabajo."
-
-#: screens/Host/HostDetail/HostDetail.js:59
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:59
-msgid "Failed to delete {name}."
-msgstr "No se pudo eliminar {name}."
-
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:51
-msgid "Failed to deny {0}."
-msgstr "No se pudo eliminar {0}."
-
-#: screens/Host/HostGroups/HostGroupsList.js:249
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:266
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:269
-msgid "Failed to disassociate one or more groups."
-msgstr "No se pudo disociar uno o más grupos."
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:299
-msgid "Failed to disassociate one or more hosts."
-msgstr "No se pudo disociar uno o más hosts."
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:315
-#: screens/InstanceGroup/Instances/InstanceList.js:313
-#: screens/Instances/InstanceDetail/InstanceDetail.js:365
-msgid "Failed to disassociate one or more instances."
-msgstr "No se pudo disociar una o más instancias."
-
-#: screens/User/UserTeams/UserTeamList.js:264
-msgid "Failed to disassociate one or more teams."
-msgstr "No se pudo disociar uno o más equipos."
-
-#: screens/Login/Login.js:243
-msgid "Failed to fetch custom login configuration settings. System defaults will be shown instead."
-msgstr "No se pudo obtener la configuración de inicio de sesión personalizada. En su lugar, se mostrarán los valores predeterminados del sistema."
-
-#: screens/Project/ProjectList/ProjectList.js:306
-msgid "Failed to fetch the updated project data."
-msgstr "No se han podido obtener los datos actualizados del proyecto."
-
-#: screens/TopologyView/MeshGraph.js:409
-msgid "Failed to get instance."
-msgstr "No se pudo obtener el tablero:"
-
-#: components/AdHocCommands/AdHocCommands.js:112
-#: components/LaunchButton/LaunchButton.js:188
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:128
-msgid "Failed to launch job."
-msgstr "No se pudo ejecutar la tarea."
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:378
-#: screens/Instances/InstanceList/InstanceList.js:246
-msgid "Failed to remove one or more instances."
-msgstr "No se pudo disociar una o más instancias."
-
-#: contexts/Config.js:98
-msgid "Failed to retrieve configuration."
-msgstr "No se pudo recuperar la configuración."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:376
-msgid "Failed to retrieve full node resource object."
-msgstr "No se pudo recuperar el objeto de recurso de nodo completo."
-
-#: screens/InstanceGroup/Instances/InstanceList.js:315
-#: screens/Instances/InstanceList/InstanceList.js:234
-msgid "Failed to run a health check on one or more instances."
-msgstr "No se ha podido ejecutar una comprobación de estado en una o más instancias."
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:172
-msgid "Failed to send test notification."
-msgstr "No se pudo enviar la notificación de prueba."
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:52
-msgid "Failed to sync inventory source."
-msgstr "No se pudo sincronizar la fuente de inventario."
-
-#: screens/Project/shared/ProjectSyncButton.js:63
-msgid "Failed to sync project."
-msgstr "No se pudo sincronizar el proyecto."
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:242
-msgid "Failed to sync some or all inventory sources."
-msgstr "No se pudieron sincronizar algunas o todas las fuentes de inventario."
-
-#: components/HostToggle/HostToggle.js:80
-msgid "Failed to toggle host."
-msgstr "No se pudo alternar el host."
-
-#: components/InstanceToggle/InstanceToggle.js:71
-msgid "Failed to toggle instance."
-msgstr "No se pudo alternar la instancia."
-
-#: components/NotificationList/NotificationList.js:250
-msgid "Failed to toggle notification."
-msgstr "No se pudo alternar la notificación."
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:77
-msgid "Failed to toggle schedule."
-msgstr "No se pudo alternar la programación."
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:314
-#: screens/InstanceGroup/Instances/InstanceListItem.js:222
-#: screens/Instances/InstanceDetail/InstanceDetail.js:364
-#: screens/Instances/InstanceList/InstanceListItem.js:238
-msgid "Failed to update capacity adjustment."
-msgstr "No se pudo actualizar el ajuste de capacidad."
-
-#: screens/TopologyView/Tooltip.js:204
-msgid "Failed to update instance."
-msgstr "No se pudo actualizar la encuesta."
-
-#: screens/Template/TemplateSurvey.js:133
-msgid "Failed to update survey."
-msgstr "No se pudo actualizar la encuesta."
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:88
-msgid "Failed to user token."
-msgstr "Error en el token de usuario."
-
-#: components/NotificationList/NotificationListItem.js:85
-#: components/NotificationList/NotificationListItem.js:86
-msgid "Failure"
-msgstr "Fallo"
-
-#: screens/Job/JobOutput/EmptyOutput.js:45
-msgid "Failure Explanation:"
-msgstr "Explicación del fallo:"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:66
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:210
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:240
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:270
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:315
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:373
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:142
-msgid "False"
-msgstr "Falso"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:160
-#: components/Schedule/shared/FrequencyDetailSubform.js:103
-msgid "February"
-msgstr "Febrero"
-
-#: components/Search/LookupTypeInput.js:52
-msgid "Field contains value."
-msgstr "El campo contiene un valor."
-
-#: components/Search/LookupTypeInput.js:80
-msgid "Field ends with value."
-msgstr "El campo termina con un valor."
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:76
-msgid "Field for passing a custom Kubernetes or OpenShift Pod specification."
-msgstr "Campo para pasar una especificación personalizada de Kubernetes u OpenShift Pod."
-
-#: components/Search/LookupTypeInput.js:94
-msgid "Field matches the given regular expression."
-msgstr "El campo coincide con la expresión regular dada."
-
-#: components/Search/LookupTypeInput.js:66
-msgid "Field starts with value."
-msgstr "El campo comienza con un valor."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:416
-msgid "Fifth"
-msgstr "Quinto"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:105
-msgid "File Difference"
-msgstr "Diferencias del fichero"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:72
-msgid "File upload rejected. Please select a single .json file."
-msgstr "Se rechazó la carga de archivos. Seleccione un único archivo .json."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:96
-msgid "File, directory or script"
-msgstr "Archivo, directorio o script"
-
-#: components/Search/Search.js:198
-#: components/Search/Search.js:222
-msgid "Filter By {name}"
-msgstr "Filtrar por {name}"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:88
-msgid "Filter by failed jobs"
-msgstr "Filtrar por trabajos fallidos"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:94
-msgid "Filter by successful jobs"
-msgstr "Trabajos exitosos recientes"
-
-#: components/JobList/JobList.js:248
-#: components/JobList/JobListItem.js:100
-msgid "Finish Time"
-msgstr "Hora de finalización"
-
-#: screens/Job/JobDetail/JobDetail.js:226
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:208
-msgid "Finished"
-msgstr "Finalizado"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:404
-msgid "First"
-msgstr "Primero"
-
-#: components/AddRole/AddResourceRole.js:28
-#: components/AddRole/AddResourceRole.js:42
-#: components/ResourceAccessList/ResourceAccessList.js:178
-#: screens/User/UserDetail/UserDetail.js:64
-#: screens/User/UserList/UserList.js:124
-#: screens/User/UserList/UserList.js:161
-#: screens/User/UserList/UserListItem.js:53
-#: screens/User/shared/UserForm.js:63
-msgid "First Name"
-msgstr "Nombre"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:332
-msgid "First Run"
-msgstr "Primera ejecución"
-
-#: components/ResourceAccessList/ResourceAccessList.js:227
-#: components/ResourceAccessList/ResourceAccessListItem.js:67
-msgid "First name"
-msgstr "Nombre"
-
-#: components/Search/AdvancedSearch.js:213
-#: components/Search/AdvancedSearch.js:227
-msgid "First, select a key"
-msgstr "Primero, seleccione una clave"
-
-#: components/Workflow/WorkflowTools.js:88
-msgid "Fit the graph to the available screen size"
-msgstr "Ajustar el gráfico al tamaño de la pantalla disponible"
-
-#: screens/TopologyView/Header.js:75
-#: screens/TopologyView/Header.js:78
-msgid "Fit to screen"
-msgstr "Ajustar a la pantalla"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:94
-msgid "Float"
-msgstr "Decimal corto"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:183
-msgid "Follow"
-msgstr "Seguir"
-
-#: screens/Job/Job.helptext.js:5
-#: screens/Template/shared/JobTemplate.helptext.js:6
-msgid "For job templates, select run to execute the playbook. Select check to only check playbook syntax, test environment setup, and report problems without executing the playbook."
-msgstr "En lo que respecta a plantillas de trabajo, seleccione ejecutar para ejecutar el manual. Seleccione marcar para marcar únicamente la sintaxis del manual, probar la configuración del entorno e informar problemas sin ejecutar el manual."
-
-#: screens/Project/shared/Project.helptext.js:98
-msgid "For more information, refer to the"
-msgstr "Para obtener más información, consulte la"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:160
-#: components/AdHocCommands/AdHocDetailsStep.js:161
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:55
-#: components/PromptDetail/PromptDetail.js:345
-#: components/PromptDetail/PromptJobTemplateDetail.js:150
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:475
-#: screens/Job/JobDetail/JobDetail.js:389
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:259
-#: screens/Template/shared/JobTemplateForm.js:409
-#: screens/TopologyView/Tooltip.js:282
-msgid "Forks"
-msgstr "Forks"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:414
-msgid "Fourth"
-msgstr "Cuarto"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:363
-#: components/Schedule/shared/ScheduleFormFields.js:146
-msgid "Frequency Details"
-msgstr "Información sobre la frecuencia"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:381
-msgid "Frequency Exception Details"
-msgstr "Frecuencia Detalles de la excepción"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:72
-#: components/Schedule/shared/FrequencyDetailSubform.js:185
-#: components/Schedule/shared/FrequencyDetailSubform.js:206
-#: components/Schedule/shared/buildRuleObj.js:91
-msgid "Frequency did not match an expected value"
-msgstr "La frecuencia no coincide con un valor esperado"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:310
-msgid "Fri"
-msgstr "Vie"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:81
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:187
-#: components/Schedule/shared/FrequencyDetailSubform.js:315
-#: components/Schedule/shared/FrequencyDetailSubform.js:453
-msgid "Friday"
-msgstr "Viernes"
-
-#: components/Search/RelatedLookupTypeInput.js:45
-msgid "Fuzzy search on id, name or description fields."
-msgstr "Búsqueda difusa en los campos id, nombre o descripción."
-
-#: components/Search/RelatedLookupTypeInput.js:32
-msgid "Fuzzy search on name field."
-msgstr "Búsqueda difusa en el campo del nombre."
-
-#: components/CredentialChip/CredentialChip.js:13
-msgid "GPG Public Key"
-msgstr "Clave pública GPG"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:153
-#: screens/Organization/shared/OrganizationForm.js:101
-msgid "Galaxy Credentials"
-msgstr "Credenciales de Galaxy"
-
-#: screens/Credential/shared/CredentialForm.js:185
-msgid "Galaxy credentials must be owned by an Organization."
-msgstr "Las credenciales de Galaxy deben ser propiedad de una organización."
-
-#: screens/Job/JobOutput/JobOutputSearch.js:106
-msgid "Gathering Facts"
-msgstr "Obteniendo facts"
-
-#: screens/Setting/Settings.js:72
-msgid "Generic OIDC"
-msgstr "OIDC genérico"
-
-#: screens/Setting/SettingList.js:85
-msgid "Generic OIDC settings"
-msgstr "Ajustes genéricos de OIDC"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:222
-msgid "Get subscription"
-msgstr "Obtener suscripción"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:216
-msgid "Get subscriptions"
-msgstr "Obtener suscripciones"
-
-#: components/Lookup/ProjectLookup.js:136
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:89
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:158
-#: screens/Job/JobDetail/JobDetail.js:75
-#: screens/Project/ProjectList/ProjectList.js:198
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:97
-msgid "Git"
-msgstr "Git"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:312
-#: screens/Template/shared/WebhookSubForm.js:106
-msgid "GitHub"
-msgstr "GitHub"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:85
-#: screens/Setting/Settings.js:51
-msgid "GitHub Default"
-msgstr "GitHub predeterminado"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:100
-#: screens/Setting/Settings.js:60
-msgid "GitHub Enterprise"
-msgstr "GitHub Enterprise"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:105
-#: screens/Setting/Settings.js:63
-msgid "GitHub Enterprise Organization"
-msgstr "Organización de GitHub Enterprise"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:110
-#: screens/Setting/Settings.js:66
-msgid "GitHub Enterprise Team"
-msgstr "Equipo de GitHub Enterprise"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:90
-#: screens/Setting/Settings.js:54
-msgid "GitHub Organization"
-msgstr "Organización de GitHub"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:95
-#: screens/Setting/Settings.js:57
-msgid "GitHub Team"
-msgstr "Equipo GitHub"
-
-#: screens/Setting/SettingList.js:61
-msgid "GitHub settings"
-msgstr "Configuración de GitHub"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:312
-#: screens/Template/shared/WebhookSubForm.js:112
-msgid "GitLab"
-msgstr "GitLab"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:79
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:84
-msgid "Globally Available"
-msgstr "Disponible globalmente"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:134
-msgid "Globally available execution environment can not be reassigned to a specific Organization"
-msgstr "El entorno de ejecución disponible globalmente no puede reasignarse a una organización específica"
-
-#: components/Pagination/Pagination.js:29
-msgid "Go to first page"
-msgstr "Ir a la primera página"
-
-#: components/Pagination/Pagination.js:31
-msgid "Go to last page"
-msgstr "Ir a la última página"
-
-#: components/Pagination/Pagination.js:32
-msgid "Go to next page"
-msgstr "Ir a la página siguiente"
-
-#: components/Pagination/Pagination.js:30
-msgid "Go to previous page"
-msgstr "Ir a la página anterior"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:99
-msgid "Google Compute Engine"
-msgstr "Google Compute Engine"
-
-#: screens/Setting/SettingList.js:65
-msgid "Google OAuth 2 settings"
-msgstr "Configuración de Google OAuth 2"
-
-#: screens/Setting/Settings.js:69
-msgid "Google OAuth2"
-msgstr "Google OAuth2"
-
-#: components/NotificationList/NotificationList.js:194
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:135
-msgid "Grafana"
-msgstr "Grafana"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:160
-msgid "Grafana API key"
-msgstr "Clave API de Grafana"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:187
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:151
-msgid "Grafana URL"
-msgstr "URL de Grafana"
-
-#: components/Search/LookupTypeInput.js:106
-msgid "Greater than comparison."
-msgstr "Mayor que la comparación."
-
-#: components/Search/LookupTypeInput.js:113
-msgid "Greater than or equal to comparison."
-msgstr "Mayor o igual que la comparación."
-
-#: components/Lookup/HostFilterLookup.js:102
-msgid "Group"
-msgstr "Grupo"
-
-#: screens/Inventory/Inventories.js:78
-msgid "Group details"
-msgstr "Detalles del grupo"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:124
-msgid "Group type"
-msgstr "Tipo de grupo"
-
-#: screens/Host/Host.js:68
-#: screens/Host/HostGroups/HostGroupsList.js:231
-#: screens/Host/Hosts.js:29
-#: screens/Inventory/Inventories.js:72
-#: screens/Inventory/Inventories.js:74
-#: screens/Inventory/Inventory.js:66
-#: screens/Inventory/InventoryHost/InventoryHost.js:83
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:248
-#: screens/Inventory/InventoryList/InventoryListItem.js:127
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:251
-#: util/getRelatedResourceDeleteDetails.js:119
-msgid "Groups"
-msgstr "Grupos"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:383
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:468
-msgid "HTTP Headers"
-msgstr "Cabeceras HTTP"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:378
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:481
-msgid "HTTP Method"
-msgstr "Método HTTP"
-
-#: components/HealthCheckAlert/HealthCheckAlert.js:22
-msgid "Health check request(s) submitted. Please wait and reload the page."
-msgstr "Solicitudes de chequeo enviadas. Por favor, espere y recargue la página."
-
-#: components/StatusLabel/StatusLabel.js:42
-msgid "Healthy"
-msgstr "Saludable"
-
-#: components/AppContainer/PageHeaderToolbar.js:116
-msgid "Help"
-msgstr "Ayuda"
-
-#: components/FormField/PasswordInput.js:35
-msgid "Hide"
-msgstr "Ocultar"
-
-#: components/LaunchPrompt/LaunchPrompt.js:135
-#: components/Schedule/shared/SchedulePromptableFields.js:102
-msgid "Hide description"
-msgstr "Ocultar descripción"
-
-#: components/NotificationList/NotificationList.js:195
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:136
-msgid "Hipchat"
-msgstr "HipChat"
-
-#: screens/Instances/InstanceList/InstanceList.js:154
-msgid "Hop"
-msgstr "Salto"
-
-#: screens/TopologyView/Legend.js:103
-msgid "Hop node"
-msgstr "Nodo de salto"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:211
-#: screens/Job/JobOutput/HostEventModal.js:109
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:149
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:78
-msgid "Host"
-msgstr "Servidor"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:107
-msgid "Host Async Failure"
-msgstr "Servidor Async fallido"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:108
-msgid "Host Async OK"
-msgstr "Servidor Async OK"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:159
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:297
-#: screens/Template/shared/JobTemplateForm.js:633
-msgid "Host Config Key"
-msgstr "Clave de configuración del servidor"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:96
-msgid "Host Count"
-msgstr "Recuento de hosts"
-
-#: screens/Job/JobOutput/HostEventModal.js:88
-msgid "Host Details"
-msgstr "Detalles del host"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:109
-msgid "Host Failed"
-msgstr "Servidor fallido"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:110
-msgid "Host Failure"
-msgstr "Fallo del servidor"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:242
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:145
-msgid "Host Filter"
-msgstr "Filtro de host"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:201
-#: screens/Instances/InstanceDetail/InstanceDetail.js:191
-#: screens/Instances/Shared/InstanceForm.js:18
-msgid "Host Name"
-msgstr "Nombre de Host"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:111
-msgid "Host OK"
-msgstr "Servidor OK"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:112
-msgid "Host Polling"
-msgstr "Sondeo al servidor"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:113
-msgid "Host Retry"
-msgstr "Reintentar servidor"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:114
-msgid "Host Skipped"
-msgstr "Servidor omitido"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:115
-msgid "Host Started"
-msgstr "Host iniciado"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:116
-msgid "Host Unreachable"
-msgstr "Servidor no alcanzable"
-
-#: screens/Inventory/Inventories.js:69
-msgid "Host details"
-msgstr "Detalles del host"
-
-#: screens/Job/JobOutput/HostEventModal.js:89
-msgid "Host details modal"
-msgstr "Modal de detalles del host"
-
-#: screens/Host/Host.js:96
-#: screens/Inventory/InventoryHost/InventoryHost.js:100
-msgid "Host not found."
-msgstr "No se encontró el host."
-
-#: screens/Job/JobOutput/shared/HostStatusBar.js:76
-msgid "Host status information for this job is unavailable."
-msgstr "La información de estado del host para esta tarea no se encuentra disponible."
-
-#: routeConfig.js:85
-#: screens/ActivityStream/ActivityStream.js:176
-#: screens/Dashboard/Dashboard.js:81
-#: screens/Host/HostList/HostList.js:143
-#: screens/Host/HostList/HostList.js:191
-#: screens/Host/Hosts.js:14
-#: screens/Host/Hosts.js:23
-#: screens/Inventory/Inventories.js:65
-#: screens/Inventory/Inventories.js:79
-#: screens/Inventory/Inventory.js:67
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:67
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:189
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:272
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:112
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:172
-#: screens/Inventory/SmartInventory.js:68
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:71
-#: screens/Job/JobOutput/shared/OutputToolbar.js:97
-#: util/getRelatedResourceDeleteDetails.js:123
-msgid "Hosts"
-msgstr "Servidores"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:92
-msgid "Hosts automated"
-msgstr "Hosts automatizados"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:118
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:125
-msgid "Hosts available"
-msgstr "Hosts disponibles"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:107
-msgid "Hosts imported"
-msgstr "Hosts importados"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:112
-msgid "Hosts remaining"
-msgstr "Hosts restantes"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:183
-#: components/Schedule/shared/FrequencyDetailSubform.js:175
-#: components/Schedule/shared/ScheduleFormFields.js:126
-#: components/Schedule/shared/ScheduleFormFields.js:186
-msgid "Hour"
-msgstr "Hora"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:209
-#: screens/Instances/InstanceList/InstanceList.js:153
-msgid "Hybrid"
-msgstr "Híbrido"
-
-#: screens/TopologyView/Legend.js:95
-msgid "Hybrid node"
-msgstr "Nodo híbrido"
-
-#: components/JobList/JobList.js:200
-#: components/Lookup/HostFilterLookup.js:98
-#: screens/Team/TeamRoles/TeamRolesList.js:155
-msgid "ID"
-msgstr "ID"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:193
-msgid "ID of the Dashboard"
-msgstr "ID del panel de control"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:198
-msgid "ID of the Panel"
-msgstr "ID de panel"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:167
-msgid "ID of the dashboard (optional)"
-msgstr "ID del panel de control (opcional)"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:173
-msgid "ID of the panel (optional)"
-msgstr "ID del panel (opcional)"
-
-#: screens/TopologyView/Tooltip.js:265
-msgid "IP address"
-msgstr "Dirección IP"
-
-#: components/NotificationList/NotificationList.js:196
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:137
-msgid "IRC"
-msgstr "IRC"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:228
-msgid "IRC Nick"
-msgstr "Alias en IRC"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:223
-msgid "IRC Server Address"
-msgstr "Dirección del servidor IRC"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:218
-msgid "IRC Server Port"
-msgstr "Puerto del servidor IRC"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:223
-msgid "IRC nick"
-msgstr "NIC de IRC"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:215
-msgid "IRC server address"
-msgstr "Dirección del servidor IRC"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:201
-msgid "IRC server password"
-msgstr "Contraseña del servidor IRC"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:206
-msgid "IRC server port"
-msgstr "Puerto del servidor IRC"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:263
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:308
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:272
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:343
-msgid "Icon URL"
-msgstr "URL de icono"
-
-#: screens/Inventory/shared/Inventory.helptext.js:100
-msgid ""
-"If checked, all variables for child groups\n"
-"and hosts will be removed and replaced by those found\n"
-"on the external source."
-msgstr "Si las opciones están marcadas, se eliminarán\n"
-"todas las variables de los grupos secundarios y se reemplazarán\n"
-"con aquellas que se hallen en la fuente externa."
-
-#: screens/Inventory/shared/Inventory.helptext.js:84
-msgid ""
-"If checked, any hosts and groups that were\n"
-"previously present on the external source but are now removed\n"
-"will be removed from the inventory. Hosts and groups\n"
-"that were not managed by the inventory source will be promoted\n"
-"to the next manually created group or if there is no manually\n"
-"created group to promote them into, they will be left in the \"all\"\n"
-"default group for the inventory."
-msgstr "Si las opciones están marcadas, cualquier grupo o host que estuvo\n"
-"presente previamente en la fuente externa pero que ahora se eliminó,\n"
-"se eliminará del inventario. Los hosts y grupos que no fueron\n"
-"gestionados por la fuente de inventario serán promovidos al siguiente\n"
-"grupo creado manualmente o, si no existe uno al que puedan ser\n"
-"promovidos, se los dejará en el grupo predeterminado \"Todo\"\n"
-"para el inventario."
-
-#: screens/Template/shared/JobTemplate.helptext.js:30
-msgid "If enabled, run this playbook as an administrator."
-msgstr "Si se encuentra habilitada la opción, ejecute este manual como administrador."
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:184
-msgid ""
-"If enabled, show the changes made\n"
-"by Ansible tasks, where supported. This is equivalent to Ansible’s\n"
-"--diff mode."
-msgstr "Si se habilita esta opción, muestre los cambios realizados\n"
-"por las tareas de Ansible, donde sea compatible. Esto es equivalente\n"
-"al modo --diff de Ansible."
-
-#: screens/Template/shared/JobTemplate.helptext.js:19
-msgid "If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible's --diff mode."
-msgstr "Si se habilita esta opción, muestre los cambios realizados\n"
-"por las tareas de Ansible, donde sea compatible. Esto es equivalente\n"
-"al modo --diff de Ansible."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:181
-msgid "If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible’s --diff mode."
-msgstr "Si se habilita esta opción, muestre los cambios realizados por las tareas de Ansible, donde sea compatible. Esto es equivalente al modo --diff de Ansible."
-
-#: screens/Template/shared/JobTemplate.helptext.js:32
-msgid "If enabled, simultaneous runs of this job template will be allowed."
-msgstr "Si se habilita esta opción, la ejecución de esta plantilla de trabajo en paralelo será permitida."
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:16
-msgid "If enabled, simultaneous runs of this workflow job template will be allowed."
-msgstr "Si se habilita esta opción, se permitirá la ejecución de esta plantilla de flujo de trabajo en paralelo."
-
-#: screens/Inventory/shared/Inventory.helptext.js:194
-msgid ""
-"If enabled, the inventory will prevent adding any organization instance groups to the list of preferred instances groups to run associated job templates on.\n"
-"Note: If this setting is enabled and you provided an empty list, the global instance groups will be applied."
-msgstr "Si se habilita, el inventario impedirá añadir cualquier grupo de instancias de la organización a la lista de grupos de instancias preferidos para ejecutar las plantillas de trabajo asociadas.\n"
-"Nota: Si esta configuración está activada y ha proporcionado una lista vacía, se aplicarán los grupos de instancias globales."
-
-#: screens/Template/shared/JobTemplate.helptext.js:33
-msgid ""
-"If enabled, the job template will prevent adding any inventory or organization instance groups to the list of preferred instances groups to run on.\n"
-"Note: If this setting is enabled and you provided an empty list, the global instance groups will be applied."
-msgstr "Si se habilita, la plantilla de trabajo impedirá que se añada cualquier grupo de instancias del inventario o de la organización a la lista de grupos de instancias preferidos para ejecutar.\n"
-"Nota: Si esta configuración está activada y ha proporcionado una lista vacía, se aplicarán los grupos de instancias globales."
-
-#: screens/Template/shared/JobTemplate.helptext.js:35
-msgid "If enabled, this will store gathered facts so they can be viewed at the host level. Facts are persisted and injected into the fact cache at runtime."
-msgstr "Si se habilita esta opción, se almacenarán los eventos recopilados para que estén visibles en el nivel de host. Los eventos persisten y\n"
-"se insertan en la caché de eventos en tiempo de ejecución."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:275
-msgid "If specified, this field will be shown on the node instead of the resource name when viewing the workflow"
-msgstr "Si se especifica, este campo se mostrará en el nodo en lugar del nombre del recurso cuando se vea el flujo de trabajo"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:180
-msgid "If you are ready to upgrade or renew, please <0>contact us.0>"
-msgstr "Si está listo para actualizar o renovar, <0>póngase en contacto con nosotros.0>"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:63
-msgid ""
-"If you do not have a subscription, you can visit\n"
-"Red Hat to obtain a trial subscription."
-msgstr "Si no tiene una suscripción, puede visitar\n"
-"Red Hat para obtener una suscripción de prueba."
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:46
-msgid "If you only want to remove access for this particular user, please remove them from the team."
-msgstr "Si solo desea eliminar el acceso de este usuario específico, elimínelo del equipo."
-
-#: screens/Inventory/shared/Inventory.helptext.js:120
-#: screens/Inventory/shared/Inventory.helptext.js:139
-msgid ""
-"If you want the Inventory Source to update on\n"
-"launch and on project update, click on Update on launch, and also go to"
-msgstr "Si desea que la fuente de inventario se actualice al\n"
-"ejecutar y en la actualización del proyecto, haga clic en Actualizar al ejecutar, y también vaya a"
-
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:80
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:91
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:101
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:54
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:141
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:147
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:166
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:75
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:98
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:89
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:108
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.js:21
-msgid "Image"
-msgstr "Imagen"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:117
-msgid "Including File"
-msgstr "Incluyendo fichero"
-
-#: components/HostToggle/HostToggle.js:16
-msgid ""
-"Indicates if a host is available and should be included in running\n"
-"jobs. For hosts that are part of an external inventory, this may be\n"
-"reset by the inventory sync process."
-msgstr "Indica si un host está disponible y debe ser incluido en la ejecución de\n"
-"tareas. Para los hosts que forman parte de un inventario externo, esto se puede\n"
-"restablecer mediante el proceso de sincronización del inventario."
-
-#: components/AppContainer/PageHeaderToolbar.js:103
-msgid "Info"
-msgstr "Información"
-
-#: screens/ActivityStream/ActivityStreamListItem.js:45
-msgid "Initiated By"
-msgstr "Inicializado por"
-
-#: screens/ActivityStream/ActivityStream.js:253
-#: screens/ActivityStream/ActivityStream.js:263
-#: screens/ActivityStream/ActivityStreamDetailButton.js:44
-msgid "Initiated by"
-msgstr "Inicializado por"
-
-#: screens/ActivityStream/ActivityStream.js:243
-msgid "Initiated by (username)"
-msgstr "Inicializado por (nombre de usuario)"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:82
-#: screens/CredentialType/shared/CredentialTypeForm.js:46
-msgid "Injector configuration"
-msgstr "Configuración del inyector"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:74
-#: screens/CredentialType/shared/CredentialTypeForm.js:38
-msgid "Input configuration"
-msgstr "Configuración de entrada"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:79
-msgid "Input schema which defines a set of ordered fields for that type."
-msgstr "Esquema de entrada que define un conjunto de campos ordenados para ese tipo."
-
-#: screens/Project/shared/ProjectSubForms/InsightsSubForm.js:31
-msgid "Insights Credential"
-msgstr "Credencial de Insights"
-
-#: components/Lookup/HostFilterLookup.js:123
-msgid "Insights system ID"
-msgstr "ID del sistema de Insights"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:249
-msgid "Install Bundle"
-msgstr "Instalar el paquete"
-
-#: components/StatusLabel/StatusLabel.js:58
-#: screens/TopologyView/Legend.js:136
-msgid "Installed"
-msgstr "Instalado"
-
-#: screens/Metrics/Metrics.js:187
-msgid "Instance"
-msgstr "Instancia"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:135
-msgid "Instance Filters"
-msgstr "Filtros de instancias"
-
-#: screens/Job/JobDetail/JobDetail.js:358
-msgid "Instance Group"
-msgstr "Grupo de instancias"
-
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:96
-#: components/LaunchPrompt/steps/useInstanceGroupsStep.js:31
-#: components/Lookup/InstanceGroupsLookup.js:74
-#: components/Lookup/InstanceGroupsLookup.js:121
-#: components/Lookup/InstanceGroupsLookup.js:141
-#: components/Lookup/InstanceGroupsLookup.js:151
-#: components/PromptDetail/PromptDetail.js:227
-#: components/PromptDetail/PromptJobTemplateDetail.js:228
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:499
-#: routeConfig.js:132
-#: screens/ActivityStream/ActivityStream.js:205
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:106
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:179
-#: screens/InstanceGroup/InstanceGroups.js:16
-#: screens/InstanceGroup/InstanceGroups.js:26
-#: screens/Instances/InstanceDetail/InstanceDetail.js:217
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:107
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:127
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:422
-#: util/getRelatedResourceDeleteDetails.js:282
-msgid "Instance Groups"
-msgstr "Grupos de instancias"
-
-#: components/Lookup/HostFilterLookup.js:115
-msgid "Instance ID"
-msgstr "ID de instancia"
-
-#: screens/Instances/Shared/InstanceForm.js:32
-msgid "Instance State"
-msgstr "Estado de instancia"
-
-#: screens/Instances/Shared/InstanceForm.js:48
-msgid "Instance Type"
-msgstr "Tipo de instancia"
-
-#: screens/InstanceGroup/InstanceGroups.js:33
-msgid "Instance details"
-msgstr "Detalles de la instancia"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:58
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:69
-msgid "Instance group"
-msgstr "Grupo de instancias"
-
-#: screens/InstanceGroup/InstanceGroup.js:92
-msgid "Instance group not found."
-msgstr "No se encontró el grupo de instancias."
-
-#: screens/InstanceGroup/Instances/InstanceListItem.js:165
-#: screens/Instances/InstanceList/InstanceListItem.js:176
-msgid "Instance group used capacity"
-msgstr "Capacidad utilizada del grupo de instancias"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:122
-#: screens/TopologyView/Tooltip.js:273
-msgid "Instance groups"
-msgstr "Grupos de instancias"
-
-#: screens/TopologyView/Tooltip.js:234
-msgid "Instance status"
-msgstr "Estado de instancia"
-
-#: screens/TopologyView/Tooltip.js:240
-msgid "Instance type"
-msgstr "tipo de instancia"
-
-#: routeConfig.js:137
-#: screens/ActivityStream/ActivityStream.js:203
-#: screens/InstanceGroup/InstanceGroup.js:74
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:198
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:73
-#: screens/InstanceGroup/InstanceGroups.js:31
-#: screens/InstanceGroup/Instances/InstanceList.js:192
-#: screens/InstanceGroup/Instances/InstanceList.js:290
-#: screens/Instances/InstanceList/InstanceList.js:136
-#: screens/Instances/Instances.js:13
-#: screens/Instances/Instances.js:22
-msgid "Instances"
-msgstr "Instancias"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:93
-msgid "Integer"
-msgstr "Entero"
-
-#: util/validators.js:94
-msgid "Invalid email address"
-msgstr "Dirección de correo electrónico no válida"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:116
-msgid "Invalid file format. Please upload a valid Red Hat Subscription Manifest."
-msgstr "Formato de archivo no válido. Cargue un manifiesto de suscripción de Red Hat válido."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:178
-msgid "Invalid link target. Unable to link to children or ancestor nodes. Graph cycles are not supported."
-msgstr "Objetivo de enlace no válido. No se puede enlazar con nodos secundarios o ancestros. Los ciclos del gráfico no son compatibles."
-
-#: util/validators.js:33
-msgid "Invalid time format"
-msgstr "Formato de hora no válido"
-
-#: screens/Login/Login.js:153
-msgid "Invalid username or password. Please try again."
-msgstr "Nombre de usuario o contraseña no válidos. Intente de nuevo."
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:119
-#: routeConfig.js:80
-#: screens/ActivityStream/ActivityStream.js:173
-#: screens/Dashboard/Dashboard.js:92
-#: screens/Inventory/Inventories.js:17
-#: screens/Inventory/InventoryList/InventoryList.js:174
-#: screens/Inventory/InventoryList/InventoryList.js:237
-#: util/getRelatedResourceDeleteDetails.js:202
-#: util/getRelatedResourceDeleteDetails.js:270
-msgid "Inventories"
-msgstr "Inventarios"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:153
-msgid "Inventories with sources cannot be copied"
-msgstr "No se pueden copiar los inventarios con fuentes"
-
-#: components/HostForm/HostForm.js:48
-#: components/JobList/JobListItem.js:223
-#: components/LaunchPrompt/steps/InventoryStep.js:105
-#: components/LaunchPrompt/steps/useInventoryStep.js:48
-#: components/Lookup/HostFilterLookup.js:424
-#: components/Lookup/HostListItem.js:10
-#: components/Lookup/InventoryLookup.js:119
-#: components/Lookup/InventoryLookup.js:128
-#: components/Lookup/InventoryLookup.js:169
-#: components/Lookup/InventoryLookup.js:184
-#: components/Lookup/InventoryLookup.js:224
-#: components/PromptDetail/PromptDetail.js:214
-#: components/PromptDetail/PromptInventorySourceDetail.js:76
-#: components/PromptDetail/PromptJobTemplateDetail.js:119
-#: components/PromptDetail/PromptJobTemplateDetail.js:129
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:79
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:430
-#: components/TemplateList/TemplateListItem.js:285
-#: components/TemplateList/TemplateListItem.js:295
-#: screens/Host/HostDetail/HostDetail.js:77
-#: screens/Host/HostList/HostList.js:171
-#: screens/Host/HostList/HostListItem.js:61
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:94
-#: screens/Inventory/InventoryList/InventoryList.js:186
-#: screens/Inventory/InventoryList/InventoryListItem.js:117
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:38
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:113
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:41
-#: screens/Job/JobDetail/JobDetail.js:107
-#: screens/Job/JobDetail/JobDetail.js:122
-#: screens/Job/JobDetail/JobDetail.js:129
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:214
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:224
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:141
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:33
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:252
-msgid "Inventory"
-msgstr "Inventario"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:104
-msgid "Inventory (Name)"
-msgstr "Inventario (Nombre)"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:99
-msgid "Inventory File"
-msgstr "Archivo de inventario"
-
-#: components/Lookup/HostFilterLookup.js:106
-msgid "Inventory ID"
-msgstr "ID de inventario"
-
-#: screens/Job/JobDetail/JobDetail.js:282
-msgid "Inventory Source"
-msgstr "Fuente de inventario"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:73
-msgid "Inventory Source Sync"
-msgstr "Sincronización de fuentes de inventario"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:299
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:110
-msgid "Inventory Source Sync Error"
-msgstr "Error en la sincronización de fuentes de inventario"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:176
-#: screens/Inventory/InventorySources/InventorySourceList.js:193
-#: util/getRelatedResourceDeleteDetails.js:67
-#: util/getRelatedResourceDeleteDetails.js:147
-msgid "Inventory Sources"
-msgstr "Fuentes de inventario"
-
-#: components/JobList/JobList.js:212
-#: components/JobList/JobListItem.js:43
-#: components/Schedule/ScheduleList/ScheduleListItem.js:36
-#: components/Workflow/WorkflowLegend.js:100
-#: screens/Job/JobDetail/JobDetail.js:66
-msgid "Inventory Sync"
-msgstr "Sincronización de inventario"
-
-#: screens/Inventory/InventoryList/InventoryList.js:183
-msgid "Inventory Type"
-msgstr "Tipo de inventario"
-
-#: components/Workflow/WorkflowNodeHelp.js:71
-msgid "Inventory Update"
-msgstr "Actualización del inventario"
-
-#: screens/Inventory/InventoryList/InventoryList.js:121
-msgid "Inventory copied successfully"
-msgstr "El inventario se copió correctamente"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:226
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:109
-msgid "Inventory file"
-msgstr "Archivo de inventario"
-
-#: screens/Inventory/Inventory.js:94
-msgid "Inventory not found."
-msgstr "No se encontró el inventario."
-
-#: screens/Dashboard/DashboardGraph.js:140
-msgid "Inventory sync"
-msgstr "Sincronización de inventario"
-
-#: screens/Dashboard/Dashboard.js:98
-msgid "Inventory sync failures"
-msgstr "Errores de sincronización de inventario"
-
-#: components/DataListToolbar/DataListToolbar.js:110
-msgid "Is expanded"
-msgstr "Expandido"
-
-#: components/DataListToolbar/DataListToolbar.js:112
-msgid "Is not expanded"
-msgstr "No se expande"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:118
-msgid "Item Failed"
-msgstr "Elemento fallido"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:119
-msgid "Item OK"
-msgstr "Elemento OK"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:120
-msgid "Item Skipped"
-msgstr "Elemento omitido"
-
-#: components/AssociateModal/AssociateModal.js:20
-#: components/PaginatedTable/PaginatedTable.js:42
-msgid "Items"
-msgstr "Elementos"
-
-#: components/Pagination/Pagination.js:27
-msgid "Items per page"
-msgstr "Elementos por página"
-
-#: components/Sparkline/Sparkline.js:28
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:157
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:36
-#: screens/Project/ProjectDetail/ProjectDetail.js:132
-#: screens/Project/ProjectList/ProjectListItem.js:70
-msgid "JOB ID:"
-msgstr "ID DE TAREA:"
-
-#: screens/Job/JobOutput/HostEventModal.js:136
-msgid "JSON"
-msgstr "JSON"
-
-#: screens/Job/JobOutput/HostEventModal.js:137
-msgid "JSON tab"
-msgstr "Pestaña JSON"
-
-#: screens/Inventory/shared/Inventory.helptext.js:49
-msgid "JSON:"
-msgstr "JSON:"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:159
-#: components/Schedule/shared/FrequencyDetailSubform.js:98
-msgid "January"
-msgstr "Enero"
-
-#: components/JobList/JobListItem.js:112
-#: screens/Job/JobDetail/JobDetail.js:599
-#: screens/Job/JobOutput/JobOutput.js:845
-#: screens/Job/JobOutput/JobOutput.js:846
-#: screens/Job/JobOutput/shared/OutputToolbar.js:136
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:88
-msgid "Job Cancel Error"
-msgstr "Error en la cancelación de tarea"
-
-#: screens/Job/JobDetail/JobDetail.js:621
-#: screens/Job/JobOutput/JobOutput.js:834
-#: screens/Job/JobOutput/JobOutput.js:835
-msgid "Job Delete Error"
-msgstr "Error en la eliminación de tareas"
-
-#: screens/Job/JobDetail/JobDetail.js:206
-msgid "Job ID"
-msgstr "Identificación del trabajo"
-
-#: screens/Dashboard/shared/LineChart.js:128
-msgid "Job Runs"
-msgstr "Ejecuciones de trabajo"
-
-#: components/JobList/JobListItem.js:314
-#: screens/Job/JobDetail/JobDetail.js:374
-msgid "Job Slice"
-msgstr "Fracción de tareas"
-
-#: components/JobList/JobListItem.js:319
-#: screens/Job/JobDetail/JobDetail.js:382
-msgid "Job Slice Parent"
-msgstr "Fraccionamiento de los trabajos principales"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:76
-#: components/PromptDetail/PromptDetail.js:349
-#: components/PromptDetail/PromptJobTemplateDetail.js:158
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:494
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:289
-#: screens/Template/shared/JobTemplateForm.js:453
-msgid "Job Slicing"
-msgstr "Fraccionamiento de trabajos"
-
-#: components/Workflow/WorkflowNodeHelp.js:164
-msgid "Job Status"
-msgstr "Estado de la tarea"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:97
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:98
-#: components/PromptDetail/PromptDetail.js:267
-#: components/PromptDetail/PromptJobTemplateDetail.js:247
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:571
-#: screens/Job/JobDetail/JobDetail.js:474
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:449
-#: screens/Template/shared/JobTemplateForm.js:521
-#: screens/Template/shared/WorkflowJobTemplateForm.js:218
-msgid "Job Tags"
-msgstr "Etiquetas de trabajo"
-
-#: components/JobList/JobListItem.js:191
-#: components/TemplateList/TemplateList.js:217
-#: components/Workflow/WorkflowLegend.js:92
-#: components/Workflow/WorkflowNodeHelp.js:59
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:97
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:19
-#: screens/Job/JobDetail/JobDetail.js:233
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:79
-msgid "Job Template"
-msgstr "Plantilla de trabajo"
-
-#: components/LaunchPrompt/steps/credentialsValidator.js:38
-msgid "Job Template default credentials must be replaced with one of the same type. Please select a credential for the following types in order to proceed: {0}"
-msgstr "Las credenciales predeterminadas de la plantilla de trabajo se deben reemplazar por una del mismo tipo. Seleccione una credencial de los siguientes tipos para continuar: {0}"
-
-#: screens/Credential/Credential.js:79
-#: screens/Credential/Credentials.js:30
-#: screens/Inventory/Inventories.js:62
-#: screens/Inventory/Inventory.js:74
-#: screens/Inventory/SmartInventory.js:74
-#: screens/Project/Project.js:107
-#: screens/Project/Projects.js:29
-#: util/getRelatedResourceDeleteDetails.js:56
-#: util/getRelatedResourceDeleteDetails.js:101
-#: util/getRelatedResourceDeleteDetails.js:133
-msgid "Job Templates"
-msgstr "Plantillas de trabajo"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.js:25
-msgid "Job Templates with a missing inventory or project cannot be selected when creating or editing nodes. Select another template or fix the missing fields to proceed."
-msgstr "Las plantillas de trabajo en las que falta un inventario o un proyecto no pueden seleccionarse al crear o modificar nodos. Seleccione otra plantilla o corrija los campos que faltan para continuar."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useWorkflowNodeSteps.js:357
-msgid "Job Templates with credentials that prompt for passwords cannot be selected when creating or editing nodes"
-msgstr "Las plantillas de trabajo con credenciales que solicitan contraseñas no pueden seleccionarse al crear o modificar nodos"
-
-#: components/JobList/JobList.js:208
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:146
-#: components/PromptDetail/PromptDetail.js:185
-#: components/PromptDetail/PromptJobTemplateDetail.js:102
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:423
-#: screens/Job/JobDetail/JobDetail.js:267
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:192
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:138
-#: screens/Template/shared/JobTemplateForm.js:256
-msgid "Job Type"
-msgstr "Tipo de trabajo"
-
-#: screens/Dashboard/Dashboard.js:125
-msgid "Job status"
-msgstr "Estado de la tarea"
-
-#: screens/Dashboard/Dashboard.js:123
-msgid "Job status graph tab"
-msgstr "Pestaña del gráfico de estado de la tarea"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:156
-#: components/RelatedTemplateList/RelatedTemplateList.js:206
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:15
-msgid "Job templates"
-msgstr "Plantillas de trabajo"
-
-#: components/JobList/JobList.js:191
-#: components/JobList/JobList.js:274
-#: routeConfig.js:39
-#: screens/ActivityStream/ActivityStream.js:150
-#: screens/Dashboard/shared/LineChart.js:64
-#: screens/Host/Host.js:73
-#: screens/Host/Hosts.js:30
-#: screens/InstanceGroup/ContainerGroup.js:71
-#: screens/InstanceGroup/InstanceGroup.js:79
-#: screens/InstanceGroup/InstanceGroups.js:34
-#: screens/InstanceGroup/InstanceGroups.js:39
-#: screens/Inventory/Inventories.js:60
-#: screens/Inventory/Inventories.js:70
-#: screens/Inventory/Inventory.js:70
-#: screens/Inventory/InventoryHost/InventoryHost.js:88
-#: screens/Inventory/SmartInventory.js:70
-#: screens/Job/Jobs.js:22
-#: screens/Job/Jobs.js:32
-#: screens/Setting/SettingList.js:91
-#: screens/Setting/Settings.js:75
-#: screens/Template/Template.js:155
-#: screens/Template/Templates.js:47
-#: screens/Template/WorkflowJobTemplate.js:141
-msgid "Jobs"
-msgstr "Trabajos"
-
-#: screens/Setting/SettingList.js:96
-msgid "Jobs settings"
-msgstr "Configuración de las tareas"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:165
-#: components/Schedule/shared/FrequencyDetailSubform.js:128
-msgid "July"
-msgstr "Julio"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:164
-#: components/Schedule/shared/FrequencyDetailSubform.js:123
-msgid "June"
-msgstr "Junio"
-
-#: components/Search/AdvancedSearch.js:262
-msgid "Key"
-msgstr "Clave"
-
-#: components/Search/AdvancedSearch.js:253
-msgid "Key select"
-msgstr "Seleccionar clave"
-
-#: components/Search/AdvancedSearch.js:256
-msgid "Key typeahead"
-msgstr "Escritura anticipada de la clave"
-
-#: screens/ActivityStream/ActivityStream.js:238
-msgid "Keyword"
-msgstr "Palabra clave"
-
-#: screens/User/UserDetail/UserDetail.js:56
-#: screens/User/UserList/UserListItem.js:44
-msgid "LDAP"
-msgstr "LDAP"
-
-#: screens/Setting/Settings.js:80
-msgid "LDAP 1"
-msgstr "LDAP 1"
-
-#: screens/Setting/Settings.js:81
-msgid "LDAP 2"
-msgstr "LDAP 2"
-
-#: screens/Setting/Settings.js:82
-msgid "LDAP 3"
-msgstr "LDAP 3"
-
-#: screens/Setting/Settings.js:83
-msgid "LDAP 4"
-msgstr "LDAP 4"
-
-#: screens/Setting/Settings.js:84
-msgid "LDAP 5"
-msgstr "LDAP 5"
-
-#: screens/Setting/Settings.js:79
-msgid "LDAP Default"
-msgstr "LDAP predeterminado"
-
-#: screens/Setting/SettingList.js:69
-msgid "LDAP settings"
-msgstr "Configuración de LDAP"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:107
-msgid "LDAP1"
-msgstr "LDAP1"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:112
-msgid "LDAP2"
-msgstr "LDAP2"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:117
-msgid "LDAP3"
-msgstr "LDAP3"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:122
-msgid "LDAP4"
-msgstr "LDAP4"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:127
-msgid "LDAP5"
-msgstr "LDAP5"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:178
-#: components/TemplateList/TemplateList.js:234
-msgid "Label"
-msgstr "Etiqueta"
-
-#: components/JobList/JobList.js:204
-msgid "Label Name"
-msgstr "Nombre de la etiqueta"
-
-#: components/JobList/JobListItem.js:284
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:223
-#: components/PromptDetail/PromptDetail.js:323
-#: components/PromptDetail/PromptJobTemplateDetail.js:209
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:116
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:551
-#: components/TemplateList/TemplateListItem.js:347
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:148
-#: screens/Inventory/shared/InventoryForm.js:83
-#: screens/Job/JobDetail/JobDetail.js:453
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:401
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:206
-#: screens/Template/shared/JobTemplateForm.js:387
-#: screens/Template/shared/WorkflowJobTemplateForm.js:195
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:273
-msgid "Labels"
-msgstr "Etiquetas"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:417
-msgid "Last"
-msgstr "Último"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:220
-#: screens/InstanceGroup/Instances/InstanceListItem.js:133
-#: screens/InstanceGroup/Instances/InstanceListItem.js:208
-#: screens/Instances/InstanceDetail/InstanceDetail.js:243
-#: screens/Instances/InstanceList/InstanceListItem.js:138
-#: screens/Instances/InstanceList/InstanceListItem.js:223
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:47
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:87
-msgid "Last Health Check"
-msgstr "Última comprobación de estado"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:183
-#: screens/Project/ProjectDetail/ProjectDetail.js:161
-msgid "Last Job Status"
-msgstr "Último estado de la tarea"
-
-#: screens/User/UserDetail/UserDetail.js:80
-msgid "Last Login"
-msgstr "Último inicio de sesión"
-
-#: components/PromptDetail/PromptDetail.js:161
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:411
-#: components/TemplateList/TemplateListItem.js:316
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:106
-#: screens/Application/ApplicationsList/ApplicationListItem.js:45
-#: screens/Application/ApplicationsList/ApplicationsList.js:159
-#: screens/Credential/CredentialDetail/CredentialDetail.js:263
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:95
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:108
-#: screens/Host/HostDetail/HostDetail.js:89
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:72
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:98
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:178
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:45
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:84
-#: screens/Job/JobDetail/JobDetail.js:538
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:398
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:120
-#: screens/Project/ProjectDetail/ProjectDetail.js:297
-#: screens/Team/TeamDetail/TeamDetail.js:48
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:358
-#: screens/User/UserDetail/UserDetail.js:84
-#: screens/User/UserTokenDetail/UserTokenDetail.js:66
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:204
-msgid "Last Modified"
-msgstr "Último modificado"
-
-#: components/AddRole/AddResourceRole.js:32
-#: components/AddRole/AddResourceRole.js:46
-#: components/ResourceAccessList/ResourceAccessList.js:182
-#: screens/User/UserDetail/UserDetail.js:65
-#: screens/User/UserList/UserList.js:128
-#: screens/User/UserList/UserList.js:162
-#: screens/User/UserList/UserListItem.js:54
-#: screens/User/shared/UserForm.js:69
-msgid "Last Name"
-msgstr "Apellido"
-
-#: components/TemplateList/TemplateList.js:245
-#: components/TemplateList/TemplateListItem.js:194
-msgid "Last Ran"
-msgstr "Último ejecutado"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:341
-msgid "Last Run"
-msgstr "Última ejecución"
-
-#: components/Lookup/HostFilterLookup.js:119
-msgid "Last job"
-msgstr "Última tarea"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:280
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:151
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:49
-#: screens/Project/ProjectList/ProjectListItem.js:308
-#: screens/TopologyView/Tooltip.js:331
-msgid "Last modified"
-msgstr "Última modificación"
-
-#: components/ResourceAccessList/ResourceAccessList.js:228
-#: components/ResourceAccessList/ResourceAccessListItem.js:68
-msgid "Last name"
-msgstr "Apellido"
-
-#: screens/TopologyView/Tooltip.js:337
-msgid "Last seen"
-msgstr "Última sincronización"
-
-#: screens/Project/ProjectList/ProjectListItem.js:313
-msgid "Last used"
-msgstr "Última utilización"
-
-#: components/AdHocCommands/useAdHocPreviewStep.js:22
-#: components/LaunchPrompt/steps/usePreviewStep.js:35
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:54
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:57
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:520
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:529
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:245
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:254
-msgid "Launch"
-msgstr "Ejecutar"
-
-#: components/TemplateList/TemplateListItem.js:214
-msgid "Launch Template"
-msgstr "Ejecutar plantilla"
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:32
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:34
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:46
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:47
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:87
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:90
-msgid "Launch management job"
-msgstr "Ejecutar tarea de gestión"
-
-#: components/TemplateList/TemplateListItem.js:222
-msgid "Launch template"
-msgstr "Ejecutar plantilla"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:119
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:120
-msgid "Launch workflow"
-msgstr "Ejecutar flujo de trabajo"
-
-#: components/LaunchPrompt/LaunchPrompt.js:130
-msgid "Launch | {0}"
-msgstr "Ejecutar | {0}"
-
-#: components/DetailList/LaunchedByDetail.js:54
-msgid "Launched By"
-msgstr "Ejecutado por"
-
-#: components/JobList/JobList.js:220
-msgid "Launched By (Username)"
-msgstr "Ejecutado por (nombre de usuario)"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:120
-msgid "Learn more about Automation Analytics"
-msgstr "Obtenga más información sobre Automation Analytics"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:69
-msgid "Leave this field blank to make the execution environment globally available."
-msgstr "Deje este campo en blanco para que el entorno de ejecución esté disponible globalmente."
-
-#: components/Workflow/WorkflowLegend.js:86
-#: screens/Metrics/LineChart.js:120
-#: screens/TopologyView/Header.js:102
-#: screens/TopologyView/Legend.js:67
-msgid "Legend"
-msgstr "Leyenda"
-
-#: components/Search/LookupTypeInput.js:120
-msgid "Less than comparison."
-msgstr "Menor que la comparación."
-
-#: components/Search/LookupTypeInput.js:127
-msgid "Less than or equal to comparison."
-msgstr "Menor o igual que la comparación."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:140
-#: components/AdHocCommands/AdHocDetailsStep.js:141
-#: components/JobList/JobList.js:238
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:65
-#: components/PromptDetail/PromptDetail.js:255
-#: components/PromptDetail/PromptJobTemplateDetail.js:153
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:90
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:473
-#: screens/Job/JobDetail/JobDetail.js:325
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:265
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:151
-#: screens/Template/shared/JobTemplateForm.js:429
-#: screens/Template/shared/WorkflowJobTemplateForm.js:159
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:238
-msgid "Limit"
-msgstr "Límite"
-
-#: screens/TopologyView/Legend.js:237
-msgid "Link state types"
-msgstr "Tipos de estado de los enlaces"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:261
-msgid "Link to an available node"
-msgstr "Enlace a un nodo disponible"
-
-#: screens/Instances/Shared/InstanceForm.js:40
-msgid "Listener Port"
-msgstr "Puerto de escucha"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:353
-msgid "Loading"
-msgstr "Cargando"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:49
-msgid "Local"
-msgstr "Local"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:343
-msgid "Local Time Zone"
-msgstr "Huso horario local"
-
-#: components/Schedule/shared/ScheduleFormFields.js:95
-msgid "Local time zone"
-msgstr "Huso horario local"
-
-#: screens/Login/Login.js:217
-msgid "Log In"
-msgstr "Iniciar sesión"
-
-#: screens/Setting/Settings.js:97
-msgid "Logging"
-msgstr "Registros"
-
-#: screens/Setting/SettingList.js:115
-msgid "Logging settings"
-msgstr "Configuración del registro"
-
-#: components/AppContainer/AppContainer.js:81
-#: components/AppContainer/AppContainer.js:150
-#: components/AppContainer/PageHeaderToolbar.js:169
-msgid "Logout"
-msgstr "Finalización de la sesión"
-
-#: components/Lookup/HostFilterLookup.js:367
-#: components/Lookup/Lookup.js:187
-msgid "Lookup modal"
-msgstr "Modal de búsqueda"
-
-#: components/Search/LookupTypeInput.js:22
-msgid "Lookup select"
-msgstr "Selección de búsqueda"
-
-#: components/Search/LookupTypeInput.js:31
-msgid "Lookup type"
-msgstr "Tipo de búsqueda"
-
-#: components/Search/LookupTypeInput.js:25
-msgid "Lookup typeahead"
-msgstr "Escritura anticipada de la búsqueda"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:155
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:34
-#: screens/Project/ProjectDetail/ProjectDetail.js:130
-#: screens/Project/ProjectList/ProjectListItem.js:68
-msgid "MOST RECENT SYNC"
-msgstr "ÚLTIMA SINCRONIZACIÓN"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:97
-#: components/AdHocCommands/AdHocCredentialStep.js:98
-#: components/AdHocCommands/AdHocCredentialStep.js:112
-#: screens/Job/JobDetail/JobDetail.js:407
-msgid "Machine Credential"
-msgstr "Credenciales de máquina"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:65
-msgid "Managed"
-msgstr "Gestionado"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:147
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:169
-msgid "Managed nodes"
-msgstr "Nodos gestionados"
-
-#: components/JobList/JobList.js:215
-#: components/JobList/JobListItem.js:46
-#: components/Schedule/ScheduleList/ScheduleListItem.js:39
-#: components/Workflow/WorkflowLegend.js:108
-#: components/Workflow/WorkflowNodeHelp.js:79
-#: screens/Job/JobDetail/JobDetail.js:69
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:102
-msgid "Management Job"
-msgstr "Trabajo de gestión"
-
-#: routeConfig.js:127
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:84
-msgid "Management Jobs"
-msgstr "Trabajos de gestión"
-
-#: screens/ManagementJob/ManagementJobs.js:20
-msgid "Management job"
-msgstr "Tarea de gestión"
-
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:109
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:110
-msgid "Management job launch error"
-msgstr "Error de ejecución de la tarea de gestión"
-
-#: screens/ManagementJob/ManagementJob.js:133
-msgid "Management job not found."
-msgstr "No se encontró la tarea de gestión."
-
-#: screens/ManagementJob/ManagementJobs.js:13
-msgid "Management jobs"
-msgstr "Tareas de gestión"
-
-#: components/Lookup/ProjectLookup.js:135
-#: components/PromptDetail/PromptProjectDetail.js:98
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:88
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:157
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:215
-#: screens/InstanceGroup/Instances/InstanceListItem.js:204
-#: screens/Instances/InstanceDetail/InstanceDetail.js:209
-#: screens/Instances/InstanceList/InstanceListItem.js:219
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:83
-#: screens/Job/JobDetail/JobDetail.js:74
-#: screens/Project/ProjectDetail/ProjectDetail.js:192
-#: screens/Project/ProjectList/ProjectList.js:197
-#: screens/Project/ProjectList/ProjectListItem.js:219
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:96
-msgid "Manual"
-msgstr "Manual"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:161
-#: components/Schedule/shared/FrequencyDetailSubform.js:108
-msgid "March"
-msgstr "Marzo"
-
-#: components/NotificationList/NotificationList.js:197
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:138
-msgid "Mattermost"
-msgstr "Mattermost"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:98
-#: screens/Organization/shared/OrganizationForm.js:71
-msgid "Max Hosts"
-msgstr "Número máximo de hosts"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:220
-msgid "Maximum"
-msgstr "Máximo"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:204
-msgid "Maximum length"
-msgstr "Longitud máxima"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:163
-#: components/Schedule/shared/FrequencyDetailSubform.js:118
-msgid "May"
-msgstr "Mayo"
-
-#: screens/Organization/OrganizationList/OrganizationList.js:144
-#: screens/Organization/OrganizationList/OrganizationListItem.js:63
-msgid "Members"
-msgstr "Miembros"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:47
-msgid "Metadata"
-msgstr "Metadatos"
-
-#: screens/Metrics/Metrics.js:207
-msgid "Metric"
-msgstr "Métrica"
-
-#: screens/Metrics/Metrics.js:179
-msgid "Metrics"
-msgstr "Métrica"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:100
-msgid "Microsoft Azure Resource Manager"
-msgstr "Microsoft Azure Resource Manager"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:214
-msgid "Minimum"
-msgstr "Mínimo"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:198
-msgid "Minimum length"
-msgstr "Longitud mínima"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:65
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:31
-msgid ""
-"Minimum number of instances that will be automatically\n"
-"assigned to this group when new instances come online."
-msgstr "Cantidad mínima de instancias que se asignará automáticamente a este grupo cuando aparezcan nuevas instancias en línea."
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:71
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:41
-msgid ""
-"Minimum percentage of all instances that will be automatically\n"
-"assigned to this group when new instances come online."
-msgstr "Porcentaje mínimo de todas las instancias que se asignará automáticamente\n"
-"a este grupo cuando aparezcan nuevas instancias en línea."
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:182
-#: components/Schedule/shared/FrequencyDetailSubform.js:173
-#: components/Schedule/shared/ScheduleFormFields.js:125
-#: components/Schedule/shared/ScheduleFormFields.js:185
-msgid "Minute"
-msgstr "Minuto"
-
-#: screens/Setting/Settings.js:100
-msgid "Miscellaneous Authentication"
-msgstr "Autenticación diversa"
-
-#: screens/Setting/SettingList.js:111
-msgid "Miscellaneous Authentication settings"
-msgstr "Varios ajustes de autenticación"
-
-#: screens/Setting/Settings.js:103
-msgid "Miscellaneous System"
-msgstr "Sistemas varios"
-
-#: screens/Setting/SettingList.js:107
-msgid "Miscellaneous System settings"
-msgstr "Configuración de sistemas varios"
-
-#: components/Workflow/WorkflowNodeHelp.js:120
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:87
-msgid "Missing"
-msgstr "No encontrado"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:66
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:109
-msgid "Missing resource"
-msgstr "Recurso no encontrado"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:192
-#: screens/User/UserTokenList/UserTokenList.js:154
-msgid "Modified"
-msgstr "Modificado"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:126
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:116
-#: components/AddRole/AddResourceRole.js:61
-#: components/AssociateModal/AssociateModal.js:148
-#: components/LaunchPrompt/steps/CredentialsStep.js:177
-#: components/LaunchPrompt/steps/InventoryStep.js:93
-#: components/Lookup/CredentialLookup.js:198
-#: components/Lookup/InventoryLookup.js:156
-#: components/Lookup/InventoryLookup.js:211
-#: components/Lookup/MultiCredentialsLookup.js:198
-#: components/Lookup/OrganizationLookup.js:138
-#: components/Lookup/ProjectLookup.js:147
-#: components/NotificationList/NotificationList.js:210
-#: components/RelatedTemplateList/RelatedTemplateList.js:170
-#: components/Schedule/ScheduleList/ScheduleList.js:202
-#: components/TemplateList/TemplateList.js:230
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:31
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:62
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:100
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:131
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:169
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:200
-#: screens/Credential/CredentialList/CredentialList.js:154
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:100
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:136
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:106
-#: screens/Host/HostGroups/HostGroupsList.js:168
-#: screens/Host/HostList/HostList.js:161
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:203
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:133
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:178
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:132
-#: screens/Inventory/InventoryList/InventoryList.js:203
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:189
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:98
-#: screens/Organization/OrganizationList/OrganizationList.js:135
-#: screens/Project/ProjectList/ProjectList.js:209
-#: screens/Team/TeamList/TeamList.js:134
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:165
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:108
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:112
-msgid "Modified By (Username)"
-msgstr "Modificado por (nombre de usuario)"
-
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:85
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:151
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:77
-msgid "Modified by (username)"
-msgstr "Modificado por (nombre de usuario)"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:59
-#: screens/Job/JobOutput/HostEventModal.js:125
-msgid "Module"
-msgstr "Módulo"
-
-#: screens/Job/JobDetail/JobDetail.js:530
-msgid "Module Arguments"
-msgstr "Argumentos del módulo"
-
-#: screens/Job/JobDetail/JobDetail.js:524
-msgid "Module Name"
-msgstr "Nombre del módulo"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:266
-msgid "Mon"
-msgstr "Lun"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:77
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:183
-#: components/Schedule/shared/FrequencyDetailSubform.js:271
-#: components/Schedule/shared/FrequencyDetailSubform.js:433
-msgid "Monday"
-msgstr "Lunes"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:186
-#: components/Schedule/shared/FrequencyDetailSubform.js:181
-#: components/Schedule/shared/ScheduleFormFields.js:129
-#: components/Schedule/shared/ScheduleFormFields.js:189
-msgid "Month"
-msgstr "Mes"
-
-#: components/Popover/Popover.js:32
-msgid "More information"
-msgstr "Más información"
-
-#: screens/Setting/shared/SharedFields.js:73
-msgid "More information for"
-msgstr "Más información para"
-
-#: screens/Template/Survey/SurveyReorderModal.js:162
-#: screens/Template/Survey/SurveyReorderModal.js:163
-msgid "Multi-Select"
-msgstr "Selección múltiple"
-
-#: screens/Template/Survey/SurveyReorderModal.js:146
-#: screens/Template/Survey/SurveyReorderModal.js:147
-msgid "Multiple Choice"
-msgstr "Selección múltiple"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:91
-msgid "Multiple Choice (multiple select)"
-msgstr "Opciones de selección múltiple"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:86
-msgid "Multiple Choice (single select)"
-msgstr "Selección múltiple"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:258
-msgid "Multiple Choice Options"
-msgstr "Opciones de selección múltiple"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:117
-#: components/AdHocCommands/AdHocCredentialStep.js:132
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:107
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:122
-#: components/AddRole/AddResourceRole.js:52
-#: components/AddRole/AddResourceRole.js:68
-#: components/AssociateModal/AssociateModal.js:139
-#: components/AssociateModal/AssociateModal.js:154
-#: components/HostForm/HostForm.js:96
-#: components/JobList/JobList.js:195
-#: components/JobList/JobList.js:244
-#: components/JobList/JobListItem.js:86
-#: components/LaunchPrompt/steps/CredentialsStep.js:168
-#: components/LaunchPrompt/steps/CredentialsStep.js:183
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:76
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:86
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:97
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:78
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:89
-#: components/LaunchPrompt/steps/InventoryStep.js:84
-#: components/LaunchPrompt/steps/InventoryStep.js:99
-#: components/Lookup/ApplicationLookup.js:100
-#: components/Lookup/ApplicationLookup.js:111
-#: components/Lookup/CredentialLookup.js:189
-#: components/Lookup/CredentialLookup.js:204
-#: components/Lookup/ExecutionEnvironmentLookup.js:177
-#: components/Lookup/ExecutionEnvironmentLookup.js:184
-#: components/Lookup/HostFilterLookup.js:93
-#: components/Lookup/HostFilterLookup.js:422
-#: components/Lookup/HostListItem.js:8
-#: components/Lookup/InstanceGroupsLookup.js:103
-#: components/Lookup/InstanceGroupsLookup.js:114
-#: components/Lookup/InventoryLookup.js:147
-#: components/Lookup/InventoryLookup.js:162
-#: components/Lookup/InventoryLookup.js:202
-#: components/Lookup/InventoryLookup.js:217
-#: components/Lookup/MultiCredentialsLookup.js:189
-#: components/Lookup/MultiCredentialsLookup.js:204
-#: components/Lookup/OrganizationLookup.js:129
-#: components/Lookup/OrganizationLookup.js:144
-#: components/Lookup/ProjectLookup.js:127
-#: components/Lookup/ProjectLookup.js:157
-#: components/NotificationList/NotificationList.js:181
-#: components/NotificationList/NotificationList.js:218
-#: components/NotificationList/NotificationListItem.js:28
-#: components/OptionsList/OptionsList.js:57
-#: components/PaginatedTable/PaginatedTable.js:72
-#: components/PromptDetail/PromptDetail.js:114
-#: components/RelatedTemplateList/RelatedTemplateList.js:161
-#: components/RelatedTemplateList/RelatedTemplateList.js:186
-#: components/ResourceAccessList/ResourceAccessListItem.js:58
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:325
-#: components/Schedule/ScheduleList/ScheduleList.js:168
-#: components/Schedule/ScheduleList/ScheduleList.js:189
-#: components/Schedule/ScheduleList/ScheduleListItem.js:86
-#: components/Schedule/shared/ScheduleFormFields.js:72
-#: components/TemplateList/TemplateList.js:205
-#: components/TemplateList/TemplateList.js:242
-#: components/TemplateList/TemplateListItem.js:142
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:18
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:37
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:49
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:68
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:80
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:110
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:122
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:137
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:149
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:179
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:191
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:206
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:60
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:109
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:135
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:28
-#: screens/Application/Applications.js:81
-#: screens/Application/ApplicationsList/ApplicationListItem.js:33
-#: screens/Application/ApplicationsList/ApplicationsList.js:118
-#: screens/Application/ApplicationsList/ApplicationsList.js:155
-#: screens/Application/shared/ApplicationForm.js:54
-#: screens/Credential/CredentialDetail/CredentialDetail.js:217
-#: screens/Credential/CredentialList/CredentialList.js:141
-#: screens/Credential/CredentialList/CredentialList.js:164
-#: screens/Credential/CredentialList/CredentialListItem.js:58
-#: screens/Credential/shared/CredentialForm.js:161
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:71
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:91
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:68
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:123
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:176
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:33
-#: screens/CredentialType/shared/CredentialTypeForm.js:21
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:49
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:136
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:165
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:69
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:89
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:115
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:12
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:89
-#: screens/Host/HostDetail/HostDetail.js:69
-#: screens/Host/HostGroups/HostGroupItem.js:28
-#: screens/Host/HostGroups/HostGroupsList.js:159
-#: screens/Host/HostGroups/HostGroupsList.js:176
-#: screens/Host/HostList/HostList.js:148
-#: screens/Host/HostList/HostList.js:169
-#: screens/Host/HostList/HostListItem.js:50
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:41
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:49
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:161
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:194
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:61
-#: screens/InstanceGroup/Instances/InstanceList.js:199
-#: screens/InstanceGroup/Instances/InstanceList.js:215
-#: screens/InstanceGroup/Instances/InstanceList.js:266
-#: screens/InstanceGroup/Instances/InstanceList.js:299
-#: screens/InstanceGroup/Instances/InstanceListItem.js:124
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:44
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:19
-#: screens/Instances/InstanceList/InstanceList.js:143
-#: screens/Instances/InstanceList/InstanceList.js:160
-#: screens/Instances/InstanceList/InstanceList.js:201
-#: screens/Instances/InstanceList/InstanceListItem.js:128
-#: screens/Instances/InstancePeers/InstancePeerList.js:80
-#: screens/Instances/InstancePeers/InstancePeerList.js:87
-#: screens/Instances/InstancePeers/InstancePeerList.js:96
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:37
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:89
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:31
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:194
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:209
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:215
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:34
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:119
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:141
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:74
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:36
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:169
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:186
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:33
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:119
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:138
-#: screens/Inventory/InventoryList/InventoryList.js:178
-#: screens/Inventory/InventoryList/InventoryList.js:209
-#: screens/Inventory/InventoryList/InventoryList.js:218
-#: screens/Inventory/InventoryList/InventoryListItem.js:92
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:180
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:195
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:232
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:181
-#: screens/Inventory/InventorySources/InventorySourceList.js:211
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:71
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:98
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:30
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:76
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:111
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:33
-#: screens/Inventory/shared/InventoryForm.js:50
-#: screens/Inventory/shared/InventoryGroupForm.js:32
-#: screens/Inventory/shared/InventorySourceForm.js:100
-#: screens/Inventory/shared/SmartInventoryForm.js:47
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:90
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:100
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:67
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:107
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:122
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:178
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:112
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:41
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:91
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:84
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:107
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.js:16
-#: screens/Organization/OrganizationList/OrganizationList.js:122
-#: screens/Organization/OrganizationList/OrganizationList.js:143
-#: screens/Organization/OrganizationList/OrganizationListItem.js:45
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:68
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:85
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:14
-#: screens/Organization/shared/OrganizationForm.js:56
-#: screens/Project/ProjectDetail/ProjectDetail.js:176
-#: screens/Project/ProjectList/ProjectList.js:185
-#: screens/Project/ProjectList/ProjectList.js:221
-#: screens/Project/ProjectList/ProjectListItem.js:179
-#: screens/Project/shared/ProjectForm.js:214
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:146
-#: screens/Team/TeamDetail/TeamDetail.js:37
-#: screens/Team/TeamList/TeamList.js:117
-#: screens/Team/TeamList/TeamList.js:142
-#: screens/Team/TeamList/TeamListItem.js:33
-#: screens/Team/shared/TeamForm.js:29
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:185
-#: screens/Template/Survey/SurveyList.js:102
-#: screens/Template/Survey/SurveyList.js:102
-#: screens/Template/Survey/SurveyListItem.js:39
-#: screens/Template/Survey/SurveyReorderModal.js:218
-#: screens/Template/Survey/SurveyReorderModal.js:218
-#: screens/Template/Survey/SurveyReorderModal.js:238
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:111
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:69
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:88
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:120
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:152
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:178
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:88
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.js:74
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.js:94
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:75
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:95
-#: screens/Template/shared/JobTemplateForm.js:243
-#: screens/Template/shared/WorkflowJobTemplateForm.js:109
-#: screens/User/UserOrganizations/UserOrganizationList.js:75
-#: screens/User/UserOrganizations/UserOrganizationList.js:79
-#: screens/User/UserOrganizations/UserOrganizationListItem.js:13
-#: screens/User/UserRoles/UserRolesList.js:155
-#: screens/User/UserRoles/UserRolesListItem.js:12
-#: screens/User/UserTeams/UserTeamList.js:180
-#: screens/User/UserTeams/UserTeamList.js:232
-#: screens/User/UserTeams/UserTeamListItem.js:18
-#: screens/User/UserTokenList/UserTokenListItem.js:22
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:140
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:123
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:163
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:43
-msgid "Name"
-msgstr "Nombre"
-
-#: components/AppContainer/AppContainer.js:95
-msgid "Navigation"
-msgstr "Navegación"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:221
-#: components/Schedule/shared/FrequencyDetailSubform.js:512
-#: screens/Dashboard/shared/ChartTooltip.js:106
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:57
-msgid "Never"
-msgstr "Nunca"
-
-#: components/Workflow/WorkflowNodeHelp.js:114
-msgid "Never Updated"
-msgstr "Nunca actualizado"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:47
-msgid "Never expires"
-msgstr "No expira nunca"
-
-#: components/JobList/JobList.js:227
-#: components/Workflow/WorkflowNodeHelp.js:90
-msgid "New"
-msgstr "Nuevo"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:51
-#: components/AdHocCommands/useAdHocCredentialStep.js:29
-#: components/AdHocCommands/useAdHocDetailsStep.js:40
-#: components/AdHocCommands/useAdHocExecutionEnvironmentStep.js:22
-#: components/AddRole/AddResourceRole.js:196
-#: components/AddRole/AddResourceRole.js:231
-#: components/LaunchPrompt/LaunchPrompt.js:160
-#: components/Schedule/shared/SchedulePromptableFields.js:127
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:66
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:59
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:132
-msgid "Next"
-msgstr "Siguiente"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:337
-#: components/Schedule/ScheduleList/ScheduleList.js:171
-#: components/Schedule/ScheduleList/ScheduleListItem.js:118
-#: components/Schedule/ScheduleList/ScheduleListItem.js:122
-msgid "Next Run"
-msgstr "Siguiente ejecución"
-
-#: components/Search/Search.js:232
-msgid "No"
-msgstr "No"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:121
-msgid "No Hosts Matched"
-msgstr "Ningún servidor corresponde"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:122
-#: screens/Job/JobOutput/JobOutputSearch.js:123
-msgid "No Hosts Remaining"
-msgstr "No más servidores"
-
-#: screens/Job/JobOutput/HostEventModal.js:150
-msgid "No JSON Available"
-msgstr "No hay ningún JSON disponible"
-
-#: screens/Dashboard/shared/ChartTooltip.js:82
-msgid "No Jobs"
-msgstr "No hay tareas"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:72
-msgid "No inventory sync failures."
-msgstr "No hay errores de sincronización de inventario."
-
-#: components/ContentEmpty/ContentEmpty.js:20
-msgid "No items found."
-msgstr "No se encontraron elementos."
-
-#: screens/Host/HostList/HostListItem.js:100
-msgid "No job data available"
-msgstr "No hay datos de tareas disponibles."
-
-#: screens/Job/JobOutput/EmptyOutput.js:52
-msgid "No output found for this job."
-msgstr "No se encontró una salida para este trabajo."
-
-#: screens/Job/JobOutput/HostEventModal.js:126
-msgid "No result found"
-msgstr "No se encontraron resultados"
-
-#: components/LabelSelect/LabelSelect.js:130
-#: components/LaunchPrompt/steps/SurveyStep.js:136
-#: components/LaunchPrompt/steps/SurveyStep.js:195
-#: components/MultiSelect/TagMultiSelect.js:60
-#: components/Search/AdvancedSearch.js:151
-#: components/Search/AdvancedSearch.js:266
-#: components/Search/LookupTypeInput.js:33
-#: components/Search/RelatedLookupTypeInput.js:26
-#: components/Search/Search.js:153
-#: components/Search/Search.js:202
-#: components/Search/Search.js:226
-#: screens/ActivityStream/ActivityStream.js:142
-#: screens/Credential/shared/CredentialForm.js:143
-#: screens/Credential/shared/CredentialFormFields/BecomeMethodField.js:65
-#: screens/Dashboard/DashboardGraph.js:106
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:137
-#: screens/Template/Survey/SurveyReorderModal.js:166
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:260
-#: screens/Template/shared/PlaybookSelect.js:72
-msgid "No results found"
-msgstr "No se encontraron resultados"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:116
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:137
-msgid "No subscriptions found"
-msgstr "No se encontraron suscripciones"
-
-#: screens/Template/Survey/SurveyList.js:147
-msgid "No survey questions found."
-msgstr "No se encontraron preguntas de la encuesta."
-
-#: screens/Job/JobDetail/JobDetail.js:399
-msgid "No timeout specified"
-msgstr "No se ha especificado el tiempo de espera"
-
-#: components/PaginatedTable/PaginatedTable.js:80
-msgid "No {pluralizedItemName} Found"
-msgstr "No se ha encontrado {pluralizedItemName}"
-
-#: components/Workflow/WorkflowNodeHelp.js:148
-#: components/Workflow/WorkflowNodeHelp.js:184
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:273
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:274
-msgid "Node Alias"
-msgstr "Alias del nodo"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:223
-#: screens/InstanceGroup/Instances/InstanceList.js:204
-#: screens/InstanceGroup/Instances/InstanceList.js:268
-#: screens/InstanceGroup/Instances/InstanceList.js:300
-#: screens/InstanceGroup/Instances/InstanceListItem.js:142
-#: screens/Instances/InstanceDetail/InstanceDetail.js:204
-#: screens/Instances/InstanceList/InstanceList.js:148
-#: screens/Instances/InstanceList/InstanceList.js:203
-#: screens/Instances/InstanceList/InstanceListItem.js:150
-#: screens/Instances/InstancePeers/InstancePeerList.js:98
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:59
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:118
-msgid "Node Type"
-msgstr "Tipo de nodo"
-
-#: screens/TopologyView/Legend.js:107
-msgid "Node state types"
-msgstr "Tipos de estado de los nodos"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.js:75
-msgid "Node type"
-msgstr "Tipo de nodo"
-
-#: screens/TopologyView/Legend.js:70
-msgid "Node types"
-msgstr "Tipos de nodo"
-
-#: components/Schedule/shared/ScheduleFormFields.js:180
-#: components/Schedule/shared/ScheduleFormFields.js:184
-#: components/Workflow/WorkflowNodeHelp.js:123
-msgid "None"
-msgstr "Ninguno"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:193
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:196
-msgid "None (Run Once)"
-msgstr "Ninguno (se ejecuta una vez)"
-
-#: components/Schedule/shared/ScheduleFormFields.js:120
-#: components/Schedule/shared/ScheduleFormFields.js:124
-msgid "None (run once)"
-msgstr "Ninguno (se ejecuta una vez)"
-
-#: screens/User/UserDetail/UserDetail.js:51
-#: screens/User/UserList/UserListItem.js:23
-#: screens/User/shared/UserForm.js:29
-msgid "Normal User"
-msgstr "Usuario normal"
-
-#: components/ContentError/ContentError.js:37
-msgid "Not Found"
-msgstr "No encontrado"
-
-#: screens/Setting/shared/SettingDetail.js:71
-#: screens/Setting/shared/SettingDetail.js:112
-msgid "Not configured"
-msgstr "No configurado"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:75
-msgid "Not configured for inventory sync."
-msgstr "No configurado para la sincronización de inventario."
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:248
-msgid ""
-"Note that only hosts directly in this group can\n"
-"be disassociated. Hosts in sub-groups must be disassociated\n"
-"directly from the sub-group level that they belong."
-msgstr "Tenga en cuenta que solo se pueden disociar los hosts asociados\n"
-"directamente a este grupo. Los hosts en subgrupos deben ser disociados\n"
-"del nivel de subgrupo al que pertenecen."
-
-#: screens/Host/HostGroups/HostGroupsList.js:212
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:230
-msgid ""
-"Note that you may still see the group in the list after\n"
-"disassociating if the host is also a member of that group’s\n"
-"children. This list shows all groups the host is associated\n"
-"with directly and indirectly."
-msgstr "Tenga en cuenta que puede seguir viendo el grupo en la lista después de la disociación si el host también es un miembro de los elementos secundarios de ese grupo. Esta lista muestra todos los grupos a los que está asociado el host\n"
-"directa e indirectamente."
-
-#: components/Lookup/InstanceGroupsLookup.js:90
-msgid "Note: The order in which these are selected sets the execution precedence. Select more than one to enable drag."
-msgstr "Nota: El orden en que se seleccionan establece la precedencia de ejecución. Seleccione más de uno para habilitar el arrastre."
-
-#: screens/Organization/shared/OrganizationForm.js:116
-msgid "Note: The order of these credentials sets precedence for the sync and lookup of the content. Select more than one to enable drag."
-msgstr "Nota: El orden de estas credenciales establece la precedencia para la sincronización y búsqueda del contenido. Seleccione más de una para habilitar el arrastre."
-
-#: screens/Project/shared/Project.helptext.js:81
-msgid "Note: This field assumes the remote name is \"origin\"."
-msgstr "Nota: Este campo asume que el nombre remoto es \"origin\"."
-
-#: screens/Project/shared/Project.helptext.js:35
-msgid ""
-"Note: When using SSH protocol for GitHub or\n"
-"Bitbucket, enter an SSH key only, do not enter a username\n"
-"(other than git). Additionally, GitHub and Bitbucket do\n"
-"not support password authentication when using SSH. GIT\n"
-"read only protocol (git://) does not use username or\n"
-"password information."
-msgstr "Note: Si utiliza el protocolo SSH para GitHub o Bitbucket,\n"
-"ingrese solo la clave de SSH; no ingrese un nombre de usuario\n"
-"(distinto de git). Además, GitHub y Bitbucket no admiten\n"
-"la autenticación de contraseña cuando se utiliza SSH. El protocolo\n"
-"de solo lectura de GIT (git://) no utiliza información\n"
-"de nombre de usuario o contraseña."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:331
-msgid "Notification Color"
-msgstr "Color de notificación"
-
-#: screens/NotificationTemplate/NotificationTemplate.js:58
-#: screens/NotificationTemplate/NotificationTemplateAdd.js:50
-msgid "Notification Template not found."
-msgstr "No se encontró ninguna plantilla de notificación."
-
-#: screens/ActivityStream/ActivityStream.js:198
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:117
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:171
-#: screens/NotificationTemplate/NotificationTemplates.js:14
-#: screens/NotificationTemplate/NotificationTemplates.js:21
-#: util/getRelatedResourceDeleteDetails.js:181
-msgid "Notification Templates"
-msgstr "Plantillas de notificación"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:134
-msgid "Notification Type"
-msgstr "Tipo de notificación"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:380
-msgid "Notification color"
-msgstr "Color de la notificación"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:193
-msgid "Notification sent successfully"
-msgstr "Notificación enviada correctamente"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:449
-msgid "Notification test failed."
-msgstr "Error en la prueba de notificación."
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:197
-msgid "Notification timed out"
-msgstr "Caducó el tiempo de la notificación"
-
-#: components/NotificationList/NotificationList.js:190
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:131
-msgid "Notification type"
-msgstr "Tipo de notificación"
-
-#: components/NotificationList/NotificationList.js:177
-#: routeConfig.js:122
-#: screens/Inventory/Inventories.js:93
-#: screens/Inventory/InventorySource/InventorySource.js:99
-#: screens/ManagementJob/ManagementJob.js:116
-#: screens/ManagementJob/ManagementJobs.js:22
-#: screens/Organization/Organization.js:135
-#: screens/Organization/Organizations.js:33
-#: screens/Project/Project.js:114
-#: screens/Project/Projects.js:28
-#: screens/Template/Template.js:141
-#: screens/Template/Templates.js:46
-#: screens/Template/WorkflowJobTemplate.js:123
-msgid "Notifications"
-msgstr "Notificación"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:169
-#: components/Schedule/shared/FrequencyDetailSubform.js:148
-msgid "November"
-msgstr "Noviembre"
-
-#: components/StatusLabel/StatusLabel.js:44
-#: components/Workflow/WorkflowNodeHelp.js:117
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:66
-#: screens/Job/JobOutput/shared/HostStatusBar.js:35
-msgid "OK"
-msgstr "OK"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:42
-#: components/Schedule/shared/FrequencyDetailSubform.js:549
-msgid "Occurrences"
-msgstr "Ocurrencias"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:168
-#: components/Schedule/shared/FrequencyDetailSubform.js:143
-msgid "October"
-msgstr "Octubre"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:189
-#: components/HostToggle/HostToggle.js:61
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:192
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:195
-#: components/PromptDetail/PromptDetail.js:362
-#: components/PromptDetail/PromptJobTemplateDetail.js:156
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:489
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:58
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:45
-#: screens/Setting/shared/SettingDetail.js:98
-#: screens/Setting/shared/SharedFields.js:150
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:284
-#: screens/Template/shared/JobTemplateForm.js:504
-msgid "Off"
-msgstr "Off"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:188
-#: components/HostToggle/HostToggle.js:60
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:192
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:194
-#: components/PromptDetail/PromptDetail.js:362
-#: components/PromptDetail/PromptJobTemplateDetail.js:156
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:489
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:57
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:45
-#: screens/Setting/shared/SettingDetail.js:98
-#: screens/Setting/shared/SharedFields.js:149
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:284
-#: screens/Template/shared/JobTemplateForm.js:504
-msgid "On"
-msgstr "On"
-
-#: components/Workflow/WorkflowLegend.js:126
-#: components/Workflow/WorkflowLinkHelp.js:30
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:39
-msgid "On Failure"
-msgstr "Con error"
-
-#: components/Workflow/WorkflowLegend.js:122
-#: components/Workflow/WorkflowLinkHelp.js:27
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:63
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:32
-msgid "On Success"
-msgstr "Con éxito"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:536
-msgid "On date"
-msgstr "En la fecha"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:99
-#: components/Schedule/shared/FrequencyDetailSubform.js:251
-msgid "On days"
-msgstr "En los días"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:18
-msgid ""
-"One Slack channel per line. The pound symbol (#)\n"
-"is required for channels. To respond to or start a thread to a specific message add the parent message Id to the channel where the parent message Id is 16 digits. A dot (.) must be manually inserted after the 10th digit. ie:#destination-channel, 1231257890.006423. See Slack"
-msgstr "Ingrese un canal de Slack por línea. Se requiere el símbolo numeral (#) para los canales. Para responder a una conversación o iniciar una en un mensaje específico, agregue el Id. del mensaje principal al canal donde se encuentra el mensaje principal de 16 dígitos. Debe insertarse un punto (.) manualmente después del décimo dígito. por ejemplo:#destino-canal, 1231257890.006423. Consulte Slack"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:154
-msgid "Only Group By"
-msgstr "Agrupar solo por"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:103
-msgid "OpenStack"
-msgstr "OpenStack"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:111
-msgid "Option Details"
-msgstr "Detalles de la opción"
-
-#: screens/Inventory/shared/Inventory.helptext.js:25
-msgid ""
-"Optional labels that describe this inventory,\n"
-"such as 'dev' or 'test'. Labels can be used to group and filter\n"
-"inventories and completed jobs."
-msgstr "Etiquetas opcionales que describen este inventario,\n"
-"como 'dev' o 'test'. Las etiquetas se pueden usar para agrupar\n"
-"y filtrar inventarios y tareas completadas."
-
-#: screens/Job/Job.helptext.js:12
-#: screens/Template/shared/JobTemplate.helptext.js:13
-msgid "Optional labels that describe this job template, such as 'dev' or 'test'. Labels can be used to group and filter job templates and completed jobs."
-msgstr "Etiquetas opcionales que describen esta plantilla de trabajo, como puede ser 'dev' o 'test'. Las etiquetas pueden ser utilizadas para agrupar y filtrar plantillas de trabajo y tareas completadas."
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:11
-msgid ""
-"Optional labels that describe this workflow job template,\n"
-"such as 'dev' or 'test'. Labels can be used to group and filter\n"
-"workflow job templates and completed jobs."
-msgstr "Etiquetas opcionales que describen esta plantilla de trabajo,\n"
-"como 'dev' o 'test'. Las etiquetas se pueden usar para agrupar\n"
-"y filtrar plantillas de trabajo y tareas completadas."
-
-#: screens/Template/shared/JobTemplate.helptext.js:26
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:19
-msgid "Optionally select the credential to use to send status updates back to the webhook service."
-msgstr "Opcionalmente, seleccione la credencial que se usará para devolver las actualizaciones de estado al servicio de Webhook."
-
-#: components/NotificationList/NotificationList.js:220
-#: components/NotificationList/NotificationListItem.js:34
-#: screens/Credential/shared/TypeInputsSubForm.js:47
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:61
-#: screens/Instances/Shared/InstanceForm.js:54
-#: screens/Inventory/shared/InventoryForm.js:94
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:69
-#: screens/Template/shared/JobTemplateForm.js:545
-#: screens/Template/shared/WorkflowJobTemplateForm.js:241
-msgid "Options"
-msgstr "Opciones"
-
-#: screens/Template/Survey/SurveyReorderModal.js:217
-#: screens/Template/Survey/SurveyReorderModal.js:217
-#: screens/Template/Survey/SurveyReorderModal.js:233
-msgid "Order"
-msgstr "Pedir"
-
-#: components/Lookup/ApplicationLookup.js:119
-#: components/Lookup/OrganizationLookup.js:101
-#: components/Lookup/OrganizationLookup.js:107
-#: components/Lookup/OrganizationLookup.js:124
-#: components/PromptDetail/PromptInventorySourceDetail.js:62
-#: components/PromptDetail/PromptInventorySourceDetail.js:72
-#: components/PromptDetail/PromptJobTemplateDetail.js:105
-#: components/PromptDetail/PromptJobTemplateDetail.js:115
-#: components/PromptDetail/PromptProjectDetail.js:77
-#: components/PromptDetail/PromptProjectDetail.js:88
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:67
-#: components/TemplateList/TemplateList.js:244
-#: components/TemplateList/TemplateListItem.js:185
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:70
-#: screens/Application/ApplicationsList/ApplicationListItem.js:38
-#: screens/Application/ApplicationsList/ApplicationsList.js:157
-#: screens/Credential/CredentialDetail/CredentialDetail.js:230
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:70
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:155
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:167
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:76
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:96
-#: screens/Inventory/InventoryList/InventoryList.js:191
-#: screens/Inventory/InventoryList/InventoryList.js:221
-#: screens/Inventory/InventoryList/InventoryListItem.js:119
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:202
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:107
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:121
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:131
-#: screens/Project/ProjectDetail/ProjectDetail.js:180
-#: screens/Project/ProjectList/ProjectListItem.js:287
-#: screens/Project/ProjectList/ProjectListItem.js:298
-#: screens/Team/TeamDetail/TeamDetail.js:40
-#: screens/Team/TeamList/TeamList.js:143
-#: screens/Team/TeamList/TeamListItem.js:38
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:199
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:210
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:120
-#: screens/User/UserTeams/UserTeamList.js:181
-#: screens/User/UserTeams/UserTeamList.js:237
-#: screens/User/UserTeams/UserTeamListItem.js:23
-msgid "Organization"
-msgstr "Organización"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:100
-msgid "Organization (Name)"
-msgstr "Organización (Nombre)"
-
-#: screens/Team/TeamList/TeamList.js:126
-msgid "Organization Name"
-msgstr "Nombre de la organización"
-
-#: screens/Organization/Organization.js:154
-msgid "Organization not found."
-msgstr "No se encontró la organización."
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:188
-#: routeConfig.js:96
-#: screens/ActivityStream/ActivityStream.js:181
-#: screens/Organization/OrganizationList/OrganizationList.js:117
-#: screens/Organization/OrganizationList/OrganizationList.js:163
-#: screens/Organization/Organizations.js:16
-#: screens/Organization/Organizations.js:26
-#: screens/User/User.js:66
-#: screens/User/UserOrganizations/UserOrganizationList.js:72
-#: screens/User/Users.js:33
-#: util/getRelatedResourceDeleteDetails.js:232
-#: util/getRelatedResourceDeleteDetails.js:266
-msgid "Organizations"
-msgstr "Organizaciones"
-
-#: components/LaunchPrompt/steps/useOtherPromptsStep.js:90
-msgid "Other prompts"
-msgstr "Otros avisos"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:79
-msgid "Out of compliance"
-msgstr "No cumple con los requisitos"
-
-#: screens/Job/Job.js:131
-#: screens/Job/JobOutput/HostEventModal.js:156
-#: screens/Job/Jobs.js:34
-msgid "Output"
-msgstr "Salida"
-
-#: screens/Job/JobOutput/HostEventModal.js:157
-msgid "Output tab"
-msgstr "Salida"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:80
-msgid "Overwrite"
-msgstr "Anular"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:41
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:121
-msgid "Overwrite local groups and hosts from remote inventory source"
-msgstr "Sobrescribir grupos locales y servidores desde una fuente remota del inventario."
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:46
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:127
-msgid "Overwrite local variables from remote inventory source"
-msgstr "Sobrescribir las variables locales desde una fuente remota del inventario."
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:86
-msgid "Overwrite variables"
-msgstr "Anular variables"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:493
-msgid "POST"
-msgstr "PUBLICAR"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:494
-msgid "PUT"
-msgstr "COLOCAR"
-
-#: components/NotificationList/NotificationList.js:198
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:139
-msgid "Pagerduty"
-msgstr "Pagerduty"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:279
-msgid "Pagerduty Subdomain"
-msgstr "Subdominio Pagerduty"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:298
-msgid "Pagerduty subdomain"
-msgstr "Subdominio Pagerduty"
-
-#: components/Pagination/Pagination.js:35
-msgid "Pagination"
-msgstr "Paginación"
-
-#: components/Workflow/WorkflowTools.js:165
-msgid "Pan Down"
-msgstr "Desplazar hacia abajo"
-
-#: components/Workflow/WorkflowTools.js:132
-msgid "Pan Left"
-msgstr "Desplazar hacia la izquierda"
-
-#: components/Workflow/WorkflowTools.js:176
-msgid "Pan Right"
-msgstr "Desplazar hacia la derecha"
-
-#: components/Workflow/WorkflowTools.js:143
-msgid "Pan Up"
-msgstr "Desplazar hacia arriba"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:243
-msgid "Pass extra command line changes. There are two ansible command line parameters:"
-msgstr "Trasladar cambios adicionales en la línea de comandos. Hay dos parámetros de línea de comandos de Ansible:"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:14
-msgid "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. Refer to the Ansible Controller documentation for example syntax."
-msgstr "Traslade variables de línea de comando adicionales al cuaderno de estrategias. Este es el parámetro de línea de comando -e o --extra-vars para el cuaderno de estrategias de Ansible. Proporcione pares de claves/valores utilizando YAML o JSON. Consulte la documentación de Ansible Tower para ver ejemplos de sintaxis."
-
-#: screens/Job/Job.helptext.js:13
-#: screens/Template/shared/JobTemplate.helptext.js:14
-msgid "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. Refer to the documentation for example syntax."
-msgstr "Traslade variables de línea de comando adicionales al playbook. Este es el\n"
-"parámetro de línea de comando -e o --extra-vars para el playbook de Ansible.\n"
-"Proporcione pares de claves/valores utilizando YAML o JSON. Consulte la\n"
-"documentación para ver ejemplos de sintaxis."
-
-#: screens/Login/Login.js:227
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:73
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:101
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:212
-#: screens/Template/Survey/SurveyQuestionForm.js:82
-#: screens/User/shared/UserForm.js:88
-msgid "Password"
-msgstr "Contraseña"
-
-#: screens/Dashboard/DashboardGraph.js:119
-msgid "Past 24 hours"
-msgstr "Últimas 24 horas"
-
-#: screens/Dashboard/DashboardGraph.js:110
-msgid "Past month"
-msgstr "Mes pasado"
-
-#: screens/Dashboard/DashboardGraph.js:113
-msgid "Past two weeks"
-msgstr "Últimas dos semanas"
-
-#: screens/Dashboard/DashboardGraph.js:116
-msgid "Past week"
-msgstr "Semana pasada"
-
-#: screens/Instances/Instance.js:51
-#: screens/Instances/InstancePeers/InstancePeerList.js:74
-msgid "Peers"
-msgstr "Colegas"
-
-#: components/JobList/JobList.js:228
-#: components/StatusLabel/StatusLabel.js:49
-#: components/Workflow/WorkflowNodeHelp.js:93
-msgid "Pending"
-msgstr "Pendiente"
-
-#: components/AppContainer/PageHeaderToolbar.js:76
-msgid "Pending Workflow Approvals"
-msgstr "Aprobaciones de flujos de trabajo pendientes"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:128
-msgid "Pending delete"
-msgstr "Eliminación pendiente"
-
-#: components/Lookup/HostFilterLookup.js:370
-msgid "Perform a search to define a host filter"
-msgstr "Realice una búsqueda para definir un filtro de host"
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:73
-#: screens/User/UserTokenList/UserTokenList.js:105
-msgid "Personal Access Token"
-msgstr "Token de acceso personal"
-
-#: screens/User/UserTokenList/UserTokenListItem.js:26
-msgid "Personal access token"
-msgstr "Token de acceso personal"
-
-#: screens/Job/JobOutput/HostEventModal.js:122
-msgid "Play"
-msgstr "Jugada"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:84
-msgid "Play Count"
-msgstr "Recuento de jugadas"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:124
-msgid "Play Started"
-msgstr "Jugada iniciada"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:148
-#: screens/Job/JobDetail/JobDetail.js:319
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:253
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:43
-#: screens/Template/shared/JobTemplateForm.js:357
-msgid "Playbook"
-msgstr "Playbook"
-
-#: components/JobList/JobListItem.js:44
-#: screens/Job/JobDetail/JobDetail.js:67
-msgid "Playbook Check"
-msgstr "Comprobación del playbook"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:125
-msgid "Playbook Complete"
-msgstr "Playbook terminado"
-
-#: components/PromptDetail/PromptProjectDetail.js:165
-#: screens/Project/ProjectDetail/ProjectDetail.js:288
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:72
-msgid "Playbook Directory"
-msgstr "Directorio de playbook"
-
-#: components/JobList/JobList.js:213
-#: components/JobList/JobListItem.js:44
-#: components/Schedule/ScheduleList/ScheduleListItem.js:37
-#: screens/Job/JobDetail/JobDetail.js:67
-msgid "Playbook Run"
-msgstr "Ejecución de playbook"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:126
-msgid "Playbook Started"
-msgstr "Playbook iniciado"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:174
-#: components/TemplateList/TemplateList.js:222
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:23
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:54
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:157
-msgid "Playbook name"
-msgstr "Nombre del playbook"
-
-#: screens/Dashboard/DashboardGraph.js:146
-msgid "Playbook run"
-msgstr "Ejecución de playbook"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:85
-msgid "Plays"
-msgstr "Jugadas"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:149
-msgid "Please add a Schedule to populate this list."
-msgstr "Añada un horario para rellenar esta lista."
-
-#: components/Schedule/ScheduleList/ScheduleList.js:152
-msgid "Please add a Schedule to populate this list. Schedules can be added to a Template, Project, or Inventory Source."
-msgstr "Añada un horario para rellenar esta lista. Las programaciones pueden añadirse a una plantilla, un proyecto o una fuente de inventario."
-
-#: screens/Template/Survey/SurveyList.js:146
-msgid "Please add survey questions."
-msgstr "Agregue preguntas de la encuesta."
-
-#: components/PaginatedTable/PaginatedTable.js:93
-msgid "Please add {pluralizedItemName} to populate this list"
-msgstr "Añada {pluralizedItemName} para poblar esta lista"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:43
-msgid "Please click the Start button to begin."
-msgstr "Haga clic en el botón de inicio para comenzar."
-
-#: components/Schedule/shared/ScheduleForm.js:421
-msgid "Please enter a number of occurrences."
-msgstr "Por favor, introduzca un número de ocurrencias."
-
-#: util/validators.js:160
-msgid "Please enter a valid URL"
-msgstr "Introduzca una URL válida."
-
-#: screens/User/shared/UserTokenForm.js:20
-msgid "Please enter a value."
-msgstr "Por favor introduzca un valor."
-
-#: screens/Login/Login.js:191
-msgid "Please log in"
-msgstr "Inicie sesión"
-
-#: components/JobList/JobList.js:190
-msgid "Please run a job to populate this list."
-msgstr "Ejecute un trabajo para rellenar esta lista."
-
-#: components/Schedule/shared/ScheduleForm.js:417
-msgid "Please select a day number between 1 and 31."
-msgstr "Seleccione un número de día entre 1 y 31."
-
-#: screens/Template/shared/JobTemplateForm.js:174
-msgid "Please select an Inventory or check the Prompt on Launch option"
-msgstr "Seleccione un inventario o marque la opción Preguntar al ejecutar."
-
-#: components/Schedule/shared/ScheduleForm.js:429
-msgid "Please select an end date/time that comes after the start date/time."
-msgstr "Seleccione una fecha/hora de finalización que sea posterior a la fecha/hora de inicio."
-
-#: components/Lookup/HostFilterLookup.js:359
-msgid "Please select an organization before editing the host filter"
-msgstr "Seleccione una organización antes de modificar el filtro del host"
-
-#: screens/Job/JobOutput/EmptyOutput.js:32
-msgid "Please try another search using the filter above"
-msgstr "Intente otra búsqueda con el filtro de arriba"
-
-#: screens/TopologyView/ContentLoading.js:40
-msgid "Please wait until the topology view is populated..."
-msgstr "Espere hasta que se complete la vista de topología..."
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:78
-msgid "Pod spec override"
-msgstr "Anulación de las especificaciones del pod"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:214
-#: screens/InstanceGroup/Instances/InstanceListItem.js:203
-#: screens/Instances/InstanceDetail/InstanceDetail.js:208
-#: screens/Instances/InstanceList/InstanceListItem.js:218
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:82
-msgid "Policy Type"
-msgstr "Tipo de política"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:63
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:26
-msgid "Policy instance minimum"
-msgstr "Mínimo de instancias de políticas"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:70
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:36
-msgid "Policy instance percentage"
-msgstr "Porcentaje de instancias de políticas"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginField.js:64
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginField.js:70
-msgid "Populate field from an external secret management system"
-msgstr "Completar el campo desde un sistema externo de gestión de claves secretas"
-
-#: components/Lookup/HostFilterLookup.js:349
-msgid ""
-"Populate the hosts for this inventory by using a search\n"
-"filter. Example: ansible_facts__ansible_distribution:\"RedHat\".\n"
-"Refer to the documentation for further syntax and\n"
-"examples. Refer to the Ansible Controller documentation for further syntax and\n"
-"examples."
-msgstr "Complete los hosts para este inventario utilizando un filtro de búsqueda\n"
-"de búsqueda. Ejemplo: ansible_facts.ansible_distribution: \"RedHat\".\n"
-"Consulte la documentación para obtener más sintaxis y ejemplos. Consulte la documentación de Ansible Tower para obtener más sintaxis y\n"
-"ejemplos."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:165
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:104
-msgid "Port"
-msgstr "Puerto"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:231
-msgid "Preconditions for running this node when there are multiple parents. Refer to the"
-msgstr "Condiciones previas para ejecutar este nodo cuando hay varios elementos primarios. Consulte"
-
-#: screens/Template/Survey/MultipleChoiceField.js:59
-msgid ""
-"Press 'Enter' to add more answer choices. One answer\n"
-"choice per line."
-msgstr "Presione 'Intro' para agregar más opciones de respuesta. Una opción de respuesta por línea."
-
-#: components/CodeEditor/CodeEditor.js:181
-msgid "Press Enter to edit. Press ESC to stop editing."
-msgstr "Presione Intro para modificar. Presione ESC para detener la edición."
-
-#: components/SelectedList/DraggableSelectedList.js:85
-msgid ""
-"Press space or enter to begin dragging,\n"
-"and use the arrow keys to navigate up or down.\n"
-"Press enter to confirm the drag, or any other key to\n"
-"cancel the drag operation."
-msgstr "Pulse la barra espaciadora o Intro para empezar a arrastrar,\n"
-"y utilice las teclas de flecha para desplazarse hacia arriba o hacia abajo.\n"
-"Pulse Intro para confirmar el arrastre, o cualquier otra tecla para cancelar la operación de arrastre."
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:71
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:130
-#: screens/Inventory/shared/InventoryForm.js:99
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:147
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:347
-#: screens/Template/shared/JobTemplateForm.js:603
-msgid "Prevent Instance Group Fallback"
-msgstr "Evitar el retroceso del grupo de instancias"
-
-#: screens/Inventory/shared/Inventory.helptext.js:197
-msgid "Prevent Instance Group Fallback: If enabled, the inventory will prevent adding any organization instance groups to the list of preferred instances groups to run associated job templates on."
-msgstr "Impedir la retroalimentación del grupo de instancias: Si se habilita, el inventario impedirá añadir cualquier grupo de instancias de la organización a la lista de grupos de instancias preferidos para ejecutar las plantillas de trabajo asociadas."
-
-#: screens/Template/shared/JobTemplate.helptext.js:43
-msgid "Prevent Instance Group Fallback: If enabled, the job template will prevent adding any inventory or organization instance groups to the list of preferred instances groups to run on."
-msgstr "Impedir el retroceso del grupo de instancias: Si está activada, la plantilla de trabajo impedirá que se añada cualquier grupo de instancias del inventario o de la organización a la lista de grupos de instancias preferidos para ejecutar."
-
-#: components/AdHocCommands/useAdHocPreviewStep.js:17
-#: components/LaunchPrompt/steps/usePreviewStep.js:23
-msgid "Preview"
-msgstr "Vista previa"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:103
-msgid "Private key passphrase"
-msgstr "Frase de paso para llave privada"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:58
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:122
-#: screens/Template/shared/JobTemplateForm.js:551
-msgid "Privilege Escalation"
-msgstr "Elevación de privilegios"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:111
-msgid "Privilege escalation password"
-msgstr "Contraseña para la elevación de privilegios"
-
-#: screens/Template/shared/JobTemplate.helptext.js:40
-msgid "Privilege escalation: If enabled, run this playbook as an administrator."
-msgstr "Si se habilita esta opción, ejecute este playbook\n"
-"como administrador."
-
-#: components/JobList/JobListItem.js:239
-#: components/Lookup/ProjectLookup.js:104
-#: components/Lookup/ProjectLookup.js:109
-#: components/Lookup/ProjectLookup.js:166
-#: components/PromptDetail/PromptInventorySourceDetail.js:87
-#: components/PromptDetail/PromptJobTemplateDetail.js:133
-#: components/PromptDetail/PromptJobTemplateDetail.js:141
-#: components/TemplateList/TemplateListItem.js:300
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:216
-#: screens/Job/JobDetail/JobDetail.js:172
-#: screens/Job/JobDetail/JobDetail.js:198
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:229
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:239
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:38
-msgid "Project"
-msgstr "Proyecto"
-
-#: components/PromptDetail/PromptProjectDetail.js:158
-#: screens/Project/ProjectDetail/ProjectDetail.js:281
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:61
-msgid "Project Base Path"
-msgstr "Ruta base del proyecto"
-
-#: components/Workflow/WorkflowLegend.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:85
-msgid "Project Sync"
-msgstr "Sincronización del proyecto"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:320
-#: screens/Project/ProjectList/ProjectListItem.js:229
-msgid "Project Sync Error"
-msgstr "Error en la sincronización del proyecto"
-
-#: components/Workflow/WorkflowNodeHelp.js:67
-msgid "Project Update"
-msgstr "Actualización del proyecto"
-
-#: screens/Job/JobDetail/JobDetail.js:180
-msgid "Project Update Status"
-msgstr "Actualización del proyecto"
-
-#: screens/Job/Job.helptext.js:22
-msgid "Project checkout results"
-msgstr "Ver resultados de verificación del proyecto"
-
-#: screens/Project/ProjectList/ProjectList.js:132
-msgid "Project copied successfully"
-msgstr "El proyecto se copió correctamente"
-
-#: screens/Project/Project.js:136
-msgid "Project not found."
-msgstr "No se encontró el proyecto."
-
-#: screens/Dashboard/Dashboard.js:109
-msgid "Project sync failures"
-msgstr "Errores de sincronización del proyecto"
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:146
-#: routeConfig.js:75
-#: screens/ActivityStream/ActivityStream.js:170
-#: screens/Dashboard/Dashboard.js:103
-#: screens/Project/ProjectList/ProjectList.js:180
-#: screens/Project/ProjectList/ProjectList.js:249
-#: screens/Project/Projects.js:12
-#: screens/Project/Projects.js:22
-#: util/getRelatedResourceDeleteDetails.js:60
-#: util/getRelatedResourceDeleteDetails.js:195
-#: util/getRelatedResourceDeleteDetails.js:225
-msgid "Projects"
-msgstr "Proyectos"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:139
-msgid "Promote Child Groups and Hosts"
-msgstr "Promover grupos secundarios y hosts"
-
-#: components/Schedule/shared/ScheduleForm.js:540
-#: components/Schedule/shared/ScheduleForm.js:543
-msgid "Prompt"
-msgstr "Aviso"
-
-#: components/PromptDetail/PromptDetail.js:182
-msgid "Prompt Overrides"
-msgstr "Anulaciones de avisos"
-
-#: components/CodeEditor/VariablesField.js:241
-#: components/FieldWithPrompt/FieldWithPrompt.js:46
-#: screens/Credential/CredentialDetail/CredentialDetail.js:175
-msgid "Prompt on launch"
-msgstr "Preguntar al ejecutar"
-
-#: components/Schedule/shared/SchedulePromptableFields.js:97
-msgid "Prompt | {0}"
-msgstr "Aviso | {0}"
-
-#: components/PromptDetail/PromptDetail.js:180
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:418
-msgid "Prompted Values"
-msgstr "Valores solicitados"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:6
-msgid ""
-"Provide a host pattern to further constrain\n"
-"the list of hosts that will be managed or affected by the\n"
-"playbook. Multiple patterns are allowed. Refer to Ansible\n"
-"documentation for more information and examples on patterns."
-msgstr "Proporcione un patrón de host para limitar aún más la lista\n"
-"de hosts que serán gestionados o que se verán afectados por el playbook.\n"
-"Se permiten distintos patrones. Consulte la documentación de Ansible\n"
-"para obtener más información y ejemplos relacionados con los patrones."
-
-#: screens/Job/Job.helptext.js:14
-#: screens/Template/shared/JobTemplate.helptext.js:15
-msgid "Provide a host pattern to further constrain the list of hosts that will be managed or affected by the playbook. Multiple patterns are allowed. Refer to Ansible documentation for more information and examples on patterns."
-msgstr "Proporcione un patrón de host para limitar aun más la lista de hosts que se encontrarán bajo la administración del manual o se verán afectados por él. Se permiten distintos patrones. Consulte la documentación de Ansible para obtener más información y ejemplos relacionados con los patrones."
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:179
-msgid "Provide a value for this field or select the Prompt on launch option."
-msgstr "Proporcione un valor para este campo o seleccione la opción Preguntar al ejecutar."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:247
-msgid ""
-"Provide key/value pairs using either\n"
-"YAML or JSON."
-msgstr "Proporcione pares de clave/valor utilizando\n"
-"YAML o JSON."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:191
-msgid ""
-"Provide your Red Hat or Red Hat Satellite credentials\n"
-"below and you can choose from a list of your available subscriptions.\n"
-"The credentials you use will be stored for future use in\n"
-"retrieving renewal or expanded subscriptions."
-msgstr "A continuación, proporcione sus credenciales de Red Hat o de Red Hat Satellite\n"
-"para poder elegir de una lista de sus suscripciones disponibles.\n"
-"Las credenciales que utilice se almacenarán para su uso futuro\n"
-"en la recuperación de las suscripciones de renovación o ampliadas."
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:83
-msgid "Provide your Red Hat or Red Hat Satellite credentials to enable Automation Analytics."
-msgstr "Proporcione sus credenciales de Red Hat o Red Hat Satellite para habilitar Automation Analytics."
-
-#: components/StatusLabel/StatusLabel.js:59
-#: screens/TopologyView/Legend.js:150
-msgid "Provisioning"
-msgstr "Aprovisionamiento"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:162
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:302
-#: screens/Template/shared/JobTemplateForm.js:620
-msgid "Provisioning Callback URL"
-msgstr "Dirección URL para las llamadas callback"
-
-#: screens/Template/shared/JobTemplateForm.js:615
-msgid "Provisioning Callback details"
-msgstr "Detalles de callback de aprovisionamiento"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:63
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:127
-#: screens/Template/shared/JobTemplateForm.js:555
-#: screens/Template/shared/JobTemplateForm.js:558
-msgid "Provisioning Callbacks"
-msgstr "Callbacks de aprovisionamiento"
-
-#: screens/Template/shared/JobTemplate.helptext.js:41
-msgid "Provisioning callbacks: Enables creation of a provisioning callback URL. Using the URL a host can contact Ansible AWX and request a configuration update using this job template."
-msgstr "Permite la creación de una URL de\n"
-"de aprovisionamiento. A través de esta URL, un host puede ponerse en contacto con y solicitar una actualización de la configuración utilizando esta plantilla de trabajo."
-
-#: components/StatusLabel/StatusLabel.js:62
-msgid "Provisioning fail"
-msgstr "Fallo de aprovisionamiento"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:86
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:114
-msgid "Pull"
-msgstr "Extraer"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:163
-msgid "Question"
-msgstr "Pregunta"
-
-#: screens/Setting/Settings.js:106
-msgid "RADIUS"
-msgstr "RADIUS"
-
-#: screens/Setting/SettingList.js:73
-msgid "RADIUS settings"
-msgstr "Configuración de RADIUS"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:244
-#: screens/InstanceGroup/Instances/InstanceListItem.js:161
-#: screens/Instances/InstanceDetail/InstanceDetail.js:287
-#: screens/Instances/InstanceList/InstanceListItem.js:171
-#: screens/TopologyView/Tooltip.js:307
-msgid "RAM {0}"
-msgstr "RAM {0}"
-
-#: screens/User/shared/UserTokenForm.js:76
-msgid "Read"
-msgstr "Lectura"
-
-#: components/StatusLabel/StatusLabel.js:57
-#: screens/TopologyView/Legend.js:122
-msgid "Ready"
-msgstr "Listo"
-
-#: screens/Dashboard/Dashboard.js:133
-msgid "Recent Jobs"
-msgstr "Tareas recientes"
-
-#: screens/Dashboard/Dashboard.js:131
-msgid "Recent Jobs list tab"
-msgstr "Pestaña de la lista de tareas recientes"
-
-#: screens/Dashboard/Dashboard.js:145
-msgid "Recent Templates"
-msgstr "Plantillas recientes"
-
-#: screens/Dashboard/Dashboard.js:143
-msgid "Recent Templates list tab"
-msgstr "Pestaña de la lista de plantillas recientes"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:188
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:112
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:38
-msgid "Recent jobs"
-msgstr "Trabajos recientes"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:154
-msgid "Recipient List"
-msgstr "Lista de destinatarios"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:86
-msgid "Recipient list"
-msgstr "Lista de destinatarios"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:105
-msgid "Red Hat Ansible Automation Platform"
-msgstr "Plataforma Red Hat Ansible Automation"
-
-#: components/Lookup/ProjectLookup.js:139
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:92
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:161
-#: screens/Job/JobDetail/JobDetail.js:77
-#: screens/Project/ProjectList/ProjectList.js:201
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:100
-msgid "Red Hat Insights"
-msgstr "Red Hat Insights"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:102
-msgid "Red Hat Satellite 6"
-msgstr "Red Hat Satellite 6"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:104
-msgid "Red Hat Virtualization"
-msgstr "Virtualización de Red Hat"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:117
-msgid "Red Hat subscription manifest"
-msgstr "Manifiesto de suscripción de Red Hat"
-
-#: components/About/About.js:36
-msgid "Red Hat, Inc."
-msgstr "Red Hat, Inc."
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:94
-#: screens/Application/shared/ApplicationForm.js:107
-msgid "Redirect URIs"
-msgstr "Redirigir URI"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:259
-msgid "Redirecting to dashboard"
-msgstr "Redirigir al panel de control"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:263
-msgid "Redirecting to subscription detail"
-msgstr "Redirigir al detalle de la suscripción"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:261
-#: screens/Template/shared/JobTemplate.helptext.js:55
-msgid "Refer to the"
-msgstr "Consulte"
-
-#: screens/Job/Job.helptext.js:27
-#: screens/Template/shared/JobTemplate.helptext.js:50
-msgid "Refer to the Ansible documentation for details about the configuration file."
-msgstr "Consulte la documentación de Ansible para obtener detalles sobre el archivo de configuración."
-
-#: screens/User/UserTokens/UserTokens.js:77
-msgid "Refresh Token"
-msgstr "Actualizar token"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:81
-msgid "Refresh Token Expiration"
-msgstr "Actualizar expiración del token"
-
-#: screens/Project/ProjectList/ProjectListItem.js:132
-msgid "Refresh for revision"
-msgstr "Actualizar para revisión"
-
-#: screens/Project/ProjectList/ProjectListItem.js:134
-msgid "Refresh project revision"
-msgstr "Actualizar la revisión del proyecto"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:116
-msgid "Regions"
-msgstr "Regiones"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:92
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:143
-msgid "Registry credential"
-msgstr "Credencial de registro"
-
-#: screens/Inventory/shared/Inventory.helptext.js:156
-msgid "Regular expression where only matching host names will be imported. The filter is applied as a post-processing step after any inventory plugin filters are applied."
-msgstr "Expresión regular en la que solo se importarán los nombres de host que coincidan. El filtro se aplica como un paso posterior al procesamiento después de que se aplique cualquier filtro de complemento de inventario."
-
-#: screens/Inventory/Inventories.js:81
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:62
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:175
-msgid "Related Groups"
-msgstr "Grupos relacionados"
-
-#: components/Search/AdvancedSearch.js:287
-msgid "Related Keys"
-msgstr "Teclas relacionadas"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:169
-#: components/Schedule/ScheduleList/ScheduleListItem.js:105
-msgid "Related resource"
-msgstr "Recursos relacionados"
-
-#: components/Search/RelatedLookupTypeInput.js:16
-#: components/Search/RelatedLookupTypeInput.js:24
-msgid "Related search type"
-msgstr "Tipo de búsqueda relacionada"
-
-#: components/Search/RelatedLookupTypeInput.js:19
-msgid "Related search type typeahead"
-msgstr "Tipo de búsqueda relacionado typeahead"
-
-#: components/JobList/JobListItem.js:146
-#: components/LaunchButton/ReLaunchDropDown.js:82
-#: screens/Job/JobDetail/JobDetail.js:580
-#: screens/Job/JobDetail/JobDetail.js:588
-#: screens/Job/JobOutput/shared/OutputToolbar.js:167
-msgid "Relaunch"
-msgstr "Relanzar"
-
-#: components/JobList/JobListItem.js:126
-#: screens/Job/JobOutput/shared/OutputToolbar.js:147
-msgid "Relaunch Job"
-msgstr "Volver a ejecutar la tarea"
-
-#: components/LaunchButton/ReLaunchDropDown.js:41
-msgid "Relaunch all hosts"
-msgstr "Volver a ejecutar todos los hosts"
-
-#: components/LaunchButton/ReLaunchDropDown.js:54
-msgid "Relaunch failed hosts"
-msgstr "Volver a ejecutar hosts fallidos"
-
-#: components/LaunchButton/ReLaunchDropDown.js:30
-#: components/LaunchButton/ReLaunchDropDown.js:35
-msgid "Relaunch on"
-msgstr "Volver a ejecutar el"
-
-#: components/JobList/JobListItem.js:125
-#: screens/Job/JobOutput/shared/OutputToolbar.js:146
-msgid "Relaunch using host parameters"
-msgstr "Relanzar utilizando los parámetros de host"
-
-#: components/HealthCheckAlert/HealthCheckAlert.js:27
-msgid "Reload"
-msgstr "Recarga"
-
-#: screens/Job/JobOutput/JobOutput.js:723
-msgid "Reload output"
-msgstr "Descargar salida"
-
-#: components/Lookup/ProjectLookup.js:138
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:91
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:160
-#: screens/Job/JobDetail/JobDetail.js:78
-#: screens/Project/ProjectList/ProjectList.js:200
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:99
-msgid "Remote Archive"
-msgstr "Archivo remoto"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:374
-#: screens/Instances/InstanceList/InstanceList.js:242
-msgid "Removal Error"
-msgstr "Error de eliminación"
-
-#: components/SelectedList/DraggableSelectedList.js:105
-#: screens/Instances/Shared/RemoveInstanceButton.js:75
-#: screens/Instances/Shared/RemoveInstanceButton.js:129
-#: screens/Instances/Shared/RemoveInstanceButton.js:143
-#: screens/Instances/Shared/RemoveInstanceButton.js:163
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:21
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:29
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:40
-msgid "Remove"
-msgstr "Eliminar"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:36
-msgid "Remove All Nodes"
-msgstr "Quitar todos los nodos"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:152
-msgid "Remove Instances"
-msgstr "Eliminar instancias"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:17
-msgid "Remove Link"
-msgstr "Quitar enlace"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:28
-msgid "Remove Node {nodeName}"
-msgstr "Eliminar nodo {nodeName}"
-
-#: screens/Project/shared/Project.helptext.js:113
-msgid "Remove any local modifications prior to performing an update."
-msgstr "Eliminar cualquier modificación local antes de realizar una actualización."
-
-#: components/Search/AdvancedSearch.js:206
-msgid "Remove the current search related to ansible facts to enable another search using this key."
-msgstr "Elimine la búsqueda actual relacionada con los hechos factibles para habilitar otra búsqueda usando esta clave."
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:14
-msgid "Remove {0} Access"
-msgstr "Eliminar el acceso de {0}"
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:45
-msgid "Remove {0} chip"
-msgstr "Eliminar el chip de {0}"
-
-#: screens/TopologyView/Legend.js:285
-msgid "Removing"
-msgstr "Eliminación de"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:48
-msgid "Removing this link will orphan the rest of the branch and cause it to be executed immediately on launch."
-msgstr "Si quita este enlace, el resto de la rama quedará huérfano y hará que se ejecute inmediatamente en el lanzamiento."
-
-#: components/SelectedList/DraggableSelectedList.js:83
-msgid "Reorder"
-msgstr "Reordenar"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:349
-msgid "Repeat Frequency"
-msgstr "Frecuencia de repetición"
-
-#: components/Schedule/shared/ScheduleFormFields.js:113
-msgid "Repeat frequency"
-msgstr "Frecuencia de repetición"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:52
-msgid "Replace"
-msgstr "Reemplazar"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:60
-msgid "Replace field with new value"
-msgstr "Reemplazar el campo con un valor nuevo"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:67
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:74
-msgid "Request subscription"
-msgstr "Solicitar subscripción"
-
-#: screens/Template/Survey/SurveyListItem.js:51
-#: screens/Template/Survey/SurveyQuestionForm.js:188
-msgid "Required"
-msgstr "Obligatorio"
-
-#: screens/TopologyView/Header.js:87
-#: screens/TopologyView/Header.js:90
-msgid "Reset zoom"
-msgstr "Restablecer zoom"
-
-#: components/Workflow/WorkflowNodeHelp.js:154
-#: components/Workflow/WorkflowNodeHelp.js:190
-#: screens/Team/TeamRoles/TeamRoleListItem.js:12
-#: screens/Team/TeamRoles/TeamRolesList.js:180
-msgid "Resource Name"
-msgstr "Nombre del recurso"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:246
-msgid "Resource deleted"
-msgstr "Recurso eliminado"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:170
-#: components/Schedule/ScheduleList/ScheduleListItem.js:111
-msgid "Resource type"
-msgstr "Agregar tipo de recurso"
-
-#: routeConfig.js:61
-#: screens/ActivityStream/ActivityStream.js:159
-msgid "Resources"
-msgstr "Recursos"
-
-#: components/TemplateList/TemplateListItem.js:149
-msgid "Resources are missing from this template."
-msgstr "Faltan recursos de esta plantilla."
-
-#: screens/Setting/shared/RevertButton.js:43
-msgid "Restore initial value."
-msgstr "Restaurar el valor inicial."
-
-#: screens/Inventory/shared/Inventory.helptext.js:153
-msgid ""
-"Retrieve the enabled state from the given dict of host variables.\n"
-"The enabled variable may be specified using dot notation, e.g: 'foo.bar'"
-msgstr "Recuperar el estado habilitado a partir del dict dado de las variables de host.\n"
-"La variable habilitada se puede especificar mediante notación de puntos,\n"
-"por ejemplo: \"foo.bar\""
-
-#: components/JobCancelButton/JobCancelButton.js:96
-#: components/JobCancelButton/JobCancelButton.js:100
-#: components/JobList/JobListCancelButton.js:160
-#: components/JobList/JobListCancelButton.js:163
-#: screens/Job/JobOutput/JobOutput.js:819
-#: screens/Job/JobOutput/JobOutput.js:822
-msgid "Return"
-msgstr "Volver"
-
-#: screens/Job/JobOutput/EmptyOutput.js:40
-msgid "Return to"
-msgstr "Volver"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:129
-msgid "Return to subscription management."
-msgstr "Volver a la gestión de suscripciones."
-
-#: components/Search/AdvancedSearch.js:171
-msgid "Returns results that have values other than this one as well as other filters."
-msgstr "Muestra los resultados que tienen valores distintos a éste y otros filtros."
-
-#: components/Search/AdvancedSearch.js:158
-msgid "Returns results that satisfy this one as well as other filters. This is the default set type if nothing is selected."
-msgstr "Muestra los resultados que cumple con este y otros filtros. Este es el tipo de conjunto predeterminado si no se selecciona nada."
-
-#: components/Search/AdvancedSearch.js:164
-msgid "Returns results that satisfy this one or any other filters."
-msgstr "Muestra los resultados que cumplen con este o cualquier otro filtro."
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:52
-#: screens/Setting/shared/RevertButton.js:53
-#: screens/Setting/shared/RevertButton.js:62
-msgid "Revert"
-msgstr "Revertir"
-
-#: screens/Setting/shared/RevertAllAlert.js:23
-msgid "Revert all"
-msgstr "Revertir todo"
-
-#: screens/Setting/shared/RevertFormActionGroup.js:21
-#: screens/Setting/shared/RevertFormActionGroup.js:27
-msgid "Revert all to default"
-msgstr "Revertir todo a valores por defecto"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:59
-msgid "Revert field to previously saved value"
-msgstr "Revertir el campo al valor guardado anteriormente"
-
-#: screens/Setting/shared/RevertAllAlert.js:11
-msgid "Revert settings"
-msgstr "Revertir configuración"
-
-#: screens/Setting/shared/RevertButton.js:42
-msgid "Revert to factory default."
-msgstr "Revertir a los valores predeterminados de fábrica."
-
-#: screens/Job/JobDetail/JobDetail.js:314
-#: screens/Project/ProjectList/ProjectList.js:224
-#: screens/Project/ProjectList/ProjectListItem.js:221
-msgid "Revision"
-msgstr "Revisión"
-
-#: screens/Project/shared/ProjectSubForms/SvnSubForm.js:22
-msgid "Revision #"
-msgstr "Revisión n°"
-
-#: components/NotificationList/NotificationList.js:199
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:140
-msgid "Rocket.Chat"
-msgstr "Rocket.Chat"
-
-#: screens/Team/TeamRoles/TeamRoleListItem.js:20
-#: screens/Team/TeamRoles/TeamRolesList.js:148
-#: screens/Team/TeamRoles/TeamRolesList.js:182
-#: screens/User/UserList/UserList.js:163
-#: screens/User/UserList/UserListItem.js:55
-#: screens/User/UserRoles/UserRolesList.js:146
-#: screens/User/UserRoles/UserRolesList.js:157
-#: screens/User/UserRoles/UserRolesListItem.js:26
-msgid "Role"
-msgstr "Rol"
-
-#: components/ResourceAccessList/ResourceAccessList.js:189
-#: components/ResourceAccessList/ResourceAccessList.js:202
-#: components/ResourceAccessList/ResourceAccessList.js:229
-#: components/ResourceAccessList/ResourceAccessListItem.js:69
-#: screens/Team/Team.js:59
-#: screens/Team/Teams.js:32
-#: screens/User/User.js:71
-#: screens/User/Users.js:31
-msgid "Roles"
-msgstr "Roles"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:134
-#: components/Workflow/WorkflowLinkHelp.js:39
-#: screens/Credential/shared/ExternalTestModal.js:89
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:49
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:23
-#: screens/Template/shared/JobTemplateForm.js:214
-msgid "Run"
-msgstr "Ejecutar"
-
-#: components/AdHocCommands/AdHocCommands.js:131
-#: components/AdHocCommands/AdHocCommands.js:135
-#: components/AdHocCommands/AdHocCommands.js:141
-#: components/AdHocCommands/AdHocCommands.js:145
-#: screens/Job/JobDetail/JobDetail.js:68
-msgid "Run Command"
-msgstr "Ejecutar comando"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:277
-#: screens/Instances/InstanceDetail/InstanceDetail.js:335
-msgid "Run a health check on the instance"
-msgstr "Ejecutar una comprobación de la salud de la instancia"
-
-#: components/AdHocCommands/AdHocCommands.js:125
-msgid "Run ad hoc command"
-msgstr "Ejecutar comando ad hoc"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:48
-msgid "Run command"
-msgstr "Ejecutar comando"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:93
-#: components/Schedule/shared/FrequencyDetailSubform.js:223
-msgid "Run every"
-msgstr "Ejecutar cada"
-
-#: components/HealthCheckButton/HealthCheckButton.js:32
-#: components/HealthCheckButton/HealthCheckButton.js:45
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:286
-#: screens/Instances/InstanceDetail/InstanceDetail.js:344
-msgid "Run health check"
-msgstr "Comprobación de estado"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:129
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:138
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:175
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:197
-#: components/Schedule/shared/FrequencyDetailSubform.js:344
-msgid "Run on"
-msgstr "Ejecutar el"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useRunTypeStep.js:32
-msgid "Run type"
-msgstr "Tipo de ejecución"
-
-#: components/JobList/JobList.js:230
-#: components/StatusLabel/StatusLabel.js:48
-#: components/TemplateList/TemplateListItem.js:118
-#: components/Workflow/WorkflowNodeHelp.js:99
-msgid "Running"
-msgstr "Ejecutándose"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:127
-msgid "Running Handlers"
-msgstr "Handlers ejecutándose"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:217
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:196
-#: screens/InstanceGroup/Instances/InstanceListItem.js:194
-#: screens/Instances/InstanceDetail/InstanceDetail.js:212
-#: screens/Instances/InstanceList/InstanceListItem.js:209
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:73
-msgid "Running Jobs"
-msgstr "Tareas en ejecución"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:284
-#: screens/Instances/InstanceDetail/InstanceDetail.js:342
-msgid "Running health check"
-msgstr "Última comprobación de estado"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:71
-msgid "Running jobs"
-msgstr "Tareas en ejecución"
-
-#: screens/Setting/Settings.js:109
-msgid "SAML"
-msgstr "SAML"
-
-#: screens/Setting/SettingList.js:77
-msgid "SAML settings"
-msgstr "Configuración de SAML"
-
-#: screens/Dashboard/DashboardGraph.js:143
-msgid "SCM update"
-msgstr "Actualización de SCM"
-
-#: screens/User/UserDetail/UserDetail.js:58
-#: screens/User/UserList/UserListItem.js:49
-msgid "SOCIAL"
-msgstr "SOCIAL"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:95
-msgid "SSH password"
-msgstr "Contraseña de SSH"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:239
-msgid "SSL Connection"
-msgstr "Conexión SSL"
-
-#: components/Workflow/WorkflowStartNode.js:60
-#: components/Workflow/workflowReducer.js:419
-msgid "START"
-msgstr "INICIAR"
-
-#: components/Sparkline/Sparkline.js:31
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:160
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:39
-#: screens/Project/ProjectDetail/ProjectDetail.js:135
-#: screens/Project/ProjectList/ProjectListItem.js:73
-msgid "STATUS:"
-msgstr "ESTADO:"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:321
-msgid "Sat"
-msgstr "Sáb"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:82
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:188
-#: components/Schedule/shared/FrequencyDetailSubform.js:326
-#: components/Schedule/shared/FrequencyDetailSubform.js:458
-msgid "Saturday"
-msgstr "Sábado"
-
-#: components/AddRole/AddResourceRole.js:247
-#: components/AssociateModal/AssociateModal.js:104
-#: components/AssociateModal/AssociateModal.js:110
-#: components/FormActionGroup/FormActionGroup.js:13
-#: components/FormActionGroup/FormActionGroup.js:19
-#: components/Schedule/shared/ScheduleForm.js:526
-#: components/Schedule/shared/ScheduleForm.js:532
-#: components/Schedule/shared/useSchedulePromptSteps.js:49
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:130
-#: screens/Credential/shared/CredentialForm.js:318
-#: screens/Credential/shared/CredentialForm.js:323
-#: screens/Setting/shared/RevertFormActionGroup.js:12
-#: screens/Setting/shared/RevertFormActionGroup.js:18
-#: screens/Template/Survey/SurveyReorderModal.js:205
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:35
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:131
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:158
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:162
-msgid "Save"
-msgstr "Guardar"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:33
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:36
-msgid "Save & Exit"
-msgstr "Guardar y salir"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:32
-msgid "Save link changes"
-msgstr "Guardar los cambios del enlace"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:254
-msgid "Save successful!"
-msgstr "Guardado correctamente"
-
-#: components/JobList/JobListItem.js:181
-#: components/JobList/JobListItem.js:187
-msgid "Schedule"
-msgstr "Planificar"
-
-#: screens/Project/Projects.js:34
-#: screens/Template/Templates.js:54
-msgid "Schedule Details"
-msgstr "Detalles de la programación"
-
-#: components/Schedule/shared/UnsupportedScheduleForm.js:15
-msgid "Schedule Rules"
-msgstr "Reglas de programación"
-
-#: screens/Inventory/Inventories.js:92
-msgid "Schedule details"
-msgstr "Detalles de la programación"
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:49
-msgid "Schedule is active"
-msgstr "La programación está activa"
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:49
-msgid "Schedule is inactive"
-msgstr "La programación está inactiva"
-
-#: components/Schedule/shared/ScheduleForm.js:394
-msgid "Schedule is missing rrule"
-msgstr "Falta una regla de programación"
-
-#: components/Schedule/Schedule.js:82
-msgid "Schedule not found."
-msgstr "Programación no encontrada."
-
-#: components/Schedule/ScheduleList/ScheduleList.js:163
-#: components/Schedule/ScheduleList/ScheduleList.js:229
-#: routeConfig.js:44
-#: screens/ActivityStream/ActivityStream.js:153
-#: screens/Inventory/Inventories.js:89
-#: screens/Inventory/InventorySource/InventorySource.js:88
-#: screens/ManagementJob/ManagementJob.js:108
-#: screens/ManagementJob/ManagementJobs.js:23
-#: screens/Project/Project.js:120
-#: screens/Project/Projects.js:31
-#: screens/Schedule/AllSchedules.js:21
-#: screens/Template/Template.js:148
-#: screens/Template/Templates.js:51
-#: screens/Template/WorkflowJobTemplate.js:130
-msgid "Schedules"
-msgstr "Programaciones"
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:136
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:33
-#: screens/User/UserTokenDetail/UserTokenDetail.js:50
-#: screens/User/UserTokenList/UserTokenList.js:142
-#: screens/User/UserTokenList/UserTokenList.js:189
-#: screens/User/UserTokenList/UserTokenListItem.js:32
-#: screens/User/shared/UserTokenForm.js:68
-msgid "Scope"
-msgstr "Ámbito"
-
-#: screens/User/shared/User.helptext.js:5
-msgid "Scope for the token's access"
-msgstr "Especifique un alcance para el acceso al token"
-
-#: screens/Job/JobOutput/PageControls.js:79
-msgid "Scroll first"
-msgstr "Desplazarse hasta el primero"
-
-#: screens/Job/JobOutput/PageControls.js:87
-msgid "Scroll last"
-msgstr "Desplazarse hasta el final"
-
-#: screens/Job/JobOutput/PageControls.js:71
-msgid "Scroll next"
-msgstr "Desplazarse hasta el siguiente"
-
-#: screens/Job/JobOutput/PageControls.js:63
-msgid "Scroll previous"
-msgstr "Desplazarse hasta el anterior"
-
-#: components/Lookup/HostFilterLookup.js:290
-#: components/Lookup/Lookup.js:143
-msgid "Search"
-msgstr "Buscar"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:151
-msgid "Search is disabled while the job is running"
-msgstr "La búsqueda se desactiva durante la ejecución de la tarea"
-
-#: components/Search/AdvancedSearch.js:311
-#: components/Search/Search.js:259
-msgid "Search submit button"
-msgstr "Botón de envío de la búsqueda"
-
-#: components/Search/Search.js:248
-msgid "Search text input"
-msgstr "Entrada de texto de búsqueda"
-
-#: components/Lookup/HostFilterLookup.js:398
-msgid "Searching by ansible_facts requires special syntax. Refer to the"
-msgstr "La búsqueda por ansible_facts requiere sintaxis especial. Consulte el"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:408
-msgid "Second"
-msgstr "Segundo"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:103
-#: components/PromptDetail/PromptProjectDetail.js:153
-#: screens/Project/ProjectDetail/ProjectDetail.js:269
-msgid "Seconds"
-msgstr "Segundos"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:34
-msgid "See Django"
-msgstr "Ver Django"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:35
-#: components/LaunchPrompt/steps/PreviewStep.js:61
-msgid "See errors on the left"
-msgstr "Ver errores a la izquierda"
-
-#: components/JobList/JobListItem.js:84
-#: components/Lookup/HostFilterLookup.js:380
-#: components/Lookup/Lookup.js:200
-#: components/Pagination/Pagination.js:33
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:98
-msgid "Select"
-msgstr "Seleccionar"
-
-#: screens/Credential/shared/CredentialForm.js:129
-msgid "Select Credential Type"
-msgstr "Seleccionar tipo de credencial"
-
-#: screens/Host/HostGroups/HostGroupsList.js:237
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:254
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:257
-msgid "Select Groups"
-msgstr "Seleccionar grupos"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:278
-msgid "Select Hosts"
-msgstr "Seleccionar hosts"
-
-#: components/AnsibleSelect/AnsibleSelect.js:38
-msgid "Select Input"
-msgstr "Seleccionar entrada"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:295
-msgid "Select Instances"
-msgstr "Seleccionar instancias"
-
-#: components/AssociateModal/AssociateModal.js:22
-msgid "Select Items"
-msgstr "Seleccionar elementos"
-
-#: components/AddRole/AddResourceRole.js:201
-msgid "Select Items from List"
-msgstr "Seleccionar elementos de la lista"
-
-#: components/LabelSelect/LabelSelect.js:127
-msgid "Select Labels"
-msgstr "Seleccionar etiquetas"
-
-#: components/AddRole/AddResourceRole.js:236
-msgid "Select Roles to Apply"
-msgstr "Seleccionar los roles para aplicar"
-
-#: screens/User/UserTeams/UserTeamList.js:251
-msgid "Select Teams"
-msgstr "Seleccionar equipos"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:25
-msgid "Select a JSON formatted service account key to autopopulate the following fields."
-msgstr "Seleccione una clave de cuenta de servicio con formato JSON para autocompletar los siguientes campos."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:122
-msgid "Select a Node Type"
-msgstr "Seleccionar un tipo de nodo"
-
-#: components/AddRole/AddResourceRole.js:170
-msgid "Select a Resource Type"
-msgstr "Seleccionar un tipo de recurso"
-
-#: screens/Job/Job.helptext.js:21
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:10
-msgid "Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch."
-msgstr "Seleccione una rama para el flujo de trabajo. Esta rama se aplica a todos los nodos de la plantilla de trabajo que indican una rama."
-
-#: screens/Credential/shared/CredentialForm.js:139
-msgid "Select a credential Type"
-msgstr "Seleccionar un tipo de credencial"
-
-#: components/JobList/JobListCancelButton.js:98
-msgid "Select a job to cancel"
-msgstr "Seleccionar una tarea para cancelar"
-
-#: screens/Metrics/Metrics.js:211
-msgid "Select a metric"
-msgstr "Seleccionar una métrica"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:75
-msgid "Select a module"
-msgstr "Seleccionar un módulo"
-
-#: screens/Template/shared/PlaybookSelect.js:60
-#: screens/Template/shared/PlaybookSelect.js:61
-msgid "Select a playbook"
-msgstr "Seleccionar un playbook"
-
-#: screens/Template/shared/JobTemplateForm.js:323
-msgid "Select a project before editing the execution environment."
-msgstr "Seleccione un proyecto antes de modificar el entorno de ejecución."
-
-#: screens/Template/Survey/SurveyToolbar.js:82
-msgid "Select a question to delete"
-msgstr "Seleccione una pregunta para eliminar"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:160
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:103
-msgid "Select a row to delete"
-msgstr "Seleccionar una fila para eliminar"
-
-#: components/DisassociateButton/DisassociateButton.js:75
-msgid "Select a row to disassociate"
-msgstr "Seleccionar una fila para disociar"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:77
-msgid "Select a row to remove"
-msgstr "Seleccionar una fila para denegar"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:87
-msgid "Select a subscription"
-msgstr "Seleccionar una suscripción"
-
-#: components/HostForm/HostForm.js:39
-#: components/Schedule/shared/FrequencyDetailSubform.js:71
-#: components/Schedule/shared/FrequencyDetailSubform.js:78
-#: components/Schedule/shared/FrequencyDetailSubform.js:88
-#: components/Schedule/shared/ScheduleFormFields.js:33
-#: components/Schedule/shared/ScheduleFormFields.js:37
-#: components/Schedule/shared/ScheduleFormFields.js:61
-#: screens/Credential/shared/CredentialForm.js:44
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:79
-#: screens/Inventory/shared/InventoryForm.js:72
-#: screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.js:46
-#: screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/GCESubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.js:46
-#: screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:97
-#: screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.js:44
-#: screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.js:46
-#: screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.js:46
-#: screens/Inventory/shared/SmartInventoryForm.js:67
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:24
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:61
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:436
-#: screens/Project/shared/ProjectForm.js:234
-#: screens/Project/shared/ProjectSubForms/InsightsSubForm.js:39
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:37
-#: screens/Team/shared/TeamForm.js:49
-#: screens/Template/Survey/SurveyQuestionForm.js:30
-#: screens/Template/shared/WorkflowJobTemplateForm.js:130
-#: screens/User/shared/UserForm.js:139
-#: util/validators.js:201
-msgid "Select a value for this field"
-msgstr "Seleccionar un valor para este campo"
-
-#: screens/Template/shared/JobTemplate.helptext.js:23
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:20
-msgid "Select a webhook service."
-msgstr "Seleccione un servicio de webhook."
-
-#: components/DataListToolbar/DataListToolbar.js:121
-#: components/DataListToolbar/DataListToolbar.js:125
-#: screens/Template/Survey/SurveyToolbar.js:49
-msgid "Select all"
-msgstr "Seleccionar todo"
-
-#: screens/ActivityStream/ActivityStream.js:129
-msgid "Select an activity type"
-msgstr "Seleccionar un tipo de actividad"
-
-#: screens/Metrics/Metrics.js:200
-msgid "Select an instance"
-msgstr "Seleccione una instancia"
-
-#: screens/Metrics/Metrics.js:242
-msgid "Select an instance and a metric to show chart"
-msgstr "Seleccionar una instancia y una métrica para mostrar el gráfico"
-
-#: components/HealthCheckButton/HealthCheckButton.js:19
-msgid "Select an instance to run a health check."
-msgstr "Seleccione una instancia para ejecutar una comprobación de estado."
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:5
-msgid "Select an inventory for the workflow. This inventory is applied to all workflow nodes that prompt for an inventory."
-msgstr "Seleccione un inventario para el flujo de trabajo. Este inventario se aplica a todos los nodos del flujo de trabajo que solicitan un inventario."
-
-#: components/LaunchPrompt/steps/SurveyStep.js:131
-msgid "Select an option"
-msgstr "Seleccione una opción"
-
-#: screens/Project/shared/ProjectForm.js:245
-msgid "Select an organization before editing the default execution environment."
-msgstr "Seleccione una organización antes de modificar el entorno de ejecución predeterminado."
-
-#: screens/Job/Job.helptext.js:11
-#: screens/Template/shared/JobTemplate.helptext.js:12
-msgid "Select credentials for accessing the nodes this job will be ran against. You can only select one credential of each type. For machine credentials (SSH), checking \"Prompt on launch\" without selecting credentials will require you to select a machine credential at run time. If you select credentials and check \"Prompt on launch\", the selected credential(s) become the defaults that can be updated at run time."
-msgstr "Seleccione las credenciales para acceder a los nodos en función de\n"
-"los cuales se ejecutará este trabajo. Solo puede seleccionar una credencial de cada tipo. Para las\n"
-"credenciales de máquina (SSH), si marca \"Preguntar al ejecutar\" sin seleccionar las credenciales,\n"
-"se le pedirá que seleccione una credencial de máquina en el momento de la ejecución. Si selecciona\n"
-"las credenciales y marca \"Preguntar al ejecutar\", las credenciales seleccionadas se convierten en las credenciales\n"
-"predeterminadas que pueden actualizarse en tiempo de ejecución."
-
-#: components/Schedule/shared/ScheduleFormFields.js:120
-#: components/Schedule/shared/ScheduleFormFields.js:179
-msgid "Select frequency"
-msgstr "Frecuencia de repetición"
-
-#: screens/Project/shared/Project.helptext.js:18
-msgid ""
-"Select from the list of directories found in\n"
-"the Project Base Path. Together the base path and the playbook\n"
-"directory provide the full path used to locate playbooks."
-msgstr "Seleccione de la lista de directorios que se encuentran en\n"
-"la ruta base del proyecto. La ruta base y el directorio del playbook\n"
-"proporcionan la ruta completa utilizada para encontrar los playbooks."
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:98
-msgid "Select items from list"
-msgstr "Seleccionar elementos de la lista"
-
-#: screens/Dashboard/DashboardGraph.js:124
-#: screens/Dashboard/DashboardGraph.js:125
-msgid "Select job type"
-msgstr "Seleccionar el tipo de tarea"
-
-#: components/LaunchPrompt/steps/SurveyStep.js:179
-msgid "Select option(s)"
-msgstr "Seleccione la(s) opción(es)"
-
-#: screens/Dashboard/DashboardGraph.js:95
-#: screens/Dashboard/DashboardGraph.js:96
-#: screens/Dashboard/DashboardGraph.js:97
-msgid "Select period"
-msgstr "Seleccionar periodo"
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:117
-msgid "Select roles to apply"
-msgstr "Seleccionar los roles para aplicar"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:128
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:129
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:130
-msgid "Select source path"
-msgstr "Seleccionar la ruta de origen"
-
-#: screens/Dashboard/DashboardGraph.js:151
-#: screens/Dashboard/DashboardGraph.js:152
-msgid "Select status"
-msgstr "Seleccionar estado"
-
-#: components/MultiSelect/TagMultiSelect.js:59
-msgid "Select tags"
-msgstr "Seleccionar etiquetas"
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:94
-msgid "Select the Execution Environment you want this command to run inside."
-msgstr "Seleccione el entorno de ejecución en el que desea que se ejecute este comando."
-
-#: screens/Inventory/shared/SmartInventoryForm.js:87
-msgid "Select the Instance Groups for this Inventory to run on."
-msgstr "Seleccione los grupos de instancias en los que se ejecutará este inventario."
-
-#: screens/Job/Job.helptext.js:18
-#: screens/Template/shared/JobTemplate.helptext.js:20
-msgid "Select the Instance Groups for this Job Template to run on."
-msgstr "Seleccione los grupos de instancias en los que se ejecutará esta plantilla de trabajo."
-
-#: screens/Organization/shared/OrganizationForm.js:83
-msgid "Select the Instance Groups for this Organization to run on."
-msgstr "Seleccione los grupos de instancias en los que se ejecutará esta organización."
-
-#: components/AdHocCommands/AdHocCredentialStep.js:104
-msgid "Select the credential you want to use when accessing the remote hosts to run the command. Choose the credential containing the username and SSH key or password that Ansible will need to log into the remote hosts."
-msgstr "Seleccione la credencial que desea utilizar cuando acceda a los hosts remotos para ejecutar el comando. Elija una credencial que contenga el nombre de usuario y la clave SSH o la contraseña que Ansible necesitará para iniciar sesión en los hosts remotos."
-
-#: components/Lookup/InventoryLookup.js:123
-msgid ""
-"Select the inventory containing the hosts\n"
-"you want this job to manage."
-msgstr "Seleccione el inventario que contenga los hosts que desea\n"
-"que gestione esta tarea."
-
-#: screens/Job/Job.helptext.js:6
-#: screens/Template/shared/JobTemplate.helptext.js:7
-msgid "Select the inventory containing the hosts you want this job to manage."
-msgstr "Seleccione el inventario que contenga los servidores que desea que este trabajo administre."
-
-#: components/HostForm/HostForm.js:32
-#: components/HostForm/HostForm.js:51
-msgid "Select the inventory that this host will belong to."
-msgstr "Seleccione el inventario al que pertenecerá este host."
-
-#: screens/Job/Job.helptext.js:10
-#: screens/Template/shared/JobTemplate.helptext.js:11
-msgid "Select the playbook to be executed by this job."
-msgstr "Seleccionar el playbook a ser ejecutado por este trabajo."
-
-#: screens/Instances/Shared/InstanceForm.js:43
-msgid "Select the port that Receptor will listen on for incoming connections. Default is 27199."
-msgstr "Seleccione el puerto en el que el Receptor escuchará las conexiones entrantes. El valor predeterminado es 27199."
-
-#: screens/Template/shared/JobTemplate.helptext.js:8
-msgid "Select the project containing the playbook you want this job to execute."
-msgstr "Seleccionar el proyecto que contiene el playbook que desea ejecutar este trabajo."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:79
-msgid "Select your Ansible Automation Platform subscription to use."
-msgstr "Seleccione su suscripción a Ansible Automation Platform para utilizarla."
-
-#: components/Lookup/Lookup.js:186
-msgid "Select {0}"
-msgstr "Seleccionar {0}"
-
-#: components/AddRole/AddResourceRole.js:212
-#: components/AddRole/AddResourceRole.js:224
-#: components/AddRole/AddResourceRole.js:242
-#: components/AddRole/SelectRoleStep.js:27
-#: components/CheckboxListItem/CheckboxListItem.js:44
-#: components/Lookup/InstanceGroupsLookup.js:87
-#: components/OptionsList/OptionsList.js:74
-#: components/Schedule/ScheduleList/ScheduleListItem.js:84
-#: components/TemplateList/TemplateListItem.js:140
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:107
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:125
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:26
-#: screens/Application/ApplicationsList/ApplicationListItem.js:31
-#: screens/Credential/CredentialList/CredentialListItem.js:56
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:31
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:65
-#: screens/Host/HostGroups/HostGroupItem.js:26
-#: screens/Host/HostList/HostListItem.js:48
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:59
-#: screens/InstanceGroup/Instances/InstanceListItem.js:122
-#: screens/Instances/InstanceList/InstanceListItem.js:126
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:42
-#: screens/Inventory/InventoryList/InventoryListItem.js:90
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:37
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:110
-#: screens/Organization/OrganizationList/OrganizationListItem.js:43
-#: screens/Organization/shared/OrganizationForm.js:113
-#: screens/Project/ProjectList/ProjectListItem.js:177
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:242
-#: screens/Team/TeamList/TeamListItem.js:31
-#: screens/Template/Survey/SurveyListItem.js:34
-#: screens/User/UserTokenList/UserTokenListItem.js:19
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:41
-msgid "Selected"
-msgstr "Seleccionado"
-
-#: components/LaunchPrompt/steps/CredentialsStep.js:142
-#: components/LaunchPrompt/steps/CredentialsStep.js:147
-#: components/Lookup/MultiCredentialsLookup.js:162
-#: components/Lookup/MultiCredentialsLookup.js:167
-msgid "Selected Category"
-msgstr "Categoría seleccionada"
-
-#: components/Schedule/shared/ScheduleForm.js:446
-#: components/Schedule/shared/ScheduleForm.js:447
-msgid "Selected date range must have at least 1 schedule occurrence."
-msgstr "El intervalo de fechas seleccionado debe tener al menos 1 ocurrencia de horario."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:160
-msgid "Sender Email"
-msgstr "Dirección de correo del remitente"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:96
-msgid "Sender e-mail"
-msgstr "Correo electrónico del remitente"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:167
-#: components/Schedule/shared/FrequencyDetailSubform.js:138
-msgid "September"
-msgstr "Septiembre"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:24
-msgid "Service account JSON file"
-msgstr "Archivo JSON de la cuenta de servicio"
-
-#: screens/Inventory/shared/InventorySourceForm.js:46
-#: screens/Project/shared/ProjectForm.js:112
-msgid "Set a value for this field"
-msgstr "Establecer un valor para este campo"
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:70
-msgid "Set how many days of data should be retained."
-msgstr "Establecer cuántos días de datos debería ser retenidos."
-
-#: screens/Setting/SettingList.js:122
-msgid "Set preferences for data collection, logos, and logins"
-msgstr "Establezca preferencias para la recopilación de datos, los logotipos y los inicios de sesión"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:131
-msgid "Set source path to"
-msgstr "Establecer la ruta de origen en"
-
-#: components/InstanceToggle/InstanceToggle.js:48
-#: screens/Instances/Shared/InstanceForm.js:59
-msgid "Set the instance enabled or disabled. If disabled, jobs will not be assigned to this instance."
-msgstr "Establezca la instancia habilitada o deshabilitada. Si se desactiva, los trabajos no se asignarán a esta instancia."
-
-#: screens/Application/shared/Application.helptext.js:5
-msgid "Set to Public or Confidential depending on how secure the client device is."
-msgstr "Establecer como Público o Confidencial según cuán seguro sea el dispositivo del cliente."
-
-#: components/Search/AdvancedSearch.js:149
-msgid "Set type"
-msgstr "Establecer tipo"
-
-#: components/Search/AdvancedSearch.js:239
-msgid "Set type disabled for related search field fuzzy searches"
-msgstr "Establecer el tipo deshabilitado para las búsquedas difusas de campos de búsqueda relacionados"
-
-#: components/Search/AdvancedSearch.js:140
-msgid "Set type select"
-msgstr "Establecer selección del tipo"
-
-#: components/Search/AdvancedSearch.js:143
-msgid "Set type typeahead"
-msgstr "Establecer escritura anticipada del tipo"
-
-#: components/Workflow/WorkflowTools.js:154
-msgid "Set zoom to 100% and center graph"
-msgstr "Establecer zoom al 100% y centrar el gráfico"
-
-#: screens/Instances/Shared/InstanceForm.js:35
-msgid "Sets the current life cycle stage of this instance. Default is \"installed.\""
-msgstr "Establece la etapa actual del ciclo de vida de esta instancia. Por defecto es \"instalado\"."
-
-#: screens/Instances/Shared/InstanceForm.js:51
-msgid "Sets the role that this instance will play within mesh topology. Default is \"execution.\""
-msgstr "Establece el papel que desempeñará esta instancia dentro de la topología de malla. Por defecto es \"ejecución\"."
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:46
-msgid "Setting category"
-msgstr "Categoría de la configuración"
-
-#: screens/Setting/shared/RevertButton.js:46
-msgid "Setting matches factory default."
-msgstr "La configuración coincide con los valores predeterminados de fábrica."
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:49
-msgid "Setting name"
-msgstr "Nombre de la configuración"
-
-#: routeConfig.js:159
-#: routeConfig.js:163
-#: screens/ActivityStream/ActivityStream.js:220
-#: screens/ActivityStream/ActivityStream.js:222
-#: screens/Setting/Settings.js:43
-msgid "Settings"
-msgstr "Ajustes"
-
-#: components/FormField/PasswordInput.js:35
-msgid "Show"
-msgstr "Mostrar"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:182
-#: components/PromptDetail/PromptDetail.js:361
-#: components/PromptDetail/PromptJobTemplateDetail.js:156
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:488
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:283
-#: screens/Template/shared/JobTemplateForm.js:497
-msgid "Show Changes"
-msgstr "Mostrar cambios"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:177
-#: components/AdHocCommands/AdHocDetailsStep.js:178
-msgid "Show changes"
-msgstr "Mostrar cambios"
-
-#: components/LaunchPrompt/LaunchPrompt.js:135
-#: components/Schedule/shared/SchedulePromptableFields.js:102
-msgid "Show description"
-msgstr "Mostrar descripción"
-
-#: components/ChipGroup/ChipGroup.js:12
-msgid "Show less"
-msgstr "Mostrar menos"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:126
-msgid "Show only root groups"
-msgstr "Mostrar solo los grupos raíz"
-
-#: screens/Login/Login.js:262
-msgid "Sign in with Azure AD"
-msgstr "Iniciar sesión con Azure AD"
-
-#: screens/Login/Login.js:276
-msgid "Sign in with GitHub"
-msgstr "Iniciar sesión con GitHub"
-
-#: screens/Login/Login.js:318
-msgid "Sign in with GitHub Enterprise"
-msgstr "Iniciar sesión con GitHub Enterprise"
-
-#: screens/Login/Login.js:333
-msgid "Sign in with GitHub Enterprise Organizations"
-msgstr "Iniciar sesión con organizaciones GitHub Enterprise"
-
-#: screens/Login/Login.js:349
-msgid "Sign in with GitHub Enterprise Teams"
-msgstr "Iniciar sesión con equipos de GitHub Enterprise"
-
-#: screens/Login/Login.js:290
-msgid "Sign in with GitHub Organizations"
-msgstr "Iniciar sesión con las organizaciones GitHub"
-
-#: screens/Login/Login.js:304
-msgid "Sign in with GitHub Teams"
-msgstr "Iniciar sesión con equipos GitHub"
-
-#: screens/Login/Login.js:364
-msgid "Sign in with Google"
-msgstr "Iniciar sesión con Google"
-
-#: screens/Login/Login.js:378
-msgid "Sign in with OIDC"
-msgstr "Iniciar sesión con SAML "
-
-#: screens/Login/Login.js:397
-msgid "Sign in with SAML"
-msgstr "Iniciar sesión con SAML"
-
-#: screens/Login/Login.js:396
-msgid "Sign in with SAML {samlIDP}"
-msgstr "Iniciar sesión con SAML {samlIDP}"
-
-#: components/Search/Search.js:145
-#: components/Search/Search.js:146
-msgid "Simple key select"
-msgstr "Selección de clave simple"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:106
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:107
-#: components/PromptDetail/PromptDetail.js:295
-#: components/PromptDetail/PromptJobTemplateDetail.js:267
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:595
-#: screens/Job/JobDetail/JobDetail.js:500
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:475
-#: screens/Template/shared/JobTemplateForm.js:533
-#: screens/Template/shared/WorkflowJobTemplateForm.js:230
-msgid "Skip Tags"
-msgstr "Omitir etiquetas"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:93
-#: components/Schedule/shared/FrequencyDetailSubform.js:223
-msgid "Skip every"
-msgstr "Saltar cada"
-
-#: screens/Job/Job.helptext.js:20
-#: screens/Template/shared/JobTemplate.helptext.js:22
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:22
-msgid "Skip tags are useful when you have a large playbook, and you want to skip specific parts of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags."
-msgstr "La omisión de etiquetas resulta útil cuando tiene un playbook\n"
-"de gran tamaño y desea omitir partes específicas de la tarea\n"
-"o la jugada. Utilice comas para separar las distintas etiquetas.\n"
-"Consulte la documentación para obtener información detallada\n"
-"sobre el uso de etiquetas."
-
-#: screens/Job/JobOutput/shared/HostStatusBar.js:39
-msgid "Skipped"
-msgstr "Omitido"
-
-#: components/StatusLabel/StatusLabel.js:50
-msgid "Skipped'"
-msgstr "Omitido'"
-
-#: components/NotificationList/NotificationList.js:200
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:141
-msgid "Slack"
-msgstr "Slack"
-
-#: screens/Host/HostList/SmartInventoryButton.js:39
-#: screens/Host/HostList/SmartInventoryButton.js:48
-#: screens/Host/HostList/SmartInventoryButton.js:52
-#: screens/Inventory/InventoryList/InventoryList.js:187
-#: screens/Inventory/InventoryList/InventoryListItem.js:117
-msgid "Smart Inventory"
-msgstr "Inventario inteligente"
-
-#: screens/Inventory/SmartInventory.js:94
-msgid "Smart Inventory not found."
-msgstr "No se encontró el inventario inteligente."
-
-#: components/Lookup/HostFilterLookup.js:345
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:116
-msgid "Smart host filter"
-msgstr "Filtro de host inteligente"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:105
-msgid "Smart inventory"
-msgstr "Inventario inteligente"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:32
-#: components/LaunchPrompt/steps/PreviewStep.js:58
-msgid "Some of the previous step(s) have errors"
-msgstr "Algunos de los pasos anteriores tienen errores"
-
-#: screens/Host/HostList/SmartInventoryButton.js:17
-msgid "Some search modifiers like not__ and __search are not supported in Smart Inventory host filters. Remove these to create a new Smart Inventory with this filter."
-msgstr "Algunos modificadores de búsqueda como not__ y __search no se admiten en los filtros de host del Inventario Inteligente. Elimínelos para crear un nuevo inventario inteligente con este filtro."
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.js:39
-msgid "Something went wrong with the request to test this credential and metadata."
-msgstr "Se produjo un error al solicitar probar esta credencial y los metadatos."
-
-#: components/ContentError/ContentError.js:37
-msgid "Something went wrong..."
-msgstr "Se produjo un error..."
-
-#: components/Sort/Sort.js:139
-msgid "Sort"
-msgstr "Ordenar"
-
-#: components/JobList/JobListItem.js:169
-#: components/PromptDetail/PromptInventorySourceDetail.js:84
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:199
-#: screens/Inventory/shared/InventorySourceForm.js:130
-#: screens/Job/JobDetail/JobDetail.js:172
-#: screens/Job/JobDetail/JobDetail.js:294
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:93
-msgid "Source"
-msgstr "Fuente"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:44
-#: components/PromptDetail/PromptDetail.js:250
-#: components/PromptDetail/PromptJobTemplateDetail.js:147
-#: components/PromptDetail/PromptProjectDetail.js:106
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:89
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:467
-#: screens/Job/JobDetail/JobDetail.js:307
-#: screens/Project/ProjectDetail/ProjectDetail.js:230
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:248
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:133
-#: screens/Template/shared/JobTemplateForm.js:335
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:245
-msgid "Source Control Branch"
-msgstr "Rama de fuente de control"
-
-#: screens/Project/shared/ProjectSubForms/GitSubForm.js:29
-msgid "Source Control Branch/Tag/Commit"
-msgstr "Rama/etiqueta/commit de fuente de control"
-
-#: components/PromptDetail/PromptProjectDetail.js:117
-#: screens/Project/ProjectDetail/ProjectDetail.js:256
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:56
-msgid "Source Control Credential"
-msgstr "Credencial de fuente de control"
-
-#: components/PromptDetail/PromptProjectDetail.js:111
-#: screens/Project/ProjectDetail/ProjectDetail.js:235
-#: screens/Project/shared/ProjectSubForms/GitSubForm.js:32
-msgid "Source Control Refspec"
-msgstr "Refspec de fuente de control"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:195
-msgid "Source Control Revision"
-msgstr "Revisión del control de fuentes"
-
-#: components/PromptDetail/PromptProjectDetail.js:96
-#: screens/Job/JobDetail/JobDetail.js:273
-#: screens/Project/ProjectDetail/ProjectDetail.js:191
-#: screens/Project/shared/ProjectForm.js:259
-msgid "Source Control Type"
-msgstr "Tipo de fuente de control"
-
-#: components/Lookup/ProjectLookup.js:143
-#: components/PromptDetail/PromptProjectDetail.js:101
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:96
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:165
-#: screens/Project/ProjectDetail/ProjectDetail.js:225
-#: screens/Project/ProjectList/ProjectList.js:205
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:16
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:104
-msgid "Source Control URL"
-msgstr "URL de fuente de control"
-
-#: components/JobList/JobList.js:211
-#: components/JobList/JobListItem.js:42
-#: components/Schedule/ScheduleList/ScheduleListItem.js:38
-#: screens/Job/JobDetail/JobDetail.js:65
-msgid "Source Control Update"
-msgstr "Actualización de fuente de control"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:340
-msgid "Source Phone Number"
-msgstr "Número de teléfono de la fuente"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:176
-msgid "Source Variables"
-msgstr "Variables de fuente"
-
-#: components/JobList/JobListItem.js:213
-#: screens/Job/JobDetail/JobDetail.js:257
-msgid "Source Workflow Job"
-msgstr "Tarea del flujo de trabajo de origen"
-
-#: screens/Template/shared/WorkflowJobTemplateForm.js:177
-msgid "Source control branch"
-msgstr "Rama de fuente de control"
-
-#: screens/Inventory/shared/InventorySourceForm.js:152
-msgid "Source details"
-msgstr "Detalles de la fuente"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:403
-msgid "Source phone number"
-msgstr "Número de teléfono de la fuente"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:269
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:21
-msgid "Source variables"
-msgstr "Variables de fuente"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:97
-msgid "Sourced from a project"
-msgstr "Extraído de un proyecto"
-
-#: screens/Inventory/Inventories.js:84
-#: screens/Inventory/Inventory.js:68
-msgid "Sources"
-msgstr "Fuentes"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:30
-msgid ""
-"Specify HTTP Headers in JSON format. Refer to\n"
-"the Ansible Controller documentation for example syntax."
-msgstr "Especifique los encabezados HTTP en formato JSON. Consulte la\n"
-"documentación de Ansible Tower para obtener ejemplos de sintaxis."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:24
-msgid ""
-"Specify a notification color. Acceptable colors are hex\n"
-"color code (example: #3af or #789abc)."
-msgstr "Especifique un color para la notificación. Los colores aceptables son\n"
-"el código de color hexadecimal (ejemplo: #3af o #789abc)."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:26
-msgid "Specify the conditions under which this node should be executed"
-msgstr "Especificar las condiciones en las que debe ejecutarse este nodo"
-
-#: screens/Job/JobOutput/HostEventModal.js:173
-msgid "Standard Error"
-msgstr "Error estándar"
-
-#: screens/Job/JobOutput/HostEventModal.js:174
-msgid "Standard error tab"
-msgstr "Pestaña de error estándar"
-
-#: components/NotificationList/NotificationListItem.js:57
-#: components/NotificationList/NotificationListItem.js:58
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:47
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:53
-msgid "Start"
-msgstr "Iniciar"
-
-#: components/JobList/JobList.js:247
-#: components/JobList/JobListItem.js:99
-msgid "Start Time"
-msgstr "Hora de inicio"
-
-#: components/Schedule/shared/DateTimePicker.js:51
-msgid "Start date"
-msgstr "Fecha de inicio"
-
-#: components/Schedule/shared/ScheduleFormFields.js:87
-msgid "Start date/time"
-msgstr "Fecha/hora de inicio"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:465
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:105
-msgid "Start message"
-msgstr "Iniciar mensaje"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:474
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:114
-msgid "Start message body"
-msgstr "Iniciar cuerpo del mensaje"
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:33
-msgid "Start sync process"
-msgstr "Iniciar proceso de sincronización"
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:37
-msgid "Start sync source"
-msgstr "Iniciar fuente de sincronización"
-
-#: components/Schedule/shared/DateTimePicker.js:61
-msgid "Start time"
-msgstr "Hora de inicio"
-
-#: screens/Job/JobDetail/JobDetail.js:220
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:165
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:63
-msgid "Started"
-msgstr "Iniciado"
-
-#: components/JobList/JobList.js:224
-#: components/JobList/JobList.js:245
-#: components/JobList/JobListItem.js:95
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:206
-#: screens/InstanceGroup/Instances/InstanceList.js:267
-#: screens/InstanceGroup/Instances/InstanceListItem.js:129
-#: screens/Instances/InstanceDetail/InstanceDetail.js:196
-#: screens/Instances/InstanceList/InstanceList.js:202
-#: screens/Instances/InstanceList/InstanceListItem.js:134
-#: screens/Instances/InstancePeers/InstancePeerList.js:97
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:43
-#: screens/Inventory/InventoryList/InventoryListItem.js:101
-#: screens/Inventory/InventorySources/InventorySourceList.js:212
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:87
-#: screens/Job/JobDetail/JobDetail.js:210
-#: screens/Job/JobOutput/HostEventModal.js:118
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:115
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:179
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:117
-#: screens/Project/ProjectList/ProjectList.js:222
-#: screens/Project/ProjectList/ProjectListItem.js:197
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:61
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:162
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:166
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:66
-msgid "Status"
-msgstr "Estado"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:91
-msgid "Stdout"
-msgstr "Stdout"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:37
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:49
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:212
-msgid "Submit"
-msgstr "Enviar"
-
-#: screens/Project/shared/Project.helptext.js:118
-msgid ""
-"Submodules will track the latest commit on\n"
-"their master branch (or other branch specified in\n"
-".gitmodules). If no, submodules will be kept at\n"
-"the revision specified by the main project.\n"
-"This is equivalent to specifying the --remote\n"
-"flag to git submodule update."
-msgstr "Los submódulos realizarán el seguimiento del último commit en\n"
-"su rama maestra (u otra rama especificada en\n"
-".gitmodules). De lo contrario, el proyecto principal mantendrá los submódulos en\n"
-"la revisión especificada.\n"
-"Esto es equivalente a especificar el indicador --remote para la actualización del submódulo Git."
-
-#: screens/Setting/SettingList.js:132
-#: screens/Setting/Settings.js:112
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:136
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:195
-msgid "Subscription"
-msgstr "Subscripción"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:41
-msgid "Subscription Details"
-msgstr "Detalles de la suscripción"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:194
-msgid "Subscription Management"
-msgstr "Administración de suscripciones"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:82
-msgid "Subscription manifest"
-msgstr "Manifiesto de suscripción"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:84
-msgid "Subscription selection modal"
-msgstr "Modal de selección de suscripción"
-
-#: screens/Setting/SettingList.js:137
-msgid "Subscription settings"
-msgstr "Configuración de la suscripción"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:131
-msgid "Subscription type"
-msgstr "Tipo de suscripción"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:142
-msgid "Subscriptions table"
-msgstr "Tabla de suscripciones"
-
-#: components/Lookup/ProjectLookup.js:137
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:90
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:159
-#: screens/Job/JobDetail/JobDetail.js:76
-#: screens/Project/ProjectList/ProjectList.js:199
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:98
-msgid "Subversion"
-msgstr "Subversion"
-
-#: components/NotificationList/NotificationListItem.js:71
-#: components/NotificationList/NotificationListItem.js:72
-#: components/StatusLabel/StatusLabel.js:41
-msgid "Success"
-msgstr "Correcto"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:483
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:123
-msgid "Success message"
-msgstr "Mensaje de éxito"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:492
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:132
-msgid "Success message body"
-msgstr "Cuerpo del mensaje de éxito"
-
-#: components/JobList/JobList.js:231
-#: components/StatusLabel/StatusLabel.js:43
-#: components/Workflow/WorkflowNodeHelp.js:102
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:96
-#: screens/Dashboard/shared/ChartTooltip.js:59
-msgid "Successful"
-msgstr "Correctamente"
-
-#: screens/Dashboard/DashboardGraph.js:166
-msgid "Successful jobs"
-msgstr "Tareas exitosas"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:28
-msgid "Successfully Approved"
-msgstr "Aprobado con éxito"
-
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:25
-msgid "Successfully Denied"
-msgstr "Denegado con éxito"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:201
-#: screens/Project/ProjectList/ProjectListItem.js:97
-msgid "Successfully copied to clipboard!"
-msgstr "Copiado correctamente en el portapapeles"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:255
-msgid "Sun"
-msgstr "Dom"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:83
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:182
-#: components/Schedule/shared/FrequencyDetailSubform.js:260
-#: components/Schedule/shared/FrequencyDetailSubform.js:428
-msgid "Sunday"
-msgstr "Domingo"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:26
-#: screens/Template/Template.js:159
-#: screens/Template/Templates.js:48
-#: screens/Template/WorkflowJobTemplate.js:145
-msgid "Survey"
-msgstr "Encuesta"
-
-#: screens/Template/Survey/SurveyToolbar.js:105
-msgid "Survey Disabled"
-msgstr "Encuesta deshabilitada"
-
-#: screens/Template/Survey/SurveyToolbar.js:104
-msgid "Survey Enabled"
-msgstr "Encuesta habilitada"
-
-#: screens/Template/Survey/SurveyReorderModal.js:191
-msgid "Survey Question Order"
-msgstr "Orden de las preguntas de la encuesta"
-
-#: screens/Template/Survey/SurveyToolbar.js:102
-msgid "Survey Toggle"
-msgstr "Alternancia de encuestas"
-
-#: screens/Template/Survey/SurveyReorderModal.js:192
-msgid "Survey preview modal"
-msgstr "Modal de vista previa de la encuesta"
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:120
-#: screens/Inventory/shared/InventorySourceSyncButton.js:41
-#: screens/Project/shared/ProjectSyncButton.js:41
-#: screens/Project/shared/ProjectSyncButton.js:53
-msgid "Sync"
-msgstr "Sincronizar"
-
-#: screens/Project/ProjectList/ProjectListItem.js:238
-#: screens/Project/shared/ProjectSyncButton.js:37
-#: screens/Project/shared/ProjectSyncButton.js:48
-msgid "Sync Project"
-msgstr "Sincronizar proyecto"
-
-#: screens/Inventory/InventoryList/InventoryList.js:219
-msgid "Sync Status"
-msgstr "Estado de sincronización"
-
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:19
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:29
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:32
-msgid "Sync all"
-msgstr "Sincronizar todo"
-
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:25
-msgid "Sync all sources"
-msgstr "Sincronizar todas las fuentes"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:236
-msgid "Sync error"
-msgstr "Error de sincronización"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:213
-#: screens/Project/ProjectList/ProjectListItem.js:109
-msgid "Sync for revision"
-msgstr "Sincronizar para revisión"
-
-#: screens/Project/ProjectList/ProjectListItem.js:122
-msgid "Syncing"
-msgstr "Sincronización"
-
-#: screens/Setting/SettingList.js:102
-#: screens/User/UserRoles/UserRolesListItem.js:18
-msgid "System"
-msgstr "Sistema"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:128
-#: screens/User/UserDetail/UserDetail.js:47
-#: screens/User/UserList/UserListItem.js:19
-#: screens/User/UserRoles/UserRolesList.js:127
-#: screens/User/shared/UserForm.js:41
-msgid "System Administrator"
-msgstr "Administrador del sistema"
-
-#: screens/User/UserDetail/UserDetail.js:49
-#: screens/User/UserList/UserListItem.js:21
-#: screens/User/shared/UserForm.js:35
-msgid "System Auditor"
-msgstr "Auditor del sistema"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:128
-msgid "System Warning"
-msgstr "Advertencia del sistema"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:131
-#: screens/User/UserRoles/UserRolesList.js:130
-msgid "System administrators have unrestricted access to all resources."
-msgstr "Los administradores del sistema tienen acceso ilimitado a todos los recursos."
-
-#: screens/Setting/Settings.js:115
-msgid "TACACS+"
-msgstr "TACACS+"
-
-#: screens/Setting/SettingList.js:81
-msgid "TACACS+ settings"
-msgstr "Configuración de TACACS+"
-
-#: screens/Dashboard/Dashboard.js:117
-#: screens/Job/JobOutput/HostEventModal.js:94
-msgid "Tabs"
-msgstr "Pestañas"
-
-#: screens/Job/Job.helptext.js:19
-#: screens/Template/shared/JobTemplate.helptext.js:21
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:21
-msgid "Tags are useful when you have a large playbook, and you want to run a specific part of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags."
-msgstr "Las etiquetas resultan útiles cuando tiene un playbook\n"
-"de gran tamaño y desea ejecutar una parte específica\n"
-"de la tarea o la jugada. Utilice comas para separar varias\n"
-"etiquetas. Consulte la documentación para obtener\n"
-"información detallada sobre el uso de etiquetas."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:203
-msgid "Tags for the Annotation"
-msgstr "Etiquetas para la anotación"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:179
-msgid "Tags for the annotation (optional)"
-msgstr "Etiquetas para anotación (opcional)"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:248
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:298
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:366
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:252
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:329
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:453
-msgid "Target URL"
-msgstr "URL destino"
-
-#: screens/Job/JobOutput/HostEventModal.js:123
-msgid "Task"
-msgstr "Tarea"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:90
-msgid "Task Count"
-msgstr "Recuento de tareas"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:129
-msgid "Task Started"
-msgstr "Tarea iniciada"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:91
-msgid "Tasks"
-msgstr "Tareas"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:13
-msgid "Team"
-msgstr "Equipo"
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:85
-#: screens/Team/TeamRoles/TeamRolesList.js:144
-msgid "Team Roles"
-msgstr "Roles de equipo"
-
-#: screens/Team/Team.js:75
-msgid "Team not found."
-msgstr "No se encontró la tarea."
-
-#: components/AddRole/AddResourceRole.js:188
-#: components/AddRole/AddResourceRole.js:189
-#: routeConfig.js:106
-#: screens/ActivityStream/ActivityStream.js:187
-#: screens/Organization/Organization.js:125
-#: screens/Organization/OrganizationList/OrganizationList.js:145
-#: screens/Organization/OrganizationList/OrganizationListItem.js:66
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:64
-#: screens/Organization/Organizations.js:32
-#: screens/Team/TeamList/TeamList.js:112
-#: screens/Team/TeamList/TeamList.js:166
-#: screens/Team/Teams.js:15
-#: screens/Team/Teams.js:25
-#: screens/User/User.js:70
-#: screens/User/UserTeams/UserTeamList.js:175
-#: screens/User/UserTeams/UserTeamList.js:246
-#: screens/User/Users.js:32
-#: util/getRelatedResourceDeleteDetails.js:174
-msgid "Teams"
-msgstr "Equipos"
-
-#: screens/Setting/Jobs/JobsEdit/JobsEdit.js:130
-msgid "Template"
-msgstr "Plantilla"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:115
-#: components/TemplateList/TemplateList.js:133
-msgid "Template copied successfully"
-msgstr "La plantilla se copió correctamente"
-
-#: screens/Template/Template.js:175
-#: screens/Template/WorkflowJobTemplate.js:175
-msgid "Template not found."
-msgstr "No se encontró la plantilla."
-
-#: components/TemplateList/TemplateList.js:200
-#: components/TemplateList/TemplateList.js:263
-#: routeConfig.js:65
-#: screens/ActivityStream/ActivityStream.js:164
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:70
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:83
-#: screens/Template/Templates.js:17
-#: util/getRelatedResourceDeleteDetails.js:218
-#: util/getRelatedResourceDeleteDetails.js:275
-msgid "Templates"
-msgstr "Plantillas"
-
-#: screens/Credential/shared/CredentialForm.js:331
-#: screens/Credential/shared/CredentialForm.js:337
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:80
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:426
-msgid "Test"
-msgstr "Probar"
-
-#: screens/Credential/shared/ExternalTestModal.js:77
-msgid "Test External Credential"
-msgstr "Credencial externa de prueba"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:128
-msgid "Test Notification"
-msgstr "Probar notificación"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:125
-msgid "Test notification"
-msgstr "Probar notificación"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.js:44
-msgid "Test passed"
-msgstr "Prueba superada"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:80
-#: screens/Template/Survey/SurveyReorderModal.js:181
-msgid "Text"
-msgstr "Texto"
-
-#: screens/Template/Survey/SurveyReorderModal.js:135
-msgid "Text Area"
-msgstr "Área de texto"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:81
-msgid "Textarea"
-msgstr "Área de texto"
-
-#: components/Lookup/Lookup.js:63
-msgid "That value was not found. Please enter or select a valid value."
-msgstr "No se encontró ese valor. Ingrese o seleccione un valor válido."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:398
-msgid "The"
-msgstr "El"
-
-#: screens/Application/shared/Application.helptext.js:4
-msgid "The Grant type the user must use to acquire tokens for this application"
-msgstr "El tipo de subvención que el usuario debe utilizar para adquirir tokens para esta aplicación"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:128
-msgid "The Instance Groups for this Organization to run on."
-msgstr "Seleccione los grupos de instancias en los que se ejecutará\n"
-"esta organización."
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:219
-msgid "The Instance Groups to which this instance belongs."
-msgstr "Los grupos de instancias a los que pertenece esta instancia."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:6
-msgid ""
-"The amount of time (in seconds) before the email\n"
-"notification stops trying to reach the host and times out. Ranges\n"
-"from 1 to 120 seconds."
-msgstr "La cantidad de tiempo (en segundos) antes de que la notificación\n"
-"de correo electrónico deje de intentar conectarse con el host\n"
-"y caduque el tiempo de espera. Rangos de 1 a 120 segundos."
-
-#: screens/Job/Job.helptext.js:17
-#: screens/Template/shared/JobTemplate.helptext.js:18
-msgid "The amount of time (in seconds) to run before the job is canceled. Defaults to 0 for no job timeout."
-msgstr "La cantidad de tiempo (en segundos) para ejecutar\n"
-"antes de que se cancele la tarea. Valores predeterminados\n"
-"en 0 para el tiempo de espera de la tarea."
-
-#: screens/User/shared/User.helptext.js:4
-msgid "The application that this token belongs to, or leave this field empty to create a Personal Access Token."
-msgstr "Seleccione la aplicación a la que pertenecerá este token, o deje este campo vacío para crear un token de acceso personal."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:9
-msgid ""
-"The base URL of the Grafana server - the\n"
-"/api/annotations endpoint will be added automatically to the base\n"
-"Grafana URL."
-msgstr "La URL base del servidor de Grafana:\n"
-"el punto de acceso /api/annotations se agregará automáticamente\n"
-"a la URL base de Grafana."
-
-#: screens/Template/shared/JobTemplate.helptext.js:9
-msgid "The container image to be used for execution."
-msgstr "La imagen del contenedor que se utilizará para la ejecución."
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:109
-msgid ""
-"The execution environment that will be used for jobs\n"
-"inside of this organization. This will be used a fallback when\n"
-"an execution environment has not been explicitly assigned at the\n"
-"project, job template or workflow level."
-msgstr "El entorno de ejecución que se utilizará para las tareas dentro de esta organización. Se utilizará como reserva cuando no se haya asignado explícitamente un entorno de ejecución en el nivel de proyecto, plantilla de trabajo o flujo de trabajo."
-
-#: screens/Organization/shared/OrganizationForm.js:93
-msgid "The execution environment that will be used for jobs inside of this organization. This will be used a fallback when an execution environment has not been explicitly assigned at the project, job template or workflow level."
-msgstr "El entorno de ejecución que se utilizará para las tareas dentro de esta organización. Se utilizará como reserva cuando no se haya asignado explícitamente un entorno de ejecución en el nivel de proyecto, plantilla de trabajo o flujo de trabajo."
-
-#: screens/Project/shared/Project.helptext.js:5
-msgid "The execution environment that will be used for jobs that use this project. This will be used as fallback when an execution environment has not been explicitly assigned at the job template or workflow level."
-msgstr "El entorno de ejecución que se utilizará para las tareas que utilizan este proyecto. Se utilizará como reserva cuando no se haya asignado explícitamente un entorno de ejecución en el nivel de plantilla de trabajo o flujo de trabajo."
-
-#: screens/Job/Job.helptext.js:9
-#: screens/Template/shared/JobTemplate.helptext.js:10
-msgid "The execution environment that will be used when launching this job template. The resolved execution environment can be overridden by explicitly assigning a different one to this job template."
-msgstr "El entorno de ejecución que se utilizará al lanzar\n"
-"esta plantilla de trabajo. El entorno de ejecución resuelto puede anularse asignando asignando explícitamente uno diferente a esta plantilla de trabajo."
-
-#: screens/Project/shared/Project.helptext.js:93
-msgid ""
-"The first fetches all references. The second\n"
-"fetches the Github pull request number 62, in this example\n"
-"the branch needs to be \"pull/62/head\"."
-msgstr "El primero extrae todas las referencias. El segundo\n"
-"extrae el número de solicitud de extracción 62 de Github; en este ejemplo,\n"
-"la rama debe ser \"pull/62/head\"."
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:7
-msgid "The full image location, including the container registry, image name, and version tag."
-msgstr "La ubicación completa de la imagen, que incluye el registro de contenedores, el nombre de la imagen y la etiqueta de la versión."
-
-#: screens/Inventory/shared/Inventory.helptext.js:191
-msgid ""
-"The inventory file\n"
-"to be synced by this source. You can select from\n"
-"the dropdown or enter a file within the input."
-msgstr "Seleccione el archivo del inventario que sincronizará\n"
-"esta fuente. Puede seleccionar del menú desplegable\n"
-"o ingresar un archivo en la entrada."
-
-#: screens/Host/HostDetail/HostDetail.js:79
-msgid "The inventory that this host belongs to."
-msgstr "Seleccione el inventario al que pertenecerá este host."
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:141
-msgid "The last {dayOfWeek}"
-msgstr "El último {dayOfWeek}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:200
-msgid "The last {weekday} of {month}"
-msgstr "El último {weekday} de {month}"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:100
-msgid ""
-"The maximum number of hosts allowed to be managed by\n"
-"this organization. Value defaults to 0 which means no limit.\n"
-"Refer to the Ansible documentation for more details."
-msgstr "La cantidad máxima de hosts que puede administrar esta organización.\n"
-"El valor predeterminado es 0, que significa sin límite. Consulte\n"
-"la documentación de Ansible para obtener más información detallada."
-
-#: screens/Organization/shared/OrganizationForm.js:72
-msgid ""
-"The maximum number of hosts allowed to be managed by this organization.\n"
-"Value defaults to 0 which means no limit. Refer to the Ansible\n"
-"documentation for more details."
-msgstr "La cantidad máxima de hosts que puede administrar esta organización.\n"
-"El valor predeterminado es 0, que significa sin límite. Consulte\n"
-"la documentación de Ansible para obtener más información detallada."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:26
-msgid ""
-"The number associated with the \"Messaging\n"
-"Service\" in Twilio with the format +18005550199."
-msgstr "Ingrese el número asociado con el \"Servicio de mensajería\"\n"
-"en Twilio con el formato +18005550199."
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:69
-msgid "The number of hosts you have automated against is below your subscription count."
-msgstr "El número de hosts que tiene automatizados es inferior al número de suscripciones."
-
-#: screens/Job/Job.helptext.js:25
-#: screens/Template/shared/JobTemplate.helptext.js:48
-msgid "The number of parallel or simultaneous processes to use while executing the playbook. An empty value, or a value less than 1 will use the Ansible default which is usually 5. The default number of forks can be overwritten with a change to"
-msgstr "El número de procesos paralelos o simultáneos para utilizar durante la ejecución del cuaderno de estrategias. Un valor vacío, o un valor menor que 1, usará el valor predeterminado de Ansible que normalmente es 5. El número predeterminado de bifurcaciones puede ser sobrescrito con un cambio a"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:164
-msgid "The number of parallel or simultaneous processes to use while executing the playbook. Inputting no value will use the default value from the ansible configuration file. You can find more information"
-msgstr "La cantidad de procesos paralelos o simultáneos para utilizar durante la ejecución del playbook. Si no ingresa un valor, se utilizará el valor predeterminado del archivo de configuración de Ansible. Para obtener más información,"
-
-#: components/ContentError/ContentError.js:41
-#: screens/Job/Job.js:161
-msgid "The page you requested could not be found."
-msgstr "No se pudo encontrar la página solicitada."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:144
-msgid "The pattern used to target hosts in the inventory. Leaving the field blank, all, and * will all target all hosts in the inventory. You can find more information about Ansible's host patterns"
-msgstr "El patrón utilizado para dirigir los hosts en el inventario. Si se deja el campo en blanco, todos y * se dirigirán a todos los hosts del inventario. Para encontrar más información sobre los patrones de hosts de Ansible,"
-
-#: screens/Job/Job.helptext.js:7
-msgid "The project containing the playbook this job will execute."
-msgstr "Seleccione el proyecto que contiene el playbook\n"
-"que desea que ejecute esta tarea."
-
-#: screens/Job/Job.helptext.js:8
-msgid "The project from which this inventory update is sourced."
-msgstr "El proyecto del que procede esta actualización del inventario."
-
-#: screens/Project/ProjectList/ProjectListItem.js:120
-msgid "The project is currently syncing and the revision will be available after the sync is complete."
-msgstr "El proyecto se está sincronizando actualmente y la revisión estará disponible una vez que se haya completado la sincronización."
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:211
-#: screens/Project/ProjectList/ProjectListItem.js:107
-msgid "The project must be synced before a revision is available."
-msgstr "El proyecto debe estar sincronizado antes de que una revisión esté disponible."
-
-#: screens/Project/ProjectList/ProjectListItem.js:130
-msgid "The project revision is currently out of date. Please refresh to fetch the most recent revision."
-msgstr "La revisión del proyecto está actualmente desactualizada. Actualice para obtener la revisión más reciente."
-
-#: components/Workflow/WorkflowNodeHelp.js:138
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:131
-msgid "The resource associated with this node has been deleted."
-msgstr "Se ha eliminado el recurso asociado a este nodo."
-
-#: screens/Job/JobOutput/EmptyOutput.js:31
-msgid "The search filter did not produce any results…"
-msgstr "El filtro de búsqueda no arrojó resultados…"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:180
-msgid ""
-"The suggested format for variable names is lowercase and\n"
-"underscore-separated (for example, foo_bar, user_id, host_name,\n"
-"etc.). Variable names with spaces are not allowed."
-msgstr "El formato sugerido para los nombres de variables es minúsculas y\n"
-"separados por guiones bajos (por ejemplo, foo_bar, user_id, host_name,\n"
-"etc.). No se permiten los nombres de variables con espacios."
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:50
-msgid ""
-"There are no available playbook directories in {project_base_dir}.\n"
-"Either that directory is empty, or all of the contents are already\n"
-"assigned to other projects. Create a new directory there and make\n"
-"sure the playbook files can be read by the \"awx\" system user,\n"
-"or have {brandName} directly retrieve your playbooks from\n"
-"source control using the Source Control Type option above."
-msgstr "No hay directorios de playbook disponibles en {project_base_dir}.\n"
-"O bien ese directorio está vacío, o todo el contenido ya está\n"
-"asignado a otros proyectos. Cree un nuevo directorio allí y asegúrese de que los archivos de playbook puedan ser leídos por el usuario del sistema \"awx\", o haga que {brandName} recupere directamente sus playbooks desde\n"
-"control de fuentes utilizando la opción de tipo de control de fuentes anterior."
-
-#: screens/Template/Survey/MultipleChoiceField.js:34
-msgid "There must be a value in at least one input"
-msgstr "Debe haber un valor en al menos una entrada"
-
-#: screens/Login/Login.js:155
-msgid "There was a problem logging in. Please try again."
-msgstr "Hubo un problema al iniciar sesión. Inténtelo de nuevo."
-
-#: components/ContentError/ContentError.js:42
-msgid "There was an error loading this content. Please reload the page."
-msgstr "Se produjo un error al cargar este contenido. Vuelva a cargar la página."
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:56
-msgid "There was an error parsing the file. Please check the file formatting and try again."
-msgstr "Se produjo un error al analizar el archivo. Compruebe el formato del archivo e inténtelo de nuevo."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:713
-msgid "There was an error saving the workflow."
-msgstr "Se produjo un error al guardar el flujo de trabajo."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:69
-msgid "These are the modules that {brandName} supports running commands against."
-msgstr "Estos son los módulos que {brandName} admite para ejecutar comandos."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:129
-msgid "These are the verbosity levels for standard out of the command run that are supported."
-msgstr "Estos son los niveles de detalle para la ejecución de comandos estándar que se admiten."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:122
-#: screens/Job/Job.helptext.js:43
-msgid "These arguments are used with the specified module."
-msgstr "Estos argumentos se utilizan con el módulo especificado."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:111
-msgid "These arguments are used with the specified module. You can find information about {0} by clicking"
-msgstr "Estos argumentos se utilizan con el módulo especificado. Para encontrar información sobre {0}, haga clic en"
-
-#: screens/Job/Job.helptext.js:33
-msgid "These arguments are used with the specified module. You can find information about {moduleName} by clicking"
-msgstr "Estos argumentos se utilizan con el módulo especificado. Para encontrar información sobre {moduleName}, haga clic en"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:410
-msgid "Third"
-msgstr "Tercero"
-
-#: screens/Template/shared/JobTemplateForm.js:157
-msgid "This Project needs to be updated"
-msgstr "Este proyecto debe actualizarse"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:286
-#: screens/Template/Survey/SurveyList.js:82
-msgid "This action will delete the following:"
-msgstr "Esta acción eliminará lo siguiente:"
-
-#: screens/User/UserTeams/UserTeamList.js:217
-msgid "This action will disassociate all roles for this user from the selected teams."
-msgstr "Esta acción disociará todos los roles de este usuario de los equipos seleccionados."
-
-#: screens/Team/TeamRoles/TeamRolesList.js:236
-#: screens/User/UserRoles/UserRolesList.js:232
-msgid "This action will disassociate the following role from {0}:"
-msgstr "Esta acción disociará el siguiente rol de {0}:"
-
-#: components/DisassociateButton/DisassociateButton.js:148
-msgid "This action will disassociate the following:"
-msgstr "Esta acción disociará lo siguiente:"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:178
-msgid "This action will remove the following instances:"
-msgstr "Esta acción eliminará las siguientes instancias:"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:113
-msgid "This container group is currently being by other resources. Are you sure you want to delete it?"
-msgstr "Este grupo de contenedores está siendo utilizado por otros recursos. ¿Está seguro de que desea eliminarlo?"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:304
-msgid "This credential is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "Esta credencial está siendo utilizada por otros recursos. ¿Está seguro de que desea eliminarla?"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:121
-msgid "This credential type is currently being used by some credentials and cannot be deleted"
-msgstr "Este tipo de credencial está siendo utilizado por algunas credenciales y no se puede eliminar"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:74
-msgid ""
-"This data is used to enhance\n"
-"future releases of the Software and to provide\n"
-"Automation Analytics."
-msgstr "Estos datos se utilizan para mejorar\n"
-"futuras versiones del software y para proporcionar Automation Analytics."
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:62
-msgid ""
-"This data is used to enhance\n"
-"future releases of the Tower Software and help\n"
-"streamline customer experience and success."
-msgstr "Estos datos se utilizan para mejorar futuras versiones\n"
-"del software Tower y para ayudar a optimizar el éxito\n"
-"y la experiencia del cliente."
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:132
-msgid "This execution environment is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "Este entorno de ejecución está siendo utilizado por otros recursos. ¿Está seguro de que desea eliminarlo?"
-
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:74
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:79
-msgid "This feature is deprecated and will be removed in a future release."
-msgstr "Esta función está obsoleta y se eliminará en una futura versión."
-
-#: screens/Inventory/shared/Inventory.helptext.js:155
-msgid "This field is ignored unless an Enabled Variable is set. If the enabled variable matches this value, the host will be enabled on import."
-msgstr "Este campo se ignora a menos que se establezca una variable habilitada. Si la variable habilitada coincide con este valor, el host se habilitará en la importación."
-
-#: components/AdHocCommands/useAdHocCredentialPasswordStep.js:44
-#: components/LaunchPrompt/steps/useCredentialPasswordsStep.js:50
-msgid "This field may not be blank"
-msgstr "Este campo no puede estar en blanco"
-
-#: util/validators.js:127
-msgid "This field must be a number"
-msgstr "Este campo debe ser un número"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:107
-msgid "This field must be a number and have a value between {0} and {1}"
-msgstr "Este campo debe ser un número y tener un valor entre {0} y {1}"
-
-#: util/validators.js:67
-msgid "This field must be a number and have a value between {min} and {max}"
-msgstr "Este campo debe ser un número y tener un valor entre {min} y {max}"
-
-#: util/validators.js:64
-msgid "This field must be a number and have a value greater than {min}"
-msgstr "Este campo debe ser un número y tener un valor mayor que {min}"
-
-#: util/validators.js:61
-msgid "This field must be a number and have a value less than {max}"
-msgstr "Este campo debe ser un número y tener un valor inferior a {max}"
-
-#: util/validators.js:184
-msgid "This field must be a regular expression"
-msgstr "Este campo debe ser una expresión regular"
-
-#: util/validators.js:111
-#: util/validators.js:194
-msgid "This field must be an integer"
-msgstr "Este campo debe ser un número entero"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:99
-msgid "This field must be at least {0} characters"
-msgstr "Este campo debe tener al menos {0} caracteres"
-
-#: util/validators.js:52
-msgid "This field must be at least {min} characters"
-msgstr "Este campo debe tener al menos {min} caracteres"
-
-#: util/validators.js:197
-msgid "This field must be greater than 0"
-msgstr "Este campo debe ser mayor que 0"
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:52
-#: components/LaunchPrompt/steps/useSurveyStep.js:111
-#: screens/Template/shared/JobTemplateForm.js:154
-#: screens/User/shared/UserForm.js:92
-#: screens/User/shared/UserForm.js:103
-#: util/validators.js:5
-#: util/validators.js:76
-msgid "This field must not be blank"
-msgstr "Este campo no debe estar en blanco"
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:46
-msgid "This field must not be blank."
-msgstr "Este campo no debe estar en blanco"
-
-#: util/validators.js:101
-msgid "This field must not contain spaces"
-msgstr "Este campo no debe contener espacios"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:102
-msgid "This field must not exceed {0} characters"
-msgstr "Este campo no debe exceder los {0} caracteres"
-
-#: util/validators.js:43
-msgid "This field must not exceed {max} characters"
-msgstr "Este campo no debe exceder los {max} caracteres"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:51
-msgid "This field will be retrieved from an external secret management system using the specified credential."
-msgstr "Este campo se recuperará de un sistema externo de gestión de claves secretas utilizando la credencial especificada."
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:82
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:89
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:104
-msgid "This has already been acted on"
-msgstr "Ya se ha actuado al respecto"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:125
-msgid "This instance group is currently being by other resources. Are you sure you want to delete it?"
-msgstr "Este grupo de instancias está siendo utilizado por otros recursos. ¿Está seguro de que desea eliminarlo?"
-
-#: components/LaunchPrompt/steps/useInventoryStep.js:59
-msgid "This inventory is applied to all workflow nodes within this workflow ({0}) that prompt for an inventory."
-msgstr "Este inventario se aplica a todos los nodos de este flujo de trabajo ({0}) que solicitan un inventario."
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:199
-msgid "This inventory is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "Este inventario está siendo utilizado por otros recursos. ¿Está seguro de que desea eliminarlo?"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:313
-msgid "This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?"
-msgstr "Esta fuente de inventario está siendo utilizada por otros recursos que dependen de ella. ¿Está seguro de que desea eliminarla?"
-
-#: screens/Application/Applications.js:77
-msgid "This is the only time the client secret will be shown."
-msgstr "Esta es la única vez que se mostrará la clave secreta del cliente."
-
-#: screens/User/UserTokens/UserTokens.js:59
-msgid "This is the only time the token value and associated refresh token value will be shown."
-msgstr "Esta es la única vez que se mostrará el valor del token y el valor del token de actualización asociado."
-
-#: screens/Job/JobOutput/EmptyOutput.js:37
-msgid "This job failed and has no output."
-msgstr "Este trabajo ha fallado y no tiene salida."
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:543
-msgid "This job template is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "Esta plantilla de trabajo está siendo utilizada por otros recursos. ¿Está seguro de que desea eliminarla?"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:197
-msgid "This organization is currently being by other resources. Are you sure you want to delete it?"
-msgstr "Esta organización está siendo utilizada por otros recursos. ¿Está seguro de que desea eliminarla?"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:338
-msgid "This project is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "Este proyecto está siendo utilizado por otros recursos. ¿Está seguro de que desea eliminarlo?"
-
-#: screens/Project/shared/Project.helptext.js:59
-msgid "This project is currently on sync and cannot be clicked until sync process completed"
-msgstr "Este proyecto se está sincronizando y no se puede hacer clic en él hasta que el proceso de sincronización se haya completado"
-
-#: components/Schedule/shared/ScheduleForm.js:460
-msgid "This schedule has no occurrences due to the selected exceptions."
-msgstr "Este horario no tiene ocurrencias debido a las excepciones seleccionadas."
-
-#: components/Schedule/ScheduleList/ScheduleList.js:122
-msgid "This schedule is missing an Inventory"
-msgstr "Falta un inventario en esta programación"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:147
-msgid "This schedule is missing required survey values"
-msgstr "Faltan los valores de la encuesta requeridos en esta programación"
-
-#: components/Schedule/shared/UnsupportedScheduleForm.js:12
-msgid ""
-"This schedule uses complex rules that are not supported in the\n"
-"UI. Please use the API to manage this schedule."
-msgstr "Esta programación utiliza reglas complejas que no son compatibles con la\n"
-"UI. Por favor, utilice la API para gestionar este horario."
-
-#: components/LaunchPrompt/steps/StepName.js:26
-msgid "This step contains errors"
-msgstr "Este paso contiene errores"
-
-#: screens/User/shared/UserForm.js:150
-msgid "This value does not match the password you entered previously. Please confirm that password."
-msgstr "Este valor no coincide con la contraseña introducida anteriormente. Confirme la contraseña."
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:106
-msgid "This will cancel all subsequent nodes in this workflow"
-msgstr "Esto cancelará todos los nodos posteriores de este flujo de trabajo"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:328
-msgid "This will cancel all subsequent nodes in this workflow."
-msgstr "Esto cancelará todos los nodos posteriores de este flujo de trabajo."
-
-#: screens/Setting/shared/RevertAllAlert.js:36
-msgid ""
-"This will revert all configuration values on this page to\n"
-"their factory defaults. Are you sure you want to proceed?"
-msgstr "Esta operación revertirá todos los valores de configuración\n"
-"a los valores predeterminados de fábrica. ¿Está seguro de desea continuar?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:40
-msgid "This workflow does not have any nodes configured."
-msgstr "Este flujo de trabajo no tiene ningún nodo configurado."
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:43
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:35
-msgid "This workflow has already been acted on"
-msgstr "Este flujo de trabajo ya ha sido actuado"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:267
-msgid "This workflow job template is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "Esta plantilla de trabajo del flujo de trabajo está siendo utilizada por otros recursos. ¿Está seguro de que desea eliminarla?"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:299
-msgid "Thu"
-msgstr "Jue"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:80
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:186
-#: components/Schedule/shared/FrequencyDetailSubform.js:304
-#: components/Schedule/shared/FrequencyDetailSubform.js:448
-msgid "Thursday"
-msgstr "Jueves"
-
-#: screens/ActivityStream/ActivityStream.js:249
-#: screens/ActivityStream/ActivityStream.js:261
-#: screens/ActivityStream/ActivityStreamDetailButton.js:41
-#: screens/ActivityStream/ActivityStreamListItem.js:42
-msgid "Time"
-msgstr "Duración"
-
-#: screens/Project/shared/Project.helptext.js:128
-msgid ""
-"Time in seconds to consider a project\n"
-"to be current. During job runs and callbacks the task\n"
-"system will evaluate the timestamp of the latest project\n"
-"update. If it is older than Cache Timeout, it is not\n"
-"considered current, and a new project update will be\n"
-"performed."
-msgstr "Tiempo en segundos para considerar que\n"
-"un proyecto es actual. Durante la ejecución de trabajos y callbacks,\n"
-"la tarea del sistema evaluará la marca de tiempo de la última\n"
-"actualización del proyecto. Si es anterior al tiempo de espera\n"
-"de la caché, no se considera actual y se realizará una nueva\n"
-"actualización del proyecto."
-
-#: screens/Inventory/shared/Inventory.helptext.js:147
-msgid ""
-"Time in seconds to consider an inventory sync\n"
-"to be current. During job runs and callbacks the task system will\n"
-"evaluate the timestamp of the latest sync. If it is older than\n"
-"Cache Timeout, it is not considered current, and a new\n"
-"inventory sync will be performed."
-msgstr "Tiempo en segundos para considerar que un proyecto es actual.\n"
-"Durante la ejecución de trabajos y callbacks, el sistema de tareas\n"
-"evaluará la marca de tiempo de la última sincronización. Si es anterior\n"
-"al tiempo de espera de la caché, no se considera actual\n"
-"y se realizará una nueva sincronización del inventario."
-
-#: components/StatusLabel/StatusLabel.js:51
-msgid "Timed out"
-msgstr "Tiempo de espera agotado"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:86
-#: components/PromptDetail/PromptDetail.js:136
-#: components/PromptDetail/PromptDetail.js:355
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:484
-#: screens/Job/JobDetail/JobDetail.js:397
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:170
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:114
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:277
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:186
-#: screens/Template/shared/JobTemplateForm.js:475
-msgid "Timeout"
-msgstr "Tiempo de espera"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:193
-msgid "Timeout minutes"
-msgstr "Tiempo de espera en minutos"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:207
-msgid "Timeout seconds"
-msgstr "Tiempo de espera en segundos"
-
-#: screens/Host/HostList/SmartInventoryButton.js:20
-msgid "To create a smart inventory using ansible facts, go to the smart inventory screen."
-msgstr "Para crear un inventario inteligente con los hechos de ansible, vaya a la pantalla de inventario inteligente."
-
-#: screens/Template/Survey/SurveyReorderModal.js:194
-msgid "To reorder the survey questions drag and drop them in the desired location."
-msgstr "Para reordenar las preguntas de la encuesta, arrástrelas y suéltelas en el lugar deseado."
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:106
-msgid "Toggle Legend"
-msgstr "Alternar leyenda"
-
-#: components/FormField/PasswordInput.js:39
-msgid "Toggle Password"
-msgstr "Alternar contraseña"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:116
-msgid "Toggle Tools"
-msgstr "Alternar herramientas"
-
-#: components/HostToggle/HostToggle.js:70
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:56
-msgid "Toggle host"
-msgstr "Alternar host"
-
-#: components/InstanceToggle/InstanceToggle.js:61
-msgid "Toggle instance"
-msgstr "Alternar instancia"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:80
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:82
-#: screens/TopologyView/Header.js:99
-msgid "Toggle legend"
-msgstr "Alternar leyenda"
-
-#: components/NotificationList/NotificationListItem.js:50
-msgid "Toggle notification approvals"
-msgstr "Aprobaciones para alternar las notificaciones"
-
-#: components/NotificationList/NotificationListItem.js:92
-msgid "Toggle notification failure"
-msgstr "No se pudieron alternar las notificaciones"
-
-#: components/NotificationList/NotificationListItem.js:64
-msgid "Toggle notification start"
-msgstr "Iniciar alternancia de notificaciones"
-
-#: components/NotificationList/NotificationListItem.js:78
-msgid "Toggle notification success"
-msgstr "Éxito de alternancia de notificaciones"
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:66
-msgid "Toggle schedule"
-msgstr "Alternar programaciones"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:92
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:94
-msgid "Toggle tools"
-msgstr "Alternar herramientas"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:373
-#: screens/User/UserTokens/UserTokens.js:64
-msgid "Token"
-msgstr "Token"
-
-#: screens/User/UserTokens/UserTokens.js:50
-#: screens/User/UserTokens/UserTokens.js:53
-msgid "Token information"
-msgstr "Información del token"
-
-#: screens/User/UserToken/UserToken.js:73
-msgid "Token not found."
-msgstr "No se encontró el token."
-
-#: screens/Application/Application/Application.js:80
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:105
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:128
-#: screens/Application/Applications.js:40
-#: screens/User/User.js:76
-#: screens/User/UserTokenList/UserTokenList.js:118
-#: screens/User/Users.js:34
-msgid "Tokens"
-msgstr "Tokens"
-
-#: components/Workflow/WorkflowTools.js:83
-msgid "Tools"
-msgstr "Herramientas"
-
-#: routeConfig.js:152
-#: screens/TopologyView/TopologyView.js:40
-msgid "Topology View"
-msgstr "Vista de topología"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:218
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:197
-#: screens/InstanceGroup/Instances/InstanceListItem.js:199
-#: screens/Instances/InstanceDetail/InstanceDetail.js:213
-#: screens/Instances/InstanceList/InstanceListItem.js:214
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:78
-msgid "Total Jobs"
-msgstr "Tareas totales"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:76
-msgid "Total Nodes"
-msgstr "Nodos totales"
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:103
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:119
-msgid "Total hosts"
-msgstr "Total de anfitriones"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:72
-msgid "Total jobs"
-msgstr "Tareas totales"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:87
-msgid "Track submodules"
-msgstr "Seguimiento de submódulos"
-
-#: components/PromptDetail/PromptProjectDetail.js:56
-#: screens/Project/ProjectDetail/ProjectDetail.js:109
-msgid "Track submodules latest commit on branch"
-msgstr "Seguimiento del último commit de los submódulos en la rama"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:141
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:168
-msgid "Trial"
-msgstr "Prueba"
-
-#: components/JobList/JobListItem.js:319
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:66
-#: screens/Job/JobDetail/JobDetail.js:383
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:210
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:240
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:270
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:315
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:373
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:142
-msgid "True"
-msgstr "Verdadero"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:277
-msgid "Tue"
-msgstr "Mar"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:78
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:184
-#: components/Schedule/shared/FrequencyDetailSubform.js:282
-#: components/Schedule/shared/FrequencyDetailSubform.js:438
-msgid "Tuesday"
-msgstr "Martes"
-
-#: components/NotificationList/NotificationList.js:201
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:142
-msgid "Twilio"
-msgstr "Twilio"
-
-#: components/JobList/JobList.js:246
-#: components/JobList/JobListItem.js:98
-#: components/Lookup/ProjectLookup.js:132
-#: components/NotificationList/NotificationList.js:219
-#: components/NotificationList/NotificationListItem.js:33
-#: components/PromptDetail/PromptDetail.js:124
-#: components/RelatedTemplateList/RelatedTemplateList.js:187
-#: components/TemplateList/TemplateList.js:214
-#: components/TemplateList/TemplateList.js:243
-#: components/TemplateList/TemplateListItem.js:184
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:85
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:154
-#: components/Workflow/WorkflowNodeHelp.js:160
-#: components/Workflow/WorkflowNodeHelp.js:196
-#: screens/Credential/CredentialList/CredentialList.js:165
-#: screens/Credential/CredentialList/CredentialListItem.js:63
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:94
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:116
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:17
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:46
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:54
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:195
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:66
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:94
-#: screens/Inventory/InventoryList/InventoryList.js:220
-#: screens/Inventory/InventoryList/InventoryListItem.js:116
-#: screens/Inventory/InventorySources/InventorySourceList.js:213
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:100
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:105
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:180
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:120
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:68
-#: screens/Project/ProjectList/ProjectList.js:194
-#: screens/Project/ProjectList/ProjectList.js:223
-#: screens/Project/ProjectList/ProjectListItem.js:218
-#: screens/Team/TeamRoles/TeamRoleListItem.js:17
-#: screens/Team/TeamRoles/TeamRolesList.js:181
-#: screens/Template/Survey/SurveyList.js:103
-#: screens/Template/Survey/SurveyList.js:103
-#: screens/Template/Survey/SurveyListItem.js:60
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:93
-#: screens/User/UserDetail/UserDetail.js:75
-#: screens/User/UserRoles/UserRolesList.js:156
-#: screens/User/UserRoles/UserRolesListItem.js:21
-msgid "Type"
-msgstr "Tipo"
-
-#: screens/Credential/shared/TypeInputsSubForm.js:25
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:47
-#: screens/Project/shared/ProjectForm.js:298
-msgid "Type Details"
-msgstr "Detalles del tipo"
-
-#: screens/Template/Survey/MultipleChoiceField.js:56
-msgid ""
-"Type answer then click checkbox on right to select answer as\n"
-"default."
-msgstr "Escriba la respuesta y marque la casilla de verificación a la derecha para seleccionar la respuesta predeterminada."
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:50
-msgid "UTC"
-msgstr "UTC"
-
-#: components/HostForm/HostForm.js:62
-msgid "Unable to change inventory on a host"
-msgstr "Imposible modificar el inventario en un servidor."
-
-#: screens/Project/ProjectList/ProjectListItem.js:211
-msgid "Unable to load last job update"
-msgstr "No se puede cargar la última actualización del trabajo"
-
-#: components/StatusLabel/StatusLabel.js:61
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:260
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:87
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:46
-#: screens/InstanceGroup/Instances/InstanceListItem.js:78
-#: screens/Instances/InstanceDetail/InstanceDetail.js:306
-#: screens/Instances/InstanceList/InstanceListItem.js:77
-#: screens/TopologyView/Tooltip.js:121
-msgid "Unavailable"
-msgstr "No disponible"
-
-#: screens/Setting/shared/RevertButton.js:53
-#: screens/Setting/shared/RevertButton.js:62
-msgid "Undo"
-msgstr "Deshacer"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:183
-msgid "Unfollow"
-msgstr "Dejar de seguir a"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:126
-msgid "Unlimited"
-msgstr "Ilimitado"
-
-#: components/StatusLabel/StatusLabel.js:47
-#: screens/Job/JobOutput/shared/HostStatusBar.js:51
-#: screens/Job/JobOutput/shared/OutputToolbar.js:103
-msgid "Unreachable"
-msgstr "Servidor inaccesible"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:102
-msgid "Unreachable Host Count"
-msgstr "Recuento de hosts inaccesibles"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:104
-msgid "Unreachable Hosts"
-msgstr "Hosts inaccesibles"
-
-#: util/dates.js:74
-msgid "Unrecognized day string"
-msgstr "Cadena de días no reconocida"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:15
-msgid "Unsaved changes modal"
-msgstr "Modal de cambios no guardados"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:94
-msgid "Update Revision on Launch"
-msgstr "Revisión de actualización durante el lanzamiento"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:51
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:133
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:92
-msgid "Update on launch"
-msgstr "Actualizar al ejecutar"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:75
-msgid "Update options"
-msgstr "Actualizar opciones"
-
-#: components/PromptDetail/PromptProjectDetail.js:61
-#: screens/Project/ProjectDetail/ProjectDetail.js:115
-msgid "Update revision on job launch"
-msgstr "Revisión de la actualización en el lanzamiento del trabajo"
-
-#: screens/Setting/SettingList.js:92
-msgid "Update settings pertaining to Jobs within {brandName}"
-msgstr "Actualizar la configuración de los trabajos en {brandName}"
-
-#: screens/Template/shared/WebhookSubForm.js:188
-msgid "Update webhook key"
-msgstr "Actualizar clave de Webhook"
-
-#: components/Workflow/WorkflowNodeHelp.js:126
-msgid "Updating"
-msgstr "Actualizando"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:118
-msgid "Upload a .zip file"
-msgstr "Cargar un archivo .zip"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:97
-msgid "Upload a Red Hat Subscription Manifest containing your subscription. To generate your subscription manifest, go to <0>subscription allocations0> on the Red Hat Customer Portal."
-msgstr "Cargue un manifiesto de suscripción de Red Hat que contenga su suscripción. Para generar su manifiesto de suscripción, vaya a las <0>asignaciones de suscripció0>n en el Portal del Cliente de Red Hat."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:53
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:132
-msgid "Use SSL"
-msgstr "Utilizar SSL"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:58
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:137
-msgid "Use TLS"
-msgstr "Utilizar TLS"
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:72
-msgid ""
-"Use custom messages to change the content of\n"
-"notifications sent when a job starts, succeeds, or fails. Use\n"
-"curly braces to access information about the job:"
-msgstr "Utilice los mensajes personalizados para cambiar el contenido de las notificaciones enviadas cuando una tarea se inicie, se realice correctamente o falle. Utilice llaves\n"
-"para acceder a la información sobre la tarea:"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:12
-msgid "Use one Annotation Tag per line, without commas."
-msgstr "Ingrese una etiqueta de anotación por línea sin comas."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:13
-msgid ""
-"Use one IRC channel or username per line. The pound\n"
-"symbol (#) for channels, and the at (@) symbol for users, are not\n"
-"required."
-msgstr "Ingrese un canal de IRC o nombre de usuario por línea. El símbolo numeral (#)\n"
-"para canales y el símbolo arroba (@) para usuarios no son\n"
-"necesarios."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:5
-msgid "Use one email address per line to create a recipient list for this type of notification."
-msgstr "Ingrese una dirección de correo electrónico por línea\n"
-"para crear una lista de destinatarios para este tipo de notificación."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:28
-msgid ""
-"Use one phone number per line to specify where to\n"
-"route SMS messages. Phone numbers should be formatted +11231231234. For more information see Twilio documentation"
-msgstr "Introduzca un número de teléfono por línea para especificar a dónde enviar los mensajes SMS. Los números de teléfono deben tener el formato +11231231234. Para más información, consulte la documentación de Twilio."
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:249
-#: screens/InstanceGroup/Instances/InstanceList.js:270
-#: screens/Instances/InstanceDetail/InstanceDetail.js:292
-#: screens/Instances/InstanceList/InstanceList.js:205
-msgid "Used Capacity"
-msgstr "Capacidad usada"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:253
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:257
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:78
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:86
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:42
-#: screens/InstanceGroup/Instances/InstanceListItem.js:74
-#: screens/Instances/InstanceDetail/InstanceDetail.js:297
-#: screens/Instances/InstanceDetail/InstanceDetail.js:303
-#: screens/Instances/InstanceList/InstanceListItem.js:73
-#: screens/TopologyView/Tooltip.js:117
-msgid "Used capacity"
-msgstr "Capacidad usada"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:13
-msgid "User"
-msgstr "Usuario"
-
-#: components/AppContainer/PageHeaderToolbar.js:160
-msgid "User Details"
-msgstr "Detalles del usuario"
-
-#: screens/Setting/SettingList.js:121
-#: screens/Setting/Settings.js:118
-msgid "User Interface"
-msgstr "Interfaz de usuario"
-
-#: screens/Setting/SettingList.js:126
-msgid "User Interface settings"
-msgstr "Configuración de la interfaz de usuario"
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:72
-#: screens/User/UserRoles/UserRolesList.js:142
-msgid "User Roles"
-msgstr "Roles de los usuarios"
-
-#: screens/User/UserDetail/UserDetail.js:72
-#: screens/User/shared/UserForm.js:119
-msgid "User Type"
-msgstr "Tipo de usuario"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:59
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:60
-msgid "User analytics"
-msgstr "Análisis de usuarios"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:34
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:202
-msgid "User and Automation Analytics"
-msgstr "Usuario y Automation Analytics"
-
-#: components/AppContainer/PageHeaderToolbar.js:154
-msgid "User details"
-msgstr "Detalles del usuario"
-
-#: screens/User/User.js:96
-msgid "User not found."
-msgstr "No se encontró el usuario."
-
-#: screens/User/UserTokenList/UserTokenList.js:180
-msgid "User tokens"
-msgstr "Tokens de usuario"
-
-#: components/AddRole/AddResourceRole.js:23
-#: components/AddRole/AddResourceRole.js:38
-#: components/ResourceAccessList/ResourceAccessList.js:173
-#: components/ResourceAccessList/ResourceAccessList.js:226
-#: screens/Login/Login.js:230
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:144
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:253
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:303
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:361
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:67
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:260
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:337
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:442
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:92
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:204
-#: screens/User/UserDetail/UserDetail.js:68
-#: screens/User/UserList/UserList.js:120
-#: screens/User/UserList/UserList.js:160
-#: screens/User/UserList/UserListItem.js:38
-#: screens/User/shared/UserForm.js:76
-msgid "Username"
-msgstr "Usuario"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:88
-msgid "Username / password"
-msgstr "Nombre de usuario/contraseña"
-
-#: components/AddRole/AddResourceRole.js:178
-#: components/AddRole/AddResourceRole.js:179
-#: routeConfig.js:101
-#: screens/ActivityStream/ActivityStream.js:184
-#: screens/Team/Teams.js:30
-#: screens/User/UserList/UserList.js:110
-#: screens/User/UserList/UserList.js:153
-#: screens/User/Users.js:15
-#: screens/User/Users.js:26
-msgid "Users"
-msgstr "Usuarios"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:101
-msgid "VMware vCenter"
-msgstr "VMware vCenter"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:69
-#: components/HostForm/HostForm.js:113
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:115
-#: components/PromptDetail/PromptDetail.js:168
-#: components/PromptDetail/PromptDetail.js:369
-#: components/PromptDetail/PromptJobTemplateDetail.js:286
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:135
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:620
-#: screens/Host/HostDetail/HostDetail.js:93
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:165
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:37
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:88
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:143
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:53
-#: screens/Inventory/shared/InventoryForm.js:108
-#: screens/Inventory/shared/InventoryGroupForm.js:46
-#: screens/Inventory/shared/SmartInventoryForm.js:93
-#: screens/Job/JobDetail/JobDetail.js:546
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:501
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:226
-#: screens/Template/shared/JobTemplateForm.js:402
-#: screens/Template/shared/WorkflowJobTemplateForm.js:212
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:293
-msgid "Variables"
-msgstr "Variables"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:130
-msgid "Variables Prompted"
-msgstr "Variables solicitadas"
-
-#: screens/Inventory/shared/Inventory.helptext.js:43
-msgid "Variables must be in JSON or YAML syntax. Use the radio button to toggle between the two."
-msgstr "Ingrese variables con sintaxis JSON o YAML. Use el botón de selección para alternar entre los dos."
-
-#: screens/Inventory/shared/Inventory.helptext.js:166
-msgid "Variables used to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>{sourceType}1> plugin configuration guide."
-msgstr "Introduzca las variables para configurar el origen del inventario. Para una descripción detallada de cómo configurar este plugin, consulte los <0>plugins de inventario0> en la documentación y la guía de configuración del plugin <1> de ovirt{sourceType}1>."
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:121
-msgid "Vault password"
-msgstr "Contraseña Vault"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:121
-msgid "Vault password | {credId}"
-msgstr "Contraseña Vault | {credId}"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:131
-msgid "Verbose"
-msgstr "Nivel de detalle"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:63
-#: components/PromptDetail/PromptDetail.js:260
-#: components/PromptDetail/PromptInventorySourceDetail.js:100
-#: components/PromptDetail/PromptJobTemplateDetail.js:154
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:478
-#: components/VerbositySelectField/VerbositySelectField.js:34
-#: components/VerbositySelectField/VerbositySelectField.js:45
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:232
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:47
-#: screens/Job/JobDetail/JobDetail.js:331
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:271
-msgid "Verbosity"
-msgstr "Nivel de detalle"
-
-#: screens/Setting/AzureAD/AzureAD.js:25
-msgid "View Azure AD settings"
-msgstr "Ver la configuración de Azure AD"
-
-#: screens/Credential/Credential.js:143
-#: screens/Credential/Credential.js:155
-msgid "View Credential Details"
-msgstr "Ver detalles de la credencial"
-
-#: components/Schedule/Schedule.js:151
-msgid "View Details"
-msgstr "Ver detalles"
-
-#: screens/Setting/GitHub/GitHub.js:58
-msgid "View GitHub Settings"
-msgstr "Ver la configuración de GitHub"
-
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2.js:26
-msgid "View Google OAuth 2.0 settings"
-msgstr "Ver la configuración de Google OAuth 2.0"
-
-#: screens/Host/Host.js:137
-msgid "View Host Details"
-msgstr "Ver detalles del host"
-
-#: screens/Instances/Instance.js:78
-msgid "View Instance Details"
-msgstr "Ver detalles de la instancia"
-
-#: screens/Inventory/Inventory.js:192
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:142
-#: screens/Inventory/SmartInventory.js:175
-msgid "View Inventory Details"
-msgstr "Ver detalles del inventario"
-
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:92
-msgid "View Inventory Groups"
-msgstr "Ver grupos de inventario"
-
-#: screens/Inventory/InventoryHost/InventoryHost.js:160
-msgid "View Inventory Host Details"
-msgstr "Ver detalles del host del inventario"
-
-#: screens/Inventory/shared/Inventory.helptext.js:55
-msgid "View JSON examples at <0>www.json.org0>"
-msgstr "Ver ejemplos de JSON en <0>www.json.org0>"
-
-#: screens/Job/Job.js:212
-msgid "View Job Details"
-msgstr "Ver detalles de la tarea"
-
-#: screens/Setting/Jobs/Jobs.js:25
-msgid "View Jobs settings"
-msgstr "Ver la configuración de las tareas"
-
-#: screens/Setting/LDAP/LDAP.js:38
-msgid "View LDAP Settings"
-msgstr "Ver la configuración de LDAP"
-
-#: screens/Setting/Logging/Logging.js:32
-msgid "View Logging settings"
-msgstr "Ver la configuración del registro"
-
-#: screens/Setting/MiscAuthentication/MiscAuthentication.js:32
-msgid "View Miscellaneous Authentication settings"
-msgstr "Ver la configuración de la autenticación de varios"
-
-#: screens/Setting/MiscSystem/MiscSystem.js:32
-msgid "View Miscellaneous System settings"
-msgstr "Ver la configuración de sistemas varios"
-
-#: screens/Setting/OIDC/OIDC.js:25
-msgid "View OIDC settings"
-msgstr "Ver la configuración de OIDC"
-
-#: screens/Organization/Organization.js:225
-msgid "View Organization Details"
-msgstr "Ver detalles de la organización"
-
-#: screens/Project/Project.js:200
-msgid "View Project Details"
-msgstr "Ver detalles del proyecto"
-
-#: screens/Setting/RADIUS/RADIUS.js:25
-msgid "View RADIUS settings"
-msgstr "Ver la configuración de RADIUS"
-
-#: screens/Setting/SAML/SAML.js:25
-msgid "View SAML settings"
-msgstr "Ver la configuración de SAML"
-
-#: components/Schedule/Schedule.js:83
-#: components/Schedule/Schedule.js:101
-msgid "View Schedules"
-msgstr "Ver programaciones"
-
-#: screens/Setting/Subscription/Subscription.js:30
-msgid "View Settings"
-msgstr "Ver configuración"
-
-#: screens/Template/Template.js:159
-#: screens/Template/WorkflowJobTemplate.js:145
-msgid "View Survey"
-msgstr "Mostrar el cuestionario"
-
-#: screens/Setting/TACACS/TACACS.js:25
-msgid "View TACACS+ settings"
-msgstr "Ver la configuración de TACACS+"
-
-#: screens/Team/Team.js:118
-msgid "View Team Details"
-msgstr "Ver detalles del equipo"
-
-#: screens/Template/Template.js:260
-#: screens/Template/WorkflowJobTemplate.js:275
-msgid "View Template Details"
-msgstr "Ver detalles de la plantilla"
-
-#: screens/User/UserToken/UserToken.js:100
-msgid "View Tokens"
-msgstr "Ver tokens"
-
-#: screens/User/User.js:141
-msgid "View User Details"
-msgstr "Ver detalles del usuario"
-
-#: screens/Setting/UI/UI.js:26
-msgid "View User Interface settings"
-msgstr "Ver la configuración de la interfaz de usuario"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:105
-msgid "View Workflow Approval Details"
-msgstr "Ver detalles de la aprobación del flujo de trabajo"
-
-#: screens/Inventory/shared/Inventory.helptext.js:66
-msgid "View YAML examples at <0>docs.ansible.com0>"
-msgstr "Ver ejemplos de YAML en <0>docs.ansible.com0>"
-
-#: components/ScreenHeader/ScreenHeader.js:65
-#: components/ScreenHeader/ScreenHeader.js:68
-msgid "View activity stream"
-msgstr "Ver el flujo de actividad"
-
-#: screens/Credential/Credential.js:99
-msgid "View all Credentials."
-msgstr "Ver todas las credenciales."
-
-#: screens/Host/Host.js:97
-msgid "View all Hosts."
-msgstr "Ver todos los hosts."
-
-#: screens/Inventory/Inventory.js:95
-#: screens/Inventory/SmartInventory.js:95
-msgid "View all Inventories."
-msgstr "Ver todos los inventarios."
-
-#: screens/Inventory/InventoryHost/InventoryHost.js:101
-msgid "View all Inventory Hosts."
-msgstr "Ver todos los hosts de inventario."
-
-#: screens/Job/JobTypeRedirect.js:40
-msgid "View all Jobs"
-msgstr "Ver todas las tareas"
-
-#: screens/Job/Job.js:162
-msgid "View all Jobs."
-msgstr "Ver todas las tareas."
-
-#: screens/NotificationTemplate/NotificationTemplate.js:60
-#: screens/NotificationTemplate/NotificationTemplateAdd.js:52
-msgid "View all Notification Templates."
-msgstr "Ver todas las plantillas de notificación."
-
-#: screens/Organization/Organization.js:155
-msgid "View all Organizations."
-msgstr "Ver todas las organizaciones."
-
-#: screens/Project/Project.js:137
-msgid "View all Projects."
-msgstr "Ver todos los proyectos."
-
-#: screens/Team/Team.js:76
-msgid "View all Teams."
-msgstr "Ver todos los equipos."
-
-#: screens/Template/Template.js:176
-#: screens/Template/WorkflowJobTemplate.js:176
-msgid "View all Templates."
-msgstr "Ver todas las plantillas."
-
-#: screens/User/User.js:97
-msgid "View all Users."
-msgstr "Ver todos los usuarios."
-
-#: screens/WorkflowApproval/WorkflowApproval.js:54
-msgid "View all Workflow Approvals."
-msgstr "Ver todas las aprobaciones del flujo de trabajo."
-
-#: screens/Application/Application/Application.js:96
-msgid "View all applications."
-msgstr "Ver todas las aplicaciones."
-
-#: screens/CredentialType/CredentialType.js:78
-msgid "View all credential types"
-msgstr "Ver todos los tipos de credencial"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:85
-msgid "View all execution environments"
-msgstr "Ver todos los entornos de ejecución"
-
-#: screens/InstanceGroup/ContainerGroup.js:86
-#: screens/InstanceGroup/InstanceGroup.js:94
-msgid "View all instance groups"
-msgstr "Ver todos los grupos de instancias"
-
-#: screens/ManagementJob/ManagementJob.js:135
-msgid "View all management jobs"
-msgstr "Ver todas las tareas de gestión"
-
-#: screens/Setting/Settings.js:204
-msgid "View all settings"
-msgstr "Ver todas las configuraciones"
-
-#: screens/User/UserToken/UserToken.js:74
-msgid "View all tokens."
-msgstr "Ver todos los tokens."
-
-#: screens/Setting/SettingList.js:133
-msgid "View and edit your subscription information"
-msgstr "Ver y modificar su información de suscripción"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:25
-#: screens/ActivityStream/ActivityStreamListItem.js:50
-msgid "View event details"
-msgstr "Mostrar detalles del evento"
-
-#: screens/Inventory/InventorySource/InventorySource.js:167
-msgid "View inventory source details"
-msgstr "Ver detalles de la fuente de inventario"
-
-#: components/Sparkline/Sparkline.js:44
-msgid "View job {0}"
-msgstr "Ver tarea {0}"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:220
-msgid "View node details"
-msgstr "Ver detalles del nodo"
-
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:85
-msgid "View smart inventory host details"
-msgstr "Ver detalles del host de inventario inteligente"
-
-#: routeConfig.js:30
-#: screens/ActivityStream/ActivityStream.js:145
-msgid "Views"
-msgstr "Vistas"
-
-#: components/TemplateList/TemplateListItem.js:198
-#: components/TemplateList/TemplateListItem.js:204
-#: screens/Template/WorkflowJobTemplate.js:137
-msgid "Visualizer"
-msgstr "Visualizador"
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:44
-msgid "WARNING:"
-msgstr "ADVERTENCIA:"
-
-#: components/JobList/JobList.js:229
-#: components/StatusLabel/StatusLabel.js:52
-#: components/Workflow/WorkflowNodeHelp.js:96
-msgid "Waiting"
-msgstr "Esperando"
-
-#: screens/Job/JobOutput/EmptyOutput.js:35
-msgid "Waiting for job output…"
-msgstr "Esperando la salida de la tarea…"
-
-#: components/Workflow/WorkflowLegend.js:118
-#: screens/Job/JobOutput/JobOutputSearch.js:132
-msgid "Warning"
-msgstr "Advertencia"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:14
-msgid "Warning: Unsaved Changes"
-msgstr "Aviso: modificaciones no guardadas"
-
-#: components/Schedule/shared/ScheduleFormFields.js:43
-msgid "Warning: {selectedValue} is a link to {0} and will be saved as that."
-msgstr "Aviso: {selectedValue} es un enlace a {0} y se guardará como tal."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:119
-msgid "We were unable to locate licenses associated with this account."
-msgstr "No pudimos localizar las licencias asociadas a esta cuenta."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:138
-msgid "We were unable to locate subscriptions associated with this account."
-msgstr "No pudimos localizar las suscripciones asociadas a esta cuenta."
-
-#: components/DetailList/LaunchedByDetail.js:24
-#: components/NotificationList/NotificationList.js:202
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:143
-msgid "Webhook"
-msgstr "Webhook"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:177
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:103
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:333
-#: screens/Template/shared/WebhookSubForm.js:199
-msgid "Webhook Credential"
-msgstr "Credencial de Webhook"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:175
-msgid "Webhook Credentials"
-msgstr "Credenciales de Webhook"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:173
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:92
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:326
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:168
-#: screens/Template/shared/WebhookSubForm.js:173
-msgid "Webhook Key"
-msgstr "Clave de Webhook"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:166
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:91
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:311
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:156
-#: screens/Template/shared/WebhookSubForm.js:129
-msgid "Webhook Service"
-msgstr "Servicio de Webhook"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:169
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:95
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:319
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:162
-#: screens/Template/shared/WebhookSubForm.js:161
-#: screens/Template/shared/WebhookSubForm.js:167
-msgid "Webhook URL"
-msgstr "URL de Webhook"
-
-#: screens/Template/shared/JobTemplateForm.js:646
-#: screens/Template/shared/WorkflowJobTemplateForm.js:271
-msgid "Webhook details"
-msgstr "Detalles de Webhook"
-
-#: screens/Template/shared/JobTemplate.helptext.js:24
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:17
-msgid "Webhook services can launch jobs with this workflow job template by making a POST request to this URL."
-msgstr "Los servicios de Webhook pueden iniciar trabajos con esta plantilla de trabajo mediante una solicitud POST a esta URL."
-
-#: screens/Template/shared/JobTemplate.helptext.js:25
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:18
-msgid "Webhook services can use this as a shared secret."
-msgstr "Los servicios de Webhook pueden usar esto como un secreto compartido."
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:78
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:41
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:142
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:63
-msgid "Webhooks"
-msgstr "Webhooks"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:26
-msgid "Webhooks: Enable Webhook for this workflow job template."
-msgstr "Habilitar Webhook para esta plantilla de trabajo del flujo de trabajo."
-
-#: screens/Template/shared/JobTemplate.helptext.js:42
-msgid "Webhooks: Enable webhook for this template."
-msgstr "Habilitar webhook para esta plantilla."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:288
-msgid "Wed"
-msgstr "Mié"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:79
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:185
-#: components/Schedule/shared/FrequencyDetailSubform.js:293
-#: components/Schedule/shared/FrequencyDetailSubform.js:443
-msgid "Wednesday"
-msgstr "Miércoles"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:185
-#: components/Schedule/shared/FrequencyDetailSubform.js:179
-#: components/Schedule/shared/ScheduleFormFields.js:128
-#: components/Schedule/shared/ScheduleFormFields.js:188
-msgid "Week"
-msgstr "Semana"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:464
-msgid "Weekday"
-msgstr "Día de la semana"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:469
-msgid "Weekend day"
-msgstr "Día del fin de semana"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:59
-msgid ""
-"Welcome to Red Hat Ansible Automation Platform!\n"
-"Please complete the steps below to activate your subscription."
-msgstr "¡Bienvenido a Red Hat Ansible Automation Platform!\n"
-"Complete los pasos a continuación para activar su suscripción."
-
-#: screens/Login/Login.js:190
-msgid "Welcome to {brandName}!"
-msgstr "Bienvenido a {brandName}"
-
-#: screens/Inventory/shared/Inventory.helptext.js:105
-msgid ""
-"When not checked, a merge will be performed,\n"
-"combining local variables with those found on the\n"
-"external source."
-msgstr "Cuando esta opción no esté marcada, se llevará a cabo una fusión,\n"
-"que combinará las variables locales con aquellas halladas\n"
-"en la fuente externa."
-
-#: screens/Inventory/shared/Inventory.helptext.js:93
-msgid ""
-"When not checked, local child\n"
-"hosts and groups not found on the external source will remain\n"
-"untouched by the inventory update process."
-msgstr "Cuando esta opción no esté marcada, los hosts y grupos secundarios\n"
-"locales que no se encuentren en la fuente externa no se modificarán\n"
-"mediante el proceso de actualización del inventario."
-
-#: components/Workflow/WorkflowLegend.js:96
-msgid "Workflow"
-msgstr "Flujo de trabajo"
-
-#: components/Workflow/WorkflowNodeHelp.js:75
-msgid "Workflow Approval"
-msgstr "Aprobación del flujo de trabajo"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:52
-msgid "Workflow Approval not found."
-msgstr "No se encontró la aprobación del flujo de trabajo."
-
-#: routeConfig.js:54
-#: screens/ActivityStream/ActivityStream.js:156
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:118
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:145
-#: screens/WorkflowApproval/WorkflowApprovals.js:13
-#: screens/WorkflowApproval/WorkflowApprovals.js:22
-msgid "Workflow Approvals"
-msgstr "Aprobaciones del flujo de trabajo"
-
-#: components/JobList/JobList.js:216
-#: components/JobList/JobListItem.js:47
-#: components/Schedule/ScheduleList/ScheduleListItem.js:40
-#: screens/Job/JobDetail/JobDetail.js:70
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:224
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:164
-msgid "Workflow Job"
-msgstr "Tarea en flujo de trabajo"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:76
-msgid "Workflow Job 1/{0}"
-msgstr "Tarea en flujo de trabajo 1/{0}"
-
-#: components/JobList/JobListItem.js:201
-#: components/Workflow/WorkflowNodeHelp.js:63
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:20
-#: screens/Job/JobDetail/JobDetail.js:244
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:91
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:186
-#: util/getRelatedResourceDeleteDetails.js:105
-msgid "Workflow Job Template"
-msgstr "Plantilla de trabajo para flujo de trabajo"
-
-#: util/getRelatedResourceDeleteDetails.js:115
-#: util/getRelatedResourceDeleteDetails.js:157
-#: util/getRelatedResourceDeleteDetails.js:260
-msgid "Workflow Job Template Nodes"
-msgstr "Nodos de la plantilla de tareas de flujo de trabajo"
-
-#: util/getRelatedResourceDeleteDetails.js:140
-msgid "Workflow Job Templates"
-msgstr "Plantillas de trabajos de flujo de trabajo"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:23
-msgid "Workflow Link"
-msgstr "Enlace del flujo de trabajo"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:100
-msgid "Workflow Nodes"
-msgstr "Nodos de flujo de trabajo"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:86
-msgid "Workflow Statuses"
-msgstr "Estados del flujo de trabajo"
-
-#: components/TemplateList/TemplateList.js:218
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:98
-msgid "Workflow Template"
-msgstr "Plantilla de flujo de trabajo"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:519
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:159
-msgid "Workflow approved message"
-msgstr "Mensaje de flujo de trabajo aprobado"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:531
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:168
-msgid "Workflow approved message body"
-msgstr "Cuerpo del mensaje de flujo de trabajo aprobado"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:543
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:177
-msgid "Workflow denied message"
-msgstr "Mensaje de flujo de trabajo denegado"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:555
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:186
-msgid "Workflow denied message body"
-msgstr "Cuerpo del mensaje de flujo de trabajo denegado"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:106
-msgid "Workflow documentation"
-msgstr "Documentación del flujo de trabajo"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:220
-msgid "Workflow job details"
-msgstr "Ver detalles de la tarea"
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:46
-msgid "Workflow job templates"
-msgstr "Plantillas de trabajo del flujo de trabajo"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:24
-msgid "Workflow link modal"
-msgstr "Modal de enlace del flujo de trabajo"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:247
-msgid "Workflow node view modal"
-msgstr "Modal de vista del nodo de flujo de trabajo"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:567
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:195
-msgid "Workflow pending message"
-msgstr "Mensaje de flujo de trabajo pendiente"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:579
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:204
-msgid "Workflow pending message body"
-msgstr "Cuerpo del mensaje de flujo de trabajo pendiente"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:591
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:213
-msgid "Workflow timed out message"
-msgstr "Mensaje de tiempo de espera agotado del flujo de trabajo"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:603
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:222
-msgid "Workflow timed out message body"
-msgstr "Cuerpo del mensaje de tiempo de espera agotado del flujo de trabajo"
-
-#: screens/User/shared/UserTokenForm.js:77
-msgid "Write"
-msgstr "Escribir"
-
-#: screens/Inventory/shared/Inventory.helptext.js:52
-msgid "YAML:"
-msgstr "YAML:"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:187
-#: components/Schedule/shared/FrequencyDetailSubform.js:183
-#: components/Schedule/shared/ScheduleFormFields.js:130
-#: components/Schedule/shared/ScheduleFormFields.js:190
-msgid "Year"
-msgstr "Año"
-
-#: components/Search/Search.js:229
-msgid "Yes"
-msgstr "SÍ"
-
-#: components/Lookup/MultiCredentialsLookup.js:155
-msgid "You cannot select multiple vault credentials with the same vault ID. Doing so will automatically deselect the other with the same vault ID."
-msgstr "No se pueden seleccionar varias credenciales con el mismo ID de Vault, ya que anulará automáticamente la selección de la otra con el mismo ID de Vault."
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:96
-msgid "You do not have permission to delete the following Groups: {itemsUnableToDelete}"
-msgstr "No tiene permiso para eliminar los siguientes Grupos: {itemsUnableToDelete}"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:152
-msgid "You do not have permission to delete {pluralizedItemName}: {itemsUnableToDelete}"
-msgstr "No tiene permiso para borrar {pluralizedItemName}: {itemsUnableToDelete}"
-
-#: components/DisassociateButton/DisassociateButton.js:66
-msgid "You do not have permission to disassociate the following: {itemsUnableToDisassociate}"
-msgstr "No tiene permiso para desvincular lo siguiente: {itemsUnableToDisassociate}"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:72
-msgid "You do not have permission to remove instances: {itemsUnableToremove}"
-msgstr "No tiene permisos para los recursos relacionados: {itemsUnableToremove}"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:82
-msgid "You have automated against more hosts than your subscription allows."
-msgstr "Has automatizado contra más hosts de los que permite tu suscripción."
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:87
-msgid ""
-"You may apply a number of possible variables in the\n"
-"message. For more information, refer to the"
-msgstr "Puede aplicar una serie de posibles variables en el\n"
-"mensaje. Para obtener más información, consulte"
-
-#: screens/Login/Login.js:198
-msgid "Your session has expired. Please log in to continue where you left off."
-msgstr "Su sesión ha expirado. Inicie sesión para continuar."
-
-#: components/AppContainer/AppContainer.js:130
-msgid "Your session is about to expire"
-msgstr "Su sesión está a punto de expirar"
-
-#: components/Workflow/WorkflowTools.js:121
-msgid "Zoom In"
-msgstr "Acercar"
-
-#: components/Workflow/WorkflowTools.js:100
-msgid "Zoom Out"
-msgstr "Alejar"
-
-#: screens/TopologyView/Header.js:51
-#: screens/TopologyView/Header.js:54
-msgid "Zoom in"
-msgstr "Acercar"
-
-#: screens/TopologyView/Header.js:63
-#: screens/TopologyView/Header.js:66
-msgid "Zoom out"
-msgstr "Alejar"
-
-#: screens/Template/shared/JobTemplateForm.js:754
-#: screens/Template/shared/WebhookSubForm.js:150
-msgid "a new webhook key will be generated on save."
-msgstr "se generará una nueva clave de Webhook al guardar."
-
-#: screens/Template/shared/JobTemplateForm.js:751
-#: screens/Template/shared/WebhookSubForm.js:140
-msgid "a new webhook url will be generated on save."
-msgstr "se generará una nueva URL de Webhook al guardar."
-
-#: screens/Inventory/shared/Inventory.helptext.js:123
-#: screens/Inventory/shared/Inventory.helptext.js:142
-msgid "and click on Update Revision on Launch"
-msgstr "y haga clic en Actualizar revisión al ejecutar"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:505
-msgid "approved"
-msgstr "aprobado"
-
-#: components/AppContainer/AppContainer.js:55
-msgid "brand logo"
-msgstr "logotipo de la marca"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:279
-#: screens/Template/Survey/SurveyList.js:72
-msgid "cancel delete"
-msgstr "cancelar eliminación"
-
-#: screens/Setting/shared/SharedFields.js:341
-msgid "cancel edit login redirect"
-msgstr "cancelar la edición de la redirección de inicio de sesión"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:169
-msgid "cancel remove"
-msgstr "Cancelar reversión"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:34
-msgid "canceled"
-msgstr "cancelado"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:217
-msgid "command"
-msgstr "comando"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:267
-#: screens/Template/Survey/SurveyList.js:63
-msgid "confirm delete"
-msgstr "confirmar eliminación"
-
-#: components/DisassociateButton/DisassociateButton.js:130
-#: screens/Team/TeamRoles/TeamRolesList.js:219
-msgid "confirm disassociate"
-msgstr "confirmar disociación"
-
-#: screens/Setting/shared/SharedFields.js:330
-msgid "confirm edit login redirect"
-msgstr "confirmar la redirección del acceso a la edición"
-
-#: screens/TopologyView/ContentLoading.js:32
-msgid "content-loading-in-progress"
-msgstr "content-loading-in-progress"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:189
-msgid "day"
-msgstr "Día"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:151
-msgid "deletion error"
-msgstr "error de eliminación"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:513
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:37
-msgid "denied"
-msgstr "denegado"
-
-#: screens/Job/JobOutput/EmptyOutput.js:41
-msgid "details."
-msgstr "Detalles"
-
-#: components/DisassociateButton/DisassociateButton.js:91
-msgid "disassociate"
-msgstr "disociar"
-
-#: components/Lookup/HostFilterLookup.js:406
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:20
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:39
-#: screens/Template/Survey/SurveyQuestionForm.js:269
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:239
-#: screens/Template/shared/JobTemplate.helptext.js:61
-msgid "documentation"
-msgstr "documentación"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:105
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:117
-#: screens/Host/HostDetail/HostDetail.js:104
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:97
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:109
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:99
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:289
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:161
-#: screens/Project/ProjectDetail/ProjectDetail.js:309
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:195
-#: screens/User/UserDetail/UserDetail.js:92
-msgid "edit"
-msgstr "modificar"
-
-#: screens/Template/Survey/SurveyListItem.js:65
-#: screens/Template/Survey/SurveyReorderModal.js:125
-msgid "encrypted"
-msgstr "cifrado"
-
-#: components/Lookup/HostFilterLookup.js:408
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:241
-msgid "for more info."
-msgstr "para obtener más información."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:21
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:40
-#: screens/Template/Survey/SurveyQuestionForm.js:271
-#: screens/Template/shared/JobTemplate.helptext.js:63
-msgid "for more information."
-msgstr "para obtener más información."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:150
-msgid "here"
-msgstr "aquí"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:118
-#: components/AdHocCommands/AdHocDetailsStep.js:170
-#: screens/Job/Job.helptext.js:39
-msgid "here."
-msgstr "aquí."
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:49
-msgid "host-description-{0}"
-msgstr "host-description-{0}"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:44
-msgid "host-name-{0}"
-msgstr "host-name-{0}"
-
-#: components/Lookup/HostFilterLookup.js:418
-msgid "hosts"
-msgstr "hosts"
-
-#: components/Pagination/Pagination.js:24
-msgid "items"
-msgstr "elementos"
-
-#: screens/User/UserList/UserListItem.js:44
-msgid "ldap user"
-msgstr "usuario ldap"
-
-#: screens/User/UserDetail/UserDetail.js:76
-msgid "login type"
-msgstr "tipo de inicio de sesión"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:203
-msgid "min"
-msgstr "min"
-
-#: screens/Template/Survey/MultipleChoiceField.js:76
-msgid "new choice"
-msgstr "nueva elección"
-
-#: components/Pagination/Pagination.js:36
-#: components/Schedule/shared/FrequencyDetailSubform.js:480
-msgid "of"
-msgstr "de"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:215
-msgid "option to the"
-msgstr "opción a"
-
-#: components/Pagination/Pagination.js:25
-msgid "page"
-msgstr "página"
-
-#: components/Pagination/Pagination.js:26
-msgid "pages"
-msgstr "páginas"
-
-#: components/Pagination/Pagination.js:28
-msgid "per page"
-msgstr "por página"
-
-#: components/LaunchButton/ReLaunchDropDown.js:77
-#: components/LaunchButton/ReLaunchDropDown.js:100
-msgid "relaunch jobs"
-msgstr "volver a ejecutar las tareas"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:217
-msgid "sec"
-msgstr "seg"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:238
-msgid "seconds"
-msgstr "segundos"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:58
-msgid "select module"
-msgstr "seleccionar módulo"
-
-#: screens/User/UserList/UserListItem.js:49
-msgid "social login"
-msgstr "inicio de sesión social"
-
-#: screens/Template/shared/JobTemplateForm.js:346
-#: screens/Template/shared/WorkflowJobTemplateForm.js:188
-msgid "source control branch"
-msgstr "rama de fuente de control"
-
-#: screens/ActivityStream/ActivityStreamListItem.js:30
-msgid "system"
-msgstr "sistema"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:511
-msgid "timed out"
-msgstr "agotado"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:195
-msgid "toggle changes"
-msgstr "alternar cambios"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:516
-msgid "updated"
-msgstr "actualizado"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:190
-msgid "weekday"
-msgstr "Día de la semana"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:191
-msgid "weekend day"
-msgstr "Día del fin de semana"
-
-#: screens/Template/shared/WebhookSubForm.js:181
-msgid "workflow job template webhook key"
-msgstr "clave de Webhook de la plantilla de trabajo del flujo de trabajo"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:65
-msgid "{0, plural, one {# source with sync failures.} other {# sources with sync failures.}}"
-msgstr "{0, plural, one {# source with sync failures.} other {# sources with sync failures.}}"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:115
-msgid "{0, plural, one {Are you sure you want delete the group below?} other {Are you sure you want delete the groups below?}}"
-msgstr "{0, plural, one {Are you sure you want delete the group below?} other {Are you sure you want delete the groups below?}}"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:86
-msgid "{0, plural, one {Delete Group?} other {Delete Groups?}}"
-msgstr "{0, plural, one {Delete Group?} other {Delete Groups?}}"
-
-#: util/validators.js:138
-msgid "{0, plural, one {Please enter a valid phone number.} other {Please enter valid phone numbers.}}"
-msgstr "{0, plural, one {Please enter a valid phone number.} other {Please enter valid phone numbers.}}"
-
-#: screens/Inventory/InventoryList/InventoryList.js:247
-msgid "{0, plural, one {The inventory will be in a pending status until the final delete is processed.} other {The inventories will be in a pending status until the final delete is processed.}}"
-msgstr "{0, plural, one {The inventory will be in a pending status until the final delete is processed.} other {The inventories will be in a pending status until the final delete is processed.}}"
-
-#: components/JobList/JobList.js:280
-msgid "{0, plural, one {The selected job cannot be deleted due to insufficient permission or a running job status} other {The selected jobs cannot be deleted due to insufficient permissions or a running job status}}"
-msgstr "{0, plural, one {The selected job cannot be deleted due to insufficient permission or a running job status} other {The selected jobs cannot be deleted due to insufficient permissions or a running job status}}"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:151
-msgid "{0, plural, one {This approval cannot be deleted due to insufficient permissions or a pending job status} other {These approvals cannot be deleted due to insufficient permissions or a pending job status}}"
-msgstr "{0, plural, one {This approval cannot be deleted due to insufficient permissions or a pending job status} other {These approvals cannot be deleted due to insufficient permissions or a pending job status}}"
-
-#: screens/Credential/CredentialList/CredentialList.js:198
-msgid "{0, plural, one {This credential is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these credentials could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This credential is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these credentials could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:164
-msgid "{0, plural, one {This credential type is currently being used by some credentials and cannot be deleted.} other {Credential types that are being used by credentials cannot be deleted. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This credential type is currently being used by some credentials and cannot be deleted.} other {Credential types that are being used by credentials cannot be deleted. Are you sure you want to delete anyway?}}"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:194
-msgid "{0, plural, one {This execution environment is currently being used by other resources. Are you sure you want to delete it?} other {These execution environments could be in use by other resources that rely on them. Are you sure you want to delete them anyway?}}"
-msgstr "{0, plural, one {This execution environment is currently being used by other resources. Are you sure you want to delete it?} other {These execution environments could be in use by other resources that rely on them. Are you sure you want to delete them anyway?}}"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:182
-msgid "{0, plural, one {This instance group is currently being by other resources. Are you sure you want to delete it?} other {Deleting these instance groups could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This instance group is currently being by other resources. Are you sure you want to delete it?} other {Deleting these instance groups could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:85
-msgid "{0, plural, one {This intance is currently being used by other resources. Are you sure you want to delete it?} other {Deprovisioning these instances could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This intance is currently being used by other resources. Are you sure you want to delete it?} other {Deprovisioning these instances could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Inventory/InventoryList/InventoryList.js:240
-msgid "{0, plural, one {This inventory is currently being used by some templates. Are you sure you want to delete it?} other {Deleting these inventories could impact some templates that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This inventory is currently being used by some templates. Are you sure you want to delete it?} other {Deleting these inventories could impact some templates that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:196
-msgid "{0, plural, one {This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?} other {Deleting these inventory sources could impact other resources that rely on them. Are you sure you want to delete anyway}}"
-msgstr "{0, plural, one {This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?} other {Deleting these inventory sources could impact other resources that rely on them. Are you sure you want to delete anyway}}"
-
-#: screens/Organization/OrganizationList/OrganizationList.js:166
-msgid "{0, plural, one {This organization is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these organizations could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This organization is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these organizations could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Project/ProjectList/ProjectList.js:252
-msgid "{0, plural, one {This project is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these projects could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This project is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these projects could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:209
-#: components/TemplateList/TemplateList.js:266
-msgid "{0, plural, one {This template is currently being used by some workflow nodes. Are you sure you want to delete it?} other {Deleting these templates could impact some workflow nodes that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This template is currently being used by some workflow nodes. Are you sure you want to delete it?} other {Deleting these templates could impact some workflow nodes that rely on them. Are you sure you want to delete anyway?}}"
-
-#: components/JobList/JobListCancelButton.js:72
-msgid "{0, plural, one {You cannot cancel the following job because it is not running:} other {You cannot cancel the following jobs because they are not running:}}"
-msgstr "{0, plural, one {You cannot cancel the following job because it is not running:} other {You cannot cancel the following jobs because they are not running:}}"
-
-#: components/JobList/JobListCancelButton.js:56
-msgid "{0, plural, one {You do not have permission to cancel the following job:} other {You do not have permission to cancel the following jobs:}}"
-msgstr "{0, plural, one {You do not have permission to cancel the following job:} other {You do not have permission to cancel the following jobs:}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:143
-msgid "{0, selectordinal, one {The first {dayOfWeek}} two {The second {dayOfWeek}} =3 {The third {dayOfWeek}} =4 {The fourth {dayOfWeek}} =5 {The fifth {dayOfWeek}}}"
-msgstr "{0, selectordinal, one {The first {dayOfWeek}} two {The second {dayOfWeek}} =3 {The third {dayOfWeek}} =4 {The fourth {dayOfWeek}} =5 {The fifth {dayOfWeek}}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:202
-msgid "{0, selectordinal, one {The first {weekday} of {month}} two {The second {weekday} of {month}} =3 {The third {weekday} of {month}} =4 {The fourth {weekday} of {month}} =5 {The fifth {weekday} of {month}}}"
-msgstr "{0, selectordinal, one {The first {weekday} de {month}} two {The second {weekday} de {month}} =3 {The third {weekday} de {month}} =4 {The fourth {weekday} de {month}} =5 {The fifth {weekday} de {month}}}"
-
-#: screens/ActivityStream/ActivityStreamListItem.js:28
-msgid "{0} (deleted)"
-msgstr "{0} (eliminado)"
-
-#: components/ChipGroup/ChipGroup.js:13
-msgid "{0} more"
-msgstr "{0} más"
-
-#: screens/Job/JobDetail/JobDetail.js:399
-msgid "{0} seconds"
-msgstr "{0} segundos"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:95
-msgid "{automatedInstancesCount} since {automatedInstancesSinceDateTime}"
-msgstr "{automatedInstancesCount} desde {automatedInstancesSinceDateTime}"
-
-#: components/AppContainer/AppContainer.js:55
-msgid "{brandName} logo"
-msgstr "{brandName} Logotipo"
-
-#: components/DetailList/UserDateDetail.js:23
-msgid "{dateStr} by <0>{username}0>"
-msgstr "{dateStr} por <0>{username}0>"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:231
-#: screens/InstanceGroup/Instances/InstanceListItem.js:148
-#: screens/Instances/InstanceDetail/InstanceDetail.js:274
-#: screens/Instances/InstanceList/InstanceListItem.js:158
-#: screens/TopologyView/Tooltip.js:288
-msgid "{forks, plural, one {# fork} other {# forks}}"
-msgstr "{forks, plural, one {# fork} other {# forks}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:41
-msgid "{interval, plural, one {{interval} day} other {{interval} days}}"
-msgstr "{interval, plural, one {{interval} día} other {{interval} días}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:33
-msgid "{interval, plural, one {{interval} hour} other {{interval} hours}}"
-msgstr "{interval, plural, one {{interval} hora} other {{interval} horas}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:25
-msgid "{interval, plural, one {{interval} minute} other {{interval} minutes}}"
-msgstr "{interval, plural, one {{interval} minuto} other {{interval} minutos}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:57
-msgid "{interval, plural, one {{interval} month} other {{interval} months}}"
-msgstr "{interval, plural, one {{interval} mes} other {{interval} meses}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:49
-msgid "{interval, plural, one {{interval} week} other {{interval} weeks}}"
-msgstr "{interval, plural, one {{interval} semana} other {{interval} semanas}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:65
-msgid "{interval, plural, one {{interval} year} other {{interval} years}}"
-msgstr "{interval, plural, one {{interval} año} other {{interval} años}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:198
-msgid "{intervalValue, plural, one {day} other {days}}"
-msgstr "{intervalValue, plural, one {day} other {days}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:196
-msgid "{intervalValue, plural, one {hour} other {hours}}"
-msgstr "{intervalValue, plural, one {hour} other {hours}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:194
-msgid "{intervalValue, plural, one {minute} other {minutes}}"
-msgstr "{intervalValue, plural, one {minute} other {minutes}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:202
-msgid "{intervalValue, plural, one {month} other {months}}"
-msgstr "{intervalValue, plural, one {month} other {months}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:200
-msgid "{intervalValue, plural, one {week} other {weeks}}"
-msgstr "{intervalValue, plural, one {week} other {weeks}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:204
-msgid "{intervalValue, plural, one {year} other {years}}"
-msgstr "{intervalValue, plural, one {year} other {years}}"
-
-#: components/PromptDetail/PromptDetail.js:44
-msgid "{minutes} min {seconds} sec"
-msgstr "{minutes} min. {seconds} seg"
-
-#: components/JobList/JobListCancelButton.js:106
-msgid "{numJobsToCancel, plural, one {Cancel job} other {Cancel jobs}}"
-msgstr "{numJobsToCancel, plural, one {Cancel job} other {Cancel jobs}}"
-
-#: components/JobList/JobListCancelButton.js:168
-msgid "{numJobsToCancel, plural, one {This action will cancel the following job:} other {This action will cancel the following jobs:}}"
-msgstr "{numJobsToCancel, plural, one {This action will cancel the following job:} other {This action will cancel the following jobs:}}"
-
-#: components/JobList/JobListCancelButton.js:91
-msgid "{numJobsToCancel, plural, one {{0}} other {{1}}}"
-msgstr "{numJobsToCancel, plural, one {{0}} other {{1}}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:226
-msgid "{numOccurrences, plural, one {After {numOccurrences} occurrence} other {After {numOccurrences} occurrences}}"
-msgstr "{numOccurrences, plural, one {After {numOccurrences} ocurrencia} other {After {numOccurrences} ocurrencias}}"
-
-#: components/PaginatedTable/PaginatedTable.js:79
-msgid "{pluralizedItemName} List"
-msgstr "{pluralizedItemName} Lista"
-
-#: components/HealthCheckButton/HealthCheckButton.js:13
-msgid "{selectedItemsCount, plural, one {Click to run a health check on the selected instance.} other {Click to run a health check on the selected instances.}}"
-msgstr "{selectedItemsCount, plural, one {Click to run a health check on the selected instance.} other {Click to run a health check on the selected instances.}}"
-
-#: components/AppContainer/AppContainer.js:154
-msgid "{sessionCountdown, plural, one {You will be logged out in # second due to inactivity} other {You will be logged out in # seconds due to inactivity}}"
-msgstr "{sessionCountdown, plural, one {You will be logged out in # second due to inactivity} other {You will be logged out in # seconds due to inactivity}}"
diff --git a/awx/ui/src/locales/fr/messages.po b/awx/ui/src/locales/fr/messages.po
deleted file mode 100644
index 6219912224fe..000000000000
--- a/awx/ui/src/locales/fr/messages.po
+++ /dev/null
@@ -1,10710 +0,0 @@
-msgid ""
-msgstr ""
-"POT-Creation-Date: 2018-12-10 10:08-0500\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: @lingui/cli\n"
-"Language: en\n"
-"Project-Id-Version: \n"
-"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: \n"
-"Last-Translator: \n"
-"Language-Team: \n"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:43
-msgid "(Limited to first 10)"
-msgstr "(10 premiers seulement)"
-
-#: components/TemplateList/TemplateListItem.js:103
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:161
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:90
-msgid "(Prompt on launch)"
-msgstr "(Me le demander au lancement)"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:283
-msgid "* This field will be retrieved from an external secret management system using the specified credential."
-msgstr "* Ce champ sera récupéré dans un système externe de gestion des secrets en utilisant le justificatif d'identité spécifié."
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:228
-msgid "/ (project root)"
-msgstr "/ (project root)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:10
-msgid "0 (Normal)"
-msgstr "0 (Normal)"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:38
-msgid "0 (Warning)"
-msgstr "0 (Avertissement)"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:39
-msgid "1 (Info)"
-msgstr "1 (info)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:11
-msgid "1 (Verbose)"
-msgstr "1 (Verbeux)"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:40
-msgid "2 (Debug)"
-msgstr "2 (Déboguer)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:12
-msgid "2 (More Verbose)"
-msgstr "2 (Verbeux +)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:13
-msgid "3 (Debug)"
-msgstr "3 (Déboguer)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:14
-msgid "4 (Connection Debug)"
-msgstr "4 (Débogage de la connexion)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:15
-msgid "5 (WinRM Debug)"
-msgstr "5 (Débogage WinRM)"
-
-#: screens/Project/shared/Project.helptext.js:76
-msgid ""
-"A refspec to fetch (passed to the Ansible git\n"
-"module). This parameter allows access to references via\n"
-"the branch field not otherwise available."
-msgstr "Refspec à récupérer (passé au module git Ansible). Ce paramètre permet d'accéder aux références via le champ de branche non disponible par ailleurs."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:122
-msgid "A subscription manifest is an export of a Red Hat Subscription. To generate a subscription manifest, go to <0>access.redhat.com0>. For more information, see the <1>User Guide1>."
-msgstr "Un manifeste d'abonnement est une exportation d'un abonnement Red Hat. Pour générer un manifeste d'abonnement, rendez-vous sur <0>access.redhat.com0>. Pour plus d’informations, consulter le <1>Guide de l’utilisateur1>."
-
-#: screens/Job/WorkflowOutput/WorkflowOutputNode.js:143
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:326
-msgid "ALL"
-msgstr "TOUS"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:284
-msgid "API Service/Integration Key"
-msgstr "Service API/Clé d’intégration"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:291
-msgid "API Token"
-msgstr "Token API"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:306
-msgid "API service/integration key"
-msgstr "Service API/Clé d’intégration"
-
-#: components/AppContainer/PageHeaderToolbar.js:125
-msgid "About"
-msgstr "À propos de "
-
-#: routeConfig.js:92
-#: screens/ActivityStream/ActivityStream.js:179
-#: screens/Credential/Credential.js:74
-#: screens/Credential/Credentials.js:29
-#: screens/Inventory/Inventories.js:59
-#: screens/Inventory/Inventory.js:65
-#: screens/Inventory/SmartInventory.js:67
-#: screens/Organization/Organization.js:124
-#: screens/Organization/Organizations.js:31
-#: screens/Project/Project.js:105
-#: screens/Project/Projects.js:27
-#: screens/Team/Team.js:58
-#: screens/Team/Teams.js:31
-#: screens/Template/Template.js:136
-#: screens/Template/Templates.js:45
-#: screens/Template/WorkflowJobTemplate.js:118
-msgid "Access"
-msgstr "Accès"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:75
-msgid "Access Token Expiration"
-msgstr "Expiration du jeton d'accès"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:352
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:421
-msgid "Account SID"
-msgstr "SID de compte"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:396
-msgid "Account token"
-msgstr "Token de compte"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:50
-msgid "Action"
-msgstr "Action"
-
-#: components/JobList/JobList.js:249
-#: components/JobList/JobListItem.js:103
-#: components/RelatedTemplateList/RelatedTemplateList.js:189
-#: components/Schedule/ScheduleList/ScheduleList.js:172
-#: components/Schedule/ScheduleList/ScheduleListItem.js:128
-#: components/SelectedList/DraggableSelectedList.js:101
-#: components/TemplateList/TemplateList.js:246
-#: components/TemplateList/TemplateListItem.js:195
-#: screens/ActivityStream/ActivityStream.js:266
-#: screens/ActivityStream/ActivityStreamListItem.js:49
-#: screens/Application/ApplicationsList/ApplicationListItem.js:48
-#: screens/Application/ApplicationsList/ApplicationsList.js:160
-#: screens/Credential/CredentialList/CredentialList.js:166
-#: screens/Credential/CredentialList/CredentialListItem.js:66
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:177
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:38
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:168
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:87
-#: screens/Host/HostGroups/HostGroupItem.js:34
-#: screens/Host/HostGroups/HostGroupsList.js:177
-#: screens/Host/HostList/HostList.js:172
-#: screens/Host/HostList/HostListItem.js:70
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:200
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:75
-#: screens/InstanceGroup/Instances/InstanceList.js:271
-#: screens/InstanceGroup/Instances/InstanceListItem.js:171
-#: screens/Instances/InstanceList/InstanceList.js:206
-#: screens/Instances/InstanceList/InstanceListItem.js:183
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:218
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:53
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:39
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:142
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:41
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:187
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:44
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:140
-#: screens/Inventory/InventoryList/InventoryList.js:222
-#: screens/Inventory/InventoryList/InventoryListItem.js:131
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:233
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:44
-#: screens/Inventory/InventorySources/InventorySourceList.js:214
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:101
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:102
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:73
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:181
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:124
-#: screens/Organization/OrganizationList/OrganizationList.js:146
-#: screens/Organization/OrganizationList/OrganizationListItem.js:69
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:86
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:19
-#: screens/Project/ProjectList/ProjectList.js:225
-#: screens/Project/ProjectList/ProjectListItem.js:222
-#: screens/Team/TeamList/TeamList.js:144
-#: screens/Team/TeamList/TeamListItem.js:47
-#: screens/Template/Survey/SurveyList.js:105
-#: screens/Template/Survey/SurveyList.js:105
-#: screens/Template/Survey/SurveyListItem.js:90
-#: screens/User/UserList/UserList.js:164
-#: screens/User/UserList/UserListItem.js:56
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:167
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:78
-msgid "Actions"
-msgstr "Actions"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:98
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:61
-#: components/TemplateList/TemplateListItem.js:277
-#: screens/Host/HostDetail/HostDetail.js:71
-#: screens/Host/HostList/HostListItem.js:95
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:217
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:50
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:76
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:100
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:32
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:115
-msgid "Activity"
-msgstr "Activité"
-
-#: routeConfig.js:49
-#: screens/ActivityStream/ActivityStream.js:43
-#: screens/ActivityStream/ActivityStream.js:121
-#: screens/Setting/Settings.js:44
-msgid "Activity Stream"
-msgstr "Flux d’activité"
-
-#: screens/ActivityStream/ActivityStream.js:124
-msgid "Activity Stream type selector"
-msgstr "Sélecteur de type de flux d'activité"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:169
-msgid "Actor"
-msgstr "Acteur"
-
-#: components/AddDropDownButton/AddDropDownButton.js:40
-#: components/PaginatedTable/ToolbarAddButton.js:14
-msgid "Add"
-msgstr "Ajouter"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkAddModal.js:14
-msgid "Add Link"
-msgstr "Ajouter un lien"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.js:78
-msgid "Add Node"
-msgstr "Ajouter un nœud"
-
-#: screens/Template/Templates.js:49
-msgid "Add Question"
-msgstr "Ajouter une question"
-
-#: components/AddRole/AddResourceRole.js:164
-msgid "Add Roles"
-msgstr "Ajouter des rôles"
-
-#: components/AddRole/AddResourceRole.js:161
-msgid "Add Team Roles"
-msgstr "Ajouter des rôles d’équipe"
-
-#: components/AddRole/AddResourceRole.js:158
-msgid "Add User Roles"
-msgstr "Ajouter des rôles d'utilisateur"
-
-#: components/Workflow/WorkflowStartNode.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:238
-msgid "Add a new node"
-msgstr "Ajouter un nouveau noeud"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:49
-msgid "Add a new node between these two nodes"
-msgstr "Ajouter un nouveau nœud entre ces deux nœuds"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:108
-msgid "Add container group"
-msgstr "Ajouter un groupe de conteneurs"
-
-#: components/Schedule/shared/ScheduleFormFields.js:171
-msgid "Add exceptions"
-msgstr "Ajouter des exceptions"
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:140
-msgid "Add existing group"
-msgstr "Ajouter un groupe existant"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:152
-msgid "Add existing host"
-msgstr "Ajouter une hôte existant"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:109
-msgid "Add instance group"
-msgstr "Ajouter un groupe d'instances"
-
-#: screens/Inventory/InventoryList/InventoryList.js:136
-msgid "Add inventory"
-msgstr "Ajouter un inventaire"
-
-#: components/TemplateList/TemplateList.js:151
-msgid "Add job template"
-msgstr "Ajouter un modèle de job"
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:141
-msgid "Add new group"
-msgstr "Ajouter un nouveau groupe"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:153
-msgid "Add new host"
-msgstr "Ajouter un nouvel hôte"
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:77
-msgid "Add resource type"
-msgstr "Ajouter un type de ressource"
-
-#: screens/Inventory/InventoryList/InventoryList.js:137
-msgid "Add smart inventory"
-msgstr "Ajouter un inventaire smart"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:202
-msgid "Add team permissions"
-msgstr "Ajouter les permissions de l'équipe"
-
-#: screens/User/UserRoles/UserRolesList.js:198
-msgid "Add user permissions"
-msgstr "Ajouter les permissions de l’utilisateur"
-
-#: components/TemplateList/TemplateList.js:152
-msgid "Add workflow template"
-msgstr "Ajouter un modèle de flux de travail"
-
-#: screens/TopologyView/Legend.js:269
-msgid "Adding"
-msgstr "Ajout"
-
-#: routeConfig.js:113
-#: screens/ActivityStream/ActivityStream.js:190
-msgid "Administration"
-msgstr "Administration"
-
-#: components/DataListToolbar/DataListToolbar.js:139
-#: screens/Job/JobOutput/JobOutputSearch.js:136
-msgid "Advanced"
-msgstr "Avancé"
-
-#: components/Search/AdvancedSearch.js:318
-msgid "Advanced search documentation"
-msgstr "Documentation sur la recherche avancée"
-
-#: components/Search/AdvancedSearch.js:211
-#: components/Search/AdvancedSearch.js:225
-msgid "Advanced search value input"
-msgstr "Saisie de la valeur de la recherche avancée"
-
-#: screens/Inventory/shared/Inventory.helptext.js:131
-msgid ""
-"After every project update where the SCM revision\n"
-"changes, refresh the inventory from the selected source\n"
-"before executing job tasks. This is intended for static content,\n"
-"like the Ansible inventory .ini file format."
-msgstr "Chaque fois qu’un projet est mis à jour et que la révision SCM est modifiée, réalisez une mise à jour de la source sélectionnée avant de lancer les tâches liées un à job. Le but est le contenu statique, comme le format .ini de fichier d'inventaire Ansible."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:524
-msgid "After number of occurrences"
-msgstr "Après le nombre d'occurrences"
-
-#: components/AlertModal/AlertModal.js:75
-msgid "Alert modal"
-msgstr "Modal d'alerte"
-
-#: components/LaunchButton/ReLaunchDropDown.js:48
-#: components/PromptDetail/PromptDetail.js:132
-#: screens/Metrics/Metrics.js:82
-#: screens/Metrics/Metrics.js:82
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:266
-msgid "All"
-msgstr "Tous"
-
-#: screens/Dashboard/DashboardGraph.js:137
-msgid "All job types"
-msgstr "Tous les types de tâche"
-
-#: screens/Dashboard/DashboardGraph.js:162
-msgid "All jobs"
-msgstr "Toutes les tâches"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:101
-msgid "Allow Branch Override"
-msgstr "Autoriser le remplacement de la branche"
-
-#: components/PromptDetail/PromptProjectDetail.js:66
-#: screens/Project/ProjectDetail/ProjectDetail.js:121
-msgid "Allow branch override"
-msgstr "Autoriser le remplacement de la branche"
-
-#: screens/Project/shared/Project.helptext.js:126
-msgid ""
-"Allow changing the Source Control branch or revision in a job\n"
-"template that uses this project."
-msgstr "Permet de modifier la branche de contrôle des sources ou la révision dans un modèle de job qui utilise ce projet."
-
-#: screens/Application/shared/Application.helptext.js:6
-msgid "Allowed URIs list, space separated"
-msgstr "Liste des URI autorisés, séparés par des espaces"
-
-#: components/Workflow/WorkflowLegend.js:130
-#: components/Workflow/WorkflowLinkHelp.js:24
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:58
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:46
-msgid "Always"
-msgstr "Toujours"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:98
-msgid "Amazon EC2"
-msgstr "Amazon EC2"
-
-#: components/Lookup/shared/LookupErrorMessage.js:12
-msgid "An error occurred"
-msgstr "Une erreur est survenue"
-
-#: components/LaunchPrompt/steps/useInventoryStep.js:35
-msgid "An inventory must be selected"
-msgstr "Un inventaire doit être sélectionné"
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:96
-msgid "Ansible Controller Documentation."
-msgstr "Documentation du contrôleur Ansible."
-
-#: screens/Template/Survey/SurveyQuestionForm.js:43
-msgid "Answer type"
-msgstr "Type de réponse"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:177
-msgid "Answer variable name"
-msgstr "Nom de variable de réponse"
-
-#: components/PromptDetail/PromptDetail.js:132
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:263
-msgid "Any"
-msgstr "Quelconque"
-
-#: components/Lookup/ApplicationLookup.js:83
-#: screens/User/UserTokenDetail/UserTokenDetail.js:39
-#: screens/User/shared/UserTokenForm.js:48
-msgid "Application"
-msgstr "Application"
-
-#: screens/User/UserTokenList/UserTokenList.js:187
-msgid "Application Name"
-msgstr "Nom d'application"
-
-#: screens/Application/Applications.js:67
-#: screens/Application/Applications.js:70
-msgid "Application information"
-msgstr "Informations sur l’application"
-
-#: screens/User/UserTokenList/UserTokenList.js:123
-#: screens/User/UserTokenList/UserTokenList.js:134
-msgid "Application name"
-msgstr "Nom de l'application"
-
-#: screens/Application/Application/Application.js:95
-msgid "Application not found."
-msgstr "Application non trouvée."
-
-#: components/Lookup/ApplicationLookup.js:96
-#: routeConfig.js:142
-#: screens/Application/Applications.js:26
-#: screens/Application/Applications.js:35
-#: screens/Application/ApplicationsList/ApplicationsList.js:113
-#: screens/Application/ApplicationsList/ApplicationsList.js:148
-#: util/getRelatedResourceDeleteDetails.js:209
-msgid "Applications"
-msgstr "Applications"
-
-#: screens/ActivityStream/ActivityStream.js:211
-msgid "Applications & Tokens"
-msgstr "Applications & Jetons"
-
-#: components/NotificationList/NotificationListItem.js:39
-#: components/NotificationList/NotificationListItem.js:40
-#: components/Workflow/WorkflowLegend.js:114
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:67
-msgid "Approval"
-msgstr "Approbation"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:82
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:44
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:47
-msgid "Approve"
-msgstr "Approuver"
-
-#: components/StatusLabel/StatusLabel.js:39
-msgid "Approved"
-msgstr "Approuvé"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:11
-msgid "Approved - {0}. See the Activity Stream for more information."
-msgstr "Approuvé - {0}. Voir le flux d'activité pour plus d'informations."
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:7
-msgid "Approved by {0} - {1}"
-msgstr "Approuvé par {0} - {1}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:162
-#: components/Schedule/shared/FrequencyDetailSubform.js:113
-msgid "April"
-msgstr "Avril"
-
-#: components/JobCancelButton/JobCancelButton.js:104
-msgid "Are you sure you want to cancel this job?"
-msgstr "Êtes-vous certain de vouloir annuler ce job ?"
-
-#: components/DeleteButton/DeleteButton.js:127
-msgid "Are you sure you want to delete:"
-msgstr "Êtes-vous sûr de vouloir supprimer :"
-
-#: screens/Setting/shared/SharedFields.js:142
-msgid "Are you sure you want to disable local authentication? Doing so could impact users' ability to log in and the system administrator's ability to reverse this change."
-msgstr "Êtes-vous sûr de vouloir désactiver l'authentification locale ? Cela pourrait avoir un impact sur la capacité des utilisateurs à se connecter et sur la capacité de l'administrateur système à annuler ce changement."
-
-#: screens/Setting/shared/SharedFields.js:350
-msgid "Are you sure you want to edit login redirect override URL? Doing so could impact users' ability to log in to the system once local authentication is also disabled."
-msgstr "Êtes-vous sûr de vouloir modifier l'URL de substitution de la redirection de la connexion ? Cela pourrait avoir un impact sur la capacité des utilisateurs à se connecter au système une fois que l'authentification locale est également désactivée."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:41
-msgid "Are you sure you want to exit the Workflow Creator without saving your changes?"
-msgstr "Voulez-vous vraiment quitter le flux de travail Creator sans enregistrer vos modifications ?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:40
-msgid "Are you sure you want to remove all the nodes in this workflow?"
-msgstr "Êtes-vous sûr de vouloir supprimer tous les nœuds de ce flux de travail ?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:56
-msgid "Are you sure you want to remove the node below:"
-msgstr "Êtes-vous sûr de vouloir supprimer le nœud ci-dessous :"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:43
-msgid "Are you sure you want to remove this link?"
-msgstr "Êtes-vous sûr de vouloir supprimer ce lien ?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:63
-msgid "Are you sure you want to remove this node?"
-msgstr "Êtes-vous sûr de vouloir supprimer ce nœud ?"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:43
-msgid "Are you sure you want to remove {0} access from {1}? Doing so affects all members of the team."
-msgstr "Êtes-vous sûr de vouloir supprimer {0} l’accès à {1}? Cela risque d’affecter tous les membres de l'équipe."
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:50
-msgid "Are you sure you want to remove {0} access from {username}?"
-msgstr "Êtes-vous sûr de vouloir supprimer {0} l’accès de {username} ?"
-
-#: screens/Job/JobOutput/JobOutput.js:826
-msgid "Are you sure you want to submit the request to cancel this job?"
-msgstr "Voulez-vous vraiment demander l'annulation de ce job ?"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:102
-#: components/AdHocCommands/AdHocDetailsStep.js:104
-msgid "Arguments"
-msgstr "Arguments"
-
-#: screens/Job/JobDetail/JobDetail.js:559
-msgid "Artifacts"
-msgstr "Artefacts"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:233
-#: screens/User/UserTeams/UserTeamList.js:208
-msgid "Associate"
-msgstr "Associé"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:244
-#: screens/User/UserRoles/UserRolesList.js:240
-msgid "Associate role error"
-msgstr "Erreur de rôle d’associé"
-
-#: components/AssociateModal/AssociateModal.js:98
-msgid "Association modal"
-msgstr "Association modale"
-
-#: components/LaunchPrompt/steps/SurveyStep.js:168
-msgid "At least one value must be selected for this field."
-msgstr "Au moins une valeur doit être sélectionnée pour ce champ."
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:166
-#: components/Schedule/shared/FrequencyDetailSubform.js:133
-msgid "August"
-msgstr "Août"
-
-#: screens/Setting/SettingList.js:52
-msgid "Authentication"
-msgstr "Authentification"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:88
-msgid "Authorization Code Expiration"
-msgstr "Expiration du code d'autorisation"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:81
-#: screens/Application/shared/ApplicationForm.js:85
-msgid "Authorization grant type"
-msgstr "Type d'autorisation"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:215
-#: screens/InstanceGroup/Instances/InstanceListItem.js:204
-#: screens/Instances/InstanceDetail/InstanceDetail.js:209
-#: screens/Instances/InstanceList/InstanceListItem.js:219
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:83
-msgid "Auto"
-msgstr "Auto"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:71
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:72
-msgid "Automation Analytics"
-msgstr "Automation Analytics"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:111
-msgid "Automation Analytics dashboard"
-msgstr "Tableau de bord d’Automation Analytics."
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:175
-msgid "Automation controller version"
-msgstr "Version de contrôleur d’Automation"
-
-#: screens/Setting/Settings.js:47
-msgid "Azure AD"
-msgstr "AD Azure"
-
-#: screens/Setting/SettingList.js:57
-msgid "Azure AD settings"
-msgstr "Paramètres AD Azure"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:49
-#: components/AddRole/AddResourceRole.js:267
-#: components/LaunchPrompt/LaunchPrompt.js:158
-#: components/Schedule/shared/SchedulePromptableFields.js:125
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:90
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:70
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:154
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:157
-msgid "Back"
-msgstr "Retour"
-
-#: screens/Credential/Credential.js:65
-msgid "Back to Credentials"
-msgstr "Retour à Références"
-
-#: components/ContentError/ContentError.js:43
-msgid "Back to Dashboard."
-msgstr "Naviguer vers le tableau de bord"
-
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:49
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:50
-msgid "Back to Groups"
-msgstr "Retour aux groupes"
-
-#: screens/Host/Host.js:50
-#: screens/Inventory/InventoryHost/InventoryHost.js:66
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:53
-msgid "Back to Hosts"
-msgstr "Retour aux hôtes"
-
-#: screens/InstanceGroup/InstanceGroup.js:61
-msgid "Back to Instance Groups"
-msgstr "Retour aux groupes d'instances"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:173
-#: screens/Instances/Instance.js:22
-msgid "Back to Instances"
-msgstr "Retour aux instances"
-
-#: screens/Inventory/Inventory.js:57
-#: screens/Inventory/SmartInventory.js:60
-msgid "Back to Inventories"
-msgstr "Retour aux inventaires"
-
-#: screens/Job/Job.js:123
-msgid "Back to Jobs"
-msgstr "Retour Jobs"
-
-#: screens/NotificationTemplate/NotificationTemplate.js:76
-msgid "Back to Notifications"
-msgstr "Retour aux notifications"
-
-#: screens/Organization/Organization.js:116
-msgid "Back to Organizations"
-msgstr "Retour à Organisations"
-
-#: screens/Project/Project.js:97
-msgid "Back to Projects"
-msgstr "Retour aux projets"
-
-#: components/Schedule/Schedule.js:64
-msgid "Back to Schedules"
-msgstr "Retour aux horaires"
-
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:44
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:78
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:44
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:58
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:95
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:69
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:43
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:90
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:44
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:49
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:45
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:34
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:49
-#: screens/Setting/UI/UIDetail/UIDetail.js:59
-msgid "Back to Settings"
-msgstr "Retour aux paramètres"
-
-#: screens/Inventory/InventorySource/InventorySource.js:76
-msgid "Back to Sources"
-msgstr "Retour aux sources"
-
-#: screens/Team/Team.js:50
-msgid "Back to Teams"
-msgstr "Retour Haut de page"
-
-#: screens/Template/Template.js:128
-#: screens/Template/WorkflowJobTemplate.js:110
-msgid "Back to Templates"
-msgstr "Retour aux modèles"
-
-#: screens/User/UserToken/UserToken.js:47
-msgid "Back to Tokens"
-msgstr "Retour Haut de page"
-
-#: screens/User/User.js:57
-msgid "Back to Users"
-msgstr "Retour aux utilisateurs"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:69
-msgid "Back to Workflow Approvals"
-msgstr "Retour à Approbation des flux de travail"
-
-#: screens/Application/Application/Application.js:72
-msgid "Back to applications"
-msgstr "Retour aux applications"
-
-#: screens/CredentialType/CredentialType.js:55
-msgid "Back to credential types"
-msgstr "Retour aux types d'informations d'identification"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:57
-msgid "Back to execution environments"
-msgstr "Retour aux environnements d'exécution"
-
-#: screens/InstanceGroup/ContainerGroup.js:59
-msgid "Back to instance groups"
-msgstr "Retour aux groupes d'instances"
-
-#: screens/ManagementJob/ManagementJob.js:98
-msgid "Back to management jobs"
-msgstr "Retour aux Jobs de gestion"
-
-#: screens/Project/shared/Project.helptext.js:8
-msgid ""
-"Base path used for locating playbooks. Directories\n"
-"found inside this path will be listed in the playbook directory drop-down.\n"
-"Together the base path and selected playbook directory provide the full\n"
-"path used to locate playbooks."
-msgstr "Chemin de base utilisé pour localiser les playbooks. Les répertoires localisés dans ce chemin sont répertoriés dans la liste déroulante des répertoires de playbooks. Le chemin de base et le répertoire de playbook sélectionnés fournissent ensemble le chemin complet servant à localiser les playbooks."
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:448
-msgid "Basic auth password"
-msgstr "Mot de passe d'auth de base"
-
-#: screens/Project/shared/Project.helptext.js:104
-msgid ""
-"Branch to checkout. In addition to branches,\n"
-"you can input tags, commit hashes, and arbitrary refs. Some\n"
-"commit hashes and refs may not be available unless you also\n"
-"provide a custom refspec."
-msgstr "Branche à extraire. En plus des branches, vous pouvez saisir des balises, des hachages de validation et des références arbitraires. Certains hachages et références de validation peuvent ne pas être disponibles à moins que vous ne fournissiez également une refspec personnalisée."
-
-#: screens/Template/shared/JobTemplate.helptext.js:27
-msgid "Branch to use in job run. Project default used if blank. Only allowed if project allow_override field is set to true."
-msgstr "Branche à utiliser dans l’exécution de la tâche. Projet par défaut utilisé si vide. Uniquement autorisé si le champ allow_override de projet est défini à true."
-
-#: components/About/About.js:45
-msgid "Brand Image"
-msgstr "Brand Image"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:158
-msgid "Browse"
-msgstr "Navigation"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:94
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:115
-msgid "Browse…"
-msgstr "Navigation...."
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:36
-msgid "By default, we collect and transmit analytics data on the service usage to Red Hat. There are two categories of data collected by the service. For more information, see <0>this Tower documentation page0>. Uncheck the following boxes to disable this feature."
-msgstr "Par défaut, nous collectons et transmettons à Red Hat des données analytiques sur l'utilisation du service. Il existe deux catégories de données collectées par le service. Pour plus d'informations, consultez <0>Tower documentation à la page0>. Décochez les cases suivantes pour désactiver cette fonctionnalité."
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:228
-#: screens/InstanceGroup/Instances/InstanceListItem.js:145
-#: screens/Instances/InstanceDetail/InstanceDetail.js:271
-#: screens/Instances/InstanceList/InstanceListItem.js:155
-#: screens/TopologyView/Tooltip.js:285
-msgid "CPU {0}"
-msgstr "CPU {0}"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:102
-#: components/PromptDetail/PromptProjectDetail.js:151
-#: screens/Project/ProjectDetail/ProjectDetail.js:268
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:118
-msgid "Cache Timeout"
-msgstr "Expiration Délai d’attente du cache"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:237
-msgid "Cache timeout"
-msgstr "Expiration du délai d’attente du cache"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:106
-msgid "Cache timeout (seconds)"
-msgstr "Expiration du délai d’attente du cache (secondes)"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:50
-#: components/AddRole/AddResourceRole.js:268
-#: components/AssociateModal/AssociateModal.js:114
-#: components/AssociateModal/AssociateModal.js:119
-#: components/DeleteButton/DeleteButton.js:120
-#: components/DeleteButton/DeleteButton.js:123
-#: components/DisassociateButton/DisassociateButton.js:139
-#: components/DisassociateButton/DisassociateButton.js:142
-#: components/FormActionGroup/FormActionGroup.js:23
-#: components/FormActionGroup/FormActionGroup.js:29
-#: components/LaunchPrompt/LaunchPrompt.js:159
-#: components/Lookup/HostFilterLookup.js:388
-#: components/Lookup/Lookup.js:209
-#: components/PaginatedTable/ToolbarDeleteButton.js:282
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:37
-#: components/Schedule/shared/ScheduleForm.js:548
-#: components/Schedule/shared/ScheduleForm.js:553
-#: components/Schedule/shared/SchedulePromptableFields.js:126
-#: components/Schedule/shared/UnsupportedScheduleForm.js:22
-#: components/Schedule/shared/UnsupportedScheduleForm.js:27
-#: screens/Credential/shared/CredentialForm.js:343
-#: screens/Credential/shared/CredentialForm.js:348
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:100
-#: screens/Credential/shared/ExternalTestModal.js:98
-#: screens/Instances/Shared/RemoveInstanceButton.js:174
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:111
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:63
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:66
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:80
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:101
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:107
-#: screens/Setting/shared/RevertAllAlert.js:32
-#: screens/Setting/shared/RevertFormActionGroup.js:31
-#: screens/Setting/shared/RevertFormActionGroup.js:37
-#: screens/Setting/shared/SharedFields.js:133
-#: screens/Setting/shared/SharedFields.js:139
-#: screens/Setting/shared/SharedFields.js:346
-#: screens/Team/TeamRoles/TeamRolesList.js:228
-#: screens/Team/TeamRoles/TeamRolesList.js:231
-#: screens/Template/Survey/SurveyList.js:78
-#: screens/Template/Survey/SurveyReorderModal.js:211
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:31
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:39
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:45
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:50
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:164
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:167
-#: screens/User/UserRoles/UserRolesList.js:224
-#: screens/User/UserRoles/UserRolesList.js:227
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:104
-msgid "Cancel"
-msgstr "Annuler"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:300
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:112
-msgid "Cancel Inventory Source Sync"
-msgstr "Annuler Sync Source d’inventaire"
-
-#: components/JobCancelButton/JobCancelButton.js:69
-#: screens/Job/JobOutput/JobOutput.js:802
-#: screens/Job/JobOutput/JobOutput.js:803
-msgid "Cancel Job"
-msgstr "Annuler Job"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:321
-#: screens/Project/ProjectList/ProjectListItem.js:230
-msgid "Cancel Project Sync"
-msgstr "Annuler Sync Projet"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:302
-#: screens/Project/ProjectDetail/ProjectDetail.js:323
-msgid "Cancel Sync"
-msgstr "Annuler Sync"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:322
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:327
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:95
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:101
-msgid "Cancel Workflow"
-msgstr "Annuler le flux de travail"
-
-#: screens/Job/JobOutput/JobOutput.js:810
-#: screens/Job/JobOutput/JobOutput.js:813
-msgid "Cancel job"
-msgstr "Annuler le job"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:42
-msgid "Cancel link changes"
-msgstr "Annuler les changements de liens"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:34
-msgid "Cancel link removal"
-msgstr "Annuler la suppression d'un lien"
-
-#: components/Lookup/Lookup.js:207
-msgid "Cancel lookup"
-msgstr "Annuler la recherche"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:28
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:47
-msgid "Cancel node removal"
-msgstr "Annuler le retrait d'un nœud"
-
-#: screens/Setting/shared/RevertAllAlert.js:29
-msgid "Cancel revert"
-msgstr "Annuler le retour"
-
-#: components/JobList/JobListCancelButton.js:93
-msgid "Cancel selected job"
-msgstr "Annuler le job sélectionné"
-
-#: components/JobList/JobListCancelButton.js:94
-msgid "Cancel selected jobs"
-msgstr "Annuler les jobs sélectionnés"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:77
-msgid "Cancel subscription edit"
-msgstr "Annuler l'édition de l'abonnement"
-
-#: components/JobList/JobListItem.js:113
-#: screens/Job/JobDetail/JobDetail.js:600
-#: screens/Job/JobOutput/shared/OutputToolbar.js:137
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:89
-msgid "Cancel {0}"
-msgstr "Annuler {0}"
-
-#: components/JobList/JobList.js:234
-#: components/StatusLabel/StatusLabel.js:54
-#: components/Workflow/WorkflowNodeHelp.js:111
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:212
-msgid "Canceled"
-msgstr "Annulé"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:129
-msgid ""
-"Cannot enable log aggregator without providing\n"
-"logging aggregator host and logging aggregator type."
-msgstr "Impossible d'activer l'agrégateur de logs sans fournir l'hôte de l'agrégateur de logs et le type d'agrégateur de logs."
-
-#: screens/Instances/InstanceList/InstanceList.js:199
-#: screens/Instances/InstancePeers/InstancePeerList.js:94
-msgid "Cannot run health check on hop nodes."
-msgstr "Impossible d’effectuer des bilans de fonctionnement sur les nœuds Hop."
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:199
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:74
-#: screens/TopologyView/Tooltip.js:312
-msgid "Capacity"
-msgstr "Capacité"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:225
-#: screens/InstanceGroup/Instances/InstanceList.js:269
-#: screens/InstanceGroup/Instances/InstanceListItem.js:143
-#: screens/Instances/InstanceDetail/InstanceDetail.js:267
-#: screens/Instances/InstanceList/InstanceList.js:204
-#: screens/Instances/InstanceList/InstanceListItem.js:153
-msgid "Capacity Adjustment"
-msgstr "Ajustement des capacités"
-
-#: components/Search/LookupTypeInput.js:59
-msgid "Case-insensitive version of contains"
-msgstr "La version non sensible à la casse de contains"
-
-#: components/Search/LookupTypeInput.js:87
-msgid "Case-insensitive version of endswith."
-msgstr "Version non sensible à la casse de endswith."
-
-#: components/Search/LookupTypeInput.js:45
-msgid "Case-insensitive version of exact."
-msgstr "Version non sensible à la casse de exact."
-
-#: components/Search/LookupTypeInput.js:100
-msgid "Case-insensitive version of regex."
-msgstr "Version non sensible à la casse de regex"
-
-#: components/Search/LookupTypeInput.js:73
-msgid "Case-insensitive version of startswith."
-msgstr "Version non sensible à la casse de startswith."
-
-#: screens/Project/shared/Project.helptext.js:14
-msgid ""
-"Change PROJECTS_ROOT when deploying\n"
-"{brandName} to change this location."
-msgstr "Modifiez PROJECTS_ROOT lorsque vous déployez {brandName} pour changer cet emplacement."
-
-#: components/StatusLabel/StatusLabel.js:55
-#: screens/Job/JobOutput/shared/HostStatusBar.js:43
-msgid "Changed"
-msgstr "Modifié"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:53
-msgid "Changes"
-msgstr "Modifications"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:258
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:266
-msgid "Channel"
-msgstr "Canal"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:138
-#: screens/Template/shared/JobTemplateForm.js:218
-msgid "Check"
-msgstr "Vérifier"
-
-#: components/Search/LookupTypeInput.js:134
-msgid "Check whether the given field or related object is null; expects a boolean value."
-msgstr "Vérifiez si le champ donné ou l'objet connexe est nul ; attendez-vous à une valeur booléenne."
-
-#: components/Search/LookupTypeInput.js:140
-msgid "Check whether the given field's value is present in the list provided; expects a comma-separated list of items."
-msgstr "Vérifiez si la valeur du champ donné est présente dans la liste fournie ; attendez-vous à une liste d'éléments séparés par des virgules."
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:32
-msgid "Choose a .json file"
-msgstr "Choisissez un fichier .json"
-
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:78
-msgid "Choose a Notification Type"
-msgstr "Choisissez un type de notification"
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:25
-msgid "Choose a Playbook Directory"
-msgstr "Choisissez un répertoire Playbook"
-
-#: screens/Project/shared/ProjectForm.js:268
-msgid "Choose a Source Control Type"
-msgstr "Choisissez un type de contrôle à la source"
-
-#: screens/Template/shared/WebhookSubForm.js:100
-msgid "Choose a Webhook Service"
-msgstr "Choisir un service de webhook"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:131
-#: screens/Template/shared/JobTemplateForm.js:211
-msgid "Choose a job type"
-msgstr "Choisir un type de job"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:82
-msgid "Choose a module"
-msgstr "Choisissez un module"
-
-#: screens/Inventory/shared/InventorySourceForm.js:139
-msgid "Choose a source"
-msgstr "Choisissez une source"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:490
-msgid "Choose an HTTP method"
-msgstr "Choisissez une méthode HTTP"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:46
-msgid ""
-"Choose an answer type or format you want as the prompt for the user.\n"
-"Refer to the Ansible Controller Documentation for more additional\n"
-"information about each option."
-msgstr "Spécifiez le type de format ou de type de réponse que vous souhaitez pour interroger l'utilisateur. Consultez la documentation du contrôleur Ansible pour en savoir plus sur chaque option."
-
-#: components/AddRole/SelectRoleStep.js:20
-msgid "Choose roles to apply to the selected resources. Note that all selected roles will be applied to all selected resources."
-msgstr "Choisissez les rôles à appliquer aux ressources sélectionnées. Notez que tous les rôles sélectionnés seront appliqués à toutes les ressources sélectionnées."
-
-#: components/AddRole/SelectResourceStep.js:81
-msgid "Choose the resources that will be receiving new roles. You'll be able to select the roles to apply in the next step. Note that the resources chosen here will receive all roles chosen in the next step."
-msgstr "Choisissez les ressources qui recevront de nouveaux rôles. Vous pourrez sélectionner les rôles à postuler lors de l'étape suivante. Notez que les ressources choisies ici recevront tous les rôles choisis à l'étape suivante."
-
-#: components/AddRole/AddResourceRole.js:174
-msgid "Choose the type of resource that will be receiving new roles. For example, if you'd like to add new roles to a set of users please choose Users and click Next. You'll be able to select the specific resources in the next step."
-msgstr "Choisissez le type de ressource qui recevra de nouveaux rôles. Par exemple, si vous souhaitez ajouter de nouveaux rôles à un ensemble d'utilisateurs, veuillez choisir Utilisateurs et cliquer sur Suivant. Vous pourrez sélectionner les ressources spécifiques dans l'étape suivante."
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:74
-msgid "Clean"
-msgstr "Nettoyer"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:95
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:116
-msgid "Clear"
-msgstr "Effacer"
-
-#: components/DataListToolbar/DataListToolbar.js:95
-#: screens/Job/JobOutput/JobOutputSearch.js:144
-msgid "Clear all filters"
-msgstr "Effacer tous les filtres"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:247
-msgid "Clear subscription"
-msgstr "Effacer l'abonnement"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:252
-msgid "Clear subscription selection"
-msgstr "Effacer la sélection d'abonnement"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerGraph.js:245
-msgid "Click an available node to create a new link. Click outside the graph to cancel."
-msgstr "Cliquez sur un nœud disponible pour créer un nouveau lien. Cliquez en dehors du graphique pour annuler."
-
-#: screens/TopologyView/Tooltip.js:191
-msgid "Click on a node icon to display the details."
-msgstr "Cliquer sur un icône de noeud pour voir les détails."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:134
-msgid "Click the Edit button below to reconfigure the node."
-msgstr "Cliquez sur le bouton Modifier ci-dessous pour reconfigurer le nœud."
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:71
-msgid "Click this button to verify connection to the secret management system using the selected credential and specified inputs."
-msgstr "Cliquez sur ce bouton pour vérifier la connexion au système de gestion du secret en utilisant le justificatif d'identité sélectionné et les entrées spécifiées."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:179
-msgid "Click to create a new link to this node."
-msgstr "Cliquez pour créer un nouveau lien vers ce nœud."
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:251
-msgid "Click to download bundle"
-msgstr "Cliquez pour télécharger l’ensemble (Bundle)"
-
-#: screens/Template/Survey/SurveyToolbar.js:64
-msgid "Click to rearrange the order of the survey questions"
-msgstr "Cliquez pour réorganiser l'ordre des questions de l'enquête"
-
-#: screens/Template/Survey/MultipleChoiceField.js:117
-msgid "Click to toggle default value"
-msgstr "Cliquez pour changer la valeur par défaut"
-
-#: components/Workflow/WorkflowNodeHelp.js:202
-msgid "Click to view job details"
-msgstr "Cliquez pour voir les détails de ce Job"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:89
-#: screens/Application/Applications.js:84
-msgid "Client ID"
-msgstr "ID du client"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:289
-msgid "Client Identifier"
-msgstr "Identifiant client"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:314
-msgid "Client identifier"
-msgstr "Identifiant client"
-
-#: screens/Application/Applications.js:97
-msgid "Client secret"
-msgstr "Question secrète du client"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:100
-#: screens/Application/shared/ApplicationForm.js:127
-msgid "Client type"
-msgstr "Type de client"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:105
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:169
-msgid "Close"
-msgstr "Fermer"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:123
-msgid "Close subscription modal"
-msgstr "Fermer la modalité d'abonnement"
-
-#: components/CredentialChip/CredentialChip.js:11
-msgid "Cloud"
-msgstr "Cloud"
-
-#: components/ExpandCollapse/ExpandCollapse.js:41
-msgid "Collapse"
-msgstr "Effondrement"
-
-#: screens/Job/JobOutput/PageControls.js:50
-msgid "Collapse all job events"
-msgstr "Effondrer tous les événements de la tâche"
-
-#: screens/Job/JobOutput/shared/JobEventLineToggle.js:39
-msgid "Collapse section"
-msgstr "Effondrer une section"
-
-#: components/JobList/JobList.js:214
-#: components/JobList/JobListItem.js:45
-#: screens/Job/JobOutput/HostEventModal.js:129
-msgid "Command"
-msgstr "Commande"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:66
-msgid "Compliant"
-msgstr "Conforme"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:68
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:36
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:132
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:58
-#: screens/Template/shared/JobTemplateForm.js:591
-msgid "Concurrent Jobs"
-msgstr "Jobs parallèles"
-
-#: screens/Template/shared/JobTemplate.helptext.js:38
-msgid "Concurrent jobs: If enabled, simultaneous runs of this job template will be allowed."
-msgstr "Jobs parallèles : si activé, il sera possible d’avoir des exécutions de ce modèle de tâche en simultané."
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:25
-msgid "Concurrent jobs: If enabled, simultaneous runs of this workflow job template will be allowed."
-msgstr "Jobs parallèles : si activé, il sera possible d’avoir des exécutions de ce modèle de job de flux de travail en simultané."
-
-#: screens/Setting/shared/SharedFields.js:121
-#: screens/Setting/shared/SharedFields.js:127
-#: screens/Setting/shared/SharedFields.js:336
-msgid "Confirm"
-msgstr "Confirmer"
-
-#: components/DeleteButton/DeleteButton.js:107
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:95
-msgid "Confirm Delete"
-msgstr "Confirmer Effacer"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:191
-msgid "Confirm Disable Local Authorization"
-msgstr "Confirmer Désactiver l'autorisation locale"
-
-#: screens/User/shared/UserForm.js:99
-msgid "Confirm Password"
-msgstr "Confirmer le mot de passe"
-
-#: components/JobCancelButton/JobCancelButton.js:86
-msgid "Confirm cancel job"
-msgstr "Confirmer l'annulation du job"
-
-#: components/JobCancelButton/JobCancelButton.js:90
-msgid "Confirm cancellation"
-msgstr "Confirmer l'annulation"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:26
-msgid "Confirm delete"
-msgstr "Confirmer la suppression"
-
-#: screens/User/UserRoles/UserRolesList.js:215
-msgid "Confirm disassociate"
-msgstr "Confirmer dissocier"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:24
-msgid "Confirm link removal"
-msgstr "Confirmer la suppression du lien"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:37
-msgid "Confirm node removal"
-msgstr "Confirmer la suppression du nœud"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:18
-msgid "Confirm removal of all nodes"
-msgstr "Confirmer la suppression de tous les nœuds"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:160
-msgid "Confirm remove"
-msgstr "Confirmer la suppression"
-
-#: screens/Setting/shared/RevertAllAlert.js:20
-msgid "Confirm revert all"
-msgstr "Confirmer annuler tout"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:91
-msgid "Confirm selection"
-msgstr "Confirmer la sélection"
-
-#: screens/Job/JobDetail/JobDetail.js:366
-msgid "Container Group"
-msgstr "Groupe de conteneurs"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:47
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:57
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:68
-msgid "Container group"
-msgstr "Groupe de conteneurs"
-
-#: screens/InstanceGroup/ContainerGroup.js:84
-msgid "Container group not found."
-msgstr "Groupe de conteneurs non trouvé."
-
-#: components/LaunchPrompt/LaunchPrompt.js:153
-#: components/Schedule/shared/SchedulePromptableFields.js:120
-msgid "Content Loading"
-msgstr "Chargement du contenu"
-
-#: components/PromptDetail/PromptProjectDetail.js:130
-#: screens/Project/ProjectDetail/ProjectDetail.js:240
-#: screens/Project/shared/ProjectForm.js:290
-msgid "Content Signature Validation Credential"
-msgstr "Certificat de validation de la signature du contenu"
-
-#: components/AppContainer/AppContainer.js:142
-msgid "Continue"
-msgstr "Continuer"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:207
-#: screens/Instances/InstanceList/InstanceList.js:151
-msgid "Control"
-msgstr "Contrôle"
-
-#: screens/TopologyView/Legend.js:77
-msgid "Control node"
-msgstr "Noeud de contrôle"
-
-#: screens/Inventory/shared/Inventory.helptext.js:79
-msgid ""
-"Control the level of output Ansible\n"
-"will produce for inventory source update jobs."
-msgstr "Contrôlez le niveau de sortie produit par Ansible pour les tâches d'actualisation de source d'inventaire."
-
-#: screens/Job/Job.helptext.js:15
-#: screens/Template/shared/JobTemplate.helptext.js:16
-msgid "Control the level of output ansible will produce as the playbook executes."
-msgstr "Contrôlez le niveau de sortie qu’Ansible génère lors de l’exécution du playbook."
-
-#: screens/Job/JobDetail/JobDetail.js:351
-msgid "Controller Node"
-msgstr "Noeud du contrôleur"
-
-#: components/PromptDetail/PromptDetail.js:130
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:225
-msgid "Convergence"
-msgstr "Convergence"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:256
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:257
-msgid "Convergence select"
-msgstr "Sélection Convergence"
-
-#: components/CopyButton/CopyButton.js:40
-msgid "Copy"
-msgstr "Copier"
-
-#: screens/Credential/CredentialList/CredentialListItem.js:80
-msgid "Copy Credential"
-msgstr "Copier les identifiants"
-
-#: components/CopyButton/CopyButton.js:48
-msgid "Copy Error"
-msgstr "Erreur de copie"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:104
-msgid "Copy Execution Environment"
-msgstr "Copier Environnement d'exécution"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:154
-msgid "Copy Inventory"
-msgstr "Copier l'inventaire"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:152
-msgid "Copy Notification Template"
-msgstr "Copie du modèle de notification"
-
-#: screens/Project/ProjectList/ProjectListItem.js:262
-msgid "Copy Project"
-msgstr "Copier le projet"
-
-#: components/TemplateList/TemplateListItem.js:248
-msgid "Copy Template"
-msgstr "Copier le modèle"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:202
-#: screens/Project/ProjectList/ProjectListItem.js:98
-msgid "Copy full revision to clipboard."
-msgstr "Copier la révision complète dans le Presse-papiers."
-
-#: components/About/About.js:35
-msgid "Copyright"
-msgstr "Copyright"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:231
-#: components/MultiSelect/TagMultiSelect.js:62
-#: screens/Credential/shared/CredentialFormFields/BecomeMethodField.js:66
-#: screens/Inventory/shared/InventoryForm.js:91
-#: screens/Template/shared/JobTemplateForm.js:396
-#: screens/Template/shared/WorkflowJobTemplateForm.js:204
-msgid "Create"
-msgstr "Créer"
-
-#: screens/Application/Applications.js:27
-#: screens/Application/Applications.js:36
-msgid "Create New Application"
-msgstr "Créer une nouvelle application"
-
-#: screens/Credential/Credentials.js:15
-#: screens/Credential/Credentials.js:25
-msgid "Create New Credential"
-msgstr "Créer de nouvelles informations d’identification"
-
-#: screens/Host/Hosts.js:15
-#: screens/Host/Hosts.js:24
-msgid "Create New Host"
-msgstr "Créer un nouvel hôte"
-
-#: screens/Template/Templates.js:18
-msgid "Create New Job Template"
-msgstr "Créer un nouveau modèle de Job"
-
-#: screens/NotificationTemplate/NotificationTemplates.js:15
-#: screens/NotificationTemplate/NotificationTemplates.js:22
-msgid "Create New Notification Template"
-msgstr "Créer un nouveau modèle de notification"
-
-#: screens/Organization/Organizations.js:17
-#: screens/Organization/Organizations.js:27
-msgid "Create New Organization"
-msgstr "Créer une nouvelle organisation"
-
-#: screens/Project/Projects.js:13
-#: screens/Project/Projects.js:23
-msgid "Create New Project"
-msgstr "Créer un nouveau projet"
-
-#: screens/Inventory/Inventories.js:91
-#: screens/ManagementJob/ManagementJobs.js:24
-#: screens/Project/Projects.js:32
-#: screens/Template/Templates.js:52
-msgid "Create New Schedule"
-msgstr "Créer une nouvelle programmation"
-
-#: screens/Team/Teams.js:16
-#: screens/Team/Teams.js:26
-msgid "Create New Team"
-msgstr "Créer une nouvelle équipe"
-
-#: screens/User/Users.js:16
-#: screens/User/Users.js:27
-msgid "Create New User"
-msgstr "Créer un nouvel utilisateur"
-
-#: screens/Template/Templates.js:19
-msgid "Create New Workflow Template"
-msgstr "Créer un nouveau modèle de flux de travail"
-
-#: screens/Host/HostList/SmartInventoryButton.js:26
-msgid "Create a new Smart Inventory with the applied filter"
-msgstr "Créer un nouvel inventaire smart avec le filtre appliqué"
-
-#: screens/Instances/Instances.js:14
-msgid "Create new Instance"
-msgstr "Créer une nouvelle instance"
-
-#: screens/InstanceGroup/InstanceGroups.js:18
-#: screens/InstanceGroup/InstanceGroups.js:28
-msgid "Create new container group"
-msgstr "Créer un nouveau groupe de conteneurs"
-
-#: screens/CredentialType/CredentialTypes.js:23
-msgid "Create new credential Type"
-msgstr "Créer un nouveau type d'informations d'identification."
-
-#: screens/CredentialType/CredentialTypes.js:14
-msgid "Create new credential type"
-msgstr "Créer un nouveau type d'informations d'identification."
-
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:14
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:23
-msgid "Create new execution environment"
-msgstr "Créer un nouvel environnement d'exécution"
-
-#: screens/Inventory/Inventories.js:75
-#: screens/Inventory/Inventories.js:82
-msgid "Create new group"
-msgstr "Créer un nouveau groupe"
-
-#: screens/Inventory/Inventories.js:66
-#: screens/Inventory/Inventories.js:80
-msgid "Create new host"
-msgstr "Créer un nouvel hôte"
-
-#: screens/InstanceGroup/InstanceGroups.js:17
-#: screens/InstanceGroup/InstanceGroups.js:27
-msgid "Create new instance group"
-msgstr "Créer un nouveau groupe d'instances"
-
-#: screens/Inventory/Inventories.js:18
-msgid "Create new inventory"
-msgstr "Créer un nouvel inventaire"
-
-#: screens/Inventory/Inventories.js:19
-msgid "Create new smart inventory"
-msgstr "Créer un nouvel inventaire smart"
-
-#: screens/Inventory/Inventories.js:85
-msgid "Create new source"
-msgstr "Créer une nouvelle source"
-
-#: screens/User/Users.js:35
-msgid "Create user token"
-msgstr "Créer un jeton d'utilisateur"
-
-#: components/Lookup/ApplicationLookup.js:115
-#: components/PromptDetail/PromptDetail.js:154
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:406
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:105
-#: screens/Credential/CredentialDetail/CredentialDetail.js:257
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:90
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:103
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:151
-#: screens/Host/HostDetail/HostDetail.js:86
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:67
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:93
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:173
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:43
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:81
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:277
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:149
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:46
-#: screens/Job/JobDetail/JobDetail.js:534
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:393
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:115
-#: screens/Project/ProjectDetail/ProjectDetail.js:292
-#: screens/Team/TeamDetail/TeamDetail.js:47
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:353
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:187
-#: screens/User/UserDetail/UserDetail.js:82
-#: screens/User/UserTokenDetail/UserTokenDetail.js:61
-#: screens/User/UserTokenList/UserTokenList.js:150
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:199
-msgid "Created"
-msgstr "Créé"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:122
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:112
-#: components/AddRole/AddResourceRole.js:57
-#: components/AssociateModal/AssociateModal.js:144
-#: components/LaunchPrompt/steps/CredentialsStep.js:173
-#: components/LaunchPrompt/steps/InventoryStep.js:89
-#: components/Lookup/CredentialLookup.js:194
-#: components/Lookup/InventoryLookup.js:152
-#: components/Lookup/InventoryLookup.js:207
-#: components/Lookup/MultiCredentialsLookup.js:194
-#: components/Lookup/OrganizationLookup.js:134
-#: components/Lookup/ProjectLookup.js:151
-#: components/NotificationList/NotificationList.js:206
-#: components/RelatedTemplateList/RelatedTemplateList.js:166
-#: components/Schedule/ScheduleList/ScheduleList.js:198
-#: components/TemplateList/TemplateList.js:226
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:27
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:58
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:104
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:127
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:173
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:196
-#: screens/Credential/CredentialList/CredentialList.js:150
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:96
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:132
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:102
-#: screens/Host/HostGroups/HostGroupsList.js:164
-#: screens/Host/HostList/HostList.js:157
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:199
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:129
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:174
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:128
-#: screens/Inventory/InventoryList/InventoryList.js:199
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:185
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:94
-#: screens/Organization/OrganizationList/OrganizationList.js:131
-#: screens/Project/ProjectList/ProjectList.js:213
-#: screens/Team/TeamList/TeamList.js:130
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:161
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:112
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:108
-msgid "Created By (Username)"
-msgstr "Créé par (nom d'utilisateur)"
-
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:81
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:147
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:73
-msgid "Created by (username)"
-msgstr "Créé par (nom d'utilisateur)"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:54
-#: components/AdHocCommands/useAdHocCredentialStep.js:24
-#: components/PromptDetail/PromptInventorySourceDetail.js:107
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:40
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:52
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:50
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:258
-#: screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.js:38
-#: screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/GCESubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.js:38
-#: screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:84
-#: screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.js:36
-#: screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.js:38
-#: screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.js:38
-#: util/getRelatedResourceDeleteDetails.js:167
-msgid "Credential"
-msgstr "Information d’identification"
-
-#: util/getRelatedResourceDeleteDetails.js:74
-msgid "Credential Input Sources"
-msgstr "Sources en entrée des informations d'identification"
-
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:83
-#: components/Lookup/InstanceGroupsLookup.js:108
-msgid "Credential Name"
-msgstr "Nom d’identification"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:241
-#: screens/Credential/CredentialList/CredentialList.js:158
-#: screens/Credential/shared/CredentialForm.js:128
-#: screens/Credential/shared/CredentialForm.js:196
-msgid "Credential Type"
-msgstr "Type d'informations d’identification"
-
-#: routeConfig.js:117
-#: screens/ActivityStream/ActivityStream.js:192
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:118
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:161
-#: screens/CredentialType/CredentialTypes.js:13
-#: screens/CredentialType/CredentialTypes.js:22
-msgid "Credential Types"
-msgstr "Types d'informations d'identification"
-
-#: screens/Credential/CredentialList/CredentialList.js:113
-msgid "Credential copied successfully"
-msgstr "Informations d’identification copiées."
-
-#: screens/Credential/Credential.js:98
-msgid "Credential not found."
-msgstr "Informations d'identification introuvables."
-
-#: components/AdHocCommands/useAdHocCredentialPasswordStep.js:23
-#: components/LaunchPrompt/steps/useCredentialPasswordsStep.js:28
-msgid "Credential passwords"
-msgstr "Mots de passes d’identification"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:53
-msgid "Credential to authenticate with Kubernetes or OpenShift"
-msgstr "Identifiant pour l'authentification avec Kubernetes ou OpenShift"
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:57
-msgid "Credential to authenticate with Kubernetes or OpenShift. Must be of type \"Kubernetes/OpenShift API Bearer Token\". If left blank, the underlying Pod's service account will be used."
-msgstr "Jeton pour s'authentifier auprès de Kubernetes ou OpenShift. Doit être de type \"Kubernetes/OpenShift API Bearer Token\". S'il est laissé vide, le compte de service du Pod sous-jacent sera utilisé."
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:21
-msgid "Credential to authenticate with a protected container registry."
-msgstr "Identifiant pour s'authentifier auprès d'un registre de conteneur protégé."
-
-#: screens/CredentialType/CredentialType.js:76
-msgid "Credential type not found."
-msgstr "Type d'informations d’identification non trouvé."
-
-#: components/JobList/JobListItem.js:260
-#: components/LaunchPrompt/steps/CredentialsStep.js:190
-#: components/LaunchPrompt/steps/useCredentialsStep.js:62
-#: components/Lookup/MultiCredentialsLookup.js:138
-#: components/Lookup/MultiCredentialsLookup.js:211
-#: components/PromptDetail/PromptDetail.js:192
-#: components/PromptDetail/PromptJobTemplateDetail.js:191
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:528
-#: components/TemplateList/TemplateListItem.js:323
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:77
-#: routeConfig.js:70
-#: screens/ActivityStream/ActivityStream.js:167
-#: screens/Credential/CredentialList/CredentialList.js:195
-#: screens/Credential/Credentials.js:14
-#: screens/Credential/Credentials.js:24
-#: screens/Job/JobDetail/JobDetail.js:429
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:374
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:49
-#: screens/Template/shared/JobTemplateForm.js:372
-#: util/getRelatedResourceDeleteDetails.js:91
-msgid "Credentials"
-msgstr "Informations d’identification"
-
-#: components/LaunchPrompt/steps/credentialsValidator.js:52
-msgid "Credentials that require passwords on launch are not permitted. Please remove or replace the following credentials with a credential of the same type in order to proceed: {0}"
-msgstr "Les informations d'identification qui nécessitent un mot de passe au lancement ne sont pas autorisées. Veuillez supprimer ou remplacer les informations d'identification suivantes par des informations d'identification du même type pour pouvoir continuer : {0}"
-
-#: components/Pagination/Pagination.js:34
-msgid "Current page"
-msgstr "Page actuelle"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:85
-msgid "Custom Kubernetes or OpenShift Pod specification."
-msgstr "Spécification pod Kubernetes ou OpenShift personnalisée."
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:79
-msgid "Custom pod spec"
-msgstr "Spécifications des pods personnalisés"
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:79
-#: screens/Organization/OrganizationList/OrganizationListItem.js:55
-#: screens/Project/ProjectList/ProjectListItem.js:188
-msgid "Custom virtual environment {0} must be replaced by an execution environment."
-msgstr "L'environnement virtuel personnalisé {0} doit être remplacé par un environnement d'exécution."
-
-#: components/TemplateList/TemplateListItem.js:163
-msgid "Custom virtual environment {0} must be replaced by an execution environment. For more information about migrating to execution environments see <0>the documentation.0>"
-msgstr "L'environnement virtuel personnalisé {0} doit être remplacé par un environnement d'exécution. Pour plus d'informations sur la migration vers des environnements d'exécution, voir la <0>the documentation.0>."
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:73
-msgid "Custom virtual environment {virtualEnvironment} must be replaced by an execution environment. For more information about migrating to execution environments see <0>the documentation.0>"
-msgstr "L'environnement virtuel personnalisé {virtualEnvironment} doit être remplacé par un environnement d'exécution. Pour plus d'informations sur la migration vers des environnements d'exécution, voir la <0>the documentation.0>."
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:61
-msgid "Customize messages…"
-msgstr "Personnaliser les messages..."
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:65
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:66
-msgid "Customize pod specification"
-msgstr "Personnaliser les spécifications du pod"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputNode.js:109
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:212
-msgid "DELETED"
-msgstr "SUPPRIMÉ"
-
-#: routeConfig.js:34
-#: screens/Dashboard/Dashboard.js:74
-msgid "Dashboard"
-msgstr "Tableau de bord"
-
-#: screens/ActivityStream/ActivityStream.js:147
-msgid "Dashboard (all activity)"
-msgstr "Tableau de bord (toutes les activités)"
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:75
-msgid "Data retention period"
-msgstr "Durée de conservation des données"
-
-#: screens/Dashboard/shared/LineChart.js:168
-msgid "Date"
-msgstr "Date"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:184
-#: components/Schedule/shared/FrequencyDetailSubform.js:177
-#: components/Schedule/shared/FrequencyDetailSubform.js:356
-#: components/Schedule/shared/FrequencyDetailSubform.js:460
-#: components/Schedule/shared/ScheduleFormFields.js:127
-#: components/Schedule/shared/ScheduleFormFields.js:187
-msgid "Day"
-msgstr "Jour"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:130
-msgid "Day {0}"
-msgstr "Jour {0}"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:399
-#: components/Schedule/shared/ScheduleFormFields.js:136
-msgid "Days of Data to Keep"
-msgstr "Nombre de jours pendant lesquels on peut conserver les données"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/DaysToKeepStep.js:28
-msgid "Days of data to be retained"
-msgstr "Jours de conservation des données "
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:167
-msgid "Days remaining"
-msgstr "Jours restants"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useDaysToKeepStep.js:35
-msgid "Days to keep"
-msgstr "Jours conservation"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:102
-msgid "Debug"
-msgstr "Déboguer"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:170
-#: components/Schedule/shared/FrequencyDetailSubform.js:153
-msgid "December"
-msgstr "Décembre"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:102
-#: screens/Template/Survey/SurveyList.js:104
-#: screens/Template/Survey/SurveyList.js:104
-#: screens/Template/Survey/SurveyListItem.js:63
-msgid "Default"
-msgstr "Par défaut"
-
-#: screens/Template/Survey/SurveyReorderModal.js:219
-#: screens/Template/Survey/SurveyReorderModal.js:219
-#: screens/Template/Survey/SurveyReorderModal.js:241
-msgid "Default Answer(s)"
-msgstr "Réponse(s) par défaut"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:40
-msgid "Default Execution Environment"
-msgstr "Environnement d'exécution par défaut"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:238
-#: screens/Template/Survey/SurveyQuestionForm.js:246
-#: screens/Template/Survey/SurveyQuestionForm.js:253
-msgid "Default answer"
-msgstr "Réponse par défaut"
-
-#: screens/Setting/SettingList.js:103
-msgid "Define system-level features and functions"
-msgstr "Définir les fonctions et fonctionnalités niveau système"
-
-#: components/DeleteButton/DeleteButton.js:75
-#: components/DeleteButton/DeleteButton.js:80
-#: components/DeleteButton/DeleteButton.js:90
-#: components/DeleteButton/DeleteButton.js:94
-#: components/DeleteButton/DeleteButton.js:114
-#: components/PaginatedTable/ToolbarDeleteButton.js:158
-#: components/PaginatedTable/ToolbarDeleteButton.js:235
-#: components/PaginatedTable/ToolbarDeleteButton.js:246
-#: components/PaginatedTable/ToolbarDeleteButton.js:250
-#: components/PaginatedTable/ToolbarDeleteButton.js:273
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:29
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:646
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:128
-#: screens/Credential/CredentialDetail/CredentialDetail.js:306
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:124
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:134
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:115
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:127
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:201
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:101
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:316
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:174
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:64
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:68
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:73
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:78
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:102
-#: screens/Job/JobDetail/JobDetail.js:612
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:436
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:199
-#: screens/Project/ProjectDetail/ProjectDetail.js:340
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:80
-#: screens/Team/TeamDetail/TeamDetail.js:70
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:545
-#: screens/Template/Survey/SurveyList.js:66
-#: screens/Template/Survey/SurveyToolbar.js:93
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:269
-#: screens/User/UserDetail/UserDetail.js:107
-#: screens/User/UserTokenDetail/UserTokenDetail.js:78
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:340
-msgid "Delete"
-msgstr "Supprimer"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:130
-msgid "Delete All Groups and Hosts"
-msgstr "Supprimer les groupes et les hôtes"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:300
-msgid "Delete Credential"
-msgstr "Supprimer les informations d’identification"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:127
-msgid "Delete Execution Environment"
-msgstr "Supprimer l'environnement d'exécution"
-
-#: screens/Host/HostDetail/HostDetail.js:114
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:109
-msgid "Delete Host"
-msgstr "Supprimer l'hôte"
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:196
-msgid "Delete Inventory"
-msgstr "Supprimer l’inventaire"
-
-#: screens/Job/JobDetail/JobDetail.js:608
-#: screens/Job/JobOutput/shared/OutputToolbar.js:195
-#: screens/Job/JobOutput/shared/OutputToolbar.js:199
-msgid "Delete Job"
-msgstr "Supprimer Job"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:539
-msgid "Delete Job Template"
-msgstr "Modèle de découpage de Job"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:432
-msgid "Delete Notification"
-msgstr "Supprimer la notification"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:193
-msgid "Delete Organization"
-msgstr "Supprimer l'organisation"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:334
-msgid "Delete Project"
-msgstr "Suppression du projet"
-
-#: screens/Template/Survey/SurveyList.js:52
-msgid "Delete Questions"
-msgstr "Supprimer les questions"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:642
-msgid "Delete Schedule"
-msgstr "Supprimer la programmation"
-
-#: screens/Template/Survey/SurveyList.js:52
-msgid "Delete Survey"
-msgstr "Supprimer le questionnaire"
-
-#: screens/Team/TeamDetail/TeamDetail.js:66
-msgid "Delete Team"
-msgstr "Supprimer l’équipe"
-
-#: screens/User/UserDetail/UserDetail.js:103
-msgid "Delete User"
-msgstr "Supprimer l’utilisateur"
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:74
-msgid "Delete User Token"
-msgstr "Supprimer un jeton d'utilisateur"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:336
-msgid "Delete Workflow Approval"
-msgstr "Supprimer l'approbation du flux de travail"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:263
-msgid "Delete Workflow Job Template"
-msgstr "Supprimer le modèle de flux de travail "
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:138
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:141
-msgid "Delete all nodes"
-msgstr "Supprimer tous les nœuds"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:124
-msgid "Delete application"
-msgstr "Supprimer l’application"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:116
-msgid "Delete credential type"
-msgstr "Supprimer le type d'informations d’identification"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:249
-msgid "Delete error"
-msgstr "Supprimer l'erreur"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:109
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:121
-msgid "Delete instance group"
-msgstr "Supprimer un groupe d'instances"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:310
-msgid "Delete inventory source"
-msgstr "Supprimer la source de l'inventaire"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:170
-msgid "Delete smart inventory"
-msgstr "Supprimer l'inventaire smart"
-
-#: screens/Template/Survey/SurveyToolbar.js:83
-msgid "Delete survey question"
-msgstr "Supprimer question de l'enquête"
-
-#: screens/Project/shared/Project.helptext.js:114
-msgid ""
-"Delete the local repository in its entirety prior to\n"
-"performing an update. Depending on the size of the\n"
-"repository this may significantly increase the amount\n"
-"of time required to complete an update."
-msgstr "Supprimez le dépôt local dans son intégralité avant d'effectuer une mise à jour. En fonction de la taille du dépôt, cela peut augmenter considérablement le temps nécessaire pour effectuer une mise à jour."
-
-#: components/PromptDetail/PromptProjectDetail.js:51
-#: screens/Project/ProjectDetail/ProjectDetail.js:100
-msgid "Delete the project before syncing"
-msgstr "Supprimez le projet avant la synchronisation."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:83
-msgid "Delete this link"
-msgstr "Supprimer ce lien"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:274
-msgid "Delete this node"
-msgstr "Supprimer ce nœud"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:163
-msgid "Delete {pluralizedItemName}?"
-msgstr "Supprimer {pluralizedItemName} ?"
-
-#: components/DetailList/DeletedDetail.js:19
-#: components/Workflow/WorkflowNodeHelp.js:157
-#: components/Workflow/WorkflowNodeHelp.js:193
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:231
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:49
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:60
-msgid "Deleted"
-msgstr "Supprimé"
-
-#: components/TemplateList/TemplateList.js:296
-#: screens/Credential/CredentialList/CredentialList.js:211
-#: screens/Inventory/InventoryList/InventoryList.js:284
-#: screens/Project/ProjectList/ProjectList.js:290
-msgid "Deletion Error"
-msgstr "Erreur de suppression"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:202
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:227
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:219
-msgid "Deletion error"
-msgstr "Erreur de suppression"
-
-#: components/StatusLabel/StatusLabel.js:40
-msgid "Denied"
-msgstr "Refusé"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:21
-msgid "Denied - {0}. See the Activity Stream for more information."
-msgstr "Refusé - {0}. Voir le flux d'activité pour plus d'informations."
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:17
-msgid "Denied by {0} - {1}"
-msgstr "Refusé par {0} - {1}"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:89
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:35
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:42
-msgid "Deny"
-msgstr "Refuser"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:103
-msgid "Deprecated"
-msgstr "Obsolète"
-
-#: components/StatusLabel/StatusLabel.js:60
-#: screens/TopologyView/Legend.js:164
-msgid "Deprovisioning"
-msgstr "Déprovisionnement"
-
-#: components/StatusLabel/StatusLabel.js:63
-msgid "Deprovisioning fail"
-msgstr "Échec du déprovisionnement"
-
-#: components/HostForm/HostForm.js:104
-#: components/Lookup/ApplicationLookup.js:105
-#: components/Lookup/ApplicationLookup.js:123
-#: components/Lookup/HostFilterLookup.js:423
-#: components/Lookup/HostListItem.js:9
-#: components/NotificationList/NotificationList.js:186
-#: components/PromptDetail/PromptDetail.js:119
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:327
-#: components/Schedule/ScheduleList/ScheduleList.js:194
-#: components/Schedule/shared/ScheduleFormFields.js:80
-#: components/TemplateList/TemplateList.js:210
-#: components/TemplateList/TemplateListItem.js:271
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:65
-#: screens/Application/ApplicationsList/ApplicationsList.js:123
-#: screens/Application/shared/ApplicationForm.js:62
-#: screens/Credential/CredentialDetail/CredentialDetail.js:223
-#: screens/Credential/CredentialList/CredentialList.js:146
-#: screens/Credential/shared/CredentialForm.js:169
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:72
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:128
-#: screens/CredentialType/shared/CredentialTypeForm.js:29
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:60
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:159
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:127
-#: screens/Host/HostDetail/HostDetail.js:75
-#: screens/Host/HostList/HostList.js:153
-#: screens/Host/HostList/HostList.js:170
-#: screens/Host/HostList/HostListItem.js:57
-#: screens/Instances/Shared/InstanceForm.js:26
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:93
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:35
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:216
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:80
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:40
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:124
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:139
-#: screens/Inventory/InventoryList/InventoryList.js:195
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:198
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:104
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:36
-#: screens/Inventory/shared/InventoryForm.js:58
-#: screens/Inventory/shared/InventoryGroupForm.js:40
-#: screens/Inventory/shared/InventorySourceForm.js:108
-#: screens/Inventory/shared/SmartInventoryForm.js:55
-#: screens/Job/JobOutput/HostEventModal.js:112
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:101
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:72
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:109
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:127
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:49
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:95
-#: screens/Organization/OrganizationList/OrganizationList.js:127
-#: screens/Organization/shared/OrganizationForm.js:64
-#: screens/Project/ProjectDetail/ProjectDetail.js:177
-#: screens/Project/ProjectList/ProjectList.js:190
-#: screens/Project/ProjectList/ProjectListItem.js:281
-#: screens/Project/shared/ProjectForm.js:222
-#: screens/Team/TeamDetail/TeamDetail.js:38
-#: screens/Team/TeamList/TeamList.js:122
-#: screens/Team/shared/TeamForm.js:37
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:187
-#: screens/Template/Survey/SurveyQuestionForm.js:171
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:112
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:183
-#: screens/Template/shared/JobTemplateForm.js:251
-#: screens/Template/shared/WorkflowJobTemplateForm.js:117
-#: screens/User/UserOrganizations/UserOrganizationList.js:80
-#: screens/User/UserOrganizations/UserOrganizationListItem.js:18
-#: screens/User/UserTeams/UserTeamList.js:182
-#: screens/User/UserTeams/UserTeamListItem.js:32
-#: screens/User/UserTokenDetail/UserTokenDetail.js:45
-#: screens/User/UserTokenList/UserTokenList.js:128
-#: screens/User/UserTokenList/UserTokenList.js:138
-#: screens/User/UserTokenList/UserTokenList.js:188
-#: screens/User/UserTokenList/UserTokenListItem.js:29
-#: screens/User/shared/UserTokenForm.js:59
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:145
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:128
-msgid "Description"
-msgstr "Description"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:325
-msgid "Destination Channels"
-msgstr "Canaux de destination"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:233
-msgid "Destination Channels or Users"
-msgstr "Canaux ou utilisateurs de destination"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:346
-msgid "Destination SMS Number(s)"
-msgstr "Numéro(s) de SMS de destination"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:412
-msgid "Destination SMS number(s)"
-msgstr "Numéro(s) de SMS de destination"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:364
-msgid "Destination channels"
-msgstr "Canaux de destination"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:231
-msgid "Destination channels or users"
-msgstr "Canaux ou utilisateurs de destination"
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:35
-#: components/ErrorDetail/ErrorDetail.js:88
-#: components/Schedule/Schedule.js:71
-#: screens/Application/Application/Application.js:79
-#: screens/Application/Applications.js:39
-#: screens/Credential/Credential.js:72
-#: screens/Credential/Credentials.js:28
-#: screens/CredentialType/CredentialType.js:63
-#: screens/CredentialType/CredentialTypes.js:26
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:65
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:26
-#: screens/Host/Host.js:58
-#: screens/Host/Hosts.js:27
-#: screens/InstanceGroup/ContainerGroup.js:66
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:180
-#: screens/InstanceGroup/InstanceGroup.js:69
-#: screens/InstanceGroup/InstanceGroups.js:30
-#: screens/InstanceGroup/InstanceGroups.js:38
-#: screens/Instances/Instance.js:29
-#: screens/Instances/Instances.js:24
-#: screens/Inventory/Inventories.js:61
-#: screens/Inventory/Inventories.js:87
-#: screens/Inventory/Inventory.js:64
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:57
-#: screens/Inventory/InventoryHost/InventoryHost.js:73
-#: screens/Inventory/InventorySource/InventorySource.js:83
-#: screens/Inventory/SmartInventory.js:66
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:60
-#: screens/Job/Job.js:130
-#: screens/Job/JobOutput/HostEventModal.js:103
-#: screens/Job/Jobs.js:35
-#: screens/ManagementJob/ManagementJobs.js:26
-#: screens/NotificationTemplate/NotificationTemplate.js:84
-#: screens/NotificationTemplate/NotificationTemplates.js:25
-#: screens/Organization/Organization.js:123
-#: screens/Organization/Organizations.js:30
-#: screens/Project/Project.js:104
-#: screens/Project/Projects.js:26
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:51
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:51
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:65
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:76
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:50
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:97
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:51
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:56
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:52
-#: screens/Setting/Settings.js:45
-#: screens/Setting/Settings.js:48
-#: screens/Setting/Settings.js:52
-#: screens/Setting/Settings.js:55
-#: screens/Setting/Settings.js:58
-#: screens/Setting/Settings.js:61
-#: screens/Setting/Settings.js:64
-#: screens/Setting/Settings.js:67
-#: screens/Setting/Settings.js:70
-#: screens/Setting/Settings.js:73
-#: screens/Setting/Settings.js:76
-#: screens/Setting/Settings.js:85
-#: screens/Setting/Settings.js:86
-#: screens/Setting/Settings.js:87
-#: screens/Setting/Settings.js:88
-#: screens/Setting/Settings.js:89
-#: screens/Setting/Settings.js:90
-#: screens/Setting/Settings.js:98
-#: screens/Setting/Settings.js:101
-#: screens/Setting/Settings.js:104
-#: screens/Setting/Settings.js:107
-#: screens/Setting/Settings.js:110
-#: screens/Setting/Settings.js:113
-#: screens/Setting/Settings.js:116
-#: screens/Setting/Settings.js:119
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:56
-#: screens/Setting/UI/UIDetail/UIDetail.js:66
-#: screens/Team/Team.js:57
-#: screens/Team/Teams.js:29
-#: screens/Template/Template.js:135
-#: screens/Template/Templates.js:43
-#: screens/Template/WorkflowJobTemplate.js:117
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:138
-#: screens/TopologyView/Tooltip.js:187
-#: screens/TopologyView/Tooltip.js:213
-#: screens/User/User.js:64
-#: screens/User/UserToken/UserToken.js:54
-#: screens/User/Users.js:30
-#: screens/User/Users.js:36
-#: screens/WorkflowApproval/WorkflowApproval.js:77
-#: screens/WorkflowApproval/WorkflowApprovals.js:24
-msgid "Details"
-msgstr "Détails"
-
-#: screens/Job/JobOutput/HostEventModal.js:100
-msgid "Details tab"
-msgstr "Onglet Détails"
-
-#: components/Search/AdvancedSearch.js:271
-msgid "Direct Keys"
-msgstr "Clés directes"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:209
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:268
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:313
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:371
-msgid "Disable SSL Verification"
-msgstr "Désactiver la vérification SSL"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:187
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:240
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:279
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:350
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:461
-msgid "Disable SSL verification"
-msgstr "Désactiver la vérification SSL"
-
-#: components/InstanceToggle/InstanceToggle.js:56
-#: components/StatusLabel/StatusLabel.js:53
-#: screens/TopologyView/Legend.js:233
-msgid "Disabled"
-msgstr "Désactivés"
-
-#: components/DisassociateButton/DisassociateButton.js:73
-#: components/DisassociateButton/DisassociateButton.js:97
-#: components/DisassociateButton/DisassociateButton.js:109
-#: components/DisassociateButton/DisassociateButton.js:113
-#: components/DisassociateButton/DisassociateButton.js:133
-#: screens/Team/TeamRoles/TeamRolesList.js:222
-#: screens/User/UserRoles/UserRolesList.js:218
-msgid "Disassociate"
-msgstr "Dissocier"
-
-#: screens/Host/HostGroups/HostGroupsList.js:211
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:229
-msgid "Disassociate group from host?"
-msgstr "Dissocier le groupe de l'hôte ?"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:247
-msgid "Disassociate host from group?"
-msgstr "Dissocier Hôte du Groupe"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:296
-#: screens/InstanceGroup/Instances/InstanceList.js:245
-msgid "Disassociate instance from instance group?"
-msgstr "Dissocier l'instance du groupe d'instances ?"
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:225
-msgid "Disassociate related group(s)?"
-msgstr "Dissocier le(s) groupe(s) lié(s) ?"
-
-#: screens/User/UserTeams/UserTeamList.js:216
-msgid "Disassociate related team(s)?"
-msgstr "Dissocier la ou les équipes liées ?"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:209
-#: screens/User/UserRoles/UserRolesList.js:205
-msgid "Disassociate role"
-msgstr "Dissocier le rôle"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:212
-#: screens/User/UserRoles/UserRolesList.js:208
-msgid "Disassociate role!"
-msgstr "Dissocier le rôle !"
-
-#: components/DisassociateButton/DisassociateButton.js:18
-msgid "Disassociate?"
-msgstr "Dissocier ?"
-
-#: components/PromptDetail/PromptProjectDetail.js:46
-#: screens/Project/ProjectDetail/ProjectDetail.js:94
-msgid "Discard local changes before syncing"
-msgstr "Ignorez les modifications locales avant de synchroniser."
-
-#: screens/Job/Job.helptext.js:16
-#: screens/Template/shared/JobTemplate.helptext.js:17
-msgid "Divide the work done by this job template into the specified number of job slices, each running the same tasks against a portion of the inventory."
-msgstr "Diviser le travail effectué à l'aide de ce modèle de job en un nombre spécifié de tranches de travail, chacune exécutant les mêmes tâches sur une partie de l'inventaire."
-
-#: screens/Project/shared/Project.helptext.js:100
-msgid "Documentation."
-msgstr "Documentation."
-
-#: components/CodeEditor/VariablesDetail.js:117
-#: components/CodeEditor/VariablesDetail.js:123
-#: components/CodeEditor/VariablesField.js:139
-#: components/CodeEditor/VariablesField.js:145
-msgid "Done"
-msgstr "Terminé"
-
-#: screens/TopologyView/Tooltip.js:251
-msgid "Download Bundle"
-msgstr "Téléchargement du Bundle"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:179
-#: screens/Job/JobOutput/shared/OutputToolbar.js:184
-msgid "Download Output"
-msgstr "Télécharger la sortie"
-
-#: screens/TopologyView/Tooltip.js:247
-msgid "Download bundle"
-msgstr "Téléchargement de l’ensemble (Bundle)"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:93
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:114
-msgid "Drag a file here or browse to upload"
-msgstr "Faites glisser un fichier ici ou naviguez pour le télécharger"
-
-#: components/SelectedList/DraggableSelectedList.js:68
-msgid "Draggable list to reorder and remove selected items."
-msgstr "Liste déroulante permettant de réorganiser et de supprimer les éléments sélectionnés."
-
-#: components/SelectedList/DraggableSelectedList.js:43
-msgid "Dragging cancelled. List is unchanged."
-msgstr "Déplacement annulé. La liste est inchangée."
-
-#: components/SelectedList/DraggableSelectedList.js:38
-msgid "Dragging item {id}. Item with index {oldIndex} in now {newIndex}."
-msgstr "Item déplacée{id}. Item avec index {oldIndex} à l’intérieur maintenant {newIndex}."
-
-#: components/SelectedList/DraggableSelectedList.js:32
-msgid "Dragging started for item id: {newId}."
-msgstr "Le déplacement a commencé pour l'élément id : {newId}."
-
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:81
-msgid "E-mail"
-msgstr "E-mail"
-
-#: screens/Inventory/shared/Inventory.helptext.js:113
-msgid ""
-"Each time a job runs using this inventory,\n"
-"refresh the inventory from the selected source before\n"
-"executing job tasks."
-msgstr "Chaque fois qu’une tâche s’exécute avec cet inventaire, réalisez une mise à jour de la source sélectionnée avant de lancer les tâches du job."
-
-#: screens/Project/shared/Project.helptext.js:124
-msgid ""
-"Each time a job runs using this project, update the\n"
-"revision of the project prior to starting the job."
-msgstr "Chaque fois qu’un job s’exécute avec ce projet, réalisez une mise à jour du projet avant de démarrer le job."
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:632
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:636
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:115
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:117
-#: screens/Credential/CredentialDetail/CredentialDetail.js:293
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:109
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:121
-#: screens/Host/HostDetail/HostDetail.js:108
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:101
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:113
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:190
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:55
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:62
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:103
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:292
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:127
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:164
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:418
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:420
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:138
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:182
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:186
-#: screens/Project/ProjectDetail/ProjectDetail.js:313
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:85
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:89
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:148
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:152
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:85
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:89
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:96
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:100
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:164
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:168
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:107
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:111
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:84
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:88
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:152
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:156
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:85
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:89
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:99
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:103
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:86
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:90
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:199
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:103
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:108
-#: screens/Setting/UI/UIDetail/UIDetail.js:105
-#: screens/Setting/UI/UIDetail/UIDetail.js:110
-#: screens/Team/TeamDetail/TeamDetail.js:55
-#: screens/Team/TeamDetail/TeamDetail.js:59
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:514
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:516
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:239
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:241
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:260
-#: screens/User/UserDetail/UserDetail.js:96
-msgid "Edit"
-msgstr "Modifier"
-
-#: screens/Credential/CredentialList/CredentialListItem.js:67
-#: screens/Credential/CredentialList/CredentialListItem.js:71
-msgid "Edit Credential"
-msgstr "Modifier les informations d’identification"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:37
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:42
-msgid "Edit Credential Plugin Configuration"
-msgstr "Modifier la configuration du plug-in Configuration"
-
-#: screens/Application/Applications.js:38
-#: screens/Credential/Credentials.js:27
-#: screens/Host/Hosts.js:26
-#: screens/ManagementJob/ManagementJobs.js:27
-#: screens/NotificationTemplate/NotificationTemplates.js:24
-#: screens/Organization/Organizations.js:29
-#: screens/Project/Projects.js:25
-#: screens/Project/Projects.js:35
-#: screens/Setting/Settings.js:46
-#: screens/Setting/Settings.js:49
-#: screens/Setting/Settings.js:53
-#: screens/Setting/Settings.js:56
-#: screens/Setting/Settings.js:59
-#: screens/Setting/Settings.js:62
-#: screens/Setting/Settings.js:65
-#: screens/Setting/Settings.js:68
-#: screens/Setting/Settings.js:71
-#: screens/Setting/Settings.js:74
-#: screens/Setting/Settings.js:77
-#: screens/Setting/Settings.js:91
-#: screens/Setting/Settings.js:92
-#: screens/Setting/Settings.js:93
-#: screens/Setting/Settings.js:94
-#: screens/Setting/Settings.js:95
-#: screens/Setting/Settings.js:96
-#: screens/Setting/Settings.js:99
-#: screens/Setting/Settings.js:102
-#: screens/Setting/Settings.js:105
-#: screens/Setting/Settings.js:108
-#: screens/Setting/Settings.js:111
-#: screens/Setting/Settings.js:114
-#: screens/Setting/Settings.js:117
-#: screens/Setting/Settings.js:120
-#: screens/Team/Teams.js:28
-#: screens/Template/Templates.js:44
-#: screens/User/Users.js:29
-msgid "Edit Details"
-msgstr "Modifier les détails"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:90
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:94
-msgid "Edit Execution Environment"
-msgstr "Modifier l'environnement d'exécution"
-
-#: screens/Host/HostGroups/HostGroupItem.js:37
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:46
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:46
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:51
-msgid "Edit Group"
-msgstr "Modifier le groupe"
-
-#: screens/Host/HostList/HostListItem.js:74
-#: screens/Host/HostList/HostListItem.js:78
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:61
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:64
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:67
-msgid "Edit Host"
-msgstr "Modifier l’hôte"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:134
-#: screens/Inventory/InventoryList/InventoryListItem.js:139
-msgid "Edit Inventory"
-msgstr "Modifier l'inventaire"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkEditModal.js:14
-msgid "Edit Link"
-msgstr "Modifier le lien"
-
-#: screens/Setting/shared/SharedFields.js:290
-msgid "Edit Login redirect override URL"
-msgstr "URL de remplacement pour la redirection de connexion"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeEditModal.js:64
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:257
-msgid "Edit Node"
-msgstr "Modifier le nœud"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:142
-msgid "Edit Notification Template"
-msgstr "Modèle de notification de modification"
-
-#: screens/Template/Survey/SurveyToolbar.js:73
-msgid "Edit Order"
-msgstr "Ordre d'édition"
-
-#: screens/Organization/OrganizationList/OrganizationListItem.js:72
-#: screens/Organization/OrganizationList/OrganizationListItem.js:76
-msgid "Edit Organization"
-msgstr "Modifier l'organisation"
-
-#: screens/Project/ProjectList/ProjectListItem.js:248
-#: screens/Project/ProjectList/ProjectListItem.js:253
-msgid "Edit Project"
-msgstr "Modifier le projet"
-
-#: screens/Template/Templates.js:50
-msgid "Edit Question"
-msgstr "Modifier la question"
-
-#: components/Schedule/ScheduleList/ScheduleListItem.js:132
-#: components/Schedule/ScheduleList/ScheduleListItem.js:136
-#: screens/Template/Templates.js:55
-msgid "Edit Schedule"
-msgstr "Modifier la programmation"
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:131
-msgid "Edit Source"
-msgstr "Modifier la source"
-
-#: screens/Template/Survey/SurveyListItem.js:92
-msgid "Edit Survey"
-msgstr "Modifier le questionnaire"
-
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:22
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:26
-#: screens/Team/TeamList/TeamListItem.js:50
-#: screens/Team/TeamList/TeamListItem.js:54
-msgid "Edit Team"
-msgstr "Modifier l’équipe"
-
-#: components/TemplateList/TemplateListItem.js:233
-#: components/TemplateList/TemplateListItem.js:239
-msgid "Edit Template"
-msgstr "Modifier le modèle"
-
-#: screens/User/UserList/UserListItem.js:59
-#: screens/User/UserList/UserListItem.js:63
-msgid "Edit User"
-msgstr "Modifier l’utilisateur"
-
-#: screens/Application/ApplicationsList/ApplicationListItem.js:51
-#: screens/Application/ApplicationsList/ApplicationListItem.js:55
-msgid "Edit application"
-msgstr "Modifier l’application"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:41
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:45
-msgid "Edit credential type"
-msgstr "Modifier le type d’identification"
-
-#: screens/CredentialType/CredentialTypes.js:25
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:25
-#: screens/InstanceGroup/InstanceGroups.js:35
-#: screens/InstanceGroup/InstanceGroups.js:40
-#: screens/Inventory/Inventories.js:63
-#: screens/Inventory/Inventories.js:68
-#: screens/Inventory/Inventories.js:77
-#: screens/Inventory/Inventories.js:88
-msgid "Edit details"
-msgstr "Modifier les détails"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:42
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:44
-msgid "Edit group"
-msgstr "Modifier le groupe"
-
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:48
-msgid "Edit host"
-msgstr "Modifier l’hôte"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:78
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:82
-msgid "Edit instance group"
-msgstr "Modifier le groupe d'instances"
-
-#: screens/Setting/shared/SharedFields.js:320
-#: screens/Setting/shared/SharedFields.js:322
-msgid "Edit login redirect override URL"
-msgstr "URL de remplacement pour la redirection de connexion"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:70
-msgid "Edit this link"
-msgstr "Modifier ce lien"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:248
-msgid "Edit this node"
-msgstr "Modifier ce nœud"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:97
-msgid "Edit workflow"
-msgstr "Modifier le flux de travail"
-
-#: components/Workflow/WorkflowNodeHelp.js:170
-#: screens/Job/JobOutput/shared/OutputToolbar.js:125
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:216
-msgid "Elapsed"
-msgstr "Écoulé"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:124
-msgid "Elapsed Time"
-msgstr "Temps écoulé"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:126
-msgid "Elapsed time that the job ran"
-msgstr "Temps écoulé (en secondes) pendant lequel la tâche s'est exécutée."
-
-#: components/NotificationList/NotificationList.js:193
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:134
-#: screens/User/UserDetail/UserDetail.js:66
-#: screens/User/UserList/UserList.js:115
-#: screens/User/shared/UserForm.js:73
-msgid "Email"
-msgstr "Email"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:177
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:125
-msgid "Email Options"
-msgstr "Options d'email"
-
-#: screens/Template/shared/WorkflowJobTemplateForm.js:263
-msgid "Enable Concurrent Jobs"
-msgstr "Activer les tâches parallèles"
-
-#: screens/Template/shared/JobTemplateForm.js:597
-msgid "Enable Fact Storage"
-msgstr "Utiliser le cache des facts"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:192
-msgid "Enable HTTPS certificate verification"
-msgstr "Activer la vérification de certificat HTTPS"
-
-#: screens/Instances/Shared/InstanceForm.js:58
-msgid "Enable Instance"
-msgstr "Activer l'instance"
-
-#: screens/Template/shared/JobTemplateForm.js:573
-#: screens/Template/shared/JobTemplateForm.js:576
-#: screens/Template/shared/WorkflowJobTemplateForm.js:244
-#: screens/Template/shared/WorkflowJobTemplateForm.js:247
-msgid "Enable Webhook"
-msgstr "Activer le webhook"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:15
-msgid "Enable Webhook for this workflow job template."
-msgstr "Activez le webhook pour ce modèle de flux de travail."
-
-#: screens/Project/shared/Project.helptext.js:108
-msgid ""
-"Enable content signing to verify that the content \n"
-"has remained secure when a project is synced. \n"
-"If the content has been tampered with, the \n"
-"job will not run."
-msgstr "Activez la signature du contenu pour vérifier que le contenu \n"
-"est resté sécurisé lorsqu'un projet est synchronisé. \n"
-"Si le contenu a été altéré, le travail ne sera pas exécuté."
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:136
-msgid "Enable external logging"
-msgstr "Activer la journalisation externe"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:168
-msgid "Enable log system tracking facts individually"
-msgstr "Activer le système de journalisation traçant des facts individuellement"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:201
-#: components/AdHocCommands/AdHocDetailsStep.js:204
-msgid "Enable privilege escalation"
-msgstr "Activer l’élévation des privilèges"
-
-#: screens/Setting/SettingList.js:53
-msgid "Enable simplified login for your {brandName} applications"
-msgstr "Activer la connexion simplifiée pour vos applications {brandName}"
-
-#: screens/Template/shared/JobTemplate.helptext.js:31
-msgid "Enable webhook for this template."
-msgstr "Activez le webhook pour ce modèle de tâche."
-
-#: components/InstanceToggle/InstanceToggle.js:55
-#: components/Lookup/HostFilterLookup.js:110
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:45
-#: screens/TopologyView/Legend.js:205
-msgid "Enabled"
-msgstr "Activé"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:171
-#: components/PromptDetail/PromptJobTemplateDetail.js:187
-#: components/PromptDetail/PromptProjectDetail.js:145
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:99
-#: screens/Credential/CredentialDetail/CredentialDetail.js:268
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:138
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:265
-#: screens/Project/ProjectDetail/ProjectDetail.js:302
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:365
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:199
-msgid "Enabled Options"
-msgstr "Options activées"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:252
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:132
-msgid "Enabled Value"
-msgstr "Valeur activée"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:247
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:119
-msgid "Enabled Variable"
-msgstr "Variable activée"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:209
-msgid ""
-"Enables creation of a provisioning\n"
-"callback URL. Using the URL a host can contact {brandName}\n"
-"and request a configuration update using this job\n"
-"template"
-msgstr "Active la création d’une URL de rappels d’exécution. Avec cette URL, un hôte peut contacter {brandName} et demander une mise à jour de la configuration à l’aide de ce modèle de tâche."
-
-#: screens/Template/shared/JobTemplate.helptext.js:29
-msgid "Enables creation of a provisioning callback URL. Using the URL a host can contact {brandName} and request a configuration update using this job template."
-msgstr "Active la création d’une URL de rappels d’exécution. Avec cette URL, un hôte peut contacter {brandName} et demander une mise à jour de la configuration à l’aide de ce modèle de tâche."
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:160
-#: screens/Setting/shared/SettingDetail.js:87
-msgid "Encrypted"
-msgstr "Crypté"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:109
-#: components/Schedule/shared/FrequencyDetailSubform.js:507
-msgid "End"
-msgstr "Fin"
-
-#: screens/Setting/Subscription/SubscriptionEdit/EulaStep.js:14
-msgid "End User License Agreement"
-msgstr "Contrat de licence utilisateur"
-
-#: components/Schedule/shared/DateTimePicker.js:51
-msgid "End date"
-msgstr "Date de fin"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:561
-msgid "End date/time"
-msgstr "Date/Heure de fin"
-
-#: components/Schedule/shared/buildRuleObj.js:110
-msgid "End did not match an expected value ({0})"
-msgstr "La fin ne correspondait pas à une valeur attendue ({0})"
-
-#: components/Schedule/shared/DateTimePicker.js:61
-msgid "End time"
-msgstr "Heure de fin"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:209
-msgid "End user license agreement"
-msgstr "Contrat de licence utilisateur"
-
-#: screens/Host/HostList/SmartInventoryButton.js:23
-msgid "Enter at least one search filter to create a new Smart Inventory"
-msgstr "Veuillez saisir une expression de recherche au moins pour créer un nouvel inventaire Smart."
-
-#: screens/CredentialType/shared/CredentialTypeForm.js:43
-msgid "Enter injectors using either JSON or YAML syntax. Refer to the Ansible Controller documentation for example syntax."
-msgstr "Entrez les injecteurs avec la syntaxe JSON ou YAML. Consultez la documentation sur le contrôleur Ansible pour avoir un exemple de syntaxe."
-
-#: screens/CredentialType/shared/CredentialTypeForm.js:35
-msgid "Enter inputs using either JSON or YAML syntax. Refer to the Ansible Controller documentation for example syntax."
-msgstr "Entrez les variables avec la syntaxe JSON ou YAML. Consultez la documentation sur le contrôleur Ansible pour avoir un exemple de syntaxe."
-
-#: screens/Inventory/shared/SmartInventoryForm.js:94
-msgid ""
-"Enter inventory variables using either JSON or YAML syntax.\n"
-"Use the radio button to toggle between the two. Refer to the\n"
-"Ansible Controller documentation for example syntax."
-msgstr "Entrez les variables d’inventaire avec la syntaxe JSON ou YAML. Utilisez le bouton radio pour basculer entre les deux. Consultez la documentation sur le contrôleur Ansible pour avoir un exemple de syntaxe."
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:87
-msgid "Environment variables or extra variables that specify the values a credential type can inject."
-msgstr "Variables d'environnement ou variables supplémentaires qui spécifient les valeurs qu'un type de justificatif peut injecter."
-
-#: components/JobList/JobList.js:233
-#: components/StatusLabel/StatusLabel.js:46
-#: components/Workflow/WorkflowNodeHelp.js:108
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:133
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:205
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:143
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:230
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:123
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:135
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:222
-#: screens/Job/JobOutput/JobOutputSearch.js:104
-#: screens/TopologyView/Legend.js:178
-msgid "Error"
-msgstr "Erreur"
-
-#: screens/Project/ProjectList/ProjectList.js:302
-msgid "Error fetching updated project"
-msgstr "Erreur de récupération du projet mis à jour"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:501
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:141
-msgid "Error message"
-msgstr "Message d'erreur"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:510
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:150
-msgid "Error message body"
-msgstr "Corps du message d'erreur"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:709
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:711
-msgid "Error saving the workflow!"
-msgstr "Erreur lors de la sauvegarde du flux de travail !"
-
-#: components/AdHocCommands/AdHocCommands.js:104
-#: components/CopyButton/CopyButton.js:51
-#: components/DeleteButton/DeleteButton.js:56
-#: components/HostToggle/HostToggle.js:76
-#: components/InstanceToggle/InstanceToggle.js:67
-#: components/JobList/JobList.js:315
-#: components/JobList/JobList.js:326
-#: components/LaunchButton/LaunchButton.js:185
-#: components/LaunchPrompt/LaunchPrompt.js:96
-#: components/NotificationList/NotificationList.js:246
-#: components/PaginatedTable/ToolbarDeleteButton.js:205
-#: components/RelatedTemplateList/RelatedTemplateList.js:241
-#: components/ResourceAccessList/ResourceAccessList.js:277
-#: components/ResourceAccessList/ResourceAccessList.js:289
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:654
-#: components/Schedule/ScheduleList/ScheduleList.js:239
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:73
-#: components/Schedule/shared/SchedulePromptableFields.js:63
-#: components/TemplateList/TemplateList.js:299
-#: contexts/Config.js:94
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:136
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:155
-#: screens/Application/ApplicationsList/ApplicationsList.js:185
-#: screens/Credential/CredentialDetail/CredentialDetail.js:314
-#: screens/Credential/CredentialList/CredentialList.js:214
-#: screens/Host/HostDetail/HostDetail.js:56
-#: screens/Host/HostDetail/HostDetail.js:123
-#: screens/Host/HostGroups/HostGroupsList.js:244
-#: screens/Host/HostList/HostList.js:233
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:310
-#: screens/InstanceGroup/Instances/InstanceList.js:308
-#: screens/InstanceGroup/Instances/InstanceListItem.js:218
-#: screens/Instances/InstanceDetail/InstanceDetail.js:360
-#: screens/Instances/InstanceDetail/InstanceDetail.js:375
-#: screens/Instances/InstanceList/InstanceList.js:231
-#: screens/Instances/InstanceList/InstanceList.js:243
-#: screens/Instances/InstanceList/InstanceListItem.js:234
-#: screens/Instances/Shared/RemoveInstanceButton.js:104
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:210
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:78
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:285
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:296
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:56
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:118
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:261
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:201
-#: screens/Inventory/InventoryList/InventoryList.js:285
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:264
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:323
-#: screens/Inventory/InventorySources/InventorySourceList.js:239
-#: screens/Inventory/InventorySources/InventorySourceList.js:252
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:183
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:152
-#: screens/Inventory/shared/InventorySourceSyncButton.js:49
-#: screens/Login/Login.js:239
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:125
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:444
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:233
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:169
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:208
-#: screens/Organization/OrganizationList/OrganizationList.js:195
-#: screens/Project/ProjectDetail/ProjectDetail.js:348
-#: screens/Project/ProjectList/ProjectList.js:291
-#: screens/Project/ProjectList/ProjectList.js:303
-#: screens/Project/shared/ProjectSyncButton.js:60
-#: screens/Team/TeamDetail/TeamDetail.js:78
-#: screens/Team/TeamList/TeamList.js:192
-#: screens/Team/TeamRoles/TeamRolesList.js:247
-#: screens/Team/TeamRoles/TeamRolesList.js:258
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:554
-#: screens/Template/TemplateSurvey.js:130
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:277
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:180
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:195
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:337
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:373
-#: screens/TopologyView/MeshGraph.js:405
-#: screens/TopologyView/Tooltip.js:199
-#: screens/User/UserDetail/UserDetail.js:115
-#: screens/User/UserList/UserList.js:189
-#: screens/User/UserRoles/UserRolesList.js:243
-#: screens/User/UserRoles/UserRolesList.js:254
-#: screens/User/UserTeams/UserTeamList.js:259
-#: screens/User/UserTokenDetail/UserTokenDetail.js:85
-#: screens/User/UserTokenList/UserTokenList.js:214
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:348
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:189
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:53
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:48
-msgid "Error!"
-msgstr "Erreur !"
-
-#: components/CodeEditor/VariablesDetail.js:105
-msgid "Error:"
-msgstr "Erreur :"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:267
-#: screens/Instances/InstanceDetail/InstanceDetail.js:315
-msgid "Errors"
-msgstr "Erreurs"
-
-#: screens/TopologyView/Legend.js:253
-msgid "Established"
-msgstr "Établi"
-
-#: screens/ActivityStream/ActivityStream.js:265
-#: screens/ActivityStream/ActivityStreamListItem.js:46
-#: screens/Job/JobOutput/JobOutputSearch.js:99
-msgid "Event"
-msgstr "Événement"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:35
-msgid "Event detail"
-msgstr "Afficher les détails de l’événement"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:36
-msgid "Event detail modal"
-msgstr "Détail de l'événement modal"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:555
-msgid "Event summary not available"
-msgstr "Récapitulatif de l’événement non disponible"
-
-#: screens/ActivityStream/ActivityStream.js:234
-msgid "Events"
-msgstr "Événements"
-
-#: screens/Job/JobOutput/JobOutput.js:713
-msgid "Events processing complete."
-msgstr "Traitement des événements terminé."
-
-#: components/Search/LookupTypeInput.js:39
-msgid "Exact match (default lookup if not specified)."
-msgstr "Correspondance exacte (recherche par défaut si non spécifiée)."
-
-#: components/Search/RelatedLookupTypeInput.js:38
-msgid "Exact search on id field."
-msgstr "Recherche exacte sur le champ d'identification."
-
-#: screens/Project/shared/Project.helptext.js:23
-msgid "Example URLs for GIT Source Control include:"
-msgstr "Voici des exemples d'URL pour le contrôle des sources de GIT :"
-
-#: screens/Project/shared/Project.helptext.js:62
-msgid "Example URLs for Remote Archive Source Control include:"
-msgstr "Voici des exemples d'URL pour Remote Archive SCM :"
-
-#: screens/Project/shared/Project.helptext.js:45
-msgid "Example URLs for Subversion Source Control include:"
-msgstr "Exemples d’URL pour le SCM Subversion :"
-
-#: screens/Project/shared/Project.helptext.js:84
-msgid "Examples include:"
-msgstr "Voici quelques exemples :"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:10
-msgid "Examples:"
-msgstr "Exemples :"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:354
-msgid "Exception Frequency"
-msgstr "Fréquence des exceptions"
-
-#: components/Schedule/shared/ScheduleFormFields.js:160
-msgid "Exceptions"
-msgstr "Exceptions"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:47
-msgid "Execute regardless of the parent node's final state."
-msgstr "Exécuter quel que soit l'état final du nœud parent."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:40
-msgid "Execute when the parent node results in a failure state."
-msgstr "Exécuter lorsque le nœud parent se trouve dans un état de défaillance."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:33
-msgid "Execute when the parent node results in a successful state."
-msgstr "Exécuter lorsque le nœud parent se trouve dans un état de réussite."
-
-#: screens/InstanceGroup/Instances/InstanceList.js:208
-#: screens/Instances/InstanceList/InstanceList.js:152
-msgid "Execution"
-msgstr "Exécution"
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:90
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:91
-#: components/AdHocCommands/AdHocPreviewStep.js:58
-#: components/AdHocCommands/useAdHocExecutionEnvironmentStep.js:15
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:41
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:105
-#: components/LaunchPrompt/steps/useExecutionEnvironmentStep.js:29
-#: components/Lookup/ExecutionEnvironmentLookup.js:159
-#: components/Lookup/ExecutionEnvironmentLookup.js:191
-#: components/Lookup/ExecutionEnvironmentLookup.js:208
-#: components/PromptDetail/PromptDetail.js:220
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:451
-msgid "Execution Environment"
-msgstr "Environnement d'exécution"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:70
-#: components/TemplateList/TemplateListItem.js:160
-msgid "Execution Environment Missing"
-msgstr "Environnement d'exécution manquant"
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:103
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:107
-#: routeConfig.js:147
-#: screens/ActivityStream/ActivityStream.js:217
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:129
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:191
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:13
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:22
-#: screens/Organization/Organization.js:127
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:78
-#: screens/Organization/Organizations.js:34
-#: util/getRelatedResourceDeleteDetails.js:81
-#: util/getRelatedResourceDeleteDetails.js:188
-msgid "Execution Environments"
-msgstr "Environnements d'exécution"
-
-#: screens/Job/JobDetail/JobDetail.js:345
-msgid "Execution Node"
-msgstr "Nœud d'exécution"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:103
-msgid "Execution environment copied successfully"
-msgstr "Environnement d'exécution copié"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:112
-msgid "Execution environment is missing or deleted."
-msgstr "L'environnement d'exécution est absent ou supprimé."
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:83
-msgid "Execution environment not found."
-msgstr "Environnement d'exécution non trouvé."
-
-#: screens/TopologyView/Legend.js:86
-msgid "Execution node"
-msgstr "Nœud d'exécution"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:23
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:26
-msgid "Exit Without Saving"
-msgstr "Sortir sans sauvegarder"
-
-#: components/ExpandCollapse/ExpandCollapse.js:52
-msgid "Expand"
-msgstr "Développer"
-
-#: components/DataListToolbar/DataListToolbar.js:105
-msgid "Expand all rows"
-msgstr "Développer toutes les lignes"
-
-#: components/CodeEditor/VariablesDetail.js:212
-#: components/CodeEditor/VariablesField.js:248
-msgid "Expand input"
-msgstr "Développer l'entrée"
-
-#: screens/Job/JobOutput/PageControls.js:50
-msgid "Expand job events"
-msgstr "Agrandir les événements de la tâche"
-
-#: screens/Job/JobOutput/shared/JobEventLineToggle.js:37
-msgid "Expand section"
-msgstr "Agrandir la section"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:46
-msgid "Expected at least one of client_email, project_id or private_key to be present in the file."
-msgstr "On s'attendait à ce qu'au moins un des éléments suivants soit présent dans le fichier : client_email, project_id ou private_key."
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:137
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:34
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:148
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:172
-#: screens/User/UserTokenDetail/UserTokenDetail.js:56
-#: screens/User/UserTokenList/UserTokenList.js:146
-#: screens/User/UserTokenList/UserTokenList.js:190
-#: screens/User/UserTokenList/UserTokenListItem.js:35
-#: screens/User/UserTokens/UserTokens.js:89
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:151
-msgid "Expires"
-msgstr "Expire"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:146
-msgid "Expires on"
-msgstr "Expire le"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:156
-msgid "Expires on UTC"
-msgstr "Expire UTC"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:50
-msgid "Expires on {0}"
-msgstr "Arrive à expiration le {0}"
-
-#: components/JobList/JobListItem.js:307
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:181
-msgid "Explanation"
-msgstr "Explication"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:113
-msgid "External Secret Management System"
-msgstr "Système externe de gestion des secrets"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:272
-#: components/AdHocCommands/AdHocDetailsStep.js:273
-msgid "Extra variables"
-msgstr "Variables supplémentaires"
-
-#: components/Sparkline/Sparkline.js:35
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:164
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:43
-#: screens/Project/ProjectDetail/ProjectDetail.js:139
-#: screens/Project/ProjectList/ProjectListItem.js:77
-msgid "FINISHED:"
-msgstr "TERMINÉ :"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:73
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:137
-msgid "Fact Storage"
-msgstr "Stockage des facts"
-
-#: screens/Template/shared/JobTemplate.helptext.js:39
-msgid "Fact storage: If enabled, this will store gathered facts so they can be viewed at the host level. Facts are persisted and injected into the fact cache at runtime.."
-msgstr "Si cette option est activée, les données recueillies seront stockées afin de pouvoir être consultées au niveau de l'hôte. Les facts sont persistants et injectés dans le cache des facts au moment de l'exécution..."
-
-#: screens/Host/Host.js:63
-#: screens/Host/HostFacts/HostFacts.js:45
-#: screens/Host/Hosts.js:28
-#: screens/Inventory/Inventories.js:71
-#: screens/Inventory/InventoryHost/InventoryHost.js:78
-#: screens/Inventory/InventoryHostFacts/InventoryHostFacts.js:39
-msgid "Facts"
-msgstr "Facts"
-
-#: components/JobList/JobList.js:232
-#: components/StatusLabel/StatusLabel.js:45
-#: components/Workflow/WorkflowNodeHelp.js:105
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:90
-#: screens/Dashboard/shared/ChartTooltip.js:66
-#: screens/Job/JobOutput/shared/HostStatusBar.js:47
-#: screens/Job/JobOutput/shared/OutputToolbar.js:113
-msgid "Failed"
-msgstr "Échec"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:112
-msgid "Failed Host Count"
-msgstr "Échec du comptage des hôtes"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:114
-msgid "Failed Hosts"
-msgstr "Échec Hôtes"
-
-#: components/LaunchButton/ReLaunchDropDown.js:61
-#: screens/Dashboard/Dashboard.js:87
-msgid "Failed hosts"
-msgstr "Échec des hôtes"
-
-#: screens/Dashboard/DashboardGraph.js:170
-msgid "Failed jobs"
-msgstr "Jobs ayant échoué"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:56
-msgid "Failed to approve {0}."
-msgstr "N'a pas approuvé {0}."
-
-#: components/ResourceAccessList/ResourceAccessList.js:281
-msgid "Failed to assign roles properly"
-msgstr "Impossible d'assigner les rôles correctement"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:250
-#: screens/User/UserRoles/UserRolesList.js:246
-msgid "Failed to associate role"
-msgstr "N'a pas réussi à associer le rôle"
-
-#: screens/Host/HostGroups/HostGroupsList.js:248
-#: screens/InstanceGroup/Instances/InstanceList.js:311
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:288
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:265
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:268
-#: screens/User/UserTeams/UserTeamList.js:263
-msgid "Failed to associate."
-msgstr "N'a pas réussi à associer."
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:301
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:111
-msgid "Failed to cancel Inventory Source Sync"
-msgstr "N'a pas réussi à annuler la synchronisation des sources d'inventaire."
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:322
-#: screens/Project/ProjectList/ProjectListItem.js:232
-msgid "Failed to cancel Project Sync"
-msgstr "Échec de l'annulation de Project Sync"
-
-#: components/JobList/JobList.js:329
-msgid "Failed to cancel one or more jobs."
-msgstr "N'a pas réussi à supprimer un ou plusieurs Jobs"
-
-#: components/JobList/JobListItem.js:114
-#: screens/Job/JobDetail/JobDetail.js:601
-#: screens/Job/JobOutput/shared/OutputToolbar.js:138
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:90
-msgid "Failed to cancel {0}"
-msgstr "Échec de l'annulation {0}"
-
-#: screens/Credential/CredentialList/CredentialListItem.js:88
-msgid "Failed to copy credential."
-msgstr "N'a pas réussi à copier les identifiants"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:112
-msgid "Failed to copy execution environment"
-msgstr "Échec de la copie de l'environnement d'exécution"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:162
-msgid "Failed to copy inventory."
-msgstr "N'a pas réussi à copier l'inventaire."
-
-#: screens/Project/ProjectList/ProjectListItem.js:270
-msgid "Failed to copy project."
-msgstr "Le projet n'a pas été copié."
-
-#: components/TemplateList/TemplateListItem.js:253
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:160
-msgid "Failed to copy template."
-msgstr "Impossible de copier le modèle."
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:139
-msgid "Failed to delete application."
-msgstr "N'a pas réussi à supprimer l’application"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:317
-msgid "Failed to delete credential."
-msgstr "N'a pas réussi à supprimer l’identifiant."
-
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:82
-msgid "Failed to delete group {0}."
-msgstr "Echec de la suppression du groupe {0}."
-
-#: screens/Host/HostDetail/HostDetail.js:126
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:121
-msgid "Failed to delete host."
-msgstr "N'a pas réussi à supprimer l'hôte."
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:327
-msgid "Failed to delete inventory source {name}."
-msgstr "Impossible de supprimer la source d'inventaire {name}."
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:213
-msgid "Failed to delete inventory."
-msgstr "N'a pas réussi à supprimer l'inventaire."
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:557
-msgid "Failed to delete job template."
-msgstr "N'a pas réussi à supprimer le modèle de Job."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:448
-msgid "Failed to delete notification."
-msgstr "N'a pas réussi à supprimer la notification."
-
-#: screens/Application/ApplicationsList/ApplicationsList.js:188
-msgid "Failed to delete one or more applications."
-msgstr "N'a pas réussi à supprimer une ou plusieurs applications"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:208
-msgid "Failed to delete one or more credential types."
-msgstr "N'a pas réussi à supprimer un ou plusieurs types d’identifiants."
-
-#: screens/Credential/CredentialList/CredentialList.js:217
-msgid "Failed to delete one or more credentials."
-msgstr "N'a pas réussi à supprimer un ou plusieurs identifiants."
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:233
-msgid "Failed to delete one or more execution environments"
-msgstr "Échec de la suppression d'un ou plusieurs environnements d'exécution"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:155
-msgid "Failed to delete one or more groups."
-msgstr "N'a pas réussi à supprimer un ou plusieurs groupes."
-
-#: screens/Host/HostList/HostList.js:236
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:204
-msgid "Failed to delete one or more hosts."
-msgstr "N'a pas réussi à supprimer un ou plusieurs hôtes."
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:225
-msgid "Failed to delete one or more instance groups."
-msgstr "N'a pas réussi à supprimer un ou plusieurs groupes d'instances."
-
-#: screens/Inventory/InventoryList/InventoryList.js:288
-msgid "Failed to delete one or more inventories."
-msgstr "N'a pas réussi à supprimer un ou plusieurs inventaires."
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:255
-msgid "Failed to delete one or more inventory sources."
-msgstr "N'a pas réussi à supprimer une ou plusieurs sources d'inventaire."
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:244
-msgid "Failed to delete one or more job templates."
-msgstr "N'a pas réussi à supprimer un ou plusieurs modèles de Jobs."
-
-#: components/JobList/JobList.js:318
-msgid "Failed to delete one or more jobs."
-msgstr "N'a pas réussi à supprimer un ou plusieurs Jobs."
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:236
-msgid "Failed to delete one or more notification template."
-msgstr "N'a pas réussi à supprimer un ou plusieurs modèles de notification."
-
-#: screens/Organization/OrganizationList/OrganizationList.js:198
-msgid "Failed to delete one or more organizations."
-msgstr "N'a pas réussi à supprimer une ou plusieurs organisations."
-
-#: screens/Project/ProjectList/ProjectList.js:294
-msgid "Failed to delete one or more projects."
-msgstr "N'a pas réussi à supprimer un ou plusieurs projets."
-
-#: components/Schedule/ScheduleList/ScheduleList.js:242
-msgid "Failed to delete one or more schedules."
-msgstr "N'a pas réussi à supprimer une ou plusieurs programmations."
-
-#: screens/Team/TeamList/TeamList.js:195
-msgid "Failed to delete one or more teams."
-msgstr "N'a pas réussi à supprimer une ou plusieurs équipes."
-
-#: components/TemplateList/TemplateList.js:302
-msgid "Failed to delete one or more templates."
-msgstr "N'a pas réussi à supprimer un ou plusieurs modèles."
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:158
-msgid "Failed to delete one or more tokens."
-msgstr "N'a pas réussi à supprimer un ou plusieurs jetons."
-
-#: screens/User/UserTokenList/UserTokenList.js:217
-msgid "Failed to delete one or more user tokens."
-msgstr "N'a pas réussi à supprimer un ou plusieurs jetons d'utilisateur."
-
-#: screens/User/UserList/UserList.js:192
-msgid "Failed to delete one or more users."
-msgstr "N'a pas réussi à supprimer un ou plusieurs utilisateurs."
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:192
-msgid "Failed to delete one or more workflow approval."
-msgstr "N'a pas réussi à supprimer une ou plusieurs approbations de flux de travail."
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:211
-msgid "Failed to delete organization."
-msgstr "N'a pas réussi à supprimer l'organisation."
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:351
-msgid "Failed to delete project."
-msgstr "N'a pas réussi à supprimer le projet."
-
-#: components/ResourceAccessList/ResourceAccessList.js:292
-msgid "Failed to delete role"
-msgstr "N'a pas réussi à supprimer le rôle"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:261
-#: screens/User/UserRoles/UserRolesList.js:257
-msgid "Failed to delete role."
-msgstr "N'a pas réussi à supprimer le rôle."
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:657
-msgid "Failed to delete schedule."
-msgstr "N'a pas réussi à supprimer la programmation."
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:186
-msgid "Failed to delete smart inventory."
-msgstr "N'a pas réussi à supprimer l'inventaire smart."
-
-#: screens/Team/TeamDetail/TeamDetail.js:81
-msgid "Failed to delete team."
-msgstr "N'a pas réussi à supprimer l'équipe."
-
-#: screens/User/UserDetail/UserDetail.js:118
-msgid "Failed to delete user."
-msgstr "Impossible de supprimer l'utilisateur."
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:351
-msgid "Failed to delete workflow approval."
-msgstr "N'a pas réussi à supprimer l'approbation du flux de travail."
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:280
-msgid "Failed to delete workflow job template."
-msgstr "N'a pas réussi à supprimer le modèle de flux de travail."
-
-#: screens/Host/HostDetail/HostDetail.js:59
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:59
-msgid "Failed to delete {name}."
-msgstr "N'a pas réussi à supprimer {name}."
-
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:51
-msgid "Failed to deny {0}."
-msgstr "N'a pas réussi à refuser {0}."
-
-#: screens/Host/HostGroups/HostGroupsList.js:249
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:266
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:269
-msgid "Failed to disassociate one or more groups."
-msgstr "N'a pas réussi à dissocier un ou plusieurs groupes."
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:299
-msgid "Failed to disassociate one or more hosts."
-msgstr "N'a pas réussi à dissocier un ou plusieurs hôtes."
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:315
-#: screens/InstanceGroup/Instances/InstanceList.js:313
-#: screens/Instances/InstanceDetail/InstanceDetail.js:365
-msgid "Failed to disassociate one or more instances."
-msgstr "N'a pas réussi à dissocier une ou plusieurs instances."
-
-#: screens/User/UserTeams/UserTeamList.js:264
-msgid "Failed to disassociate one or more teams."
-msgstr "N'a pas réussi à dissocier une ou plusieurs équipes."
-
-#: screens/Login/Login.js:243
-msgid "Failed to fetch custom login configuration settings. System defaults will be shown instead."
-msgstr "Impossible de récupérer les paramètres de configuration de connexion personnalisés. Les paramètres par défaut du système seront affichés à la place."
-
-#: screens/Project/ProjectList/ProjectList.js:306
-msgid "Failed to fetch the updated project data."
-msgstr "Échec de la récupération des données de projet mises à jour."
-
-#: screens/TopologyView/MeshGraph.js:409
-msgid "Failed to get instance."
-msgstr "Impossible d’obtenir une instance."
-
-#: components/AdHocCommands/AdHocCommands.js:112
-#: components/LaunchButton/LaunchButton.js:188
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:128
-msgid "Failed to launch job."
-msgstr "Echec du lancement du Job."
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:378
-#: screens/Instances/InstanceList/InstanceList.js:246
-msgid "Failed to remove one or more instances."
-msgstr "N'a pas réussi à supprimer une ou plusieurs instances."
-
-#: contexts/Config.js:98
-msgid "Failed to retrieve configuration."
-msgstr "Impossible de récupérer la configuration."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:376
-msgid "Failed to retrieve full node resource object."
-msgstr "Echec de la récupération de l'objet ressource de noeud complet."
-
-#: screens/InstanceGroup/Instances/InstanceList.js:315
-#: screens/Instances/InstanceList/InstanceList.js:234
-msgid "Failed to run a health check on one or more instances."
-msgstr "Échec de l'exécution d'un contrôle de fonctionnement sur une ou plusieurs instances."
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:172
-msgid "Failed to send test notification."
-msgstr "Échec de l'envoi de la notification de test."
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:52
-msgid "Failed to sync inventory source."
-msgstr "Impossible de synchroniser la source de l'inventaire."
-
-#: screens/Project/shared/ProjectSyncButton.js:63
-msgid "Failed to sync project."
-msgstr "Échec de la synchronisation du projet."
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:242
-msgid "Failed to sync some or all inventory sources."
-msgstr "N'a pas réussi à synchroniser une partie ou la totalité des sources d'inventaire."
-
-#: components/HostToggle/HostToggle.js:80
-msgid "Failed to toggle host."
-msgstr "Impossible de changer d'hôte."
-
-#: components/InstanceToggle/InstanceToggle.js:71
-msgid "Failed to toggle instance."
-msgstr "N'a pas réussi à faire basculer l'instance."
-
-#: components/NotificationList/NotificationList.js:250
-msgid "Failed to toggle notification."
-msgstr "N'a pas réussi à basculer la notification."
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:77
-msgid "Failed to toggle schedule."
-msgstr "Impossible de basculer le calendrier."
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:314
-#: screens/InstanceGroup/Instances/InstanceListItem.js:222
-#: screens/Instances/InstanceDetail/InstanceDetail.js:364
-#: screens/Instances/InstanceList/InstanceListItem.js:238
-msgid "Failed to update capacity adjustment."
-msgstr "Échec de la mise à jour de l'ajustement des capacités."
-
-#: screens/TopologyView/Tooltip.js:204
-msgid "Failed to update instance."
-msgstr "N'a pas réussi à mettre à jour l'instance."
-
-#: screens/Template/TemplateSurvey.js:133
-msgid "Failed to update survey."
-msgstr "N'a pas réussi à mettre à jour l'enquête."
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:88
-msgid "Failed to user token."
-msgstr "Échec du jeton d'utilisateur."
-
-#: components/NotificationList/NotificationListItem.js:85
-#: components/NotificationList/NotificationListItem.js:86
-msgid "Failure"
-msgstr "Échec"
-
-#: screens/Job/JobOutput/EmptyOutput.js:45
-msgid "Failure Explanation:"
-msgstr "Explication de l'échec :"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:66
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:210
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:240
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:270
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:315
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:373
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:142
-msgid "False"
-msgstr "Faux"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:160
-#: components/Schedule/shared/FrequencyDetailSubform.js:103
-msgid "February"
-msgstr "Février"
-
-#: components/Search/LookupTypeInput.js:52
-msgid "Field contains value."
-msgstr "Le champ contient une valeur."
-
-#: components/Search/LookupTypeInput.js:80
-msgid "Field ends with value."
-msgstr "Le champ se termine par une valeur."
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:76
-msgid "Field for passing a custom Kubernetes or OpenShift Pod specification."
-msgstr "Champ permettant de passer une spécification de pod Kubernetes ou OpenShift personnalisée."
-
-#: components/Search/LookupTypeInput.js:94
-msgid "Field matches the given regular expression."
-msgstr "Le champ correspond à l'expression régulière donnée."
-
-#: components/Search/LookupTypeInput.js:66
-msgid "Field starts with value."
-msgstr "Le champ commence par la valeur."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:416
-msgid "Fifth"
-msgstr "Cinquième"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:105
-msgid "File Difference"
-msgstr "Écart entre les fichiers"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:72
-msgid "File upload rejected. Please select a single .json file."
-msgstr "Téléchargement de fichier rejeté. Veuillez sélectionner un seul fichier .json."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:96
-msgid "File, directory or script"
-msgstr "Fichier, répertoire ou script"
-
-#: components/Search/Search.js:198
-#: components/Search/Search.js:222
-msgid "Filter By {name}"
-msgstr "Filtrer par {name}"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:88
-msgid "Filter by failed jobs"
-msgstr "Filtrer par travaux échoués"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:94
-msgid "Filter by successful jobs"
-msgstr "Filtrer par tâches ayant réussi"
-
-#: components/JobList/JobList.js:248
-#: components/JobList/JobListItem.js:100
-msgid "Finish Time"
-msgstr "Heure de Fin"
-
-#: screens/Job/JobDetail/JobDetail.js:226
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:208
-msgid "Finished"
-msgstr "Terminé"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:404
-msgid "First"
-msgstr "Première"
-
-#: components/AddRole/AddResourceRole.js:28
-#: components/AddRole/AddResourceRole.js:42
-#: components/ResourceAccessList/ResourceAccessList.js:178
-#: screens/User/UserDetail/UserDetail.js:64
-#: screens/User/UserList/UserList.js:124
-#: screens/User/UserList/UserList.js:161
-#: screens/User/UserList/UserListItem.js:53
-#: screens/User/shared/UserForm.js:63
-msgid "First Name"
-msgstr "Prénom"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:332
-msgid "First Run"
-msgstr "Première exécution"
-
-#: components/ResourceAccessList/ResourceAccessList.js:227
-#: components/ResourceAccessList/ResourceAccessListItem.js:67
-msgid "First name"
-msgstr "Prénom"
-
-#: components/Search/AdvancedSearch.js:213
-#: components/Search/AdvancedSearch.js:227
-msgid "First, select a key"
-msgstr "Tout d'abord, sélectionnez une clé"
-
-#: components/Workflow/WorkflowTools.js:88
-msgid "Fit the graph to the available screen size"
-msgstr "Adapter le graphique à la taille de l'écran disponible"
-
-#: screens/TopologyView/Header.js:75
-#: screens/TopologyView/Header.js:78
-msgid "Fit to screen"
-msgstr "Adapter à l’écran"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:94
-msgid "Float"
-msgstr "Flottement"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:183
-msgid "Follow"
-msgstr "Suivez"
-
-#: screens/Job/Job.helptext.js:5
-#: screens/Template/shared/JobTemplate.helptext.js:6
-msgid "For job templates, select run to execute the playbook. Select check to only check playbook syntax, test environment setup, and report problems without executing the playbook."
-msgstr "Pour les modèles de job, sélectionner «run» (exécuter) pour exécuter le playbook. Sélectionner «check» (vérifier) uniquement pour vérifier la syntaxe du playbook, tester la configuration de l’environnement et signaler les problèmes."
-
-#: screens/Project/shared/Project.helptext.js:98
-msgid "For more information, refer to the"
-msgstr "Pour plus d'informations, reportez-vous à"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:160
-#: components/AdHocCommands/AdHocDetailsStep.js:161
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:55
-#: components/PromptDetail/PromptDetail.js:345
-#: components/PromptDetail/PromptJobTemplateDetail.js:150
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:475
-#: screens/Job/JobDetail/JobDetail.js:389
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:259
-#: screens/Template/shared/JobTemplateForm.js:409
-#: screens/TopologyView/Tooltip.js:282
-msgid "Forks"
-msgstr "Forks"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:414
-msgid "Fourth"
-msgstr "Quatrième"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:363
-#: components/Schedule/shared/ScheduleFormFields.js:146
-msgid "Frequency Details"
-msgstr "Informations sur la fréquence"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:381
-msgid "Frequency Exception Details"
-msgstr "Fréquence Détails de l'exception"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:72
-#: components/Schedule/shared/FrequencyDetailSubform.js:185
-#: components/Schedule/shared/FrequencyDetailSubform.js:206
-#: components/Schedule/shared/buildRuleObj.js:91
-msgid "Frequency did not match an expected value"
-msgstr "La fréquence ne correspondait pas à une valeur attendue"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:310
-msgid "Fri"
-msgstr "Ven."
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:81
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:187
-#: components/Schedule/shared/FrequencyDetailSubform.js:315
-#: components/Schedule/shared/FrequencyDetailSubform.js:453
-msgid "Friday"
-msgstr "Vendredi"
-
-#: components/Search/RelatedLookupTypeInput.js:45
-msgid "Fuzzy search on id, name or description fields."
-msgstr "Recherche floue sur les champs id, nom ou description."
-
-#: components/Search/RelatedLookupTypeInput.js:32
-msgid "Fuzzy search on name field."
-msgstr "Recherche floue sur le champ du nom."
-
-#: components/CredentialChip/CredentialChip.js:13
-msgid "GPG Public Key"
-msgstr "Clé publique GPG"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:153
-#: screens/Organization/shared/OrganizationForm.js:101
-msgid "Galaxy Credentials"
-msgstr "Informations d’identification Galaxy"
-
-#: screens/Credential/shared/CredentialForm.js:185
-msgid "Galaxy credentials must be owned by an Organization."
-msgstr "Les identifiants Galaxy doivent appartenir à une Organisation."
-
-#: screens/Job/JobOutput/JobOutputSearch.js:106
-msgid "Gathering Facts"
-msgstr "Collecte des facts"
-
-#: screens/Setting/Settings.js:72
-msgid "Generic OIDC"
-msgstr "Générique OIDC"
-
-#: screens/Setting/SettingList.js:85
-msgid "Generic OIDC settings"
-msgstr "Paramètres génériques de l'OIDC"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:222
-msgid "Get subscription"
-msgstr "Obtenir un abonnement"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:216
-msgid "Get subscriptions"
-msgstr "Obtenir des abonnements"
-
-#: components/Lookup/ProjectLookup.js:136
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:89
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:158
-#: screens/Job/JobDetail/JobDetail.js:75
-#: screens/Project/ProjectList/ProjectList.js:198
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:97
-msgid "Git"
-msgstr "Git"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:312
-#: screens/Template/shared/WebhookSubForm.js:106
-msgid "GitHub"
-msgstr "GitHub"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:85
-#: screens/Setting/Settings.js:51
-msgid "GitHub Default"
-msgstr "GitHub (Par défaut)"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:100
-#: screens/Setting/Settings.js:60
-msgid "GitHub Enterprise"
-msgstr "GitHub Enterprise"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:105
-#: screens/Setting/Settings.js:63
-msgid "GitHub Enterprise Organization"
-msgstr "Organisation GitHub Enterprise"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:110
-#: screens/Setting/Settings.js:66
-msgid "GitHub Enterprise Team"
-msgstr "GitHub Enterprise Team"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:90
-#: screens/Setting/Settings.js:54
-msgid "GitHub Organization"
-msgstr "Organisation GitHub"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:95
-#: screens/Setting/Settings.js:57
-msgid "GitHub Team"
-msgstr "GitHub Team"
-
-#: screens/Setting/SettingList.js:61
-msgid "GitHub settings"
-msgstr "Paramètres de GitHub"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:312
-#: screens/Template/shared/WebhookSubForm.js:112
-msgid "GitLab"
-msgstr "GitLab"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:79
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:84
-msgid "Globally Available"
-msgstr "Disponible dans le monde entier"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:134
-msgid "Globally available execution environment can not be reassigned to a specific Organization"
-msgstr "L'environnement d'exécution disponible globalement ne peut pas être réaffecté à une organisation spécifique"
-
-#: components/Pagination/Pagination.js:29
-msgid "Go to first page"
-msgstr "Allez à la première page"
-
-#: components/Pagination/Pagination.js:31
-msgid "Go to last page"
-msgstr "Allez à la dernière page de la liste"
-
-#: components/Pagination/Pagination.js:32
-msgid "Go to next page"
-msgstr "Allez à la page suivante de la liste"
-
-#: components/Pagination/Pagination.js:30
-msgid "Go to previous page"
-msgstr "Obtenir la page précédente"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:99
-msgid "Google Compute Engine"
-msgstr "Google Compute Engine"
-
-#: screens/Setting/SettingList.js:65
-msgid "Google OAuth 2 settings"
-msgstr "Paramètres de Google OAuth 2"
-
-#: screens/Setting/Settings.js:69
-msgid "Google OAuth2"
-msgstr "Google OAuth2"
-
-#: components/NotificationList/NotificationList.js:194
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:135
-msgid "Grafana"
-msgstr "Grafana"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:160
-msgid "Grafana API key"
-msgstr "Clé API Grafana"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:187
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:151
-msgid "Grafana URL"
-msgstr "URL Grafana"
-
-#: components/Search/LookupTypeInput.js:106
-msgid "Greater than comparison."
-msgstr "Supérieur à la comparaison."
-
-#: components/Search/LookupTypeInput.js:113
-msgid "Greater than or equal to comparison."
-msgstr "Supérieur ou égal à la comparaison."
-
-#: components/Lookup/HostFilterLookup.js:102
-msgid "Group"
-msgstr "Groupe"
-
-#: screens/Inventory/Inventories.js:78
-msgid "Group details"
-msgstr "Détails du groupe"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:124
-msgid "Group type"
-msgstr "Type de groupe"
-
-#: screens/Host/Host.js:68
-#: screens/Host/HostGroups/HostGroupsList.js:231
-#: screens/Host/Hosts.js:29
-#: screens/Inventory/Inventories.js:72
-#: screens/Inventory/Inventories.js:74
-#: screens/Inventory/Inventory.js:66
-#: screens/Inventory/InventoryHost/InventoryHost.js:83
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:248
-#: screens/Inventory/InventoryList/InventoryListItem.js:127
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:251
-#: util/getRelatedResourceDeleteDetails.js:119
-msgid "Groups"
-msgstr "Groupes"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:383
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:468
-msgid "HTTP Headers"
-msgstr "En-têtes HTTP"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:378
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:481
-msgid "HTTP Method"
-msgstr "Méthode HTTP"
-
-#: components/HealthCheckAlert/HealthCheckAlert.js:22
-msgid "Health check request(s) submitted. Please wait and reload the page."
-msgstr "Demande(s) de bilan de santé soumise(s). Veuillez patienter et recharger la page."
-
-#: components/StatusLabel/StatusLabel.js:42
-msgid "Healthy"
-msgstr "Fonctionne correctement"
-
-#: components/AppContainer/PageHeaderToolbar.js:116
-msgid "Help"
-msgstr "Aide"
-
-#: components/FormField/PasswordInput.js:35
-msgid "Hide"
-msgstr "Masquer"
-
-#: components/LaunchPrompt/LaunchPrompt.js:135
-#: components/Schedule/shared/SchedulePromptableFields.js:102
-msgid "Hide description"
-msgstr "Masquer la description"
-
-#: components/NotificationList/NotificationList.js:195
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:136
-msgid "Hipchat"
-msgstr "HipChat"
-
-#: screens/Instances/InstanceList/InstanceList.js:154
-msgid "Hop"
-msgstr "Hop"
-
-#: screens/TopologyView/Legend.js:103
-msgid "Hop node"
-msgstr "Noeud Hop"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:211
-#: screens/Job/JobOutput/HostEventModal.js:109
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:149
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:78
-msgid "Host"
-msgstr "Hôte"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:107
-msgid "Host Async Failure"
-msgstr "Échec de désynchronisation des hôtes"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:108
-msgid "Host Async OK"
-msgstr "Désynchronisation des hôtes OK"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:159
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:297
-#: screens/Template/shared/JobTemplateForm.js:633
-msgid "Host Config Key"
-msgstr "Clé de configuration de l’hôte"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:96
-msgid "Host Count"
-msgstr "Nombre d'hôtes"
-
-#: screens/Job/JobOutput/HostEventModal.js:88
-msgid "Host Details"
-msgstr "Détails sur l'hôte"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:109
-msgid "Host Failed"
-msgstr "Échec de l'hôte"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:110
-msgid "Host Failure"
-msgstr "Échec de l'hôte"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:242
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:145
-msgid "Host Filter"
-msgstr "Filtre d'hôte"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:201
-#: screens/Instances/InstanceDetail/InstanceDetail.js:191
-#: screens/Instances/Shared/InstanceForm.js:18
-msgid "Host Name"
-msgstr "Nom d'hôte"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:111
-msgid "Host OK"
-msgstr "Hôte OK"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:112
-msgid "Host Polling"
-msgstr "Interrogation de l'hôte"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:113
-msgid "Host Retry"
-msgstr "Nouvel essai de l'hôte"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:114
-msgid "Host Skipped"
-msgstr "Hôte ignoré"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:115
-msgid "Host Started"
-msgstr "Hôte démarré"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:116
-msgid "Host Unreachable"
-msgstr "Hôte inaccessible"
-
-#: screens/Inventory/Inventories.js:69
-msgid "Host details"
-msgstr "Informations sur l'hôte"
-
-#: screens/Job/JobOutput/HostEventModal.js:89
-msgid "Host details modal"
-msgstr "Détails sur l'hôte modal"
-
-#: screens/Host/Host.js:96
-#: screens/Inventory/InventoryHost/InventoryHost.js:100
-msgid "Host not found."
-msgstr "Hôte non trouvé."
-
-#: screens/Job/JobOutput/shared/HostStatusBar.js:76
-msgid "Host status information for this job is unavailable."
-msgstr "Les informations relatives au statut d'hôte pour ce Job ne sont pas disponibles."
-
-#: routeConfig.js:85
-#: screens/ActivityStream/ActivityStream.js:176
-#: screens/Dashboard/Dashboard.js:81
-#: screens/Host/HostList/HostList.js:143
-#: screens/Host/HostList/HostList.js:191
-#: screens/Host/Hosts.js:14
-#: screens/Host/Hosts.js:23
-#: screens/Inventory/Inventories.js:65
-#: screens/Inventory/Inventories.js:79
-#: screens/Inventory/Inventory.js:67
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:67
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:189
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:272
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:112
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:172
-#: screens/Inventory/SmartInventory.js:68
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:71
-#: screens/Job/JobOutput/shared/OutputToolbar.js:97
-#: util/getRelatedResourceDeleteDetails.js:123
-msgid "Hosts"
-msgstr "Hôtes"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:92
-msgid "Hosts automated"
-msgstr "Hôtes automatisés"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:118
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:125
-msgid "Hosts available"
-msgstr "Hôtes disponibles"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:107
-msgid "Hosts imported"
-msgstr "Hôtes importés"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:112
-msgid "Hosts remaining"
-msgstr "Hôtes restants"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:183
-#: components/Schedule/shared/FrequencyDetailSubform.js:175
-#: components/Schedule/shared/ScheduleFormFields.js:126
-#: components/Schedule/shared/ScheduleFormFields.js:186
-msgid "Hour"
-msgstr "Heure"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:209
-#: screens/Instances/InstanceList/InstanceList.js:153
-msgid "Hybrid"
-msgstr "Hybride"
-
-#: screens/TopologyView/Legend.js:95
-msgid "Hybrid node"
-msgstr "Noeud hybride"
-
-#: components/JobList/JobList.js:200
-#: components/Lookup/HostFilterLookup.js:98
-#: screens/Team/TeamRoles/TeamRolesList.js:155
-msgid "ID"
-msgstr "ID"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:193
-msgid "ID of the Dashboard"
-msgstr "ID du tableau de bord (facultatif)"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:198
-msgid "ID of the Panel"
-msgstr "ID du panneau (facultatif)"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:167
-msgid "ID of the dashboard (optional)"
-msgstr "ID du tableau de bord (facultatif)"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:173
-msgid "ID of the panel (optional)"
-msgstr "ID du panneau (facultatif)"
-
-#: screens/TopologyView/Tooltip.js:265
-msgid "IP address"
-msgstr "Adresse IP"
-
-#: components/NotificationList/NotificationList.js:196
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:137
-msgid "IRC"
-msgstr "IRC"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:228
-msgid "IRC Nick"
-msgstr "IRC Nick"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:223
-msgid "IRC Server Address"
-msgstr "Adresse du serveur IRC"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:218
-msgid "IRC Server Port"
-msgstr "Port du serveur IRC"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:223
-msgid "IRC nick"
-msgstr "IRC nick"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:215
-msgid "IRC server address"
-msgstr "Adresse du serveur IRC"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:201
-msgid "IRC server password"
-msgstr "Mot de passe du serveur IRC"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:206
-msgid "IRC server port"
-msgstr "Port du serveur IRC"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:263
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:308
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:272
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:343
-msgid "Icon URL"
-msgstr "Icône URL"
-
-#: screens/Inventory/shared/Inventory.helptext.js:100
-msgid ""
-"If checked, all variables for child groups\n"
-"and hosts will be removed and replaced by those found\n"
-"on the external source."
-msgstr "Si cochées, toutes les variables des groupes et hôtes dépendants seront supprimées et remplacées par celles qui se trouvent dans la source externe."
-
-#: screens/Inventory/shared/Inventory.helptext.js:84
-msgid ""
-"If checked, any hosts and groups that were\n"
-"previously present on the external source but are now removed\n"
-"will be removed from the inventory. Hosts and groups\n"
-"that were not managed by the inventory source will be promoted\n"
-"to the next manually created group or if there is no manually\n"
-"created group to promote them into, they will be left in the \"all\"\n"
-"default group for the inventory."
-msgstr "Si cochés, tous les hôtes et groupes qui étaient présent auparavant sur la source externe, mais qui sont maintenant supprimés, seront supprimés de l'inventaire. Les hôtes et les groupes qui n'étaient pas gérés par la source de l'inventaire seront promus au prochain groupe créé manuellement ou s'il n'y a pas de groupe créé manuellement dans lequel les promouvoir, ils devront rester dans le groupe \"all\" par défaut de cet inventaire."
-
-#: screens/Template/shared/JobTemplate.helptext.js:30
-msgid "If enabled, run this playbook as an administrator."
-msgstr "Si activé, exécuter ce playbook en tant qu'administrateur."
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:184
-msgid ""
-"If enabled, show the changes made\n"
-"by Ansible tasks, where supported. This is equivalent to Ansible’s\n"
-"--diff mode."
-msgstr "Si activé, afficher les changements faits par les tâches Ansible, si supporté. C'est équivalent au mode --diff d’Ansible."
-
-#: screens/Template/shared/JobTemplate.helptext.js:19
-msgid "If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible's --diff mode."
-msgstr "Si activé, afficher les changements faits par les tâches Ansible, si supporté. C'est équivalent au mode --diff d’Ansible."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:181
-msgid "If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible’s --diff mode."
-msgstr "Si activé, afficher les changements de facts par les tâches Ansible, si supporté. C'est équivalent au mode --diff d’Ansible."
-
-#: screens/Template/shared/JobTemplate.helptext.js:32
-msgid "If enabled, simultaneous runs of this job template will be allowed."
-msgstr "Si activé, il sera possible d’avoir des exécutions de ce modèle de tâche en simultané."
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:16
-msgid "If enabled, simultaneous runs of this workflow job template will be allowed."
-msgstr "Si activé, il sera possible d’avoir des exécutions de ce modèle de job de flux de travail en simultané."
-
-#: screens/Inventory/shared/Inventory.helptext.js:194
-msgid ""
-"If enabled, the inventory will prevent adding any organization instance groups to the list of preferred instances groups to run associated job templates on.\n"
-"Note: If this setting is enabled and you provided an empty list, the global instance groups will be applied."
-msgstr "Si cette option est activée, l'inventaire empêchera l'ajout de tout groupe d'instances d'organisation à la liste des groupes d'instances préférés pour l'exécution des modèles de tâches associés.\n"
-"Remarque : Si ce paramètre est activé et que vous avez fourni une liste vide, les groupes d'instances globaux seront appliqués."
-
-#: screens/Template/shared/JobTemplate.helptext.js:33
-msgid ""
-"If enabled, the job template will prevent adding any inventory or organization instance groups to the list of preferred instances groups to run on.\n"
-"Note: If this setting is enabled and you provided an empty list, the global instance groups will be applied."
-msgstr "S'il est activé, le modèle de tâche empêchera l'ajout de tout groupe d'instance d'inventaire ou d'organisation à la liste des groupes d'instances préférés pour l'exécution.\n"
-"Remarque : Si ce paramètre est activé et que vous avez fourni une liste vide, les groupes d'instances globaux seront appliqués."
-
-#: screens/Template/shared/JobTemplate.helptext.js:35
-msgid "If enabled, this will store gathered facts so they can be viewed at the host level. Facts are persisted and injected into the fact cache at runtime."
-msgstr "Si cette option est activée, les données recueillies seront stockées afin de pouvoir être consultées au niveau de l'hôte. Les faits sont persistants et injectés dans le cache des faits au moment de l'exécution."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:275
-msgid "If specified, this field will be shown on the node instead of the resource name when viewing the workflow"
-msgstr "S'il est spécifié, ce champ sera affiché sur le nœud au lieu du nom de la ressource lors de la visualisation du flux de travail"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:180
-msgid "If you are ready to upgrade or renew, please <0>contact us.0>"
-msgstr "Si vous êtes prêts à mettre à niveau ou à renouveler, veuillez<0>nous contacter.0>"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:63
-msgid ""
-"If you do not have a subscription, you can visit\n"
-"Red Hat to obtain a trial subscription."
-msgstr "Si vous ne disposez pas d'un abonnement, vous pouvez vous rendre sur le site de Red Hat pour obtenir un abonnement d'essai."
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:46
-msgid "If you only want to remove access for this particular user, please remove them from the team."
-msgstr "Si vous souhaitez uniquement supprimer l'accès de cet utilisateur particulier, veuillez le supprimer de l'équipe."
-
-#: screens/Inventory/shared/Inventory.helptext.js:120
-#: screens/Inventory/shared/Inventory.helptext.js:139
-msgid ""
-"If you want the Inventory Source to update on\n"
-"launch and on project update, click on Update on launch, and also go to"
-msgstr "Si vous voulez que la source de l'inventaire soit mise à jour au lancement et à la mise à jour du projet, cliquez sur Mettre à jour au lancement, et aller à"
-
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:80
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:91
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:101
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:54
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:141
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:147
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:166
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:75
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:98
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:89
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:108
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.js:21
-msgid "Image"
-msgstr "Image"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:117
-msgid "Including File"
-msgstr "Ajout de fichier"
-
-#: components/HostToggle/HostToggle.js:16
-msgid ""
-"Indicates if a host is available and should be included in running\n"
-"jobs. For hosts that are part of an external inventory, this may be\n"
-"reset by the inventory sync process."
-msgstr "Indique si un hôte est disponible et doit être inclus dans les Jobs en cours. Pour les hôtes qui font partie d'un inventaire externe, cela peut être réinitialisé par le processus de synchronisation de l'inventaire."
-
-#: components/AppContainer/PageHeaderToolbar.js:103
-msgid "Info"
-msgstr "Info"
-
-#: screens/ActivityStream/ActivityStreamListItem.js:45
-msgid "Initiated By"
-msgstr "Initié par"
-
-#: screens/ActivityStream/ActivityStream.js:253
-#: screens/ActivityStream/ActivityStream.js:263
-#: screens/ActivityStream/ActivityStreamDetailButton.js:44
-msgid "Initiated by"
-msgstr "Initié par"
-
-#: screens/ActivityStream/ActivityStream.js:243
-msgid "Initiated by (username)"
-msgstr "Initié par (nom d'utilisateur)"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:82
-#: screens/CredentialType/shared/CredentialTypeForm.js:46
-msgid "Injector configuration"
-msgstr "Configuration d'Injector"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:74
-#: screens/CredentialType/shared/CredentialTypeForm.js:38
-msgid "Input configuration"
-msgstr "Configuration de l'entrée"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:79
-msgid "Input schema which defines a set of ordered fields for that type."
-msgstr "Schéma d'entrée qui définit un ensemble de champs ordonnés pour ce type."
-
-#: screens/Project/shared/ProjectSubForms/InsightsSubForm.js:31
-msgid "Insights Credential"
-msgstr "Insights - Information d’identification"
-
-#: components/Lookup/HostFilterLookup.js:123
-msgid "Insights system ID"
-msgstr "ID du système Insights"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:249
-msgid "Install Bundle"
-msgstr "Installer Bundle"
-
-#: components/StatusLabel/StatusLabel.js:58
-#: screens/TopologyView/Legend.js:136
-msgid "Installed"
-msgstr "Installé"
-
-#: screens/Metrics/Metrics.js:187
-msgid "Instance"
-msgstr "Instance"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:135
-msgid "Instance Filters"
-msgstr "Filtres de l'instance"
-
-#: screens/Job/JobDetail/JobDetail.js:358
-msgid "Instance Group"
-msgstr "Groupe d'instance"
-
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:96
-#: components/LaunchPrompt/steps/useInstanceGroupsStep.js:31
-#: components/Lookup/InstanceGroupsLookup.js:74
-#: components/Lookup/InstanceGroupsLookup.js:121
-#: components/Lookup/InstanceGroupsLookup.js:141
-#: components/Lookup/InstanceGroupsLookup.js:151
-#: components/PromptDetail/PromptDetail.js:227
-#: components/PromptDetail/PromptJobTemplateDetail.js:228
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:499
-#: routeConfig.js:132
-#: screens/ActivityStream/ActivityStream.js:205
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:106
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:179
-#: screens/InstanceGroup/InstanceGroups.js:16
-#: screens/InstanceGroup/InstanceGroups.js:26
-#: screens/Instances/InstanceDetail/InstanceDetail.js:217
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:107
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:127
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:422
-#: util/getRelatedResourceDeleteDetails.js:282
-msgid "Instance Groups"
-msgstr "Groupes d'instances"
-
-#: components/Lookup/HostFilterLookup.js:115
-msgid "Instance ID"
-msgstr "ID d'instance"
-
-#: screens/Instances/Shared/InstanceForm.js:32
-msgid "Instance State"
-msgstr "État de l'instance"
-
-#: screens/Instances/Shared/InstanceForm.js:48
-msgid "Instance Type"
-msgstr "Type d'instance"
-
-#: screens/InstanceGroup/InstanceGroups.js:33
-msgid "Instance details"
-msgstr "Détail de l'instance"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:58
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:69
-msgid "Instance group"
-msgstr "Groupe d'instance"
-
-#: screens/InstanceGroup/InstanceGroup.js:92
-msgid "Instance group not found."
-msgstr "Groupe d'instance non trouvé."
-
-#: screens/InstanceGroup/Instances/InstanceListItem.js:165
-#: screens/Instances/InstanceList/InstanceListItem.js:176
-msgid "Instance group used capacity"
-msgstr "La capacité utilisée par le groupe d'instances"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:122
-#: screens/TopologyView/Tooltip.js:273
-msgid "Instance groups"
-msgstr "Groupes d'instances"
-
-#: screens/TopologyView/Tooltip.js:234
-msgid "Instance status"
-msgstr "État de l'instance"
-
-#: screens/TopologyView/Tooltip.js:240
-msgid "Instance type"
-msgstr "Type d'instance"
-
-#: routeConfig.js:137
-#: screens/ActivityStream/ActivityStream.js:203
-#: screens/InstanceGroup/InstanceGroup.js:74
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:198
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:73
-#: screens/InstanceGroup/InstanceGroups.js:31
-#: screens/InstanceGroup/Instances/InstanceList.js:192
-#: screens/InstanceGroup/Instances/InstanceList.js:290
-#: screens/Instances/InstanceList/InstanceList.js:136
-#: screens/Instances/Instances.js:13
-#: screens/Instances/Instances.js:22
-msgid "Instances"
-msgstr "Instances"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:93
-msgid "Integer"
-msgstr "Entier relatif"
-
-#: util/validators.js:94
-msgid "Invalid email address"
-msgstr "Adresse électronique invalide"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:116
-msgid "Invalid file format. Please upload a valid Red Hat Subscription Manifest."
-msgstr "Format de fichier non valide. Veuillez télécharger un manifeste d'abonnement à Red Hat valide."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:178
-msgid "Invalid link target. Unable to link to children or ancestor nodes. Graph cycles are not supported."
-msgstr "Cible de lien invalide. Impossible d'établir un lien avec les dépendants ou les nœuds des ancêtres. Les cycles de graphiques ne sont pas pris en charge."
-
-#: util/validators.js:33
-msgid "Invalid time format"
-msgstr "Format d'heure non valide"
-
-#: screens/Login/Login.js:153
-msgid "Invalid username or password. Please try again."
-msgstr "Nom d’utilisateur et/ou mot de passe non valide. Veuillez réessayer."
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:119
-#: routeConfig.js:80
-#: screens/ActivityStream/ActivityStream.js:173
-#: screens/Dashboard/Dashboard.js:92
-#: screens/Inventory/Inventories.js:17
-#: screens/Inventory/InventoryList/InventoryList.js:174
-#: screens/Inventory/InventoryList/InventoryList.js:237
-#: util/getRelatedResourceDeleteDetails.js:202
-#: util/getRelatedResourceDeleteDetails.js:270
-msgid "Inventories"
-msgstr "Inventaires"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:153
-msgid "Inventories with sources cannot be copied"
-msgstr "Les inventaires et les sources ne peuvent pas être copiés"
-
-#: components/HostForm/HostForm.js:48
-#: components/JobList/JobListItem.js:223
-#: components/LaunchPrompt/steps/InventoryStep.js:105
-#: components/LaunchPrompt/steps/useInventoryStep.js:48
-#: components/Lookup/HostFilterLookup.js:424
-#: components/Lookup/HostListItem.js:10
-#: components/Lookup/InventoryLookup.js:119
-#: components/Lookup/InventoryLookup.js:128
-#: components/Lookup/InventoryLookup.js:169
-#: components/Lookup/InventoryLookup.js:184
-#: components/Lookup/InventoryLookup.js:224
-#: components/PromptDetail/PromptDetail.js:214
-#: components/PromptDetail/PromptInventorySourceDetail.js:76
-#: components/PromptDetail/PromptJobTemplateDetail.js:119
-#: components/PromptDetail/PromptJobTemplateDetail.js:129
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:79
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:430
-#: components/TemplateList/TemplateListItem.js:285
-#: components/TemplateList/TemplateListItem.js:295
-#: screens/Host/HostDetail/HostDetail.js:77
-#: screens/Host/HostList/HostList.js:171
-#: screens/Host/HostList/HostListItem.js:61
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:94
-#: screens/Inventory/InventoryList/InventoryList.js:186
-#: screens/Inventory/InventoryList/InventoryListItem.js:117
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:38
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:113
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:41
-#: screens/Job/JobDetail/JobDetail.js:107
-#: screens/Job/JobDetail/JobDetail.js:122
-#: screens/Job/JobDetail/JobDetail.js:129
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:214
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:224
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:141
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:33
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:252
-msgid "Inventory"
-msgstr "Inventaire"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:104
-msgid "Inventory (Name)"
-msgstr "Inventaire (nom)"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:99
-msgid "Inventory File"
-msgstr "Fichier d'inventaire"
-
-#: components/Lookup/HostFilterLookup.js:106
-msgid "Inventory ID"
-msgstr "ID Inventaire"
-
-#: screens/Job/JobDetail/JobDetail.js:282
-msgid "Inventory Source"
-msgstr "Sources d'inventaire"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:73
-msgid "Inventory Source Sync"
-msgstr "Sync Source d’inventaire"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:299
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:110
-msgid "Inventory Source Sync Error"
-msgstr "Erreur de synchronisation de la source de l'inventaire"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:176
-#: screens/Inventory/InventorySources/InventorySourceList.js:193
-#: util/getRelatedResourceDeleteDetails.js:67
-#: util/getRelatedResourceDeleteDetails.js:147
-msgid "Inventory Sources"
-msgstr "Sources d'inventaire"
-
-#: components/JobList/JobList.js:212
-#: components/JobList/JobListItem.js:43
-#: components/Schedule/ScheduleList/ScheduleListItem.js:36
-#: components/Workflow/WorkflowLegend.js:100
-#: screens/Job/JobDetail/JobDetail.js:66
-msgid "Inventory Sync"
-msgstr "Sync Inventaires"
-
-#: screens/Inventory/InventoryList/InventoryList.js:183
-msgid "Inventory Type"
-msgstr "Type d’inventaire"
-
-#: components/Workflow/WorkflowNodeHelp.js:71
-msgid "Inventory Update"
-msgstr "Mise à jour de l'inventaire"
-
-#: screens/Inventory/InventoryList/InventoryList.js:121
-msgid "Inventory copied successfully"
-msgstr "Inventaire copié"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:226
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:109
-msgid "Inventory file"
-msgstr "Fichier d'inventaire"
-
-#: screens/Inventory/Inventory.js:94
-msgid "Inventory not found."
-msgstr "Inventaire non trouvé."
-
-#: screens/Dashboard/DashboardGraph.js:140
-msgid "Inventory sync"
-msgstr "Synchronisation des inventaires"
-
-#: screens/Dashboard/Dashboard.js:98
-msgid "Inventory sync failures"
-msgstr "Erreurs de synchronisation des inventaires"
-
-#: components/DataListToolbar/DataListToolbar.js:110
-msgid "Is expanded"
-msgstr "Est élargi"
-
-#: components/DataListToolbar/DataListToolbar.js:112
-msgid "Is not expanded"
-msgstr "N'est pas élargi"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:118
-msgid "Item Failed"
-msgstr "Échec de l'élément"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:119
-msgid "Item OK"
-msgstr "Élément OK"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:120
-msgid "Item Skipped"
-msgstr "Élément ignoré"
-
-#: components/AssociateModal/AssociateModal.js:20
-#: components/PaginatedTable/PaginatedTable.js:42
-msgid "Items"
-msgstr "Éléments"
-
-#: components/Pagination/Pagination.js:27
-msgid "Items per page"
-msgstr "Éléments par page"
-
-#: components/Sparkline/Sparkline.js:28
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:157
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:36
-#: screens/Project/ProjectDetail/ProjectDetail.js:132
-#: screens/Project/ProjectList/ProjectListItem.js:70
-msgid "JOB ID:"
-msgstr "ID JOB :"
-
-#: screens/Job/JobOutput/HostEventModal.js:136
-msgid "JSON"
-msgstr "JSON"
-
-#: screens/Job/JobOutput/HostEventModal.js:137
-msgid "JSON tab"
-msgstr "Onglet JSON"
-
-#: screens/Inventory/shared/Inventory.helptext.js:49
-msgid "JSON:"
-msgstr "JSON :"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:159
-#: components/Schedule/shared/FrequencyDetailSubform.js:98
-msgid "January"
-msgstr "Janvier"
-
-#: components/JobList/JobListItem.js:112
-#: screens/Job/JobDetail/JobDetail.js:599
-#: screens/Job/JobOutput/JobOutput.js:845
-#: screens/Job/JobOutput/JobOutput.js:846
-#: screens/Job/JobOutput/shared/OutputToolbar.js:136
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:88
-msgid "Job Cancel Error"
-msgstr "Erreur d'annulation d'un Job"
-
-#: screens/Job/JobDetail/JobDetail.js:621
-#: screens/Job/JobOutput/JobOutput.js:834
-#: screens/Job/JobOutput/JobOutput.js:835
-msgid "Job Delete Error"
-msgstr "Erreur de suppression d’un Job"
-
-#: screens/Job/JobDetail/JobDetail.js:206
-msgid "Job ID"
-msgstr "ID Job"
-
-#: screens/Dashboard/shared/LineChart.js:128
-msgid "Job Runs"
-msgstr "Exécutions Job"
-
-#: components/JobList/JobListItem.js:314
-#: screens/Job/JobDetail/JobDetail.js:374
-msgid "Job Slice"
-msgstr "Tranche de job"
-
-#: components/JobList/JobListItem.js:319
-#: screens/Job/JobDetail/JobDetail.js:382
-msgid "Job Slice Parent"
-msgstr "Parent de tranche de job"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:76
-#: components/PromptDetail/PromptDetail.js:349
-#: components/PromptDetail/PromptJobTemplateDetail.js:158
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:494
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:289
-#: screens/Template/shared/JobTemplateForm.js:453
-msgid "Job Slicing"
-msgstr "Tranche de job"
-
-#: components/Workflow/WorkflowNodeHelp.js:164
-msgid "Job Status"
-msgstr "Statut Job"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:97
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:98
-#: components/PromptDetail/PromptDetail.js:267
-#: components/PromptDetail/PromptJobTemplateDetail.js:247
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:571
-#: screens/Job/JobDetail/JobDetail.js:474
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:449
-#: screens/Template/shared/JobTemplateForm.js:521
-#: screens/Template/shared/WorkflowJobTemplateForm.js:218
-msgid "Job Tags"
-msgstr "Balises Job"
-
-#: components/JobList/JobListItem.js:191
-#: components/TemplateList/TemplateList.js:217
-#: components/Workflow/WorkflowLegend.js:92
-#: components/Workflow/WorkflowNodeHelp.js:59
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:97
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:19
-#: screens/Job/JobDetail/JobDetail.js:233
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:79
-msgid "Job Template"
-msgstr "Modèle de Job"
-
-#: components/LaunchPrompt/steps/credentialsValidator.js:38
-msgid "Job Template default credentials must be replaced with one of the same type. Please select a credential for the following types in order to proceed: {0}"
-msgstr "Les informations d'identification par défaut du modèle de Job doivent être remplacées par une information du même type. Veuillez sélectionner un justificatif d'identité pour les types suivants afin de procéder : {0}"
-
-#: screens/Credential/Credential.js:79
-#: screens/Credential/Credentials.js:30
-#: screens/Inventory/Inventories.js:62
-#: screens/Inventory/Inventory.js:74
-#: screens/Inventory/SmartInventory.js:74
-#: screens/Project/Project.js:107
-#: screens/Project/Projects.js:29
-#: util/getRelatedResourceDeleteDetails.js:56
-#: util/getRelatedResourceDeleteDetails.js:101
-#: util/getRelatedResourceDeleteDetails.js:133
-msgid "Job Templates"
-msgstr "Modèles de Jobs"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.js:25
-msgid "Job Templates with a missing inventory or project cannot be selected when creating or editing nodes. Select another template or fix the missing fields to proceed."
-msgstr "Les modèles de Job dont l'inventaire ou le projet est manquant ne peuvent pas être sélectionnés lors de la création ou de la modification de nœuds. Sélectionnez un autre modèle ou corrigez les champs manquants pour continuer."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useWorkflowNodeSteps.js:357
-msgid "Job Templates with credentials that prompt for passwords cannot be selected when creating or editing nodes"
-msgstr "Les modèles de Job dont les informations d'identification demandent un mot de passe ne peuvent pas être sélectionnés lors de la création ou de la modification de nœuds"
-
-#: components/JobList/JobList.js:208
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:146
-#: components/PromptDetail/PromptDetail.js:185
-#: components/PromptDetail/PromptJobTemplateDetail.js:102
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:423
-#: screens/Job/JobDetail/JobDetail.js:267
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:192
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:138
-#: screens/Template/shared/JobTemplateForm.js:256
-msgid "Job Type"
-msgstr "Type de Job"
-
-#: screens/Dashboard/Dashboard.js:125
-msgid "Job status"
-msgstr "Statut Job"
-
-#: screens/Dashboard/Dashboard.js:123
-msgid "Job status graph tab"
-msgstr "Onglet Graphique de l'état des Jobs"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:156
-#: components/RelatedTemplateList/RelatedTemplateList.js:206
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:15
-msgid "Job templates"
-msgstr "Modèles de Jobs"
-
-#: components/JobList/JobList.js:191
-#: components/JobList/JobList.js:274
-#: routeConfig.js:39
-#: screens/ActivityStream/ActivityStream.js:150
-#: screens/Dashboard/shared/LineChart.js:64
-#: screens/Host/Host.js:73
-#: screens/Host/Hosts.js:30
-#: screens/InstanceGroup/ContainerGroup.js:71
-#: screens/InstanceGroup/InstanceGroup.js:79
-#: screens/InstanceGroup/InstanceGroups.js:34
-#: screens/InstanceGroup/InstanceGroups.js:39
-#: screens/Inventory/Inventories.js:60
-#: screens/Inventory/Inventories.js:70
-#: screens/Inventory/Inventory.js:70
-#: screens/Inventory/InventoryHost/InventoryHost.js:88
-#: screens/Inventory/SmartInventory.js:70
-#: screens/Job/Jobs.js:22
-#: screens/Job/Jobs.js:32
-#: screens/Setting/SettingList.js:91
-#: screens/Setting/Settings.js:75
-#: screens/Template/Template.js:155
-#: screens/Template/Templates.js:47
-#: screens/Template/WorkflowJobTemplate.js:141
-msgid "Jobs"
-msgstr "Jobs"
-
-#: screens/Setting/SettingList.js:96
-msgid "Jobs settings"
-msgstr "Paramètres Job"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:165
-#: components/Schedule/shared/FrequencyDetailSubform.js:128
-msgid "July"
-msgstr "Juillet"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:164
-#: components/Schedule/shared/FrequencyDetailSubform.js:123
-msgid "June"
-msgstr "Juin"
-
-#: components/Search/AdvancedSearch.js:262
-msgid "Key"
-msgstr "Clé"
-
-#: components/Search/AdvancedSearch.js:253
-msgid "Key select"
-msgstr "Sélection de la clé"
-
-#: components/Search/AdvancedSearch.js:256
-msgid "Key typeahead"
-msgstr "Clé Typeahead"
-
-#: screens/ActivityStream/ActivityStream.js:238
-msgid "Keyword"
-msgstr "Mot-clé "
-
-#: screens/User/UserDetail/UserDetail.js:56
-#: screens/User/UserList/UserListItem.js:44
-msgid "LDAP"
-msgstr "LDAP"
-
-#: screens/Setting/Settings.js:80
-msgid "LDAP 1"
-msgstr "LDAP 1"
-
-#: screens/Setting/Settings.js:81
-msgid "LDAP 2"
-msgstr "LDAP 2"
-
-#: screens/Setting/Settings.js:82
-msgid "LDAP 3"
-msgstr "LDAP 3"
-
-#: screens/Setting/Settings.js:83
-msgid "LDAP 4"
-msgstr "LDAP 4"
-
-#: screens/Setting/Settings.js:84
-msgid "LDAP 5"
-msgstr "LDAP 5"
-
-#: screens/Setting/Settings.js:79
-msgid "LDAP Default"
-msgstr "Défaut LDAP"
-
-#: screens/Setting/SettingList.js:69
-msgid "LDAP settings"
-msgstr "Paramètres LDAP"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:107
-msgid "LDAP1"
-msgstr "LDAP1"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:112
-msgid "LDAP2"
-msgstr "LDAP2"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:117
-msgid "LDAP3"
-msgstr "LDAP3"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:122
-msgid "LDAP4"
-msgstr "LDAP4"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:127
-msgid "LDAP5"
-msgstr "LDAP5"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:178
-#: components/TemplateList/TemplateList.js:234
-msgid "Label"
-msgstr "Libellé"
-
-#: components/JobList/JobList.js:204
-msgid "Label Name"
-msgstr "Nom du label"
-
-#: components/JobList/JobListItem.js:284
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:223
-#: components/PromptDetail/PromptDetail.js:323
-#: components/PromptDetail/PromptJobTemplateDetail.js:209
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:116
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:551
-#: components/TemplateList/TemplateListItem.js:347
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:148
-#: screens/Inventory/shared/InventoryForm.js:83
-#: screens/Job/JobDetail/JobDetail.js:453
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:401
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:206
-#: screens/Template/shared/JobTemplateForm.js:387
-#: screens/Template/shared/WorkflowJobTemplateForm.js:195
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:273
-msgid "Labels"
-msgstr "Libellés"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:417
-msgid "Last"
-msgstr "Dernier"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:220
-#: screens/InstanceGroup/Instances/InstanceListItem.js:133
-#: screens/InstanceGroup/Instances/InstanceListItem.js:208
-#: screens/Instances/InstanceDetail/InstanceDetail.js:243
-#: screens/Instances/InstanceList/InstanceListItem.js:138
-#: screens/Instances/InstanceList/InstanceListItem.js:223
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:47
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:87
-msgid "Last Health Check"
-msgstr "Dernier bilan de fonctionnement"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:183
-#: screens/Project/ProjectDetail/ProjectDetail.js:161
-msgid "Last Job Status"
-msgstr "Statut du dernier Job"
-
-#: screens/User/UserDetail/UserDetail.js:80
-msgid "Last Login"
-msgstr "Dernière connexion"
-
-#: components/PromptDetail/PromptDetail.js:161
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:411
-#: components/TemplateList/TemplateListItem.js:316
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:106
-#: screens/Application/ApplicationsList/ApplicationListItem.js:45
-#: screens/Application/ApplicationsList/ApplicationsList.js:159
-#: screens/Credential/CredentialDetail/CredentialDetail.js:263
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:95
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:108
-#: screens/Host/HostDetail/HostDetail.js:89
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:72
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:98
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:178
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:45
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:84
-#: screens/Job/JobDetail/JobDetail.js:538
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:398
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:120
-#: screens/Project/ProjectDetail/ProjectDetail.js:297
-#: screens/Team/TeamDetail/TeamDetail.js:48
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:358
-#: screens/User/UserDetail/UserDetail.js:84
-#: screens/User/UserTokenDetail/UserTokenDetail.js:66
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:204
-msgid "Last Modified"
-msgstr "Dernière modification"
-
-#: components/AddRole/AddResourceRole.js:32
-#: components/AddRole/AddResourceRole.js:46
-#: components/ResourceAccessList/ResourceAccessList.js:182
-#: screens/User/UserDetail/UserDetail.js:65
-#: screens/User/UserList/UserList.js:128
-#: screens/User/UserList/UserList.js:162
-#: screens/User/UserList/UserListItem.js:54
-#: screens/User/shared/UserForm.js:69
-msgid "Last Name"
-msgstr "Nom"
-
-#: components/TemplateList/TemplateList.js:245
-#: components/TemplateList/TemplateListItem.js:194
-msgid "Last Ran"
-msgstr "Dernière exécution"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:341
-msgid "Last Run"
-msgstr "Dernière exécution"
-
-#: components/Lookup/HostFilterLookup.js:119
-msgid "Last job"
-msgstr "Dernier Job"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:280
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:151
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:49
-#: screens/Project/ProjectList/ProjectListItem.js:308
-#: screens/TopologyView/Tooltip.js:331
-msgid "Last modified"
-msgstr "Dernière modification"
-
-#: components/ResourceAccessList/ResourceAccessList.js:228
-#: components/ResourceAccessList/ResourceAccessListItem.js:68
-msgid "Last name"
-msgstr "Nom"
-
-#: screens/TopologyView/Tooltip.js:337
-msgid "Last seen"
-msgstr "Dernière vue"
-
-#: screens/Project/ProjectList/ProjectListItem.js:313
-msgid "Last used"
-msgstr "Dernière utilisation"
-
-#: components/AdHocCommands/useAdHocPreviewStep.js:22
-#: components/LaunchPrompt/steps/usePreviewStep.js:35
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:54
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:57
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:520
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:529
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:245
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:254
-msgid "Launch"
-msgstr "Lancer"
-
-#: components/TemplateList/TemplateListItem.js:214
-msgid "Launch Template"
-msgstr "Lacer le modèle."
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:32
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:34
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:46
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:47
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:87
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:90
-msgid "Launch management job"
-msgstr "Lancer le Job de gestion"
-
-#: components/TemplateList/TemplateListItem.js:222
-msgid "Launch template"
-msgstr "Lancer le modèle"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:119
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:120
-msgid "Launch workflow"
-msgstr "Lancer le flux de travail"
-
-#: components/LaunchPrompt/LaunchPrompt.js:130
-msgid "Launch | {0}"
-msgstr "Lancer | {0}"
-
-#: components/DetailList/LaunchedByDetail.js:54
-msgid "Launched By"
-msgstr "Lancé par"
-
-#: components/JobList/JobList.js:220
-msgid "Launched By (Username)"
-msgstr "Lancé par (Nom d'utilisateur)"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:120
-msgid "Learn more about Automation Analytics"
-msgstr "Pour en savoir plus sur Automation Analytics"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:69
-msgid "Leave this field blank to make the execution environment globally available."
-msgstr "Laissez ce champ vide pour rendre l'environnement d'exécution globalement disponible."
-
-#: components/Workflow/WorkflowLegend.js:86
-#: screens/Metrics/LineChart.js:120
-#: screens/TopologyView/Header.js:102
-#: screens/TopologyView/Legend.js:67
-msgid "Legend"
-msgstr "Légende"
-
-#: components/Search/LookupTypeInput.js:120
-msgid "Less than comparison."
-msgstr "Moins que la comparaison."
-
-#: components/Search/LookupTypeInput.js:127
-msgid "Less than or equal to comparison."
-msgstr "Moins ou égal à la comparaison."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:140
-#: components/AdHocCommands/AdHocDetailsStep.js:141
-#: components/JobList/JobList.js:238
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:65
-#: components/PromptDetail/PromptDetail.js:255
-#: components/PromptDetail/PromptJobTemplateDetail.js:153
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:90
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:473
-#: screens/Job/JobDetail/JobDetail.js:325
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:265
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:151
-#: screens/Template/shared/JobTemplateForm.js:429
-#: screens/Template/shared/WorkflowJobTemplateForm.js:159
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:238
-msgid "Limit"
-msgstr "Limite"
-
-#: screens/TopologyView/Legend.js:237
-msgid "Link state types"
-msgstr "Types d'états de liaison"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:261
-msgid "Link to an available node"
-msgstr "Lien vers un nœud disponible"
-
-#: screens/Instances/Shared/InstanceForm.js:40
-msgid "Listener Port"
-msgstr "Port de l'écouteur"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:353
-msgid "Loading"
-msgstr "Chargement en cours..."
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:49
-msgid "Local"
-msgstr "Local"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:343
-msgid "Local Time Zone"
-msgstr "Fuseau horaire local"
-
-#: components/Schedule/shared/ScheduleFormFields.js:95
-msgid "Local time zone"
-msgstr "Fuseau horaire local"
-
-#: screens/Login/Login.js:217
-msgid "Log In"
-msgstr "Connexion"
-
-#: screens/Setting/Settings.js:97
-msgid "Logging"
-msgstr "Journalisation"
-
-#: screens/Setting/SettingList.js:115
-msgid "Logging settings"
-msgstr "Paramètres de journalisation"
-
-#: components/AppContainer/AppContainer.js:81
-#: components/AppContainer/AppContainer.js:150
-#: components/AppContainer/PageHeaderToolbar.js:169
-msgid "Logout"
-msgstr "Déconnexion"
-
-#: components/Lookup/HostFilterLookup.js:367
-#: components/Lookup/Lookup.js:187
-msgid "Lookup modal"
-msgstr "Recherche modale"
-
-#: components/Search/LookupTypeInput.js:22
-msgid "Lookup select"
-msgstr "Sélection de la recherche"
-
-#: components/Search/LookupTypeInput.js:31
-msgid "Lookup type"
-msgstr "Type de recherche"
-
-#: components/Search/LookupTypeInput.js:25
-msgid "Lookup typeahead"
-msgstr "Recherche Typeahead"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:155
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:34
-#: screens/Project/ProjectDetail/ProjectDetail.js:130
-#: screens/Project/ProjectList/ProjectListItem.js:68
-msgid "MOST RECENT SYNC"
-msgstr "DERNIÈRE SYNCHRONISATION"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:97
-#: components/AdHocCommands/AdHocCredentialStep.js:98
-#: components/AdHocCommands/AdHocCredentialStep.js:112
-#: screens/Job/JobDetail/JobDetail.js:407
-msgid "Machine Credential"
-msgstr "Informations d’identification de la machine"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:65
-msgid "Managed"
-msgstr "Géré"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:147
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:169
-msgid "Managed nodes"
-msgstr "Nœuds gérés"
-
-#: components/JobList/JobList.js:215
-#: components/JobList/JobListItem.js:46
-#: components/Schedule/ScheduleList/ScheduleListItem.js:39
-#: components/Workflow/WorkflowLegend.js:108
-#: components/Workflow/WorkflowNodeHelp.js:79
-#: screens/Job/JobDetail/JobDetail.js:69
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:102
-msgid "Management Job"
-msgstr "Job de gestion"
-
-#: routeConfig.js:127
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:84
-msgid "Management Jobs"
-msgstr "Jobs de gestion"
-
-#: screens/ManagementJob/ManagementJobs.js:20
-msgid "Management job"
-msgstr "Job de gestion"
-
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:109
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:110
-msgid "Management job launch error"
-msgstr "Erreur de lancement d'un job de gestion"
-
-#: screens/ManagementJob/ManagementJob.js:133
-msgid "Management job not found."
-msgstr "Job de gestion non trouvé."
-
-#: screens/ManagementJob/ManagementJobs.js:13
-msgid "Management jobs"
-msgstr "Jobs de gestion"
-
-#: components/Lookup/ProjectLookup.js:135
-#: components/PromptDetail/PromptProjectDetail.js:98
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:88
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:157
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:215
-#: screens/InstanceGroup/Instances/InstanceListItem.js:204
-#: screens/Instances/InstanceDetail/InstanceDetail.js:209
-#: screens/Instances/InstanceList/InstanceListItem.js:219
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:83
-#: screens/Job/JobDetail/JobDetail.js:74
-#: screens/Project/ProjectDetail/ProjectDetail.js:192
-#: screens/Project/ProjectList/ProjectList.js:197
-#: screens/Project/ProjectList/ProjectListItem.js:219
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:96
-msgid "Manual"
-msgstr "Manuel"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:161
-#: components/Schedule/shared/FrequencyDetailSubform.js:108
-msgid "March"
-msgstr "Mars"
-
-#: components/NotificationList/NotificationList.js:197
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:138
-msgid "Mattermost"
-msgstr "Mattermost"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:98
-#: screens/Organization/shared/OrganizationForm.js:71
-msgid "Max Hosts"
-msgstr "Hôtes max."
-
-#: screens/Template/Survey/SurveyQuestionForm.js:220
-msgid "Maximum"
-msgstr "Maximum"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:204
-msgid "Maximum length"
-msgstr "Longueur maximale"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:163
-#: components/Schedule/shared/FrequencyDetailSubform.js:118
-msgid "May"
-msgstr "Mai"
-
-#: screens/Organization/OrganizationList/OrganizationList.js:144
-#: screens/Organization/OrganizationList/OrganizationListItem.js:63
-msgid "Members"
-msgstr "Membres"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:47
-msgid "Metadata"
-msgstr "Métadonnées"
-
-#: screens/Metrics/Metrics.js:207
-msgid "Metric"
-msgstr "Métrique"
-
-#: screens/Metrics/Metrics.js:179
-msgid "Metrics"
-msgstr "Métriques"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:100
-msgid "Microsoft Azure Resource Manager"
-msgstr "Microsoft Azure Resource Manager"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:214
-msgid "Minimum"
-msgstr "Minimum"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:198
-msgid "Minimum length"
-msgstr "Longueur minimale"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:65
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:31
-msgid ""
-"Minimum number of instances that will be automatically\n"
-"assigned to this group when new instances come online."
-msgstr "Nombre minimum statique d'instances qui seront automatiquement assignées à ce groupe lors de la mise en ligne de nouvelles instances."
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:71
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:41
-msgid ""
-"Minimum percentage of all instances that will be automatically\n"
-"assigned to this group when new instances come online."
-msgstr "Le pourcentage minimum de toutes les instances qui seront automatiquement assignées à ce groupe lorsque de nouvelles instances seront mises en ligne."
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:182
-#: components/Schedule/shared/FrequencyDetailSubform.js:173
-#: components/Schedule/shared/ScheduleFormFields.js:125
-#: components/Schedule/shared/ScheduleFormFields.js:185
-msgid "Minute"
-msgstr "Minute"
-
-#: screens/Setting/Settings.js:100
-msgid "Miscellaneous Authentication"
-msgstr "Divers Authentification"
-
-#: screens/Setting/SettingList.js:111
-msgid "Miscellaneous Authentication settings"
-msgstr "Paramètres d'authentification divers"
-
-#: screens/Setting/Settings.js:103
-msgid "Miscellaneous System"
-msgstr "Système divers"
-
-#: screens/Setting/SettingList.js:107
-msgid "Miscellaneous System settings"
-msgstr "Réglages divers du système"
-
-#: components/Workflow/WorkflowNodeHelp.js:120
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:87
-msgid "Missing"
-msgstr "Manquant"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:66
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:109
-msgid "Missing resource"
-msgstr "Ressource manquante"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:192
-#: screens/User/UserTokenList/UserTokenList.js:154
-msgid "Modified"
-msgstr "Modifié"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:126
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:116
-#: components/AddRole/AddResourceRole.js:61
-#: components/AssociateModal/AssociateModal.js:148
-#: components/LaunchPrompt/steps/CredentialsStep.js:177
-#: components/LaunchPrompt/steps/InventoryStep.js:93
-#: components/Lookup/CredentialLookup.js:198
-#: components/Lookup/InventoryLookup.js:156
-#: components/Lookup/InventoryLookup.js:211
-#: components/Lookup/MultiCredentialsLookup.js:198
-#: components/Lookup/OrganizationLookup.js:138
-#: components/Lookup/ProjectLookup.js:147
-#: components/NotificationList/NotificationList.js:210
-#: components/RelatedTemplateList/RelatedTemplateList.js:170
-#: components/Schedule/ScheduleList/ScheduleList.js:202
-#: components/TemplateList/TemplateList.js:230
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:31
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:62
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:100
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:131
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:169
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:200
-#: screens/Credential/CredentialList/CredentialList.js:154
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:100
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:136
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:106
-#: screens/Host/HostGroups/HostGroupsList.js:168
-#: screens/Host/HostList/HostList.js:161
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:203
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:133
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:178
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:132
-#: screens/Inventory/InventoryList/InventoryList.js:203
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:189
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:98
-#: screens/Organization/OrganizationList/OrganizationList.js:135
-#: screens/Project/ProjectList/ProjectList.js:209
-#: screens/Team/TeamList/TeamList.js:134
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:165
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:108
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:112
-msgid "Modified By (Username)"
-msgstr "Modifié par (nom d'utilisateur)"
-
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:85
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:151
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:77
-msgid "Modified by (username)"
-msgstr "Modifié par (nom d'utilisateur)"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:59
-#: screens/Job/JobOutput/HostEventModal.js:125
-msgid "Module"
-msgstr "Module"
-
-#: screens/Job/JobDetail/JobDetail.js:530
-msgid "Module Arguments"
-msgstr "Arguments du module"
-
-#: screens/Job/JobDetail/JobDetail.js:524
-msgid "Module Name"
-msgstr "Nom du module"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:266
-msgid "Mon"
-msgstr "Lun."
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:77
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:183
-#: components/Schedule/shared/FrequencyDetailSubform.js:271
-#: components/Schedule/shared/FrequencyDetailSubform.js:433
-msgid "Monday"
-msgstr "Lundi"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:186
-#: components/Schedule/shared/FrequencyDetailSubform.js:181
-#: components/Schedule/shared/ScheduleFormFields.js:129
-#: components/Schedule/shared/ScheduleFormFields.js:189
-msgid "Month"
-msgstr "Mois"
-
-#: components/Popover/Popover.js:32
-msgid "More information"
-msgstr "Plus d'informations"
-
-#: screens/Setting/shared/SharedFields.js:73
-msgid "More information for"
-msgstr "Plus d'informations pour"
-
-#: screens/Template/Survey/SurveyReorderModal.js:162
-#: screens/Template/Survey/SurveyReorderModal.js:163
-msgid "Multi-Select"
-msgstr "Multi-Select"
-
-#: screens/Template/Survey/SurveyReorderModal.js:146
-#: screens/Template/Survey/SurveyReorderModal.js:147
-msgid "Multiple Choice"
-msgstr "Options à choix multiples."
-
-#: screens/Template/Survey/SurveyQuestionForm.js:91
-msgid "Multiple Choice (multiple select)"
-msgstr "Options à choix multiples (sélection multiple)"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:86
-msgid "Multiple Choice (single select)"
-msgstr "Options à choix multiples (une seule sélection)"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:258
-msgid "Multiple Choice Options"
-msgstr "Options à choix multiples."
-
-#: components/AdHocCommands/AdHocCredentialStep.js:117
-#: components/AdHocCommands/AdHocCredentialStep.js:132
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:107
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:122
-#: components/AddRole/AddResourceRole.js:52
-#: components/AddRole/AddResourceRole.js:68
-#: components/AssociateModal/AssociateModal.js:139
-#: components/AssociateModal/AssociateModal.js:154
-#: components/HostForm/HostForm.js:96
-#: components/JobList/JobList.js:195
-#: components/JobList/JobList.js:244
-#: components/JobList/JobListItem.js:86
-#: components/LaunchPrompt/steps/CredentialsStep.js:168
-#: components/LaunchPrompt/steps/CredentialsStep.js:183
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:76
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:86
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:97
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:78
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:89
-#: components/LaunchPrompt/steps/InventoryStep.js:84
-#: components/LaunchPrompt/steps/InventoryStep.js:99
-#: components/Lookup/ApplicationLookup.js:100
-#: components/Lookup/ApplicationLookup.js:111
-#: components/Lookup/CredentialLookup.js:189
-#: components/Lookup/CredentialLookup.js:204
-#: components/Lookup/ExecutionEnvironmentLookup.js:177
-#: components/Lookup/ExecutionEnvironmentLookup.js:184
-#: components/Lookup/HostFilterLookup.js:93
-#: components/Lookup/HostFilterLookup.js:422
-#: components/Lookup/HostListItem.js:8
-#: components/Lookup/InstanceGroupsLookup.js:103
-#: components/Lookup/InstanceGroupsLookup.js:114
-#: components/Lookup/InventoryLookup.js:147
-#: components/Lookup/InventoryLookup.js:162
-#: components/Lookup/InventoryLookup.js:202
-#: components/Lookup/InventoryLookup.js:217
-#: components/Lookup/MultiCredentialsLookup.js:189
-#: components/Lookup/MultiCredentialsLookup.js:204
-#: components/Lookup/OrganizationLookup.js:129
-#: components/Lookup/OrganizationLookup.js:144
-#: components/Lookup/ProjectLookup.js:127
-#: components/Lookup/ProjectLookup.js:157
-#: components/NotificationList/NotificationList.js:181
-#: components/NotificationList/NotificationList.js:218
-#: components/NotificationList/NotificationListItem.js:28
-#: components/OptionsList/OptionsList.js:57
-#: components/PaginatedTable/PaginatedTable.js:72
-#: components/PromptDetail/PromptDetail.js:114
-#: components/RelatedTemplateList/RelatedTemplateList.js:161
-#: components/RelatedTemplateList/RelatedTemplateList.js:186
-#: components/ResourceAccessList/ResourceAccessListItem.js:58
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:325
-#: components/Schedule/ScheduleList/ScheduleList.js:168
-#: components/Schedule/ScheduleList/ScheduleList.js:189
-#: components/Schedule/ScheduleList/ScheduleListItem.js:86
-#: components/Schedule/shared/ScheduleFormFields.js:72
-#: components/TemplateList/TemplateList.js:205
-#: components/TemplateList/TemplateList.js:242
-#: components/TemplateList/TemplateListItem.js:142
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:18
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:37
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:49
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:68
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:80
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:110
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:122
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:137
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:149
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:179
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:191
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:206
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:60
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:109
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:135
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:28
-#: screens/Application/Applications.js:81
-#: screens/Application/ApplicationsList/ApplicationListItem.js:33
-#: screens/Application/ApplicationsList/ApplicationsList.js:118
-#: screens/Application/ApplicationsList/ApplicationsList.js:155
-#: screens/Application/shared/ApplicationForm.js:54
-#: screens/Credential/CredentialDetail/CredentialDetail.js:217
-#: screens/Credential/CredentialList/CredentialList.js:141
-#: screens/Credential/CredentialList/CredentialList.js:164
-#: screens/Credential/CredentialList/CredentialListItem.js:58
-#: screens/Credential/shared/CredentialForm.js:161
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:71
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:91
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:68
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:123
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:176
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:33
-#: screens/CredentialType/shared/CredentialTypeForm.js:21
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:49
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:136
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:165
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:69
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:89
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:115
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:12
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:89
-#: screens/Host/HostDetail/HostDetail.js:69
-#: screens/Host/HostGroups/HostGroupItem.js:28
-#: screens/Host/HostGroups/HostGroupsList.js:159
-#: screens/Host/HostGroups/HostGroupsList.js:176
-#: screens/Host/HostList/HostList.js:148
-#: screens/Host/HostList/HostList.js:169
-#: screens/Host/HostList/HostListItem.js:50
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:41
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:49
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:161
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:194
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:61
-#: screens/InstanceGroup/Instances/InstanceList.js:199
-#: screens/InstanceGroup/Instances/InstanceList.js:215
-#: screens/InstanceGroup/Instances/InstanceList.js:266
-#: screens/InstanceGroup/Instances/InstanceList.js:299
-#: screens/InstanceGroup/Instances/InstanceListItem.js:124
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:44
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:19
-#: screens/Instances/InstanceList/InstanceList.js:143
-#: screens/Instances/InstanceList/InstanceList.js:160
-#: screens/Instances/InstanceList/InstanceList.js:201
-#: screens/Instances/InstanceList/InstanceListItem.js:128
-#: screens/Instances/InstancePeers/InstancePeerList.js:80
-#: screens/Instances/InstancePeers/InstancePeerList.js:87
-#: screens/Instances/InstancePeers/InstancePeerList.js:96
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:37
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:89
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:31
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:194
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:209
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:215
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:34
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:119
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:141
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:74
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:36
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:169
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:186
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:33
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:119
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:138
-#: screens/Inventory/InventoryList/InventoryList.js:178
-#: screens/Inventory/InventoryList/InventoryList.js:209
-#: screens/Inventory/InventoryList/InventoryList.js:218
-#: screens/Inventory/InventoryList/InventoryListItem.js:92
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:180
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:195
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:232
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:181
-#: screens/Inventory/InventorySources/InventorySourceList.js:211
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:71
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:98
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:30
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:76
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:111
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:33
-#: screens/Inventory/shared/InventoryForm.js:50
-#: screens/Inventory/shared/InventoryGroupForm.js:32
-#: screens/Inventory/shared/InventorySourceForm.js:100
-#: screens/Inventory/shared/SmartInventoryForm.js:47
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:90
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:100
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:67
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:107
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:122
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:178
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:112
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:41
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:91
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:84
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:107
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.js:16
-#: screens/Organization/OrganizationList/OrganizationList.js:122
-#: screens/Organization/OrganizationList/OrganizationList.js:143
-#: screens/Organization/OrganizationList/OrganizationListItem.js:45
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:68
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:85
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:14
-#: screens/Organization/shared/OrganizationForm.js:56
-#: screens/Project/ProjectDetail/ProjectDetail.js:176
-#: screens/Project/ProjectList/ProjectList.js:185
-#: screens/Project/ProjectList/ProjectList.js:221
-#: screens/Project/ProjectList/ProjectListItem.js:179
-#: screens/Project/shared/ProjectForm.js:214
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:146
-#: screens/Team/TeamDetail/TeamDetail.js:37
-#: screens/Team/TeamList/TeamList.js:117
-#: screens/Team/TeamList/TeamList.js:142
-#: screens/Team/TeamList/TeamListItem.js:33
-#: screens/Team/shared/TeamForm.js:29
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:185
-#: screens/Template/Survey/SurveyList.js:102
-#: screens/Template/Survey/SurveyList.js:102
-#: screens/Template/Survey/SurveyListItem.js:39
-#: screens/Template/Survey/SurveyReorderModal.js:218
-#: screens/Template/Survey/SurveyReorderModal.js:218
-#: screens/Template/Survey/SurveyReorderModal.js:238
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:111
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:69
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:88
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:120
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:152
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:178
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:88
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.js:74
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.js:94
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:75
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:95
-#: screens/Template/shared/JobTemplateForm.js:243
-#: screens/Template/shared/WorkflowJobTemplateForm.js:109
-#: screens/User/UserOrganizations/UserOrganizationList.js:75
-#: screens/User/UserOrganizations/UserOrganizationList.js:79
-#: screens/User/UserOrganizations/UserOrganizationListItem.js:13
-#: screens/User/UserRoles/UserRolesList.js:155
-#: screens/User/UserRoles/UserRolesListItem.js:12
-#: screens/User/UserTeams/UserTeamList.js:180
-#: screens/User/UserTeams/UserTeamList.js:232
-#: screens/User/UserTeams/UserTeamListItem.js:18
-#: screens/User/UserTokenList/UserTokenListItem.js:22
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:140
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:123
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:163
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:43
-msgid "Name"
-msgstr "Nom"
-
-#: components/AppContainer/AppContainer.js:95
-msgid "Navigation"
-msgstr "Navigation"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:221
-#: components/Schedule/shared/FrequencyDetailSubform.js:512
-#: screens/Dashboard/shared/ChartTooltip.js:106
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:57
-msgid "Never"
-msgstr "Jamais"
-
-#: components/Workflow/WorkflowNodeHelp.js:114
-msgid "Never Updated"
-msgstr "Jamais mis à jour"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:47
-msgid "Never expires"
-msgstr "N'expire jamais"
-
-#: components/JobList/JobList.js:227
-#: components/Workflow/WorkflowNodeHelp.js:90
-msgid "New"
-msgstr "Nouveau"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:51
-#: components/AdHocCommands/useAdHocCredentialStep.js:29
-#: components/AdHocCommands/useAdHocDetailsStep.js:40
-#: components/AdHocCommands/useAdHocExecutionEnvironmentStep.js:22
-#: components/AddRole/AddResourceRole.js:196
-#: components/AddRole/AddResourceRole.js:231
-#: components/LaunchPrompt/LaunchPrompt.js:160
-#: components/Schedule/shared/SchedulePromptableFields.js:127
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:66
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:59
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:132
-msgid "Next"
-msgstr "Suivant"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:337
-#: components/Schedule/ScheduleList/ScheduleList.js:171
-#: components/Schedule/ScheduleList/ScheduleListItem.js:118
-#: components/Schedule/ScheduleList/ScheduleListItem.js:122
-msgid "Next Run"
-msgstr "Exécution suivante"
-
-#: components/Search/Search.js:232
-msgid "No"
-msgstr "Non"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:121
-msgid "No Hosts Matched"
-msgstr "Aucun hôte correspondant"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:122
-#: screens/Job/JobOutput/JobOutputSearch.js:123
-msgid "No Hosts Remaining"
-msgstr "Aucun hôte restant"
-
-#: screens/Job/JobOutput/HostEventModal.js:150
-msgid "No JSON Available"
-msgstr "Pas de JSON disponible"
-
-#: screens/Dashboard/shared/ChartTooltip.js:82
-msgid "No Jobs"
-msgstr "Aucun Job"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:72
-msgid "No inventory sync failures."
-msgstr "Aucune erreurs de synchronisation des inventaires"
-
-#: components/ContentEmpty/ContentEmpty.js:20
-msgid "No items found."
-msgstr "Aucun objet trouvé."
-
-#: screens/Host/HostList/HostListItem.js:100
-msgid "No job data available"
-msgstr "Aucune donnée de tâche disponible."
-
-#: screens/Job/JobOutput/EmptyOutput.js:52
-msgid "No output found for this job."
-msgstr "Aucune sortie de données pour ce job."
-
-#: screens/Job/JobOutput/HostEventModal.js:126
-msgid "No result found"
-msgstr "Aucun résultat trouvé"
-
-#: components/LabelSelect/LabelSelect.js:130
-#: components/LaunchPrompt/steps/SurveyStep.js:136
-#: components/LaunchPrompt/steps/SurveyStep.js:195
-#: components/MultiSelect/TagMultiSelect.js:60
-#: components/Search/AdvancedSearch.js:151
-#: components/Search/AdvancedSearch.js:266
-#: components/Search/LookupTypeInput.js:33
-#: components/Search/RelatedLookupTypeInput.js:26
-#: components/Search/Search.js:153
-#: components/Search/Search.js:202
-#: components/Search/Search.js:226
-#: screens/ActivityStream/ActivityStream.js:142
-#: screens/Credential/shared/CredentialForm.js:143
-#: screens/Credential/shared/CredentialFormFields/BecomeMethodField.js:65
-#: screens/Dashboard/DashboardGraph.js:106
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:137
-#: screens/Template/Survey/SurveyReorderModal.js:166
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:260
-#: screens/Template/shared/PlaybookSelect.js:72
-msgid "No results found"
-msgstr "Aucun résultat trouvé"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:116
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:137
-msgid "No subscriptions found"
-msgstr "Aucun abonnement trouvé"
-
-#: screens/Template/Survey/SurveyList.js:147
-msgid "No survey questions found."
-msgstr "Aucune question d'enquête trouvée."
-
-#: screens/Job/JobDetail/JobDetail.js:399
-msgid "No timeout specified"
-msgstr "Aucun délai d'attente spécifié"
-
-#: components/PaginatedTable/PaginatedTable.js:80
-msgid "No {pluralizedItemName} Found"
-msgstr "Aucun(e) {pluralizedItemName} trouvé(e)"
-
-#: components/Workflow/WorkflowNodeHelp.js:148
-#: components/Workflow/WorkflowNodeHelp.js:184
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:273
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:274
-msgid "Node Alias"
-msgstr "Alias de nœud"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:223
-#: screens/InstanceGroup/Instances/InstanceList.js:204
-#: screens/InstanceGroup/Instances/InstanceList.js:268
-#: screens/InstanceGroup/Instances/InstanceList.js:300
-#: screens/InstanceGroup/Instances/InstanceListItem.js:142
-#: screens/Instances/InstanceDetail/InstanceDetail.js:204
-#: screens/Instances/InstanceList/InstanceList.js:148
-#: screens/Instances/InstanceList/InstanceList.js:203
-#: screens/Instances/InstanceList/InstanceListItem.js:150
-#: screens/Instances/InstancePeers/InstancePeerList.js:98
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:59
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:118
-msgid "Node Type"
-msgstr "Type de nœud"
-
-#: screens/TopologyView/Legend.js:107
-msgid "Node state types"
-msgstr "Types d'état des nœuds"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.js:75
-msgid "Node type"
-msgstr "Type de nœud"
-
-#: screens/TopologyView/Legend.js:70
-msgid "Node types"
-msgstr "Types de nœud"
-
-#: components/Schedule/shared/ScheduleFormFields.js:180
-#: components/Schedule/shared/ScheduleFormFields.js:184
-#: components/Workflow/WorkflowNodeHelp.js:123
-msgid "None"
-msgstr "Aucun"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:193
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:196
-msgid "None (Run Once)"
-msgstr "Aucun (exécution unique)"
-
-#: components/Schedule/shared/ScheduleFormFields.js:120
-#: components/Schedule/shared/ScheduleFormFields.js:124
-msgid "None (run once)"
-msgstr "Aucune (exécution unique)"
-
-#: screens/User/UserDetail/UserDetail.js:51
-#: screens/User/UserList/UserListItem.js:23
-#: screens/User/shared/UserForm.js:29
-msgid "Normal User"
-msgstr "Utilisateur normal"
-
-#: components/ContentError/ContentError.js:37
-msgid "Not Found"
-msgstr "Introuvable"
-
-#: screens/Setting/shared/SettingDetail.js:71
-#: screens/Setting/shared/SettingDetail.js:112
-msgid "Not configured"
-msgstr "Non configuré"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:75
-msgid "Not configured for inventory sync."
-msgstr "Non configuré pour la synchronisation de l'inventaire."
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:248
-msgid ""
-"Note that only hosts directly in this group can\n"
-"be disassociated. Hosts in sub-groups must be disassociated\n"
-"directly from the sub-group level that they belong."
-msgstr "Notez que seuls les hôtes qui sont directement dans ce groupe peuvent être dissociés. Les hôtes qui se trouvent dans les sous-groupes doivent être dissociés directement au niveau du sous-groupe auquel ils appartiennent."
-
-#: screens/Host/HostGroups/HostGroupsList.js:212
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:230
-msgid ""
-"Note that you may still see the group in the list after\n"
-"disassociating if the host is also a member of that group’s\n"
-"children. This list shows all groups the host is associated\n"
-"with directly and indirectly."
-msgstr "Notez que vous pouvez toujours voir le groupe dans la liste après l'avoir dissocié si l'hôte est également membre des dépendants de ce groupe. Cette liste montre tous les groupes auxquels l'hôte est associé directement et indirectement."
-
-#: components/Lookup/InstanceGroupsLookup.js:90
-msgid "Note: The order in which these are selected sets the execution precedence. Select more than one to enable drag."
-msgstr "Remarque : L'ordre dans lequel ces éléments sont sélectionnés définit la priorité d'exécution. Sélectionner plus d’une option pour permettre le déplacement."
-
-#: screens/Organization/shared/OrganizationForm.js:116
-msgid "Note: The order of these credentials sets precedence for the sync and lookup of the content. Select more than one to enable drag."
-msgstr "Remarque : L'ordre de ces informations d'identification détermine la priorité pour la synchronisation et la consultation du contenu. Sélectionner plus d’une option pour permettre le déplacement."
-
-#: screens/Project/shared/Project.helptext.js:81
-msgid "Note: This field assumes the remote name is \"origin\"."
-msgstr "Remarque : ce champ suppose que le nom distant est \"origin\"."
-
-#: screens/Project/shared/Project.helptext.js:35
-msgid ""
-"Note: When using SSH protocol for GitHub or\n"
-"Bitbucket, enter an SSH key only, do not enter a username\n"
-"(other than git). Additionally, GitHub and Bitbucket do\n"
-"not support password authentication when using SSH. GIT\n"
-"read only protocol (git://) does not use username or\n"
-"password information."
-msgstr "Remarque : si vous utilisez le protocole SSH pour GitHub ou Bitbucket, entrez uniquement une clé SSH sans nom d’utilisateur (autre que git). De plus, GitHub et Bitbucket ne prennent pas en charge l’authentification par mot de passe lorsque SSH est utilisé. Le protocole GIT en lecture seule (git://) n’utilise pas les informations de nom d’utilisateur ou de mot de passe."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:331
-msgid "Notification Color"
-msgstr "Couleur des notifications"
-
-#: screens/NotificationTemplate/NotificationTemplate.js:58
-#: screens/NotificationTemplate/NotificationTemplateAdd.js:50
-msgid "Notification Template not found."
-msgstr "Modèle de notification introuvable."
-
-#: screens/ActivityStream/ActivityStream.js:198
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:117
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:171
-#: screens/NotificationTemplate/NotificationTemplates.js:14
-#: screens/NotificationTemplate/NotificationTemplates.js:21
-#: util/getRelatedResourceDeleteDetails.js:181
-msgid "Notification Templates"
-msgstr "Modèles de notification"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:134
-msgid "Notification Type"
-msgstr "Type de notification"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:380
-msgid "Notification color"
-msgstr "Couleur de la notification"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:193
-msgid "Notification sent successfully"
-msgstr "Notification envoyée avec succès"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:449
-msgid "Notification test failed."
-msgstr "Le test de notification a échoué."
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:197
-msgid "Notification timed out"
-msgstr "La notification a expiré."
-
-#: components/NotificationList/NotificationList.js:190
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:131
-msgid "Notification type"
-msgstr "Type de notification"
-
-#: components/NotificationList/NotificationList.js:177
-#: routeConfig.js:122
-#: screens/Inventory/Inventories.js:93
-#: screens/Inventory/InventorySource/InventorySource.js:99
-#: screens/ManagementJob/ManagementJob.js:116
-#: screens/ManagementJob/ManagementJobs.js:22
-#: screens/Organization/Organization.js:135
-#: screens/Organization/Organizations.js:33
-#: screens/Project/Project.js:114
-#: screens/Project/Projects.js:28
-#: screens/Template/Template.js:141
-#: screens/Template/Templates.js:46
-#: screens/Template/WorkflowJobTemplate.js:123
-msgid "Notifications"
-msgstr "Notifications"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:169
-#: components/Schedule/shared/FrequencyDetailSubform.js:148
-msgid "November"
-msgstr "Novembre"
-
-#: components/StatusLabel/StatusLabel.js:44
-#: components/Workflow/WorkflowNodeHelp.js:117
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:66
-#: screens/Job/JobOutput/shared/HostStatusBar.js:35
-msgid "OK"
-msgstr "OK"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:42
-#: components/Schedule/shared/FrequencyDetailSubform.js:549
-msgid "Occurrences"
-msgstr "Occurrences"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:168
-#: components/Schedule/shared/FrequencyDetailSubform.js:143
-msgid "October"
-msgstr "Octobre"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:189
-#: components/HostToggle/HostToggle.js:61
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:192
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:195
-#: components/PromptDetail/PromptDetail.js:362
-#: components/PromptDetail/PromptJobTemplateDetail.js:156
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:489
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:58
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:45
-#: screens/Setting/shared/SettingDetail.js:98
-#: screens/Setting/shared/SharedFields.js:150
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:284
-#: screens/Template/shared/JobTemplateForm.js:504
-msgid "Off"
-msgstr "Désactivé"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:188
-#: components/HostToggle/HostToggle.js:60
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:192
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:194
-#: components/PromptDetail/PromptDetail.js:362
-#: components/PromptDetail/PromptJobTemplateDetail.js:156
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:489
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:57
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:45
-#: screens/Setting/shared/SettingDetail.js:98
-#: screens/Setting/shared/SharedFields.js:149
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:284
-#: screens/Template/shared/JobTemplateForm.js:504
-msgid "On"
-msgstr "Le"
-
-#: components/Workflow/WorkflowLegend.js:126
-#: components/Workflow/WorkflowLinkHelp.js:30
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:39
-msgid "On Failure"
-msgstr "En cas d'échec"
-
-#: components/Workflow/WorkflowLegend.js:122
-#: components/Workflow/WorkflowLinkHelp.js:27
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:63
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:32
-msgid "On Success"
-msgstr "En cas de succès"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:536
-msgid "On date"
-msgstr "À la date du"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:99
-#: components/Schedule/shared/FrequencyDetailSubform.js:251
-msgid "On days"
-msgstr "Tels jours"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:18
-msgid ""
-"One Slack channel per line. The pound symbol (#)\n"
-"is required for channels. To respond to or start a thread to a specific message add the parent message Id to the channel where the parent message Id is 16 digits. A dot (.) must be manually inserted after the 10th digit. ie:#destination-channel, 1231257890.006423. See Slack"
-msgstr "Saisissez un canal Slack par ligne. Le symbole dièse (#)\n"
-"est obligatoire pour les canaux. Pour répondre ou démarrer un fil de discussion sur un message spécifique, ajoutez l'Id du message parent au canal où l'Id du message parent est composé de 16 chiffres. Un point (.) doit être inséré manuellement après le 10ème chiffre. ex : #destination-channel, 1231257890.006423. Voir Slack"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:154
-msgid "Only Group By"
-msgstr "Grouper seulement par"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:103
-msgid "OpenStack"
-msgstr "OpenStack"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:111
-msgid "Option Details"
-msgstr "Détails de l'option"
-
-#: screens/Inventory/shared/Inventory.helptext.js:25
-msgid ""
-"Optional labels that describe this inventory,\n"
-"such as 'dev' or 'test'. Labels can be used to group and filter\n"
-"inventories and completed jobs."
-msgstr "Libellés facultatifs décrivant cet inventaire, par exemple 'dev' ou 'test'. Les libellés peuvent être utilisés pour regrouper et filtrer les inventaires et les jobs terminés."
-
-#: screens/Job/Job.helptext.js:12
-#: screens/Template/shared/JobTemplate.helptext.js:13
-msgid "Optional labels that describe this job template, such as 'dev' or 'test'. Labels can be used to group and filter job templates and completed jobs."
-msgstr "Libellés facultatifs décrivant ce modèle de job, par exemple 'dev' ou 'test'. Les libellés peuvent être utilisés pour regrouper et filtrer les modèles de jobs et les jobs terminés."
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:11
-msgid ""
-"Optional labels that describe this workflow job template,\n"
-"such as 'dev' or 'test'. Labels can be used to group and filter\n"
-"workflow job templates and completed jobs."
-msgstr "Libellés facultatifs décrivant ce modèle de flux de job, par exemple 'dev' ou 'test'. Les libellés peuvent être utilisés pour regrouper et filtrer les modèles de flux de jobs et les jobs terminés."
-
-#: screens/Template/shared/JobTemplate.helptext.js:26
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:19
-msgid "Optionally select the credential to use to send status updates back to the webhook service."
-msgstr "En option, sélectionnez les informations d'identification à utiliser pour renvoyer les mises à jour de statut au service webhook."
-
-#: components/NotificationList/NotificationList.js:220
-#: components/NotificationList/NotificationListItem.js:34
-#: screens/Credential/shared/TypeInputsSubForm.js:47
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:61
-#: screens/Instances/Shared/InstanceForm.js:54
-#: screens/Inventory/shared/InventoryForm.js:94
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:69
-#: screens/Template/shared/JobTemplateForm.js:545
-#: screens/Template/shared/WorkflowJobTemplateForm.js:241
-msgid "Options"
-msgstr "Options"
-
-#: screens/Template/Survey/SurveyReorderModal.js:217
-#: screens/Template/Survey/SurveyReorderModal.js:217
-#: screens/Template/Survey/SurveyReorderModal.js:233
-msgid "Order"
-msgstr "Commande"
-
-#: components/Lookup/ApplicationLookup.js:119
-#: components/Lookup/OrganizationLookup.js:101
-#: components/Lookup/OrganizationLookup.js:107
-#: components/Lookup/OrganizationLookup.js:124
-#: components/PromptDetail/PromptInventorySourceDetail.js:62
-#: components/PromptDetail/PromptInventorySourceDetail.js:72
-#: components/PromptDetail/PromptJobTemplateDetail.js:105
-#: components/PromptDetail/PromptJobTemplateDetail.js:115
-#: components/PromptDetail/PromptProjectDetail.js:77
-#: components/PromptDetail/PromptProjectDetail.js:88
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:67
-#: components/TemplateList/TemplateList.js:244
-#: components/TemplateList/TemplateListItem.js:185
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:70
-#: screens/Application/ApplicationsList/ApplicationListItem.js:38
-#: screens/Application/ApplicationsList/ApplicationsList.js:157
-#: screens/Credential/CredentialDetail/CredentialDetail.js:230
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:70
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:155
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:167
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:76
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:96
-#: screens/Inventory/InventoryList/InventoryList.js:191
-#: screens/Inventory/InventoryList/InventoryList.js:221
-#: screens/Inventory/InventoryList/InventoryListItem.js:119
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:202
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:107
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:121
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:131
-#: screens/Project/ProjectDetail/ProjectDetail.js:180
-#: screens/Project/ProjectList/ProjectListItem.js:287
-#: screens/Project/ProjectList/ProjectListItem.js:298
-#: screens/Team/TeamDetail/TeamDetail.js:40
-#: screens/Team/TeamList/TeamList.js:143
-#: screens/Team/TeamList/TeamListItem.js:38
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:199
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:210
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:120
-#: screens/User/UserTeams/UserTeamList.js:181
-#: screens/User/UserTeams/UserTeamList.js:237
-#: screens/User/UserTeams/UserTeamListItem.js:23
-msgid "Organization"
-msgstr "Organisation"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:100
-msgid "Organization (Name)"
-msgstr "Organisation (Nom)"
-
-#: screens/Team/TeamList/TeamList.js:126
-msgid "Organization Name"
-msgstr "Nom de l'organisation"
-
-#: screens/Organization/Organization.js:154
-msgid "Organization not found."
-msgstr "Organisation non trouvée."
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:188
-#: routeConfig.js:96
-#: screens/ActivityStream/ActivityStream.js:181
-#: screens/Organization/OrganizationList/OrganizationList.js:117
-#: screens/Organization/OrganizationList/OrganizationList.js:163
-#: screens/Organization/Organizations.js:16
-#: screens/Organization/Organizations.js:26
-#: screens/User/User.js:66
-#: screens/User/UserOrganizations/UserOrganizationList.js:72
-#: screens/User/Users.js:33
-#: util/getRelatedResourceDeleteDetails.js:232
-#: util/getRelatedResourceDeleteDetails.js:266
-msgid "Organizations"
-msgstr "Organisations"
-
-#: components/LaunchPrompt/steps/useOtherPromptsStep.js:90
-msgid "Other prompts"
-msgstr "Autres invites"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:79
-msgid "Out of compliance"
-msgstr "Non-conformité"
-
-#: screens/Job/Job.js:131
-#: screens/Job/JobOutput/HostEventModal.js:156
-#: screens/Job/Jobs.js:34
-msgid "Output"
-msgstr "Sortie"
-
-#: screens/Job/JobOutput/HostEventModal.js:157
-msgid "Output tab"
-msgstr "Onglet de sortie"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:80
-msgid "Overwrite"
-msgstr "Remplacer"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:41
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:121
-msgid "Overwrite local groups and hosts from remote inventory source"
-msgstr "Remplacer les groupes locaux et les hôtes de la source d'inventaire distante."
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:46
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:127
-msgid "Overwrite local variables from remote inventory source"
-msgstr "Remplacer les variables locales de la source d'inventaire distante."
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:86
-msgid "Overwrite variables"
-msgstr "Remplacer les variables"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:493
-msgid "POST"
-msgstr "PUBLICATION"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:494
-msgid "PUT"
-msgstr "PLACER"
-
-#: components/NotificationList/NotificationList.js:198
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:139
-msgid "Pagerduty"
-msgstr "Pagerduty"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:279
-msgid "Pagerduty Subdomain"
-msgstr "Sous-domaine Pagerduty"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:298
-msgid "Pagerduty subdomain"
-msgstr "Sous-domaine Pagerduty"
-
-#: components/Pagination/Pagination.js:35
-msgid "Pagination"
-msgstr "Pagination"
-
-#: components/Workflow/WorkflowTools.js:165
-msgid "Pan Down"
-msgstr "Pan En bas"
-
-#: components/Workflow/WorkflowTools.js:132
-msgid "Pan Left"
-msgstr "Pan Gauche"
-
-#: components/Workflow/WorkflowTools.js:176
-msgid "Pan Right"
-msgstr "Pan droite"
-
-#: components/Workflow/WorkflowTools.js:143
-msgid "Pan Up"
-msgstr "Pan En haut"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:243
-msgid "Pass extra command line changes. There are two ansible command line parameters:"
-msgstr "Passez des changements supplémentaires en ligne de commande. Il y a deux paramètres de ligne de commande possibles :"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:14
-msgid "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. Refer to the Ansible Controller documentation for example syntax."
-msgstr "Transmettez des variables de ligne de commandes supplémentaires au playbook. Voici le paramètre de ligne de commande -e or --extra-vars pour ansible-playbook. Fournir la paire clé/valeur en utilisant YAML ou JSON. Consulter la documentation sur le contrôleur Ansible pour obtenir des exemples de syntaxe."
-
-#: screens/Job/Job.helptext.js:13
-#: screens/Template/shared/JobTemplate.helptext.js:14
-msgid "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. Refer to the documentation for example syntax."
-msgstr "Transmettez des variables de ligne de commandes supplémentaires au playbook. Voici le paramètre de ligne de commande -e or --extra-vars pour ansible-playbook. Fournir la paire clé/valeur en utilisant YAML ou JSON. Consulter la documentation pour obtenir des exemples de syntaxe."
-
-#: screens/Login/Login.js:227
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:73
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:101
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:212
-#: screens/Template/Survey/SurveyQuestionForm.js:82
-#: screens/User/shared/UserForm.js:88
-msgid "Password"
-msgstr "Mot de passe"
-
-#: screens/Dashboard/DashboardGraph.js:119
-msgid "Past 24 hours"
-msgstr "Après 24 heures"
-
-#: screens/Dashboard/DashboardGraph.js:110
-msgid "Past month"
-msgstr "Le mois dernier"
-
-#: screens/Dashboard/DashboardGraph.js:113
-msgid "Past two weeks"
-msgstr "Les deux dernières semaines"
-
-#: screens/Dashboard/DashboardGraph.js:116
-msgid "Past week"
-msgstr "La semaine dernière"
-
-#: screens/Instances/Instance.js:51
-#: screens/Instances/InstancePeers/InstancePeerList.js:74
-msgid "Peers"
-msgstr "Pairs"
-
-#: components/JobList/JobList.js:228
-#: components/StatusLabel/StatusLabel.js:49
-#: components/Workflow/WorkflowNodeHelp.js:93
-msgid "Pending"
-msgstr "En attente"
-
-#: components/AppContainer/PageHeaderToolbar.js:76
-msgid "Pending Workflow Approvals"
-msgstr "En attente d'approbation des flux de travail"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:128
-msgid "Pending delete"
-msgstr "En attente de suppression"
-
-#: components/Lookup/HostFilterLookup.js:370
-msgid "Perform a search to define a host filter"
-msgstr "Effectuez une recherche ci-dessus pour définir un filtre d'hôte"
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:73
-#: screens/User/UserTokenList/UserTokenList.js:105
-msgid "Personal Access Token"
-msgstr "Jeton d'accès personnel"
-
-#: screens/User/UserTokenList/UserTokenListItem.js:26
-msgid "Personal access token"
-msgstr "Jeton d'accès personnel"
-
-#: screens/Job/JobOutput/HostEventModal.js:122
-msgid "Play"
-msgstr "Play"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:84
-msgid "Play Count"
-msgstr "Play - Nombre"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:124
-msgid "Play Started"
-msgstr "Play - Démarrage"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:148
-#: screens/Job/JobDetail/JobDetail.js:319
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:253
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:43
-#: screens/Template/shared/JobTemplateForm.js:357
-msgid "Playbook"
-msgstr "Playbook"
-
-#: components/JobList/JobListItem.js:44
-#: screens/Job/JobDetail/JobDetail.js:67
-msgid "Playbook Check"
-msgstr "Vérification du Playbook"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:125
-msgid "Playbook Complete"
-msgstr "Playbook terminé"
-
-#: components/PromptDetail/PromptProjectDetail.js:165
-#: screens/Project/ProjectDetail/ProjectDetail.js:288
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:72
-msgid "Playbook Directory"
-msgstr "Répertoire Playbook"
-
-#: components/JobList/JobList.js:213
-#: components/JobList/JobListItem.js:44
-#: components/Schedule/ScheduleList/ScheduleListItem.js:37
-#: screens/Job/JobDetail/JobDetail.js:67
-msgid "Playbook Run"
-msgstr "Exécution Playbook"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:126
-msgid "Playbook Started"
-msgstr "Playbook démarré"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:174
-#: components/TemplateList/TemplateList.js:222
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:23
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:54
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:157
-msgid "Playbook name"
-msgstr "Nom du playbook"
-
-#: screens/Dashboard/DashboardGraph.js:146
-msgid "Playbook run"
-msgstr "Exécution du playbook"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:85
-msgid "Plays"
-msgstr "Plays"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:149
-msgid "Please add a Schedule to populate this list."
-msgstr "Veuillez ajouter une programmation pour remplir cette liste"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:152
-msgid "Please add a Schedule to populate this list. Schedules can be added to a Template, Project, or Inventory Source."
-msgstr "Veuillez ajouter une programmation pour remplir cette liste. Les programmations peuvent être ajoutées à un modèle, un projet ou une source d'inventaire."
-
-#: screens/Template/Survey/SurveyList.js:146
-msgid "Please add survey questions."
-msgstr "Veuillez ajouter des questions d'enquête."
-
-#: components/PaginatedTable/PaginatedTable.js:93
-msgid "Please add {pluralizedItemName} to populate this list"
-msgstr "Veuillez ajouter {pluralizedItemName} pour remplir cette liste"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:43
-msgid "Please click the Start button to begin."
-msgstr "Veuillez cliquer sur le bouton de démarrage pour commencer."
-
-#: components/Schedule/shared/ScheduleForm.js:421
-msgid "Please enter a number of occurrences."
-msgstr "Veuillez saisir un nombre d'occurrences."
-
-#: util/validators.js:160
-msgid "Please enter a valid URL"
-msgstr "Veuillez entrer une URL valide"
-
-#: screens/User/shared/UserTokenForm.js:20
-msgid "Please enter a value."
-msgstr "Entrez une valeur."
-
-#: screens/Login/Login.js:191
-msgid "Please log in"
-msgstr "Veuillez vous connecter"
-
-#: components/JobList/JobList.js:190
-msgid "Please run a job to populate this list."
-msgstr "Veuillez ajouter un job pour remplir cette liste"
-
-#: components/Schedule/shared/ScheduleForm.js:417
-msgid "Please select a day number between 1 and 31."
-msgstr "Veuillez choisir un numéro de jour entre 1 et 31."
-
-#: screens/Template/shared/JobTemplateForm.js:174
-msgid "Please select an Inventory or check the Prompt on Launch option"
-msgstr "Sélectionnez un inventaire ou cochez l’option Me le demander au lancement."
-
-#: components/Schedule/shared/ScheduleForm.js:429
-msgid "Please select an end date/time that comes after the start date/time."
-msgstr "Veuillez choisir une date/heure de fin qui vient après la date/heure de début."
-
-#: components/Lookup/HostFilterLookup.js:359
-msgid "Please select an organization before editing the host filter"
-msgstr "Veuillez sélectionner une organisation avant d'éditer le filtre de l'hôte."
-
-#: screens/Job/JobOutput/EmptyOutput.js:32
-msgid "Please try another search using the filter above"
-msgstr "Veuillez sélectionner une autre recherche par le filtre ci-dessus"
-
-#: screens/TopologyView/ContentLoading.js:40
-msgid "Please wait until the topology view is populated..."
-msgstr "Veuillez patienter jusqu’à ce que la topologie soit remplie..."
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:78
-msgid "Pod spec override"
-msgstr "Remplacement des spécifications du pod"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:214
-#: screens/InstanceGroup/Instances/InstanceListItem.js:203
-#: screens/Instances/InstanceDetail/InstanceDetail.js:208
-#: screens/Instances/InstanceList/InstanceListItem.js:218
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:82
-msgid "Policy Type"
-msgstr "Type de politique"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:63
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:26
-msgid "Policy instance minimum"
-msgstr "Instances de stratégies minimum"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:70
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:36
-msgid "Policy instance percentage"
-msgstr "Pourcentage d'instances de stratégie"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginField.js:64
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginField.js:70
-msgid "Populate field from an external secret management system"
-msgstr "Remplir le champ à partir d'un système de gestion des secrets externes"
-
-#: components/Lookup/HostFilterLookup.js:349
-msgid ""
-"Populate the hosts for this inventory by using a search\n"
-"filter. Example: ansible_facts__ansible_distribution:\"RedHat\".\n"
-"Refer to the documentation for further syntax and\n"
-"examples. Refer to the Ansible Controller documentation for further syntax and\n"
-"examples."
-msgstr "Remplissez les hôtes pour cet inventaire en utilisant un filtre de recherche. Exemple : ansible_facts.ansible_distribution : \"RedHat\". Reportez-vous à la documentation pour plus de syntaxe et d'exemples. Voir la documentation sur le contrôleur Ansible pour des exemples de syntaxe."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:165
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:104
-msgid "Port"
-msgstr "Port"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:231
-msgid "Preconditions for running this node when there are multiple parents. Refer to the"
-msgstr "Conditions préalables à l'exécution de ce nœud lorsqu'il y a plusieurs parents. Reportez-vous à "
-
-#: screens/Template/Survey/MultipleChoiceField.js:59
-msgid ""
-"Press 'Enter' to add more answer choices. One answer\n"
-"choice per line."
-msgstr "Appuyez sur \"Entrée\" pour ajouter d'autres choix de réponses. Un choix de réponse par ligne."
-
-#: components/CodeEditor/CodeEditor.js:181
-msgid "Press Enter to edit. Press ESC to stop editing."
-msgstr "Appuyez sur Entrée pour modifier. Appuyez sur ESC pour arrêter la modification."
-
-#: components/SelectedList/DraggableSelectedList.js:85
-msgid ""
-"Press space or enter to begin dragging,\n"
-"and use the arrow keys to navigate up or down.\n"
-"Press enter to confirm the drag, or any other key to\n"
-"cancel the drag operation."
-msgstr "Appuyez sur la touche Espace ou Entrée pour commencer à faire glisser,\n"
-"et utilisez les touches fléchées pour vous déplacer vers le haut ou le bas.\n"
-"Appuyez sur la touche Entrée pour confirmer le déplacement, ou sur une autre touche pour annuler l'opération de déplacement."
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:71
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:130
-#: screens/Inventory/shared/InventoryForm.js:99
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:147
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:347
-#: screens/Template/shared/JobTemplateForm.js:603
-msgid "Prevent Instance Group Fallback"
-msgstr "Empêcher le repli du groupe d'instances"
-
-#: screens/Inventory/shared/Inventory.helptext.js:197
-msgid "Prevent Instance Group Fallback: If enabled, the inventory will prevent adding any organization instance groups to the list of preferred instances groups to run associated job templates on."
-msgstr "Empêcher le repli des groupes d'instances : s'il est activé, l'inventaire empêchera l'ajout de tout groupe d'instances d'organisation à la liste des groupes d'instances préférés pour exécuter les modèles de tâches associés."
-
-#: screens/Template/shared/JobTemplate.helptext.js:43
-msgid "Prevent Instance Group Fallback: If enabled, the job template will prevent adding any inventory or organization instance groups to the list of preferred instances groups to run on."
-msgstr "Empêcher le repli des groupes d'instances : s'il est activé, le modèle de tâche empêchera l'ajout de tout groupe d'instances d'inventaire ou d'organisation à la liste des groupes d'instances préférés pour l'exécution."
-
-#: components/AdHocCommands/useAdHocPreviewStep.js:17
-#: components/LaunchPrompt/steps/usePreviewStep.js:23
-msgid "Preview"
-msgstr "Prévisualisation"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:103
-msgid "Private key passphrase"
-msgstr "Phrase de passe pour la clé privée"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:58
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:122
-#: screens/Template/shared/JobTemplateForm.js:551
-msgid "Privilege Escalation"
-msgstr "Élévation des privilèges"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:111
-msgid "Privilege escalation password"
-msgstr "Mot de passe pour l’élévation des privilèges"
-
-#: screens/Template/shared/JobTemplate.helptext.js:40
-msgid "Privilege escalation: If enabled, run this playbook as an administrator."
-msgstr "Élévation des privilèges: si activé, exécuter ce playbook en tant qu'administrateur."
-
-#: components/JobList/JobListItem.js:239
-#: components/Lookup/ProjectLookup.js:104
-#: components/Lookup/ProjectLookup.js:109
-#: components/Lookup/ProjectLookup.js:166
-#: components/PromptDetail/PromptInventorySourceDetail.js:87
-#: components/PromptDetail/PromptJobTemplateDetail.js:133
-#: components/PromptDetail/PromptJobTemplateDetail.js:141
-#: components/TemplateList/TemplateListItem.js:300
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:216
-#: screens/Job/JobDetail/JobDetail.js:172
-#: screens/Job/JobDetail/JobDetail.js:198
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:229
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:239
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:38
-msgid "Project"
-msgstr "Projet"
-
-#: components/PromptDetail/PromptProjectDetail.js:158
-#: screens/Project/ProjectDetail/ProjectDetail.js:281
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:61
-msgid "Project Base Path"
-msgstr "Chemin de base du projet"
-
-#: components/Workflow/WorkflowLegend.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:85
-msgid "Project Sync"
-msgstr "Sync Projet"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:320
-#: screens/Project/ProjectList/ProjectListItem.js:229
-msgid "Project Sync Error"
-msgstr "Erreur de synchronisation du projet"
-
-#: components/Workflow/WorkflowNodeHelp.js:67
-msgid "Project Update"
-msgstr "Mise à jour du projet"
-
-#: screens/Job/JobDetail/JobDetail.js:180
-msgid "Project Update Status"
-msgstr "Statut de Mise à jour du projet"
-
-#: screens/Job/Job.helptext.js:22
-msgid "Project checkout results"
-msgstr "Résultats d'extraction du projet"
-
-#: screens/Project/ProjectList/ProjectList.js:132
-msgid "Project copied successfully"
-msgstr "Projet copié"
-
-#: screens/Project/Project.js:136
-msgid "Project not found."
-msgstr "Projet non trouvé."
-
-#: screens/Dashboard/Dashboard.js:109
-msgid "Project sync failures"
-msgstr "Erreurs de synchronisation du projet"
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:146
-#: routeConfig.js:75
-#: screens/ActivityStream/ActivityStream.js:170
-#: screens/Dashboard/Dashboard.js:103
-#: screens/Project/ProjectList/ProjectList.js:180
-#: screens/Project/ProjectList/ProjectList.js:249
-#: screens/Project/Projects.js:12
-#: screens/Project/Projects.js:22
-#: util/getRelatedResourceDeleteDetails.js:60
-#: util/getRelatedResourceDeleteDetails.js:195
-#: util/getRelatedResourceDeleteDetails.js:225
-msgid "Projects"
-msgstr "Projets"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:139
-msgid "Promote Child Groups and Hosts"
-msgstr "Promouvoir les groupes de dépendants et les hôtes"
-
-#: components/Schedule/shared/ScheduleForm.js:540
-#: components/Schedule/shared/ScheduleForm.js:543
-msgid "Prompt"
-msgstr "Invite"
-
-#: components/PromptDetail/PromptDetail.js:182
-msgid "Prompt Overrides"
-msgstr "Invite Remplacements"
-
-#: components/CodeEditor/VariablesField.js:241
-#: components/FieldWithPrompt/FieldWithPrompt.js:46
-#: screens/Credential/CredentialDetail/CredentialDetail.js:175
-msgid "Prompt on launch"
-msgstr "Me le demander au lancement"
-
-#: components/Schedule/shared/SchedulePromptableFields.js:97
-msgid "Prompt | {0}"
-msgstr "Invite | {0}"
-
-#: components/PromptDetail/PromptDetail.js:180
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:418
-msgid "Prompted Values"
-msgstr "Valeurs incitatrices"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:6
-msgid ""
-"Provide a host pattern to further constrain\n"
-"the list of hosts that will be managed or affected by the\n"
-"playbook. Multiple patterns are allowed. Refer to Ansible\n"
-"documentation for more information and examples on patterns."
-msgstr "Entrez un modèle d’hôte pour limiter davantage la liste des hôtes qui seront gérés ou attribués par le playbook. Plusieurs modèles sont autorisés. Voir la documentation Ansible pour plus d'informations et pour obtenir des exemples de modèles."
-
-#: screens/Job/Job.helptext.js:14
-#: screens/Template/shared/JobTemplate.helptext.js:15
-msgid "Provide a host pattern to further constrain the list of hosts that will be managed or affected by the playbook. Multiple patterns are allowed. Refer to Ansible documentation for more information and examples on patterns."
-msgstr "Entrez un modèle d’hôte pour limiter davantage la liste des hôtes qui seront gérés ou attribués par le playbook. Plusieurs modèles sont autorisés. Voir la documentation Ansible pour plus d'informations et pour obtenir des exemples de modèles."
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:179
-msgid "Provide a value for this field or select the Prompt on launch option."
-msgstr "Indiquez une valeur pour ce champ ou sélectionnez l'option Me le demander au lancement."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:247
-msgid ""
-"Provide key/value pairs using either\n"
-"YAML or JSON."
-msgstr "Fournir les paires clé/valeur en utilisant soit YAML soit JSON."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:191
-msgid ""
-"Provide your Red Hat or Red Hat Satellite credentials\n"
-"below and you can choose from a list of your available subscriptions.\n"
-"The credentials you use will be stored for future use in\n"
-"retrieving renewal or expanded subscriptions."
-msgstr "Fournissez vos informations d’identification client Red Hat ou Red Hat Satellite et choisissez parmi une liste d’abonnements disponibles. Les informations d'identification que vous utilisez seront stockées pour une utilisation ultérieure lors de la récupération des abonnements renouvelés ou étendus."
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:83
-msgid "Provide your Red Hat or Red Hat Satellite credentials to enable Automation Analytics."
-msgstr "Fournissez vos informations d'identification Red Hat ou Red Hat Satellite pour activer Automation Analytics."
-
-#: components/StatusLabel/StatusLabel.js:59
-#: screens/TopologyView/Legend.js:150
-msgid "Provisioning"
-msgstr "Approvisionnement"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:162
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:302
-#: screens/Template/shared/JobTemplateForm.js:620
-msgid "Provisioning Callback URL"
-msgstr "URL de rappel d’exécution "
-
-#: screens/Template/shared/JobTemplateForm.js:615
-msgid "Provisioning Callback details"
-msgstr "Détails de rappel d’exécution"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:63
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:127
-#: screens/Template/shared/JobTemplateForm.js:555
-#: screens/Template/shared/JobTemplateForm.js:558
-msgid "Provisioning Callbacks"
-msgstr "Rappels d’exécution "
-
-#: screens/Template/shared/JobTemplate.helptext.js:41
-msgid "Provisioning callbacks: Enables creation of a provisioning callback URL. Using the URL a host can contact Ansible AWX and request a configuration update using this job template."
-msgstr "Rappels d’exécution : active la création d’une URL de rappels d’exécution. Avec cette URL, un hôte peut contacter Ansible AWX et demander une mise à jour de la configuration à l’aide de ce modèle de tâche."
-
-#: components/StatusLabel/StatusLabel.js:62
-msgid "Provisioning fail"
-msgstr "Échec du provisionnement"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:86
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:114
-msgid "Pull"
-msgstr "Extraire"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:163
-msgid "Question"
-msgstr "Question"
-
-#: screens/Setting/Settings.js:106
-msgid "RADIUS"
-msgstr "RADIUS"
-
-#: screens/Setting/SettingList.js:73
-msgid "RADIUS settings"
-msgstr "Paramètres RADIUS"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:244
-#: screens/InstanceGroup/Instances/InstanceListItem.js:161
-#: screens/Instances/InstanceDetail/InstanceDetail.js:287
-#: screens/Instances/InstanceList/InstanceListItem.js:171
-#: screens/TopologyView/Tooltip.js:307
-msgid "RAM {0}"
-msgstr "RAM {0}"
-
-#: screens/User/shared/UserTokenForm.js:76
-msgid "Read"
-msgstr "Lecture"
-
-#: components/StatusLabel/StatusLabel.js:57
-#: screens/TopologyView/Legend.js:122
-msgid "Ready"
-msgstr "Prêt"
-
-#: screens/Dashboard/Dashboard.js:133
-msgid "Recent Jobs"
-msgstr "Jobs récents"
-
-#: screens/Dashboard/Dashboard.js:131
-msgid "Recent Jobs list tab"
-msgstr "Onglet Liste des Jobs récents"
-
-#: screens/Dashboard/Dashboard.js:145
-msgid "Recent Templates"
-msgstr "Modèles récents"
-
-#: screens/Dashboard/Dashboard.js:143
-msgid "Recent Templates list tab"
-msgstr "Onglet Liste des modèles récents"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:188
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:112
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:38
-msgid "Recent jobs"
-msgstr "Jobs récents"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:154
-msgid "Recipient List"
-msgstr "Liste de destinataires"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:86
-msgid "Recipient list"
-msgstr "Liste de destinataires"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:105
-msgid "Red Hat Ansible Automation Platform"
-msgstr "Red Hat Ansible Automation Platform"
-
-#: components/Lookup/ProjectLookup.js:139
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:92
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:161
-#: screens/Job/JobDetail/JobDetail.js:77
-#: screens/Project/ProjectList/ProjectList.js:201
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:100
-msgid "Red Hat Insights"
-msgstr "Red Hat Insights"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:102
-msgid "Red Hat Satellite 6"
-msgstr "Red Hat Satellite 6"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:104
-msgid "Red Hat Virtualization"
-msgstr "Red Hat Virtualization"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:117
-msgid "Red Hat subscription manifest"
-msgstr "Manifeste de souscription à Red Hat"
-
-#: components/About/About.js:36
-msgid "Red Hat, Inc."
-msgstr "Red Hat, Inc."
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:94
-#: screens/Application/shared/ApplicationForm.js:107
-msgid "Redirect URIs"
-msgstr "Redirection d'URIs."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:259
-msgid "Redirecting to dashboard"
-msgstr "Redirection vers le tableau de bord"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:263
-msgid "Redirecting to subscription detail"
-msgstr "Redirection vers le détail de l'abonnement"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:261
-#: screens/Template/shared/JobTemplate.helptext.js:55
-msgid "Refer to the"
-msgstr "Reportez-vous à "
-
-#: screens/Job/Job.helptext.js:27
-#: screens/Template/shared/JobTemplate.helptext.js:50
-msgid "Refer to the Ansible documentation for details about the configuration file."
-msgstr "Reportez-vous à la documentation Ansible pour plus de détails sur le fichier de configuration."
-
-#: screens/User/UserTokens/UserTokens.js:77
-msgid "Refresh Token"
-msgstr "Actualiser Jeton"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:81
-msgid "Refresh Token Expiration"
-msgstr "Actualiser l’expiration du jeton"
-
-#: screens/Project/ProjectList/ProjectListItem.js:132
-msgid "Refresh for revision"
-msgstr "Actualiser pour réviser"
-
-#: screens/Project/ProjectList/ProjectListItem.js:134
-msgid "Refresh project revision"
-msgstr "Actualiser la révision du projet"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:116
-msgid "Regions"
-msgstr "Régions"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:92
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:143
-msgid "Registry credential"
-msgstr "Information d’identification au registre"
-
-#: screens/Inventory/shared/Inventory.helptext.js:156
-msgid "Regular expression where only matching host names will be imported. The filter is applied as a post-processing step after any inventory plugin filters are applied."
-msgstr "Expression régulière où seuls les noms d'hôtes correspondants seront importés. Le filtre est appliqué comme une étape de post-traitement après l'application de tout filtre de plugin d'inventaire."
-
-#: screens/Inventory/Inventories.js:81
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:62
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:175
-msgid "Related Groups"
-msgstr "Groupes liés"
-
-#: components/Search/AdvancedSearch.js:287
-msgid "Related Keys"
-msgstr "Clés associées"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:169
-#: components/Schedule/ScheduleList/ScheduleListItem.js:105
-msgid "Related resource"
-msgstr "Ressource connexe"
-
-#: components/Search/RelatedLookupTypeInput.js:16
-#: components/Search/RelatedLookupTypeInput.js:24
-msgid "Related search type"
-msgstr "Type de recherche connexe"
-
-#: components/Search/RelatedLookupTypeInput.js:19
-msgid "Related search type typeahead"
-msgstr "Recherche connexe : type typeahead"
-
-#: components/JobList/JobListItem.js:146
-#: components/LaunchButton/ReLaunchDropDown.js:82
-#: screens/Job/JobDetail/JobDetail.js:580
-#: screens/Job/JobDetail/JobDetail.js:588
-#: screens/Job/JobOutput/shared/OutputToolbar.js:167
-msgid "Relaunch"
-msgstr "Relancer"
-
-#: components/JobList/JobListItem.js:126
-#: screens/Job/JobOutput/shared/OutputToolbar.js:147
-msgid "Relaunch Job"
-msgstr "Relancer le Job"
-
-#: components/LaunchButton/ReLaunchDropDown.js:41
-msgid "Relaunch all hosts"
-msgstr "Relancer tous les hôtes"
-
-#: components/LaunchButton/ReLaunchDropDown.js:54
-msgid "Relaunch failed hosts"
-msgstr "Relancer les hôtes défaillants"
-
-#: components/LaunchButton/ReLaunchDropDown.js:30
-#: components/LaunchButton/ReLaunchDropDown.js:35
-msgid "Relaunch on"
-msgstr "Relancer sur"
-
-#: components/JobList/JobListItem.js:125
-#: screens/Job/JobOutput/shared/OutputToolbar.js:146
-msgid "Relaunch using host parameters"
-msgstr "Relancer en utilisant les paramètres de l'hôte"
-
-#: components/HealthCheckAlert/HealthCheckAlert.js:27
-msgid "Reload"
-msgstr "Rechargez"
-
-#: screens/Job/JobOutput/JobOutput.js:723
-msgid "Reload output"
-msgstr "Recharger la sortie"
-
-#: components/Lookup/ProjectLookup.js:138
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:91
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:160
-#: screens/Job/JobDetail/JobDetail.js:78
-#: screens/Project/ProjectList/ProjectList.js:200
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:99
-msgid "Remote Archive"
-msgstr "Archive à distance"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:374
-#: screens/Instances/InstanceList/InstanceList.js:242
-msgid "Removal Error"
-msgstr "Erreur de suppression"
-
-#: components/SelectedList/DraggableSelectedList.js:105
-#: screens/Instances/Shared/RemoveInstanceButton.js:75
-#: screens/Instances/Shared/RemoveInstanceButton.js:129
-#: screens/Instances/Shared/RemoveInstanceButton.js:143
-#: screens/Instances/Shared/RemoveInstanceButton.js:163
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:21
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:29
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:40
-msgid "Remove"
-msgstr "Supprimer"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:36
-msgid "Remove All Nodes"
-msgstr "Supprimer tous les nœuds"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:152
-msgid "Remove Instances"
-msgstr "Supprimer les instances"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:17
-msgid "Remove Link"
-msgstr "Supprimer le lien"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:28
-msgid "Remove Node {nodeName}"
-msgstr "Supprimer le nœud {nodeName}"
-
-#: screens/Project/shared/Project.helptext.js:113
-msgid "Remove any local modifications prior to performing an update."
-msgstr "Supprimez toutes les modifications locales avant d’effectuer une mise à jour."
-
-#: components/Search/AdvancedSearch.js:206
-msgid "Remove the current search related to ansible facts to enable another search using this key."
-msgstr "Supprimer la recherche en cours liée aux facts ansible pour activer une autre recherche par cette clé."
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:14
-msgid "Remove {0} Access"
-msgstr "Supprimer l’accès {0}"
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:45
-msgid "Remove {0} chip"
-msgstr "Supprimer {0} chip"
-
-#: screens/TopologyView/Legend.js:285
-msgid "Removing"
-msgstr "Suppression"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:48
-msgid "Removing this link will orphan the rest of the branch and cause it to be executed immediately on launch."
-msgstr "La suppression de ce lien rendra le reste de la branche orphelin et entraînera son exécution dès le lancement."
-
-#: components/SelectedList/DraggableSelectedList.js:83
-msgid "Reorder"
-msgstr "Réorganiser"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:349
-msgid "Repeat Frequency"
-msgstr "Fréquence de répétition"
-
-#: components/Schedule/shared/ScheduleFormFields.js:113
-msgid "Repeat frequency"
-msgstr "Fréquence de répétition"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:52
-msgid "Replace"
-msgstr "Remplacer"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:60
-msgid "Replace field with new value"
-msgstr "Remplacer le champ par la nouvelle valeur"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:67
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:74
-msgid "Request subscription"
-msgstr "Demande d’abonnement"
-
-#: screens/Template/Survey/SurveyListItem.js:51
-#: screens/Template/Survey/SurveyQuestionForm.js:188
-msgid "Required"
-msgstr "Obligatoire"
-
-#: screens/TopologyView/Header.js:87
-#: screens/TopologyView/Header.js:90
-msgid "Reset zoom"
-msgstr "Réinitialiser zoom"
-
-#: components/Workflow/WorkflowNodeHelp.js:154
-#: components/Workflow/WorkflowNodeHelp.js:190
-#: screens/Team/TeamRoles/TeamRoleListItem.js:12
-#: screens/Team/TeamRoles/TeamRolesList.js:180
-msgid "Resource Name"
-msgstr "Nom de la ressource"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:246
-msgid "Resource deleted"
-msgstr "Ressource supprimée"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:170
-#: components/Schedule/ScheduleList/ScheduleListItem.js:111
-msgid "Resource type"
-msgstr "Type de ressources"
-
-#: routeConfig.js:61
-#: screens/ActivityStream/ActivityStream.js:159
-msgid "Resources"
-msgstr "Ressources"
-
-#: components/TemplateList/TemplateListItem.js:149
-msgid "Resources are missing from this template."
-msgstr "Ressources manquantes dans ce modèle."
-
-#: screens/Setting/shared/RevertButton.js:43
-msgid "Restore initial value."
-msgstr "Rétablir la valeur initiale."
-
-#: screens/Inventory/shared/Inventory.helptext.js:153
-msgid ""
-"Retrieve the enabled state from the given dict of host variables.\n"
-"The enabled variable may be specified using dot notation, e.g: 'foo.bar'"
-msgstr "Récupérez l'état activé à partir des variables dict donnée de l'hôte. La variable activée peut être spécifiée en utilisant la notation par points, par exemple : \"foo.bar\""
-
-#: components/JobCancelButton/JobCancelButton.js:96
-#: components/JobCancelButton/JobCancelButton.js:100
-#: components/JobList/JobListCancelButton.js:160
-#: components/JobList/JobListCancelButton.js:163
-#: screens/Job/JobOutput/JobOutput.js:819
-#: screens/Job/JobOutput/JobOutput.js:822
-msgid "Return"
-msgstr "Renvoi"
-
-#: screens/Job/JobOutput/EmptyOutput.js:40
-msgid "Return to"
-msgstr "Renvoi à"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:129
-msgid "Return to subscription management."
-msgstr "Retour à la gestion des abonnements."
-
-#: components/Search/AdvancedSearch.js:171
-msgid "Returns results that have values other than this one as well as other filters."
-msgstr "Renvoie les résultats qui ont des valeurs autres que celle-ci ainsi que d'autres filtres."
-
-#: components/Search/AdvancedSearch.js:158
-msgid "Returns results that satisfy this one as well as other filters. This is the default set type if nothing is selected."
-msgstr "Retourne des résultats qui satisfont à ce filtre ainsi qu'à d'autres filtres. Il s'agit du type d'ensemble par défaut si rien n'est sélectionné."
-
-#: components/Search/AdvancedSearch.js:164
-msgid "Returns results that satisfy this one or any other filters."
-msgstr "Retourne les résultats qui satisfont à ce filtre ou à tout autre filtre."
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:52
-#: screens/Setting/shared/RevertButton.js:53
-#: screens/Setting/shared/RevertButton.js:62
-msgid "Revert"
-msgstr "Rétablir"
-
-#: screens/Setting/shared/RevertAllAlert.js:23
-msgid "Revert all"
-msgstr "Tout rétablir"
-
-#: screens/Setting/shared/RevertFormActionGroup.js:21
-#: screens/Setting/shared/RevertFormActionGroup.js:27
-msgid "Revert all to default"
-msgstr "Revenir aux valeurs par défaut"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:59
-msgid "Revert field to previously saved value"
-msgstr "Retourner le champ à la valeur précédemment enregistrée"
-
-#: screens/Setting/shared/RevertAllAlert.js:11
-msgid "Revert settings"
-msgstr "Inverser les paramètres"
-
-#: screens/Setting/shared/RevertButton.js:42
-msgid "Revert to factory default."
-msgstr "Revenir à la valeur usine par défaut."
-
-#: screens/Job/JobDetail/JobDetail.js:314
-#: screens/Project/ProjectList/ProjectList.js:224
-#: screens/Project/ProjectList/ProjectListItem.js:221
-msgid "Revision"
-msgstr "Révision"
-
-#: screens/Project/shared/ProjectSubForms/SvnSubForm.js:22
-msgid "Revision #"
-msgstr "Révision n°"
-
-#: components/NotificationList/NotificationList.js:199
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:140
-msgid "Rocket.Chat"
-msgstr "Rocket.Chat"
-
-#: screens/Team/TeamRoles/TeamRoleListItem.js:20
-#: screens/Team/TeamRoles/TeamRolesList.js:148
-#: screens/Team/TeamRoles/TeamRolesList.js:182
-#: screens/User/UserList/UserList.js:163
-#: screens/User/UserList/UserListItem.js:55
-#: screens/User/UserRoles/UserRolesList.js:146
-#: screens/User/UserRoles/UserRolesList.js:157
-#: screens/User/UserRoles/UserRolesListItem.js:26
-msgid "Role"
-msgstr "Rôle"
-
-#: components/ResourceAccessList/ResourceAccessList.js:189
-#: components/ResourceAccessList/ResourceAccessList.js:202
-#: components/ResourceAccessList/ResourceAccessList.js:229
-#: components/ResourceAccessList/ResourceAccessListItem.js:69
-#: screens/Team/Team.js:59
-#: screens/Team/Teams.js:32
-#: screens/User/User.js:71
-#: screens/User/Users.js:31
-msgid "Roles"
-msgstr "Rôles"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:134
-#: components/Workflow/WorkflowLinkHelp.js:39
-#: screens/Credential/shared/ExternalTestModal.js:89
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:49
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:23
-#: screens/Template/shared/JobTemplateForm.js:214
-msgid "Run"
-msgstr "Exécuter"
-
-#: components/AdHocCommands/AdHocCommands.js:131
-#: components/AdHocCommands/AdHocCommands.js:135
-#: components/AdHocCommands/AdHocCommands.js:141
-#: components/AdHocCommands/AdHocCommands.js:145
-#: screens/Job/JobDetail/JobDetail.js:68
-msgid "Run Command"
-msgstr "Exécuter Commande"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:277
-#: screens/Instances/InstanceDetail/InstanceDetail.js:335
-msgid "Run a health check on the instance"
-msgstr "Exécuter un contrôle de vérification de fonctionnement sur l'instance"
-
-#: components/AdHocCommands/AdHocCommands.js:125
-msgid "Run ad hoc command"
-msgstr "Exécuter une commande ad hoc"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:48
-msgid "Run command"
-msgstr "Exécuter Commande"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:93
-#: components/Schedule/shared/FrequencyDetailSubform.js:223
-msgid "Run every"
-msgstr "Exécutez tous les"
-
-#: components/HealthCheckButton/HealthCheckButton.js:32
-#: components/HealthCheckButton/HealthCheckButton.js:45
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:286
-#: screens/Instances/InstanceDetail/InstanceDetail.js:344
-msgid "Run health check"
-msgstr "Bilan de fonctionnement"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:129
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:138
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:175
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:197
-#: components/Schedule/shared/FrequencyDetailSubform.js:344
-msgid "Run on"
-msgstr "Continuer"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useRunTypeStep.js:32
-msgid "Run type"
-msgstr "Type d’exécution"
-
-#: components/JobList/JobList.js:230
-#: components/StatusLabel/StatusLabel.js:48
-#: components/TemplateList/TemplateListItem.js:118
-#: components/Workflow/WorkflowNodeHelp.js:99
-msgid "Running"
-msgstr "En cours d'exécution"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:127
-msgid "Running Handlers"
-msgstr "Descripteurs d'exécution"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:217
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:196
-#: screens/InstanceGroup/Instances/InstanceListItem.js:194
-#: screens/Instances/InstanceDetail/InstanceDetail.js:212
-#: screens/Instances/InstanceList/InstanceListItem.js:209
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:73
-msgid "Running Jobs"
-msgstr "Jobs en cours d'exécution"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:284
-#: screens/Instances/InstanceDetail/InstanceDetail.js:342
-msgid "Running health check"
-msgstr "Dernier bilan de fonctionnement"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:71
-msgid "Running jobs"
-msgstr "Jobs en cours d'exécution"
-
-#: screens/Setting/Settings.js:109
-msgid "SAML"
-msgstr "SAML"
-
-#: screens/Setting/SettingList.js:77
-msgid "SAML settings"
-msgstr "Paramètres SAML"
-
-#: screens/Dashboard/DashboardGraph.js:143
-msgid "SCM update"
-msgstr "Mise à jour SCM"
-
-#: screens/User/UserDetail/UserDetail.js:58
-#: screens/User/UserList/UserListItem.js:49
-msgid "SOCIAL"
-msgstr "SOCIAL"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:95
-msgid "SSH password"
-msgstr "Mot de passe SSH"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:239
-msgid "SSL Connection"
-msgstr "Connexion SSL"
-
-#: components/Workflow/WorkflowStartNode.js:60
-#: components/Workflow/workflowReducer.js:419
-msgid "START"
-msgstr "DÉMARRER"
-
-#: components/Sparkline/Sparkline.js:31
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:160
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:39
-#: screens/Project/ProjectDetail/ProjectDetail.js:135
-#: screens/Project/ProjectList/ProjectListItem.js:73
-msgid "STATUS:"
-msgstr "ÉTAT :"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:321
-msgid "Sat"
-msgstr "Sam."
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:82
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:188
-#: components/Schedule/shared/FrequencyDetailSubform.js:326
-#: components/Schedule/shared/FrequencyDetailSubform.js:458
-msgid "Saturday"
-msgstr "Samedi"
-
-#: components/AddRole/AddResourceRole.js:247
-#: components/AssociateModal/AssociateModal.js:104
-#: components/AssociateModal/AssociateModal.js:110
-#: components/FormActionGroup/FormActionGroup.js:13
-#: components/FormActionGroup/FormActionGroup.js:19
-#: components/Schedule/shared/ScheduleForm.js:526
-#: components/Schedule/shared/ScheduleForm.js:532
-#: components/Schedule/shared/useSchedulePromptSteps.js:49
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:130
-#: screens/Credential/shared/CredentialForm.js:318
-#: screens/Credential/shared/CredentialForm.js:323
-#: screens/Setting/shared/RevertFormActionGroup.js:12
-#: screens/Setting/shared/RevertFormActionGroup.js:18
-#: screens/Template/Survey/SurveyReorderModal.js:205
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:35
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:131
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:158
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:162
-msgid "Save"
-msgstr "Enregistrer"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:33
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:36
-msgid "Save & Exit"
-msgstr "Sauvegarde & Sortie"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:32
-msgid "Save link changes"
-msgstr "Enregistrer les changements de liens"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:254
-msgid "Save successful!"
-msgstr "Enregistrement réussi"
-
-#: components/JobList/JobListItem.js:181
-#: components/JobList/JobListItem.js:187
-msgid "Schedule"
-msgstr "Planifier"
-
-#: screens/Project/Projects.js:34
-#: screens/Template/Templates.js:54
-msgid "Schedule Details"
-msgstr "Détails de programmation"
-
-#: components/Schedule/shared/UnsupportedScheduleForm.js:15
-msgid "Schedule Rules"
-msgstr "Règles de l'horaire"
-
-#: screens/Inventory/Inventories.js:92
-msgid "Schedule details"
-msgstr "Détails de programmation"
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:49
-msgid "Schedule is active"
-msgstr "Le planning est actif."
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:49
-msgid "Schedule is inactive"
-msgstr "Le planning est inactif."
-
-#: components/Schedule/shared/ScheduleForm.js:394
-msgid "Schedule is missing rrule"
-msgstr "La programmation manque de règles"
-
-#: components/Schedule/Schedule.js:82
-msgid "Schedule not found."
-msgstr "Programme non trouvé."
-
-#: components/Schedule/ScheduleList/ScheduleList.js:163
-#: components/Schedule/ScheduleList/ScheduleList.js:229
-#: routeConfig.js:44
-#: screens/ActivityStream/ActivityStream.js:153
-#: screens/Inventory/Inventories.js:89
-#: screens/Inventory/InventorySource/InventorySource.js:88
-#: screens/ManagementJob/ManagementJob.js:108
-#: screens/ManagementJob/ManagementJobs.js:23
-#: screens/Project/Project.js:120
-#: screens/Project/Projects.js:31
-#: screens/Schedule/AllSchedules.js:21
-#: screens/Template/Template.js:148
-#: screens/Template/Templates.js:51
-#: screens/Template/WorkflowJobTemplate.js:130
-msgid "Schedules"
-msgstr "Programmations"
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:136
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:33
-#: screens/User/UserTokenDetail/UserTokenDetail.js:50
-#: screens/User/UserTokenList/UserTokenList.js:142
-#: screens/User/UserTokenList/UserTokenList.js:189
-#: screens/User/UserTokenList/UserTokenListItem.js:32
-#: screens/User/shared/UserTokenForm.js:68
-msgid "Scope"
-msgstr "Champ d'application"
-
-#: screens/User/shared/User.helptext.js:5
-msgid "Scope for the token's access"
-msgstr "Spécifier le champ d'application du jeton"
-
-#: screens/Job/JobOutput/PageControls.js:79
-msgid "Scroll first"
-msgstr "Faites défiler d'abord"
-
-#: screens/Job/JobOutput/PageControls.js:87
-msgid "Scroll last"
-msgstr "Défilement en dernier"
-
-#: screens/Job/JobOutput/PageControls.js:71
-msgid "Scroll next"
-msgstr "Faites défiler la page suivante"
-
-#: screens/Job/JobOutput/PageControls.js:63
-msgid "Scroll previous"
-msgstr "Faire défiler la page précédente"
-
-#: components/Lookup/HostFilterLookup.js:290
-#: components/Lookup/Lookup.js:143
-msgid "Search"
-msgstr "Rechercher"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:151
-msgid "Search is disabled while the job is running"
-msgstr "La recherche est désactivée pendant que le job est en cours"
-
-#: components/Search/AdvancedSearch.js:311
-#: components/Search/Search.js:259
-msgid "Search submit button"
-msgstr "Bouton de soumission de recherche"
-
-#: components/Search/Search.js:248
-msgid "Search text input"
-msgstr "Saisie de texte de recherche"
-
-#: components/Lookup/HostFilterLookup.js:398
-msgid "Searching by ansible_facts requires special syntax. Refer to the"
-msgstr "Une recherche par ansible_facts requiert une syntaxe particulière. Voir"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:408
-msgid "Second"
-msgstr "Deuxième"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:103
-#: components/PromptDetail/PromptProjectDetail.js:153
-#: screens/Project/ProjectDetail/ProjectDetail.js:269
-msgid "Seconds"
-msgstr "Secondes"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:34
-msgid "See Django"
-msgstr "Voir Django"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:35
-#: components/LaunchPrompt/steps/PreviewStep.js:61
-msgid "See errors on the left"
-msgstr "Voir les erreurs sur la gauche"
-
-#: components/JobList/JobListItem.js:84
-#: components/Lookup/HostFilterLookup.js:380
-#: components/Lookup/Lookup.js:200
-#: components/Pagination/Pagination.js:33
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:98
-msgid "Select"
-msgstr "Sélectionner"
-
-#: screens/Credential/shared/CredentialForm.js:129
-msgid "Select Credential Type"
-msgstr "Modifier le type d’identification"
-
-#: screens/Host/HostGroups/HostGroupsList.js:237
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:254
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:257
-msgid "Select Groups"
-msgstr "Sélectionner les groupes"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:278
-msgid "Select Hosts"
-msgstr "Sélectionner les hôtes"
-
-#: components/AnsibleSelect/AnsibleSelect.js:38
-msgid "Select Input"
-msgstr "Sélectionnez une entrée"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:295
-msgid "Select Instances"
-msgstr "Sélectionner les instances"
-
-#: components/AssociateModal/AssociateModal.js:22
-msgid "Select Items"
-msgstr "Sélectionnez les éléments"
-
-#: components/AddRole/AddResourceRole.js:201
-msgid "Select Items from List"
-msgstr "Sélectionnez les éléments de la liste"
-
-#: components/LabelSelect/LabelSelect.js:127
-msgid "Select Labels"
-msgstr "Sélectionner les libellés"
-
-#: components/AddRole/AddResourceRole.js:236
-msgid "Select Roles to Apply"
-msgstr "Sélectionnez les rôles à appliquer"
-
-#: screens/User/UserTeams/UserTeamList.js:251
-msgid "Select Teams"
-msgstr "Sélectionner des équipes"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:25
-msgid "Select a JSON formatted service account key to autopopulate the following fields."
-msgstr "Sélectionnez une clé de compte de service formatée en JSON pour remplir automatiquement les champs suivants."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:122
-msgid "Select a Node Type"
-msgstr "Sélectionnez un type de nœud"
-
-#: components/AddRole/AddResourceRole.js:170
-msgid "Select a Resource Type"
-msgstr "Sélectionnez un type de ressource"
-
-#: screens/Job/Job.helptext.js:21
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:10
-msgid "Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch."
-msgstr "Sélectionnez une branche pour le flux de travail. Cette branche est appliquée à tous les nœuds de modèle de tâche qui demandent une branche."
-
-#: screens/Credential/shared/CredentialForm.js:139
-msgid "Select a credential Type"
-msgstr "Sélectionnez un type d’identifiant"
-
-#: components/JobList/JobListCancelButton.js:98
-msgid "Select a job to cancel"
-msgstr "Sélectionnez un Job à annuler"
-
-#: screens/Metrics/Metrics.js:211
-msgid "Select a metric"
-msgstr "Sélectionnez une métrique"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:75
-msgid "Select a module"
-msgstr "Sélectionnez un module"
-
-#: screens/Template/shared/PlaybookSelect.js:60
-#: screens/Template/shared/PlaybookSelect.js:61
-msgid "Select a playbook"
-msgstr "Choisir un playbook"
-
-#: screens/Template/shared/JobTemplateForm.js:323
-msgid "Select a project before editing the execution environment."
-msgstr "Sélectionnez un projet avant de modifier l'environnement d'exécution."
-
-#: screens/Template/Survey/SurveyToolbar.js:82
-msgid "Select a question to delete"
-msgstr "Sélectionnez une question à supprimer"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:160
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:103
-msgid "Select a row to delete"
-msgstr "Sélectionnez une ligne à supprimer"
-
-#: components/DisassociateButton/DisassociateButton.js:75
-msgid "Select a row to disassociate"
-msgstr "Sélectionnez une ligne à dissocier"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:77
-msgid "Select a row to remove"
-msgstr "Sélectionnez une ligne à supprimer"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:87
-msgid "Select a subscription"
-msgstr "Sélectionnez un abonnement"
-
-#: components/HostForm/HostForm.js:39
-#: components/Schedule/shared/FrequencyDetailSubform.js:71
-#: components/Schedule/shared/FrequencyDetailSubform.js:78
-#: components/Schedule/shared/FrequencyDetailSubform.js:88
-#: components/Schedule/shared/ScheduleFormFields.js:33
-#: components/Schedule/shared/ScheduleFormFields.js:37
-#: components/Schedule/shared/ScheduleFormFields.js:61
-#: screens/Credential/shared/CredentialForm.js:44
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:79
-#: screens/Inventory/shared/InventoryForm.js:72
-#: screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.js:46
-#: screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/GCESubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.js:46
-#: screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:97
-#: screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.js:44
-#: screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.js:46
-#: screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.js:46
-#: screens/Inventory/shared/SmartInventoryForm.js:67
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:24
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:61
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:436
-#: screens/Project/shared/ProjectForm.js:234
-#: screens/Project/shared/ProjectSubForms/InsightsSubForm.js:39
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:37
-#: screens/Team/shared/TeamForm.js:49
-#: screens/Template/Survey/SurveyQuestionForm.js:30
-#: screens/Template/shared/WorkflowJobTemplateForm.js:130
-#: screens/User/shared/UserForm.js:139
-#: util/validators.js:201
-msgid "Select a value for this field"
-msgstr "Sélectionnez une valeur pour ce champ"
-
-#: screens/Template/shared/JobTemplate.helptext.js:23
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:20
-msgid "Select a webhook service."
-msgstr "Sélectionnez un service webhook."
-
-#: components/DataListToolbar/DataListToolbar.js:121
-#: components/DataListToolbar/DataListToolbar.js:125
-#: screens/Template/Survey/SurveyToolbar.js:49
-msgid "Select all"
-msgstr "Tout sélectionner"
-
-#: screens/ActivityStream/ActivityStream.js:129
-msgid "Select an activity type"
-msgstr "Sélectionnez un type d'activité"
-
-#: screens/Metrics/Metrics.js:200
-msgid "Select an instance"
-msgstr "Sélectionnez une instance"
-
-#: screens/Metrics/Metrics.js:242
-msgid "Select an instance and a metric to show chart"
-msgstr "Sélectionnez une instance et une métrique pour afficher le graphique"
-
-#: components/HealthCheckButton/HealthCheckButton.js:19
-msgid "Select an instance to run a health check."
-msgstr "Sélectionnez une instance pour effectuer un bilan de fonctionnement."
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:5
-msgid "Select an inventory for the workflow. This inventory is applied to all workflow nodes that prompt for an inventory."
-msgstr "Sélectionnez un inventaire pour le flux de travail. Cet inventaire est appliqué à tous les nœuds de modèle de tâche qui demandent un inventaire."
-
-#: components/LaunchPrompt/steps/SurveyStep.js:131
-msgid "Select an option"
-msgstr "Sélectionnez une option"
-
-#: screens/Project/shared/ProjectForm.js:245
-msgid "Select an organization before editing the default execution environment."
-msgstr "Sélectionnez une organisation avant de modifier l'environnement d'exécution par défaut."
-
-#: screens/Job/Job.helptext.js:11
-#: screens/Template/shared/JobTemplate.helptext.js:12
-msgid "Select credentials for accessing the nodes this job will be ran against. You can only select one credential of each type. For machine credentials (SSH), checking \"Prompt on launch\" without selecting credentials will require you to select a machine credential at run time. If you select credentials and check \"Prompt on launch\", the selected credential(s) become the defaults that can be updated at run time."
-msgstr "Sélectionnez les informations d'identification pour accéder aux nœuds qui déterminent l'exécution de cette tâche. Vous pouvez uniquement sélectionner une information d'identification de chaque type. Pour les informations d'identification machine (SSH), cocher la case \"Me demander au lancement\" sans la sélection des informations d'identification vous obligera à sélectionner des informations d'identification au moment de l’exécution. Si vous sélectionnez \"Me demander au lancement\", les informations d'identification sélectionnées deviennent les informations d'identification par défaut qui peuvent être mises à jour au moment de l'exécution."
-
-#: components/Schedule/shared/ScheduleFormFields.js:120
-#: components/Schedule/shared/ScheduleFormFields.js:179
-msgid "Select frequency"
-msgstr "Sélectionner la fréquence"
-
-#: screens/Project/shared/Project.helptext.js:18
-msgid ""
-"Select from the list of directories found in\n"
-"the Project Base Path. Together the base path and the playbook\n"
-"directory provide the full path used to locate playbooks."
-msgstr "Faites une sélection à partir de la liste des répertoires trouvés dans le chemin de base du projet. Le chemin de base et le répertoire de playbook fournissent ensemble le chemin complet servant à localiser les playbooks."
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:98
-msgid "Select items from list"
-msgstr "Sélectionnez les éléments de la liste"
-
-#: screens/Dashboard/DashboardGraph.js:124
-#: screens/Dashboard/DashboardGraph.js:125
-msgid "Select job type"
-msgstr "Sélectionnez le type de Job"
-
-#: components/LaunchPrompt/steps/SurveyStep.js:179
-msgid "Select option(s)"
-msgstr "Sélectionnez une ou plusieurs options"
-
-#: screens/Dashboard/DashboardGraph.js:95
-#: screens/Dashboard/DashboardGraph.js:96
-#: screens/Dashboard/DashboardGraph.js:97
-msgid "Select period"
-msgstr "Sélectionnez une période"
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:117
-msgid "Select roles to apply"
-msgstr "Sélectionner les rôles à pourvoir"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:128
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:129
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:130
-msgid "Select source path"
-msgstr "Sélectionner le chemin d'accès de la source"
-
-#: screens/Dashboard/DashboardGraph.js:151
-#: screens/Dashboard/DashboardGraph.js:152
-msgid "Select status"
-msgstr "Sélectionner le statut"
-
-#: components/MultiSelect/TagMultiSelect.js:59
-msgid "Select tags"
-msgstr "Sélectionner des balises"
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:94
-msgid "Select the Execution Environment you want this command to run inside."
-msgstr "Sélectionnez l'environnement d'exécution dans lequel vous voulez que cette commande soit exécutée."
-
-#: screens/Inventory/shared/SmartInventoryForm.js:87
-msgid "Select the Instance Groups for this Inventory to run on."
-msgstr "Sélectionnez les groupes d'instances sur lesquels exécuter cet inventaire."
-
-#: screens/Job/Job.helptext.js:18
-#: screens/Template/shared/JobTemplate.helptext.js:20
-msgid "Select the Instance Groups for this Job Template to run on."
-msgstr "Sélectionnez les groupes d'instances sur lesquels exécuter ce modèle de job."
-
-#: screens/Organization/shared/OrganizationForm.js:83
-msgid "Select the Instance Groups for this Organization to run on."
-msgstr "Sélectionnez les groupes d'instances sur lesquels exécuter cette organisation."
-
-#: components/AdHocCommands/AdHocCredentialStep.js:104
-msgid "Select the credential you want to use when accessing the remote hosts to run the command. Choose the credential containing the username and SSH key or password that Ansible will need to log into the remote hosts."
-msgstr "Sélectionnez les informations d’identification qu’il vous faut utiliser lors de l’accès à des hôtes distants pour exécuter la commande. Choisissez les informations d’identification contenant le nom d’utilisateur et la clé SSH ou le mot de passe dont Ansible aura besoin pour se connecter aux hôtes distants."
-
-#: components/Lookup/InventoryLookup.js:123
-msgid ""
-"Select the inventory containing the hosts\n"
-"you want this job to manage."
-msgstr "Sélectionnez l'inventaire contenant les hôtes\n"
-"que vous voulez que ce Job gère."
-
-#: screens/Job/Job.helptext.js:6
-#: screens/Template/shared/JobTemplate.helptext.js:7
-msgid "Select the inventory containing the hosts you want this job to manage."
-msgstr "Sélectionnez l’inventaire contenant les hôtes que vous souhaitez gérer."
-
-#: components/HostForm/HostForm.js:32
-#: components/HostForm/HostForm.js:51
-msgid "Select the inventory that this host will belong to."
-msgstr "Sélectionnez l'inventaire auquel cet hôte appartiendra."
-
-#: screens/Job/Job.helptext.js:10
-#: screens/Template/shared/JobTemplate.helptext.js:11
-msgid "Select the playbook to be executed by this job."
-msgstr "Sélectionnez le playbook qui devra être exécuté par cette tâche."
-
-#: screens/Instances/Shared/InstanceForm.js:43
-msgid "Select the port that Receptor will listen on for incoming connections. Default is 27199."
-msgstr "Sélectionnez le port sur lequel le Récepteur écoutera les connexions entrantes. La valeur par défaut est 27199."
-
-#: screens/Template/shared/JobTemplate.helptext.js:8
-msgid "Select the project containing the playbook you want this job to execute."
-msgstr "Sélectionnez le projet contenant le playbook que ce job devra exécuter."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:79
-msgid "Select your Ansible Automation Platform subscription to use."
-msgstr "Sélectionnez votre abonnement à la Plateforme d'Automatisation Ansible à utiliser."
-
-#: components/Lookup/Lookup.js:186
-msgid "Select {0}"
-msgstr "Sélectionnez {0}"
-
-#: components/AddRole/AddResourceRole.js:212
-#: components/AddRole/AddResourceRole.js:224
-#: components/AddRole/AddResourceRole.js:242
-#: components/AddRole/SelectRoleStep.js:27
-#: components/CheckboxListItem/CheckboxListItem.js:44
-#: components/Lookup/InstanceGroupsLookup.js:87
-#: components/OptionsList/OptionsList.js:74
-#: components/Schedule/ScheduleList/ScheduleListItem.js:84
-#: components/TemplateList/TemplateListItem.js:140
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:107
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:125
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:26
-#: screens/Application/ApplicationsList/ApplicationListItem.js:31
-#: screens/Credential/CredentialList/CredentialListItem.js:56
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:31
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:65
-#: screens/Host/HostGroups/HostGroupItem.js:26
-#: screens/Host/HostList/HostListItem.js:48
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:59
-#: screens/InstanceGroup/Instances/InstanceListItem.js:122
-#: screens/Instances/InstanceList/InstanceListItem.js:126
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:42
-#: screens/Inventory/InventoryList/InventoryListItem.js:90
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:37
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:110
-#: screens/Organization/OrganizationList/OrganizationListItem.js:43
-#: screens/Organization/shared/OrganizationForm.js:113
-#: screens/Project/ProjectList/ProjectListItem.js:177
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:242
-#: screens/Team/TeamList/TeamListItem.js:31
-#: screens/Template/Survey/SurveyListItem.js:34
-#: screens/User/UserTokenList/UserTokenListItem.js:19
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:41
-msgid "Selected"
-msgstr "Sélectionné"
-
-#: components/LaunchPrompt/steps/CredentialsStep.js:142
-#: components/LaunchPrompt/steps/CredentialsStep.js:147
-#: components/Lookup/MultiCredentialsLookup.js:162
-#: components/Lookup/MultiCredentialsLookup.js:167
-msgid "Selected Category"
-msgstr "Catégorie sélectionnée"
-
-#: components/Schedule/shared/ScheduleForm.js:446
-#: components/Schedule/shared/ScheduleForm.js:447
-msgid "Selected date range must have at least 1 schedule occurrence."
-msgstr "La plage de dates sélectionnée doit avoir au moins une occurrence de calendrier."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:160
-msgid "Sender Email"
-msgstr "E-mail de l’expéditeur"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:96
-msgid "Sender e-mail"
-msgstr "E-mail de l'expéditeur"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:167
-#: components/Schedule/shared/FrequencyDetailSubform.js:138
-msgid "September"
-msgstr "Septembre"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:24
-msgid "Service account JSON file"
-msgstr "Fichier JSON Compte de service"
-
-#: screens/Inventory/shared/InventorySourceForm.js:46
-#: screens/Project/shared/ProjectForm.js:112
-msgid "Set a value for this field"
-msgstr "Définir une valeur pour ce champ"
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:70
-msgid "Set how many days of data should be retained."
-msgstr "Définissez le nombre de jours pendant lesquels les données doivent être conservées."
-
-#: screens/Setting/SettingList.js:122
-msgid "Set preferences for data collection, logos, and logins"
-msgstr "Définissez des préférences pour la collection des données, les logos et logins."
-
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:131
-msgid "Set source path to"
-msgstr "Définir le chemin source à"
-
-#: components/InstanceToggle/InstanceToggle.js:48
-#: screens/Instances/Shared/InstanceForm.js:59
-msgid "Set the instance enabled or disabled. If disabled, jobs will not be assigned to this instance."
-msgstr "Mettez l'instance en ligne ou hors ligne. Si elle est hors ligne, les Jobs ne seront pas attribués à cette instance."
-
-#: screens/Application/shared/Application.helptext.js:5
-msgid "Set to Public or Confidential depending on how secure the client device is."
-msgstr "Définir sur sur Public ou Confidentiel selon le degré de sécurité du périphérique client."
-
-#: components/Search/AdvancedSearch.js:149
-msgid "Set type"
-msgstr "Type d'ensemble"
-
-#: components/Search/AdvancedSearch.js:239
-msgid "Set type disabled for related search field fuzzy searches"
-msgstr "Désactiver le type pour les recherches floues dans les champs de recherche associés"
-
-#: components/Search/AdvancedSearch.js:140
-msgid "Set type select"
-msgstr "Sélection du type d’ensemble"
-
-#: components/Search/AdvancedSearch.js:143
-msgid "Set type typeahead"
-msgstr "Définir type Typeahead"
-
-#: components/Workflow/WorkflowTools.js:154
-msgid "Set zoom to 100% and center graph"
-msgstr "Régler le zoom à 100% et centrer le graphique"
-
-#: screens/Instances/Shared/InstanceForm.js:35
-msgid "Sets the current life cycle stage of this instance. Default is \"installed.\""
-msgstr "Définit l'étape actuelle du cycle de vie de cette instance. La valeur par défaut est \"installé\"."
-
-#: screens/Instances/Shared/InstanceForm.js:51
-msgid "Sets the role that this instance will play within mesh topology. Default is \"execution.\""
-msgstr "Définit le rôle que cette instance jouera dans la topologie du maillage. La valeur par défaut est \"exécution\"."
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:46
-msgid "Setting category"
-msgstr "Catégorie de paramètre"
-
-#: screens/Setting/shared/RevertButton.js:46
-msgid "Setting matches factory default."
-msgstr "Le réglage correspond à la valeur d’usine par défaut."
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:49
-msgid "Setting name"
-msgstr "Nom du paramètre"
-
-#: routeConfig.js:159
-#: routeConfig.js:163
-#: screens/ActivityStream/ActivityStream.js:220
-#: screens/ActivityStream/ActivityStream.js:222
-#: screens/Setting/Settings.js:43
-msgid "Settings"
-msgstr "Paramètres"
-
-#: components/FormField/PasswordInput.js:35
-msgid "Show"
-msgstr "Afficher"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:182
-#: components/PromptDetail/PromptDetail.js:361
-#: components/PromptDetail/PromptJobTemplateDetail.js:156
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:488
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:283
-#: screens/Template/shared/JobTemplateForm.js:497
-msgid "Show Changes"
-msgstr "Afficher Modifications"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:177
-#: components/AdHocCommands/AdHocDetailsStep.js:178
-msgid "Show changes"
-msgstr "Afficher les modifications"
-
-#: components/LaunchPrompt/LaunchPrompt.js:135
-#: components/Schedule/shared/SchedulePromptableFields.js:102
-msgid "Show description"
-msgstr "Afficher la description"
-
-#: components/ChipGroup/ChipGroup.js:12
-msgid "Show less"
-msgstr "Afficher moins de détails"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:126
-msgid "Show only root groups"
-msgstr "Afficher uniquement les groupes racines"
-
-#: screens/Login/Login.js:262
-msgid "Sign in with Azure AD"
-msgstr "Connectez-vous avec Azure AD"
-
-#: screens/Login/Login.js:276
-msgid "Sign in with GitHub"
-msgstr "Connectez-vous à GitHub"
-
-#: screens/Login/Login.js:318
-msgid "Sign in with GitHub Enterprise"
-msgstr "Connectez-vous à GitHub Enterprise"
-
-#: screens/Login/Login.js:333
-msgid "Sign in with GitHub Enterprise Organizations"
-msgstr "Connectez-vous avec GitHub Enterprise Organizations"
-
-#: screens/Login/Login.js:349
-msgid "Sign in with GitHub Enterprise Teams"
-msgstr "Connectez-vous avec GitHub Enterprise Teams"
-
-#: screens/Login/Login.js:290
-msgid "Sign in with GitHub Organizations"
-msgstr "Connectez-vous avec GitHub Organizations"
-
-#: screens/Login/Login.js:304
-msgid "Sign in with GitHub Teams"
-msgstr "Connectez-vous avec GitHub Teams"
-
-#: screens/Login/Login.js:364
-msgid "Sign in with Google"
-msgstr "Connectez-vous avec Google"
-
-#: screens/Login/Login.js:378
-msgid "Sign in with OIDC"
-msgstr "Connectez-vous avec OIDC"
-
-#: screens/Login/Login.js:397
-msgid "Sign in with SAML"
-msgstr "Connectez-vous avec SAML"
-
-#: screens/Login/Login.js:396
-msgid "Sign in with SAML {samlIDP}"
-msgstr "Connectez-vous avec SAML {samlIDP}"
-
-#: components/Search/Search.js:145
-#: components/Search/Search.js:146
-msgid "Simple key select"
-msgstr "Sélection par simple pression d'une touche"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:106
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:107
-#: components/PromptDetail/PromptDetail.js:295
-#: components/PromptDetail/PromptJobTemplateDetail.js:267
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:595
-#: screens/Job/JobDetail/JobDetail.js:500
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:475
-#: screens/Template/shared/JobTemplateForm.js:533
-#: screens/Template/shared/WorkflowJobTemplateForm.js:230
-msgid "Skip Tags"
-msgstr "Balises de sauts"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:93
-#: components/Schedule/shared/FrequencyDetailSubform.js:223
-msgid "Skip every"
-msgstr "Sauter tous les"
-
-#: screens/Job/Job.helptext.js:20
-#: screens/Template/shared/JobTemplate.helptext.js:22
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:22
-msgid "Skip tags are useful when you have a large playbook, and you want to skip specific parts of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags."
-msgstr "Les balises de sauts sont utiles si votre playbook est important et que vous souhaitez ignorer certaines parties d’un Job ou d’une Lecture. Utiliser des virgules pour séparer plusieurs balises. Consulter la documentation pour obtenir des détails sur l'utilisation des balises."
-
-#: screens/Job/JobOutput/shared/HostStatusBar.js:39
-msgid "Skipped"
-msgstr "Ignoré"
-
-#: components/StatusLabel/StatusLabel.js:50
-msgid "Skipped'"
-msgstr "Ignoré"
-
-#: components/NotificationList/NotificationList.js:200
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:141
-msgid "Slack"
-msgstr "Slack"
-
-#: screens/Host/HostList/SmartInventoryButton.js:39
-#: screens/Host/HostList/SmartInventoryButton.js:48
-#: screens/Host/HostList/SmartInventoryButton.js:52
-#: screens/Inventory/InventoryList/InventoryList.js:187
-#: screens/Inventory/InventoryList/InventoryListItem.js:117
-msgid "Smart Inventory"
-msgstr "Inventaire smart"
-
-#: screens/Inventory/SmartInventory.js:94
-msgid "Smart Inventory not found."
-msgstr "Inventaire smart non trouvé."
-
-#: components/Lookup/HostFilterLookup.js:345
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:116
-msgid "Smart host filter"
-msgstr "Filtre d'hôte smart"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:105
-msgid "Smart inventory"
-msgstr "Inventaire smart"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:32
-#: components/LaunchPrompt/steps/PreviewStep.js:58
-msgid "Some of the previous step(s) have errors"
-msgstr "Certaines des étapes précédentes comportent des erreurs"
-
-#: screens/Host/HostList/SmartInventoryButton.js:17
-msgid "Some search modifiers like not__ and __search are not supported in Smart Inventory host filters. Remove these to create a new Smart Inventory with this filter."
-msgstr "Certains modificateurs de recherche, comme not__ et __search, ne sont pas pris en charge par les filtres hôte de Smart Inventory. Supprimez-les pour créer un nouveau Smart Inventory avec ce filtre."
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.js:39
-msgid "Something went wrong with the request to test this credential and metadata."
-msgstr "Quelque chose s'est mal passé avec la demande de tester cet identifiant et ses métadonnées."
-
-#: components/ContentError/ContentError.js:37
-msgid "Something went wrong..."
-msgstr "Quelque chose a mal tourné..."
-
-#: components/Sort/Sort.js:139
-msgid "Sort"
-msgstr "Trier"
-
-#: components/JobList/JobListItem.js:169
-#: components/PromptDetail/PromptInventorySourceDetail.js:84
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:199
-#: screens/Inventory/shared/InventorySourceForm.js:130
-#: screens/Job/JobDetail/JobDetail.js:172
-#: screens/Job/JobDetail/JobDetail.js:294
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:93
-msgid "Source"
-msgstr "Source"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:44
-#: components/PromptDetail/PromptDetail.js:250
-#: components/PromptDetail/PromptJobTemplateDetail.js:147
-#: components/PromptDetail/PromptProjectDetail.js:106
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:89
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:467
-#: screens/Job/JobDetail/JobDetail.js:307
-#: screens/Project/ProjectDetail/ProjectDetail.js:230
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:248
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:133
-#: screens/Template/shared/JobTemplateForm.js:335
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:245
-msgid "Source Control Branch"
-msgstr "Branche Contrôle de la source"
-
-#: screens/Project/shared/ProjectSubForms/GitSubForm.js:29
-msgid "Source Control Branch/Tag/Commit"
-msgstr "Branche/ Balise / Commit du Contrôle de la source"
-
-#: components/PromptDetail/PromptProjectDetail.js:117
-#: screens/Project/ProjectDetail/ProjectDetail.js:256
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:56
-msgid "Source Control Credential"
-msgstr "Identifiant Contrôle de la source"
-
-#: components/PromptDetail/PromptProjectDetail.js:111
-#: screens/Project/ProjectDetail/ProjectDetail.js:235
-#: screens/Project/shared/ProjectSubForms/GitSubForm.js:32
-msgid "Source Control Refspec"
-msgstr "Refspec Contrôle de la source"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:195
-msgid "Source Control Revision"
-msgstr "Révision Contrôle de la source"
-
-#: components/PromptDetail/PromptProjectDetail.js:96
-#: screens/Job/JobDetail/JobDetail.js:273
-#: screens/Project/ProjectDetail/ProjectDetail.js:191
-#: screens/Project/shared/ProjectForm.js:259
-msgid "Source Control Type"
-msgstr "Type de Contrôle de la source"
-
-#: components/Lookup/ProjectLookup.js:143
-#: components/PromptDetail/PromptProjectDetail.js:101
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:96
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:165
-#: screens/Project/ProjectDetail/ProjectDetail.js:225
-#: screens/Project/ProjectList/ProjectList.js:205
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:16
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:104
-msgid "Source Control URL"
-msgstr "URL Contrôle de la source"
-
-#: components/JobList/JobList.js:211
-#: components/JobList/JobListItem.js:42
-#: components/Schedule/ScheduleList/ScheduleListItem.js:38
-#: screens/Job/JobDetail/JobDetail.js:65
-msgid "Source Control Update"
-msgstr "Mise à jour du Contrôle de la source"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:340
-msgid "Source Phone Number"
-msgstr "Numéro de téléphone de la source"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:176
-msgid "Source Variables"
-msgstr "Variables Source"
-
-#: components/JobList/JobListItem.js:213
-#: screens/Job/JobDetail/JobDetail.js:257
-msgid "Source Workflow Job"
-msgstr "Flux de travail Source"
-
-#: screens/Template/shared/WorkflowJobTemplateForm.js:177
-msgid "Source control branch"
-msgstr "Branche Contrôle de la source"
-
-#: screens/Inventory/shared/InventorySourceForm.js:152
-msgid "Source details"
-msgstr "Détails de la source"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:403
-msgid "Source phone number"
-msgstr "Numéro de téléphone de la source"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:269
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:21
-msgid "Source variables"
-msgstr "Variables sources"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:97
-msgid "Sourced from a project"
-msgstr "Provenance d'un projet"
-
-#: screens/Inventory/Inventories.js:84
-#: screens/Inventory/Inventory.js:68
-msgid "Sources"
-msgstr "Sources"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:30
-msgid ""
-"Specify HTTP Headers in JSON format. Refer to\n"
-"the Ansible Controller documentation for example syntax."
-msgstr "Spécifier les En-têtes HTTP en format JSON. Voir la documentation sur le contrôleur Ansible pour obtenir des exemples de syntaxe."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:24
-msgid ""
-"Specify a notification color. Acceptable colors are hex\n"
-"color code (example: #3af or #789abc)."
-msgstr "Spécifier une couleur de notification. Les couleurs acceptées sont d'un code de couleur hex (exemple : #3af or #789abc) ."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:26
-msgid "Specify the conditions under which this node should be executed"
-msgstr "Préciser les conditions dans lesquelles ce nœud doit être exécuté"
-
-#: screens/Job/JobOutput/HostEventModal.js:173
-msgid "Standard Error"
-msgstr "Erreur standard"
-
-#: screens/Job/JobOutput/HostEventModal.js:174
-msgid "Standard error tab"
-msgstr "Onglet Erreur standard"
-
-#: components/NotificationList/NotificationListItem.js:57
-#: components/NotificationList/NotificationListItem.js:58
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:47
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:53
-msgid "Start"
-msgstr "Démarrer"
-
-#: components/JobList/JobList.js:247
-#: components/JobList/JobListItem.js:99
-msgid "Start Time"
-msgstr "Heure de début"
-
-#: components/Schedule/shared/DateTimePicker.js:51
-msgid "Start date"
-msgstr "Date de début"
-
-#: components/Schedule/shared/ScheduleFormFields.js:87
-msgid "Start date/time"
-msgstr "Date/Heure de début"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:465
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:105
-msgid "Start message"
-msgstr "Message de départ"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:474
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:114
-msgid "Start message body"
-msgstr "Démarrer le corps du message"
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:33
-msgid "Start sync process"
-msgstr "Démarrer le processus de synchronisation"
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:37
-msgid "Start sync source"
-msgstr "Démarrer la source de synchronisation"
-
-#: components/Schedule/shared/DateTimePicker.js:61
-msgid "Start time"
-msgstr "Heure de début"
-
-#: screens/Job/JobDetail/JobDetail.js:220
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:165
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:63
-msgid "Started"
-msgstr "Démarré"
-
-#: components/JobList/JobList.js:224
-#: components/JobList/JobList.js:245
-#: components/JobList/JobListItem.js:95
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:206
-#: screens/InstanceGroup/Instances/InstanceList.js:267
-#: screens/InstanceGroup/Instances/InstanceListItem.js:129
-#: screens/Instances/InstanceDetail/InstanceDetail.js:196
-#: screens/Instances/InstanceList/InstanceList.js:202
-#: screens/Instances/InstanceList/InstanceListItem.js:134
-#: screens/Instances/InstancePeers/InstancePeerList.js:97
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:43
-#: screens/Inventory/InventoryList/InventoryListItem.js:101
-#: screens/Inventory/InventorySources/InventorySourceList.js:212
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:87
-#: screens/Job/JobDetail/JobDetail.js:210
-#: screens/Job/JobOutput/HostEventModal.js:118
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:115
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:179
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:117
-#: screens/Project/ProjectList/ProjectList.js:222
-#: screens/Project/ProjectList/ProjectListItem.js:197
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:61
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:162
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:166
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:66
-msgid "Status"
-msgstr "État"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:91
-msgid "Stdout"
-msgstr "Stdout"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:37
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:49
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:212
-msgid "Submit"
-msgstr "Valider"
-
-#: screens/Project/shared/Project.helptext.js:118
-msgid ""
-"Submodules will track the latest commit on\n"
-"their master branch (or other branch specified in\n"
-".gitmodules). If no, submodules will be kept at\n"
-"the revision specified by the main project.\n"
-"This is equivalent to specifying the --remote\n"
-"flag to git submodule update."
-msgstr "Les sous-modules suivront le dernier commit sur\n"
-"leur branche principale (ou toute autre branche spécifiée dans\n"
-".gitmodules). Si non, les sous-modules seront maintenus à\n"
-"la révision spécifiée par le projet principal.\n"
-"Cela équivaut à spécifier l'option --remote\n"
-"à git submodule update."
-
-#: screens/Setting/SettingList.js:132
-#: screens/Setting/Settings.js:112
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:136
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:195
-msgid "Subscription"
-msgstr "Abonnement"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:41
-msgid "Subscription Details"
-msgstr "Détails d’abonnement"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:194
-msgid "Subscription Management"
-msgstr "Gestion des abonnements"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:82
-msgid "Subscription manifest"
-msgstr "Manifeste de souscription"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:84
-msgid "Subscription selection modal"
-msgstr "Modalité de sélection de l'abonnement"
-
-#: screens/Setting/SettingList.js:137
-msgid "Subscription settings"
-msgstr "Paramètres d'abonnement"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:131
-msgid "Subscription type"
-msgstr "Type d’abonnement"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:142
-msgid "Subscriptions table"
-msgstr "Table des abonnements"
-
-#: components/Lookup/ProjectLookup.js:137
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:90
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:159
-#: screens/Job/JobDetail/JobDetail.js:76
-#: screens/Project/ProjectList/ProjectList.js:199
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:98
-msgid "Subversion"
-msgstr "Subversion"
-
-#: components/NotificationList/NotificationListItem.js:71
-#: components/NotificationList/NotificationListItem.js:72
-#: components/StatusLabel/StatusLabel.js:41
-msgid "Success"
-msgstr "Réussite"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:483
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:123
-msgid "Success message"
-msgstr "Message de réussite"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:492
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:132
-msgid "Success message body"
-msgstr "Corps du message de réussite"
-
-#: components/JobList/JobList.js:231
-#: components/StatusLabel/StatusLabel.js:43
-#: components/Workflow/WorkflowNodeHelp.js:102
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:96
-#: screens/Dashboard/shared/ChartTooltip.js:59
-msgid "Successful"
-msgstr "Réussi"
-
-#: screens/Dashboard/DashboardGraph.js:166
-msgid "Successful jobs"
-msgstr "Tâches ayant réussi"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:28
-msgid "Successfully Approved"
-msgstr "Approuvé avec succès"
-
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:25
-msgid "Successfully Denied"
-msgstr "Refusé avec succès"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:201
-#: screens/Project/ProjectList/ProjectListItem.js:97
-msgid "Successfully copied to clipboard!"
-msgstr "Copie réussie dans le presse-papiers !"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:255
-msgid "Sun"
-msgstr "Dim."
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:83
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:182
-#: components/Schedule/shared/FrequencyDetailSubform.js:260
-#: components/Schedule/shared/FrequencyDetailSubform.js:428
-msgid "Sunday"
-msgstr "Dimanche"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:26
-#: screens/Template/Template.js:159
-#: screens/Template/Templates.js:48
-#: screens/Template/WorkflowJobTemplate.js:145
-msgid "Survey"
-msgstr "Questionnaire"
-
-#: screens/Template/Survey/SurveyToolbar.js:105
-msgid "Survey Disabled"
-msgstr "Questionnaire désactivé"
-
-#: screens/Template/Survey/SurveyToolbar.js:104
-msgid "Survey Enabled"
-msgstr "Questionnaire activé"
-
-#: screens/Template/Survey/SurveyReorderModal.js:191
-msgid "Survey Question Order"
-msgstr "Ordre des questions de l’enquête"
-
-#: screens/Template/Survey/SurveyToolbar.js:102
-msgid "Survey Toggle"
-msgstr "Basculement Questionnaire"
-
-#: screens/Template/Survey/SurveyReorderModal.js:192
-msgid "Survey preview modal"
-msgstr "Modalité d'aperçu de l'enquête"
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:120
-#: screens/Inventory/shared/InventorySourceSyncButton.js:41
-#: screens/Project/shared/ProjectSyncButton.js:41
-#: screens/Project/shared/ProjectSyncButton.js:53
-msgid "Sync"
-msgstr "Sync"
-
-#: screens/Project/ProjectList/ProjectListItem.js:238
-#: screens/Project/shared/ProjectSyncButton.js:37
-#: screens/Project/shared/ProjectSyncButton.js:48
-msgid "Sync Project"
-msgstr "Projet Sync"
-
-#: screens/Inventory/InventoryList/InventoryList.js:219
-msgid "Sync Status"
-msgstr "Statut de la synchronisation"
-
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:19
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:29
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:32
-msgid "Sync all"
-msgstr "Tout sync"
-
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:25
-msgid "Sync all sources"
-msgstr "Synchroniser toutes les sources"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:236
-msgid "Sync error"
-msgstr "Erreur de synchronisation"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:213
-#: screens/Project/ProjectList/ProjectListItem.js:109
-msgid "Sync for revision"
-msgstr "Synchronisation pour la révision"
-
-#: screens/Project/ProjectList/ProjectListItem.js:122
-msgid "Syncing"
-msgstr "Synchronisation"
-
-#: screens/Setting/SettingList.js:102
-#: screens/User/UserRoles/UserRolesListItem.js:18
-msgid "System"
-msgstr "Système"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:128
-#: screens/User/UserDetail/UserDetail.js:47
-#: screens/User/UserList/UserListItem.js:19
-#: screens/User/UserRoles/UserRolesList.js:127
-#: screens/User/shared/UserForm.js:41
-msgid "System Administrator"
-msgstr "Administrateur du système"
-
-#: screens/User/UserDetail/UserDetail.js:49
-#: screens/User/UserList/UserListItem.js:21
-#: screens/User/shared/UserForm.js:35
-msgid "System Auditor"
-msgstr "Auditeur système"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:128
-msgid "System Warning"
-msgstr "Avertissement système"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:131
-#: screens/User/UserRoles/UserRolesList.js:130
-msgid "System administrators have unrestricted access to all resources."
-msgstr "Les administrateurs système ont un accès illimité à toutes les ressources."
-
-#: screens/Setting/Settings.js:115
-msgid "TACACS+"
-msgstr "TACACS+"
-
-#: screens/Setting/SettingList.js:81
-msgid "TACACS+ settings"
-msgstr "Paramètres de la TACACS"
-
-#: screens/Dashboard/Dashboard.js:117
-#: screens/Job/JobOutput/HostEventModal.js:94
-msgid "Tabs"
-msgstr "Balises"
-
-#: screens/Job/Job.helptext.js:19
-#: screens/Template/shared/JobTemplate.helptext.js:21
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:21
-msgid "Tags are useful when you have a large playbook, and you want to run a specific part of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags."
-msgstr "Les balises sont utiles si votre playbook est important et que vous souhaitez la lecture de certaines parties ou exécuter une tâche particulière. Utiliser des virgules pour séparer plusieurs balises. Consulter la documentation pour obtenir des détails sur l'utilisation des balises."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:203
-msgid "Tags for the Annotation"
-msgstr "Balises pour l'annotation"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:179
-msgid "Tags for the annotation (optional)"
-msgstr "Balises pour l'annotation (facultatif)"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:248
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:298
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:366
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:252
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:329
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:453
-msgid "Target URL"
-msgstr "URL cible"
-
-#: screens/Job/JobOutput/HostEventModal.js:123
-msgid "Task"
-msgstr "Tâche"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:90
-msgid "Task Count"
-msgstr "Nombre de tâches"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:129
-msgid "Task Started"
-msgstr "Tâche démarrée"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:91
-msgid "Tasks"
-msgstr "Tâches"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:13
-msgid "Team"
-msgstr "Équipe"
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:85
-#: screens/Team/TeamRoles/TeamRolesList.js:144
-msgid "Team Roles"
-msgstr "Rôles d’équipe"
-
-#: screens/Team/Team.js:75
-msgid "Team not found."
-msgstr "Équipe non trouvée."
-
-#: components/AddRole/AddResourceRole.js:188
-#: components/AddRole/AddResourceRole.js:189
-#: routeConfig.js:106
-#: screens/ActivityStream/ActivityStream.js:187
-#: screens/Organization/Organization.js:125
-#: screens/Organization/OrganizationList/OrganizationList.js:145
-#: screens/Organization/OrganizationList/OrganizationListItem.js:66
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:64
-#: screens/Organization/Organizations.js:32
-#: screens/Team/TeamList/TeamList.js:112
-#: screens/Team/TeamList/TeamList.js:166
-#: screens/Team/Teams.js:15
-#: screens/Team/Teams.js:25
-#: screens/User/User.js:70
-#: screens/User/UserTeams/UserTeamList.js:175
-#: screens/User/UserTeams/UserTeamList.js:246
-#: screens/User/Users.js:32
-#: util/getRelatedResourceDeleteDetails.js:174
-msgid "Teams"
-msgstr "Équipes"
-
-#: screens/Setting/Jobs/JobsEdit/JobsEdit.js:130
-msgid "Template"
-msgstr "Modèle"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:115
-#: components/TemplateList/TemplateList.js:133
-msgid "Template copied successfully"
-msgstr "Modèle copié"
-
-#: screens/Template/Template.js:175
-#: screens/Template/WorkflowJobTemplate.js:175
-msgid "Template not found."
-msgstr "Mise à jour introuvable"
-
-#: components/TemplateList/TemplateList.js:200
-#: components/TemplateList/TemplateList.js:263
-#: routeConfig.js:65
-#: screens/ActivityStream/ActivityStream.js:164
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:70
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:83
-#: screens/Template/Templates.js:17
-#: util/getRelatedResourceDeleteDetails.js:218
-#: util/getRelatedResourceDeleteDetails.js:275
-msgid "Templates"
-msgstr "Modèles"
-
-#: screens/Credential/shared/CredentialForm.js:331
-#: screens/Credential/shared/CredentialForm.js:337
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:80
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:426
-msgid "Test"
-msgstr "Test"
-
-#: screens/Credential/shared/ExternalTestModal.js:77
-msgid "Test External Credential"
-msgstr "Test des informations d'identification externes"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:128
-msgid "Test Notification"
-msgstr "Notification test"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:125
-msgid "Test notification"
-msgstr "Notification test"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.js:44
-msgid "Test passed"
-msgstr "Test réussi."
-
-#: screens/Template/Survey/SurveyQuestionForm.js:80
-#: screens/Template/Survey/SurveyReorderModal.js:181
-msgid "Text"
-msgstr "Texte"
-
-#: screens/Template/Survey/SurveyReorderModal.js:135
-msgid "Text Area"
-msgstr "Zone de texte"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:81
-msgid "Textarea"
-msgstr "Zone de texte"
-
-#: components/Lookup/Lookup.js:63
-msgid "That value was not found. Please enter or select a valid value."
-msgstr "Cette valeur n’a pas été trouvée. Veuillez entrer ou sélectionner une valeur valide."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:398
-msgid "The"
-msgstr "Le"
-
-#: screens/Application/shared/Application.helptext.js:4
-msgid "The Grant type the user must use to acquire tokens for this application"
-msgstr "Le type d’autorisation que l'utilisateur doit utiliser pour acquérir des jetons pour cette application"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:128
-msgid "The Instance Groups for this Organization to run on."
-msgstr "Sélectionnez les groupes d'instances sur lesquels exécuter cette organisation."
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:219
-msgid "The Instance Groups to which this instance belongs."
-msgstr "Les groupes d'instances auxquels appartient cette instance."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:6
-msgid ""
-"The amount of time (in seconds) before the email\n"
-"notification stops trying to reach the host and times out. Ranges\n"
-"from 1 to 120 seconds."
-msgstr "Délai (en secondes) avant que la notification par e-mail cesse d'essayer de joindre l'hôte et expire. Compris entre 1 et 120 secondes."
-
-#: screens/Job/Job.helptext.js:17
-#: screens/Template/shared/JobTemplate.helptext.js:18
-msgid "The amount of time (in seconds) to run before the job is canceled. Defaults to 0 for no job timeout."
-msgstr "Délai (en secondes) avant l'annulation de la tâche. La valeur par défaut est 0 pour aucun délai d'expiration du job."
-
-#: screens/User/shared/User.helptext.js:4
-msgid "The application that this token belongs to, or leave this field empty to create a Personal Access Token."
-msgstr "Sélectionnez l'application à laquelle ce jeton appartiendra, ou laissez ce champ vide pour créer un jeton d'accès personnel."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:9
-msgid ""
-"The base URL of the Grafana server - the\n"
-"/api/annotations endpoint will be added automatically to the base\n"
-"Grafana URL."
-msgstr "URL de base du serveur Grafana - le point de terminaison /api/annotations sera ajouté automatiquement à l'URL Grafana de base."
-
-#: screens/Template/shared/JobTemplate.helptext.js:9
-msgid "The container image to be used for execution."
-msgstr "L'image du conteneur à utiliser pour l'exécution."
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:109
-msgid ""
-"The execution environment that will be used for jobs\n"
-"inside of this organization. This will be used a fallback when\n"
-"an execution environment has not been explicitly assigned at the\n"
-"project, job template or workflow level."
-msgstr "L'environnement d'exécution qui sera utilisé pour les tâches au sein de cette organisation. Il sera utilisé comme solution de rechange lorsqu'un environnement d'exécution n'a pas été explicitement attribué au niveau du projet, du modèle de job ou du flux de travail."
-
-#: screens/Organization/shared/OrganizationForm.js:93
-msgid "The execution environment that will be used for jobs inside of this organization. This will be used a fallback when an execution environment has not been explicitly assigned at the project, job template or workflow level."
-msgstr "L'environnement d'exécution qui sera utilisé pour les tâches au sein de cette organisation. Il sera utilisé comme solution de rechange lorsqu'un environnement d'exécution n'a pas été explicitement attribué au niveau du projet, du modèle de job ou du flux de travail."
-
-#: screens/Project/shared/Project.helptext.js:5
-msgid "The execution environment that will be used for jobs that use this project. This will be used as fallback when an execution environment has not been explicitly assigned at the job template or workflow level."
-msgstr "L'environnement d'exécution qui sera utilisé pour les jobs qui utilisent ce projet. Il sera utilisé comme solution de rechange lorsqu'un environnement d'exécution n'a pas été explicitement attribué au niveau du modèle de job ou du flux de travail."
-
-#: screens/Job/Job.helptext.js:9
-#: screens/Template/shared/JobTemplate.helptext.js:10
-msgid "The execution environment that will be used when launching this job template. The resolved execution environment can be overridden by explicitly assigning a different one to this job template."
-msgstr "L'environnement d'exécution qui sera utilisé lors du lancement\n"
-"ce modèle de tâche. L'environnement d'exécution résolu peut être remplacé en\n"
-"en affectant explicitement un environnement différent à ce modèle de tâche."
-
-#: screens/Project/shared/Project.helptext.js:93
-msgid ""
-"The first fetches all references. The second\n"
-"fetches the Github pull request number 62, in this example\n"
-"the branch needs to be \"pull/62/head\"."
-msgstr "Le premier extrait toutes les références. Le second extrait la requête Github pull numéro 62, dans cet exemple la branche doit être `pull/62/head`."
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:7
-msgid "The full image location, including the container registry, image name, and version tag."
-msgstr "L'emplacement complet de l'image, y compris le registre du conteneur, le nom de l'image et la balise de version."
-
-#: screens/Inventory/shared/Inventory.helptext.js:191
-msgid ""
-"The inventory file\n"
-"to be synced by this source. You can select from\n"
-"the dropdown or enter a file within the input."
-msgstr "Fichier d'inventaire à synchroniser par cette source. Vous pouvez le choisir dans le menu déroulant ou saisir un fichier dans l'entrée."
-
-#: screens/Host/HostDetail/HostDetail.js:79
-msgid "The inventory that this host belongs to."
-msgstr "Inventaire auquel cet hôte appartiendra."
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:141
-msgid "The last {dayOfWeek}"
-msgstr "Dernier {dayOfWeek}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:200
-msgid "The last {weekday} of {month}"
-msgstr "Le dernier {weekday} de {month}"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:100
-msgid ""
-"The maximum number of hosts allowed to be managed by\n"
-"this organization. Value defaults to 0 which means no limit.\n"
-"Refer to the Ansible documentation for more details."
-msgstr "Nombre maximal d'hôtes pouvant être gérés par cette organisation. La valeur par défaut est 0, ce qui signifie aucune limite. Reportez-vous à la documentation Ansible pour plus de détails."
-
-#: screens/Organization/shared/OrganizationForm.js:72
-msgid ""
-"The maximum number of hosts allowed to be managed by this organization.\n"
-"Value defaults to 0 which means no limit. Refer to the Ansible\n"
-"documentation for more details."
-msgstr "Nombre maximal d'hôtes pouvant être gérés par cette organisation. La valeur par défaut est 0, ce qui signifie aucune limite. Reportez-vous à la documentation Ansible pour plus de détails."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:26
-msgid ""
-"The number associated with the \"Messaging\n"
-"Service\" in Twilio with the format +18005550199."
-msgstr "Numéro associé au \"Service de messagerie\" de Twilio sous le format +18005550199."
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:69
-msgid "The number of hosts you have automated against is below your subscription count."
-msgstr "Le nombre d'hôtes contre lesquels vous avez automatisé est inférieur au nombre d'abonnements."
-
-#: screens/Job/Job.helptext.js:25
-#: screens/Template/shared/JobTemplate.helptext.js:48
-msgid "The number of parallel or simultaneous processes to use while executing the playbook. An empty value, or a value less than 1 will use the Ansible default which is usually 5. The default number of forks can be overwritten with a change to"
-msgstr "Le nombre de processus parallèles ou simultanés à utiliser lors de l'exécution du playbook. Une valeur vide, ou une valeur inférieure à 1 utilisera la valeur par défaut Ansible qui est généralement 5. Le nombre de fourches par défaut peut être remplacé par un changement vers"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:164
-msgid "The number of parallel or simultaneous processes to use while executing the playbook. Inputting no value will use the default value from the ansible configuration file. You can find more information"
-msgstr "Nombre de processus parallèles ou simultanés à utiliser lors de l'exécution du playbook. La saisie d'aucune valeur entraînera l'utilisation de la valeur par défaut du fichier de configuration ansible. Vous pourrez trouver plus d’informations."
-
-#: components/ContentError/ContentError.js:41
-#: screens/Job/Job.js:161
-msgid "The page you requested could not be found."
-msgstr "La page que vous avez demandée n'a pas été trouvée."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:144
-msgid "The pattern used to target hosts in the inventory. Leaving the field blank, all, and * will all target all hosts in the inventory. You can find more information about Ansible's host patterns"
-msgstr "Le modèle utilisé pour cibler les hôtes dans l'inventaire. En laissant le champ vide, tous et * cibleront tous les hôtes de l'inventaire. Vous pouvez trouver plus d'informations sur les modèles d'hôtes d'Ansible"
-
-#: screens/Job/Job.helptext.js:7
-msgid "The project containing the playbook this job will execute."
-msgstr "Sélectionnez le projet contenant le playbook que ce job devra exécuter."
-
-#: screens/Job/Job.helptext.js:8
-msgid "The project from which this inventory update is sourced."
-msgstr "Le projet d'où provient cette mise à jour de l'inventaire."
-
-#: screens/Project/ProjectList/ProjectListItem.js:120
-msgid "The project is currently syncing and the revision will be available after the sync is complete."
-msgstr "Le projet est en cours de synchronisation et la révision sera disponible une fois la synchronisation terminée."
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:211
-#: screens/Project/ProjectList/ProjectListItem.js:107
-msgid "The project must be synced before a revision is available."
-msgstr "Le projet doit être synchronisé avant qu'une révision soit disponible."
-
-#: screens/Project/ProjectList/ProjectListItem.js:130
-msgid "The project revision is currently out of date. Please refresh to fetch the most recent revision."
-msgstr "La révision du projet est actuellement périmée. Veuillez actualiser pour obtenir la révision la plus récente."
-
-#: components/Workflow/WorkflowNodeHelp.js:138
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:131
-msgid "The resource associated with this node has been deleted."
-msgstr "La ressource associée à ce nœud a été supprimée."
-
-#: screens/Job/JobOutput/EmptyOutput.js:31
-msgid "The search filter did not produce any results…"
-msgstr "Le résultat de la recherche n’a produit aucun résultat…"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:180
-msgid ""
-"The suggested format for variable names is lowercase and\n"
-"underscore-separated (for example, foo_bar, user_id, host_name,\n"
-"etc.). Variable names with spaces are not allowed."
-msgstr "Le format suggéré pour les noms de variables est en minuscules avec des tirets de séparation (exemple, foo_bar, user_id, host_name, etc.). Les noms de variables contenant des espaces ne sont pas autorisés."
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:50
-msgid ""
-"There are no available playbook directories in {project_base_dir}.\n"
-"Either that directory is empty, or all of the contents are already\n"
-"assigned to other projects. Create a new directory there and make\n"
-"sure the playbook files can be read by the \"awx\" system user,\n"
-"or have {brandName} directly retrieve your playbooks from\n"
-"source control using the Source Control Type option above."
-msgstr "Il n'y a pas d'annuaires de playbooks disponibles dans {project_base_dir}. Soit ce répertoire est vide, soit tout le contenu est déjà affecté à d'autres projets. Créez-y un nouveau répertoire et assurez-vous que les fichiers du playbook peuvent être lus par l'utilisateur du système \"awx\", ou bien il faut que {brandName} récupére directement vos playbooks à partir du contrôle des sources en utilisant l'option Type de contrôle des sources ci-dessus."
-
-#: screens/Template/Survey/MultipleChoiceField.js:34
-msgid "There must be a value in at least one input"
-msgstr "Il doit y avoir une valeur dans une entrée au moins"
-
-#: screens/Login/Login.js:155
-msgid "There was a problem logging in. Please try again."
-msgstr "Il y a eu un problème de connexion. Veuillez réessayer."
-
-#: components/ContentError/ContentError.js:42
-msgid "There was an error loading this content. Please reload the page."
-msgstr "Il y a eu une erreur lors du chargement de ce contenu. Veuillez recharger la page."
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:56
-msgid "There was an error parsing the file. Please check the file formatting and try again."
-msgstr "Il y a eu une erreur dans l'analyse du fichier. Veuillez vérifier le formatage du fichier et réessayer."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:713
-msgid "There was an error saving the workflow."
-msgstr "Une erreur s'est produite lors de la sauvegarde du flux de travail."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:69
-msgid "These are the modules that {brandName} supports running commands against."
-msgstr "Il s'agit des modules pris en charge par {brandName} pour l'exécution de commandes."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:129
-msgid "These are the verbosity levels for standard out of the command run that are supported."
-msgstr "Il s'agit des niveaux de verbosité pour les standards hors du cycle de commande qui sont pris en charge."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:122
-#: screens/Job/Job.helptext.js:43
-msgid "These arguments are used with the specified module."
-msgstr "Ces arguments sont utilisés avec le module spécifié."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:111
-msgid "These arguments are used with the specified module. You can find information about {0} by clicking"
-msgstr "Ces arguments sont utilisés avec le module spécifié. Vous pouvez trouver des informations sur {0} en cliquant sur"
-
-#: screens/Job/Job.helptext.js:33
-msgid "These arguments are used with the specified module. You can find information about {moduleName} by clicking"
-msgstr "Ces arguments sont utilisés avec le module spécifié. Vous pouvez trouver des informations sur {moduleName} en cliquant sur"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:410
-msgid "Third"
-msgstr "Troisième"
-
-#: screens/Template/shared/JobTemplateForm.js:157
-msgid "This Project needs to be updated"
-msgstr "Ce projet doit être mis à jour"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:286
-#: screens/Template/Survey/SurveyList.js:82
-msgid "This action will delete the following:"
-msgstr "Cette action supprimera les éléments suivants :"
-
-#: screens/User/UserTeams/UserTeamList.js:217
-msgid "This action will disassociate all roles for this user from the selected teams."
-msgstr "Cette action permettra de dissocier tous les rôles de cet utilisateur des équipes sélectionnées."
-
-#: screens/Team/TeamRoles/TeamRolesList.js:236
-#: screens/User/UserRoles/UserRolesList.js:232
-msgid "This action will disassociate the following role from {0}:"
-msgstr "Cette action permettra de dissocier le rôle suivant de {0} :"
-
-#: components/DisassociateButton/DisassociateButton.js:148
-msgid "This action will disassociate the following:"
-msgstr "Cette action dissociera les éléments suivants :"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:178
-msgid "This action will remove the following instances:"
-msgstr "Cette action supprimera les instances suivantes :"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:113
-msgid "This container group is currently being by other resources. Are you sure you want to delete it?"
-msgstr "Ce groupe de conteneurs est actuellement utilisé par d'autres ressources. Êtes-vous sûr de vouloir le supprimer ?"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:304
-msgid "This credential is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "Cette accréditation est actuellement utilisée par d'autres ressources. Êtes-vous sûr de vouloir la supprimer ?"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:121
-msgid "This credential type is currently being used by some credentials and cannot be deleted"
-msgstr "Ce type d’accréditation est actuellement utilisé par certaines informations d’accréditation et ne peut être supprimé"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:74
-msgid ""
-"This data is used to enhance\n"
-"future releases of the Software and to provide\n"
-"Automation Analytics."
-msgstr "Ces données sont utilisées pour améliorer\n"
-"les futures versions du logiciel et pour fournir des données d’ Automation Analytics.."
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:62
-msgid ""
-"This data is used to enhance\n"
-"future releases of the Tower Software and help\n"
-"streamline customer experience and success."
-msgstr "Ces données sont utilisées pour améliorer\n"
-"les futures versions du logiciel Tower et contribuer à\n"
-"à rationaliser l'expérience des clients."
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:132
-msgid "This execution environment is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "Cet environnement d'exécution est actuellement utilisé par d'autres ressources. Êtes-vous sûr de vouloir le supprimer ?"
-
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:74
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:79
-msgid "This feature is deprecated and will be removed in a future release."
-msgstr "Cette fonctionnalité est obsolète et sera supprimée dans une prochaine version."
-
-#: screens/Inventory/shared/Inventory.helptext.js:155
-msgid "This field is ignored unless an Enabled Variable is set. If the enabled variable matches this value, the host will be enabled on import."
-msgstr "Ce champ est ignoré à moins qu'une variable activée ne soit définie. Si la variable activée correspond à cette valeur, l'hôte sera activé lors de l'importation."
-
-#: components/AdHocCommands/useAdHocCredentialPasswordStep.js:44
-#: components/LaunchPrompt/steps/useCredentialPasswordsStep.js:50
-msgid "This field may not be blank"
-msgstr "Ce champ ne doit pas être vide"
-
-#: util/validators.js:127
-msgid "This field must be a number"
-msgstr "Ce champ doit être un numéro"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:107
-msgid "This field must be a number and have a value between {0} and {1}"
-msgstr "Ce champ doit être un nombre et avoir une valeur comprise entre {0} et {1}"
-
-#: util/validators.js:67
-msgid "This field must be a number and have a value between {min} and {max}"
-msgstr "Ce champ doit être un nombre et avoir une valeur comprise entre {min} et {max}"
-
-#: util/validators.js:64
-msgid "This field must be a number and have a value greater than {min}"
-msgstr "Ce champ doit être un nombre et avoir une valeur supérieure à {min}"
-
-#: util/validators.js:61
-msgid "This field must be a number and have a value less than {max}"
-msgstr "Ce champ doit être un nombre et avoir une valeur inférieure à {max}"
-
-#: util/validators.js:184
-msgid "This field must be a regular expression"
-msgstr "Ce champ doit être une expression régulière"
-
-#: util/validators.js:111
-#: util/validators.js:194
-msgid "This field must be an integer"
-msgstr "Ce champ doit être un nombre entier"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:99
-msgid "This field must be at least {0} characters"
-msgstr "Ce champ doit comporter au moins {0} caractères"
-
-#: util/validators.js:52
-msgid "This field must be at least {min} characters"
-msgstr "Ce champ doit comporter au moins {min} caractères"
-
-#: util/validators.js:197
-msgid "This field must be greater than 0"
-msgstr "Ce champ doit être supérieur à 0"
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:52
-#: components/LaunchPrompt/steps/useSurveyStep.js:111
-#: screens/Template/shared/JobTemplateForm.js:154
-#: screens/User/shared/UserForm.js:92
-#: screens/User/shared/UserForm.js:103
-#: util/validators.js:5
-#: util/validators.js:76
-msgid "This field must not be blank"
-msgstr "Ce champ ne doit pas être vide"
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:46
-msgid "This field must not be blank."
-msgstr "Ce champ ne doit pas être vide."
-
-#: util/validators.js:101
-msgid "This field must not contain spaces"
-msgstr "Ce champ ne doit pas contenir d'espaces"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:102
-msgid "This field must not exceed {0} characters"
-msgstr "Ce champ ne doit pas dépasser {0} caractères"
-
-#: util/validators.js:43
-msgid "This field must not exceed {max} characters"
-msgstr "Ce champ ne doit pas dépasser {max} caractères"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:51
-msgid "This field will be retrieved from an external secret management system using the specified credential."
-msgstr "Ce champ sera récupéré dans un système externe de gestion des secrets en utilisant l’identifiant spécifié."
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:82
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:89
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:104
-msgid "This has already been acted on"
-msgstr "Ce point a déjà fait l'objet d'une action"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:125
-msgid "This instance group is currently being by other resources. Are you sure you want to delete it?"
-msgstr "Ce groupe d'instance est actuellement utilisé par d'autres ressources. Êtes-vous sûr de vouloir le supprimer ?"
-
-#: components/LaunchPrompt/steps/useInventoryStep.js:59
-msgid "This inventory is applied to all workflow nodes within this workflow ({0}) that prompt for an inventory."
-msgstr "Cet inventaire est appliqué à tous les nœuds de flux de travail de ce flux de travail ({0}) qui requiert un inventaire."
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:199
-msgid "This inventory is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "Cet inventaire est actuellement utilisé par d'autres ressources. Êtes-vous sûr de vouloir le supprimer ?"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:313
-msgid "This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?"
-msgstr "Cette source d'inventaire est actuellement utilisée par d'autres ressources qui en dépendent. Êtes-vous sûr de vouloir la supprimer ?"
-
-#: screens/Application/Applications.js:77
-msgid "This is the only time the client secret will be shown."
-msgstr "C'est la seule fois où le secret du client sera révélé."
-
-#: screens/User/UserTokens/UserTokens.js:59
-msgid "This is the only time the token value and associated refresh token value will be shown."
-msgstr "C'est la seule fois où la valeur du jeton et la valeur du jeton de rafraîchissement associée seront affichées."
-
-#: screens/Job/JobOutput/EmptyOutput.js:37
-msgid "This job failed and has no output."
-msgstr "Ce travail a échoué et n'a pas de résultat."
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:543
-msgid "This job template is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "Ce modèle de poste est actuellement utilisé par d'autres ressources. Êtes-vous sûr de vouloir le supprimer ?"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:197
-msgid "This organization is currently being by other resources. Are you sure you want to delete it?"
-msgstr "Cette organisation est actuellement en cours de traitement par d'autres ressources. Êtes-vous sûr de vouloir la supprimer ?"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:338
-msgid "This project is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "Ce projet est actuellement utilisé par d'autres ressources. Êtes-vous sûr de vouloir le supprimer ?"
-
-#: screens/Project/shared/Project.helptext.js:59
-msgid "This project is currently on sync and cannot be clicked until sync process completed"
-msgstr "Ce projet est actuellement en cours de synchronisation et ne peut être cliqué tant que le processus de synchronisation n'est pas terminé"
-
-#: components/Schedule/shared/ScheduleForm.js:460
-msgid "This schedule has no occurrences due to the selected exceptions."
-msgstr "Cet horaire n'a pas d'occurrences en raison des exceptions sélectionnées."
-
-#: components/Schedule/ScheduleList/ScheduleList.js:122
-msgid "This schedule is missing an Inventory"
-msgstr "Il manque un inventaire pour cette programmation d’horaire"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:147
-msgid "This schedule is missing required survey values"
-msgstr "Cette programmation d’horaire ne contient pas les valeurs d'enquête requises"
-
-#: components/Schedule/shared/UnsupportedScheduleForm.js:12
-msgid ""
-"This schedule uses complex rules that are not supported in the\n"
-"UI. Please use the API to manage this schedule."
-msgstr "Les programmations d’horaires complexes ne sont pas encore pris en charge par l'interface utilisateur, veuillez utiliser l'API pour gérer cette programmation d’horaire."
-
-#: components/LaunchPrompt/steps/StepName.js:26
-msgid "This step contains errors"
-msgstr "Cette étape contient des erreurs"
-
-#: screens/User/shared/UserForm.js:150
-msgid "This value does not match the password you entered previously. Please confirm that password."
-msgstr "Cette valeur ne correspond pas au mot de passe que vous avez entré précédemment. Veuillez confirmer ce mot de passe."
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:106
-msgid "This will cancel all subsequent nodes in this workflow"
-msgstr "Cela annulera tous les nœuds suivants dans ce flux de travail."
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:328
-msgid "This will cancel all subsequent nodes in this workflow."
-msgstr "Cela annulera tous les nœuds suivants dans ce flux de travail."
-
-#: screens/Setting/shared/RevertAllAlert.js:36
-msgid ""
-"This will revert all configuration values on this page to\n"
-"their factory defaults. Are you sure you want to proceed?"
-msgstr "Ceci rétablira toutes les valeurs de configuration sur cette page à\n"
-"à leurs valeurs par défaut. Êtes-vous sûr de vouloir continuer ?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:40
-msgid "This workflow does not have any nodes configured."
-msgstr "Ce flux de travail ne comporte aucun nœud configuré."
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:43
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:35
-msgid "This workflow has already been acted on"
-msgstr "Ce flux de travail a déjà été traité"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:267
-msgid "This workflow job template is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "Ce modèle de tâche de flux de travail est actuellement utilisé par d'autres ressources. Êtes-vous sûr de vouloir le supprimer ?"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:299
-msgid "Thu"
-msgstr "Jeu."
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:80
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:186
-#: components/Schedule/shared/FrequencyDetailSubform.js:304
-#: components/Schedule/shared/FrequencyDetailSubform.js:448
-msgid "Thursday"
-msgstr "Jeudi"
-
-#: screens/ActivityStream/ActivityStream.js:249
-#: screens/ActivityStream/ActivityStream.js:261
-#: screens/ActivityStream/ActivityStreamDetailButton.js:41
-#: screens/ActivityStream/ActivityStreamListItem.js:42
-msgid "Time"
-msgstr "Durée"
-
-#: screens/Project/shared/Project.helptext.js:128
-msgid ""
-"Time in seconds to consider a project\n"
-"to be current. During job runs and callbacks the task\n"
-"system will evaluate the timestamp of the latest project\n"
-"update. If it is older than Cache Timeout, it is not\n"
-"considered current, and a new project update will be\n"
-"performed."
-msgstr "Délai en secondes à prévoir pour qu’un projet soit actualisé. Durant l’exécution des tâches et les rappels, le système de tâches évalue l’horodatage de la dernière mise à jour du projet. Si elle est plus ancienne que le délai d’expiration du cache, elle n’est pas considérée comme actualisée, et une nouvelle mise à jour du projet sera effectuée."
-
-#: screens/Inventory/shared/Inventory.helptext.js:147
-msgid ""
-"Time in seconds to consider an inventory sync\n"
-"to be current. During job runs and callbacks the task system will\n"
-"evaluate the timestamp of the latest sync. If it is older than\n"
-"Cache Timeout, it is not considered current, and a new\n"
-"inventory sync will be performed."
-msgstr "Délai en secondes à prévoir pour qu'une synchronisation d'inventaire soit actualisée. Durant l’exécution du Job et les rappels, le système de tâches évalue l’horodatage de la dernière mise à jour du projet. Si elle est plus ancienne que le délai d’expiration du cache, elle n’est pas considérée comme actualisée, et une nouvelle synchronisation de l'inventaire sera effectuée."
-
-#: components/StatusLabel/StatusLabel.js:51
-msgid "Timed out"
-msgstr "Expiré"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:86
-#: components/PromptDetail/PromptDetail.js:136
-#: components/PromptDetail/PromptDetail.js:355
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:484
-#: screens/Job/JobDetail/JobDetail.js:397
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:170
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:114
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:277
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:186
-#: screens/Template/shared/JobTemplateForm.js:475
-msgid "Timeout"
-msgstr "Délai d'attente"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:193
-msgid "Timeout minutes"
-msgstr "Délai d'attente (minutes)"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:207
-msgid "Timeout seconds"
-msgstr "Délai d’attente (secondes)"
-
-#: screens/Host/HostList/SmartInventoryButton.js:20
-msgid "To create a smart inventory using ansible facts, go to the smart inventory screen."
-msgstr "Pour créer un inventaire smart, utiliser des facts ansibles, et rendez-vous sur l’écran d’inventaire smart."
-
-#: screens/Template/Survey/SurveyReorderModal.js:194
-msgid "To reorder the survey questions drag and drop them in the desired location."
-msgstr "Pour réorganiser les questions de l'enquête, faites-les glisser et déposez-les à l'endroit souhaité."
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:106
-msgid "Toggle Legend"
-msgstr "Basculer la légende"
-
-#: components/FormField/PasswordInput.js:39
-msgid "Toggle Password"
-msgstr "Changer de mot de passe"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:116
-msgid "Toggle Tools"
-msgstr "Basculer les outils"
-
-#: components/HostToggle/HostToggle.js:70
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:56
-msgid "Toggle host"
-msgstr "Basculer l'hôte"
-
-#: components/InstanceToggle/InstanceToggle.js:61
-msgid "Toggle instance"
-msgstr "Basculer l'instance"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:80
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:82
-#: screens/TopologyView/Header.js:99
-msgid "Toggle legend"
-msgstr "Basculer la légende"
-
-#: components/NotificationList/NotificationListItem.js:50
-msgid "Toggle notification approvals"
-msgstr "Basculer les approbations de notification"
-
-#: components/NotificationList/NotificationListItem.js:92
-msgid "Toggle notification failure"
-msgstr "Échec de la notification de basculement"
-
-#: components/NotificationList/NotificationListItem.js:64
-msgid "Toggle notification start"
-msgstr "Début de la notification de basculement"
-
-#: components/NotificationList/NotificationListItem.js:78
-msgid "Toggle notification success"
-msgstr "Succès de la notification de basculement"
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:66
-msgid "Toggle schedule"
-msgstr "Supprimer la programmation"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:92
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:94
-msgid "Toggle tools"
-msgstr "Basculer les outils"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:373
-#: screens/User/UserTokens/UserTokens.js:64
-msgid "Token"
-msgstr "Jeton"
-
-#: screens/User/UserTokens/UserTokens.js:50
-#: screens/User/UserTokens/UserTokens.js:53
-msgid "Token information"
-msgstr "Informations sur le jeton"
-
-#: screens/User/UserToken/UserToken.js:73
-msgid "Token not found."
-msgstr "Jeton non trouvé."
-
-#: screens/Application/Application/Application.js:80
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:105
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:128
-#: screens/Application/Applications.js:40
-#: screens/User/User.js:76
-#: screens/User/UserTokenList/UserTokenList.js:118
-#: screens/User/Users.js:34
-msgid "Tokens"
-msgstr "Jetons"
-
-#: components/Workflow/WorkflowTools.js:83
-msgid "Tools"
-msgstr "Outils"
-
-#: routeConfig.js:152
-#: screens/TopologyView/TopologyView.js:40
-msgid "Topology View"
-msgstr "Vue topologique"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:218
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:197
-#: screens/InstanceGroup/Instances/InstanceListItem.js:199
-#: screens/Instances/InstanceDetail/InstanceDetail.js:213
-#: screens/Instances/InstanceList/InstanceListItem.js:214
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:78
-msgid "Total Jobs"
-msgstr "Total Jobs"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:76
-msgid "Total Nodes"
-msgstr "Total Nœuds"
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:103
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:119
-msgid "Total hosts"
-msgstr "Total Hôtes"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:72
-msgid "Total jobs"
-msgstr "Total Jobs"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:87
-msgid "Track submodules"
-msgstr "Suivi des sous-modules"
-
-#: components/PromptDetail/PromptProjectDetail.js:56
-#: screens/Project/ProjectDetail/ProjectDetail.js:109
-msgid "Track submodules latest commit on branch"
-msgstr "Suivre le dernier commit des sous-modules sur la branche"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:141
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:168
-msgid "Trial"
-msgstr "Essai"
-
-#: components/JobList/JobListItem.js:319
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:66
-#: screens/Job/JobDetail/JobDetail.js:383
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:210
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:240
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:270
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:315
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:373
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:142
-msgid "True"
-msgstr "Vrai"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:277
-msgid "Tue"
-msgstr "Mar."
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:78
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:184
-#: components/Schedule/shared/FrequencyDetailSubform.js:282
-#: components/Schedule/shared/FrequencyDetailSubform.js:438
-msgid "Tuesday"
-msgstr "Mardi"
-
-#: components/NotificationList/NotificationList.js:201
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:142
-msgid "Twilio"
-msgstr "Twilio"
-
-#: components/JobList/JobList.js:246
-#: components/JobList/JobListItem.js:98
-#: components/Lookup/ProjectLookup.js:132
-#: components/NotificationList/NotificationList.js:219
-#: components/NotificationList/NotificationListItem.js:33
-#: components/PromptDetail/PromptDetail.js:124
-#: components/RelatedTemplateList/RelatedTemplateList.js:187
-#: components/TemplateList/TemplateList.js:214
-#: components/TemplateList/TemplateList.js:243
-#: components/TemplateList/TemplateListItem.js:184
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:85
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:154
-#: components/Workflow/WorkflowNodeHelp.js:160
-#: components/Workflow/WorkflowNodeHelp.js:196
-#: screens/Credential/CredentialList/CredentialList.js:165
-#: screens/Credential/CredentialList/CredentialListItem.js:63
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:94
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:116
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:17
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:46
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:54
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:195
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:66
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:94
-#: screens/Inventory/InventoryList/InventoryList.js:220
-#: screens/Inventory/InventoryList/InventoryListItem.js:116
-#: screens/Inventory/InventorySources/InventorySourceList.js:213
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:100
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:105
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:180
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:120
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:68
-#: screens/Project/ProjectList/ProjectList.js:194
-#: screens/Project/ProjectList/ProjectList.js:223
-#: screens/Project/ProjectList/ProjectListItem.js:218
-#: screens/Team/TeamRoles/TeamRoleListItem.js:17
-#: screens/Team/TeamRoles/TeamRolesList.js:181
-#: screens/Template/Survey/SurveyList.js:103
-#: screens/Template/Survey/SurveyList.js:103
-#: screens/Template/Survey/SurveyListItem.js:60
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:93
-#: screens/User/UserDetail/UserDetail.js:75
-#: screens/User/UserRoles/UserRolesList.js:156
-#: screens/User/UserRoles/UserRolesListItem.js:21
-msgid "Type"
-msgstr "Type"
-
-#: screens/Credential/shared/TypeInputsSubForm.js:25
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:47
-#: screens/Project/shared/ProjectForm.js:298
-msgid "Type Details"
-msgstr "Détails sur le type"
-
-#: screens/Template/Survey/MultipleChoiceField.js:56
-msgid ""
-"Type answer then click checkbox on right to select answer as\n"
-"default."
-msgstr "Saisir la réponse puis cliquez sur la case à cocher à droite pour sélectionner la réponse comme défaut."
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:50
-msgid "UTC"
-msgstr "UTC"
-
-#: components/HostForm/HostForm.js:62
-msgid "Unable to change inventory on a host"
-msgstr "Impossible de modifier l'inventaire sur un hôte."
-
-#: screens/Project/ProjectList/ProjectListItem.js:211
-msgid "Unable to load last job update"
-msgstr "Impossible de charger la dernière mise à jour du job"
-
-#: components/StatusLabel/StatusLabel.js:61
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:260
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:87
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:46
-#: screens/InstanceGroup/Instances/InstanceListItem.js:78
-#: screens/Instances/InstanceDetail/InstanceDetail.js:306
-#: screens/Instances/InstanceList/InstanceListItem.js:77
-#: screens/TopologyView/Tooltip.js:121
-msgid "Unavailable"
-msgstr "Non disponible"
-
-#: screens/Setting/shared/RevertButton.js:53
-#: screens/Setting/shared/RevertButton.js:62
-msgid "Undo"
-msgstr "Annuler"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:183
-msgid "Unfollow"
-msgstr "Ne plus suivre"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:126
-msgid "Unlimited"
-msgstr "Illimité"
-
-#: components/StatusLabel/StatusLabel.js:47
-#: screens/Job/JobOutput/shared/HostStatusBar.js:51
-#: screens/Job/JobOutput/shared/OutputToolbar.js:103
-msgid "Unreachable"
-msgstr "Inaccessible"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:102
-msgid "Unreachable Host Count"
-msgstr "Nombre d'hôtes inaccessibles"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:104
-msgid "Unreachable Hosts"
-msgstr "Hôtes inaccessibles"
-
-#: util/dates.js:74
-msgid "Unrecognized day string"
-msgstr "Chaîne du jour non reconnue"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:15
-msgid "Unsaved changes modal"
-msgstr "Annuler les modifications non enregistrées"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:94
-msgid "Update Revision on Launch"
-msgstr "Mettre à jour Révision au lancement"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:51
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:133
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:92
-msgid "Update on launch"
-msgstr "Mettre à jour au lancement"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:75
-msgid "Update options"
-msgstr "Mettre à jour les options"
-
-#: components/PromptDetail/PromptProjectDetail.js:61
-#: screens/Project/ProjectDetail/ProjectDetail.js:115
-msgid "Update revision on job launch"
-msgstr "Mettre à jour Révision au lancement"
-
-#: screens/Setting/SettingList.js:92
-msgid "Update settings pertaining to Jobs within {brandName}"
-msgstr "Mettre à jour les paramètres relatifs aux Jobs dans {brandName}"
-
-#: screens/Template/shared/WebhookSubForm.js:188
-msgid "Update webhook key"
-msgstr "Mettre à jour la clé de webhook"
-
-#: components/Workflow/WorkflowNodeHelp.js:126
-msgid "Updating"
-msgstr "Mise à jour en cours"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:118
-msgid "Upload a .zip file"
-msgstr "Télécharger un fichier .zip"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:97
-msgid "Upload a Red Hat Subscription Manifest containing your subscription. To generate your subscription manifest, go to <0>subscription allocations0> on the Red Hat Customer Portal."
-msgstr "Téléchargez un manifeste d'abonnement Red Hat contenant votre abonnement. Pour générer votre manifeste d'abonnement, accédez à <0>subscription allocations0> (octroi d’allocations) sur le portail client de Red Hat."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:53
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:132
-msgid "Use SSL"
-msgstr "Utiliser SSL"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:58
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:137
-msgid "Use TLS"
-msgstr "Utiliser TLS"
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:72
-msgid ""
-"Use custom messages to change the content of\n"
-"notifications sent when a job starts, succeeds, or fails. Use\n"
-"curly braces to access information about the job:"
-msgstr "Utilisez des messages personnalisés pour modifier le contenu des notifications envoyées lorsqu'un job démarre, réussit ou échoue. Utilisez des parenthèses en accolade pour accéder aux informations sur le job :"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:12
-msgid "Use one Annotation Tag per line, without commas."
-msgstr "Entrez une balise d'annotation par ligne, sans virgule."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:13
-msgid ""
-"Use one IRC channel or username per line. The pound\n"
-"symbol (#) for channels, and the at (@) symbol for users, are not\n"
-"required."
-msgstr "Saisir un canal IRC ou un nom d'utilisateur par ligne. Le symbole dièse (#) pour les canaux et (@) pour le utilisateurs, ne sont pas nécessaires."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:5
-msgid "Use one email address per line to create a recipient list for this type of notification."
-msgstr "Saisir une adresse email par ligne pour créer une liste des destinataires pour ce type de notification."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:28
-msgid ""
-"Use one phone number per line to specify where to\n"
-"route SMS messages. Phone numbers should be formatted +11231231234. For more information see Twilio documentation"
-msgstr "Saisissez un numéro de téléphone par ligne pour indiquer où acheminer les messages SMS. Les numéros de téléphone doivent être formatés ainsi +11231231234. Pour plus d'informations, voir la documentation de Twilio"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:249
-#: screens/InstanceGroup/Instances/InstanceList.js:270
-#: screens/Instances/InstanceDetail/InstanceDetail.js:292
-#: screens/Instances/InstanceList/InstanceList.js:205
-msgid "Used Capacity"
-msgstr "Capacité utilisée"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:253
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:257
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:78
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:86
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:42
-#: screens/InstanceGroup/Instances/InstanceListItem.js:74
-#: screens/Instances/InstanceDetail/InstanceDetail.js:297
-#: screens/Instances/InstanceDetail/InstanceDetail.js:303
-#: screens/Instances/InstanceList/InstanceListItem.js:73
-#: screens/TopologyView/Tooltip.js:117
-msgid "Used capacity"
-msgstr "Capacité utilisée"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:13
-msgid "User"
-msgstr "Utilisateur"
-
-#: components/AppContainer/PageHeaderToolbar.js:160
-msgid "User Details"
-msgstr "Détails de l'erreur"
-
-#: screens/Setting/SettingList.js:121
-#: screens/Setting/Settings.js:118
-msgid "User Interface"
-msgstr "Interface utilisateur"
-
-#: screens/Setting/SettingList.js:126
-msgid "User Interface settings"
-msgstr "Paramètres de l'interface utilisateur"
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:72
-#: screens/User/UserRoles/UserRolesList.js:142
-msgid "User Roles"
-msgstr "Rôles des utilisateurs"
-
-#: screens/User/UserDetail/UserDetail.js:72
-#: screens/User/shared/UserForm.js:119
-msgid "User Type"
-msgstr "Type d’utilisateur"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:59
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:60
-msgid "User analytics"
-msgstr "Analyse des utilisateurs"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:34
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:202
-msgid "User and Automation Analytics"
-msgstr "Utilisateur & Automation Analytics"
-
-#: components/AppContainer/PageHeaderToolbar.js:154
-msgid "User details"
-msgstr "Informations sur l'utilisateur"
-
-#: screens/User/User.js:96
-msgid "User not found."
-msgstr "Utilisateur non trouvé."
-
-#: screens/User/UserTokenList/UserTokenList.js:180
-msgid "User tokens"
-msgstr "Jetons d'utilisateur"
-
-#: components/AddRole/AddResourceRole.js:23
-#: components/AddRole/AddResourceRole.js:38
-#: components/ResourceAccessList/ResourceAccessList.js:173
-#: components/ResourceAccessList/ResourceAccessList.js:226
-#: screens/Login/Login.js:230
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:144
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:253
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:303
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:361
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:67
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:260
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:337
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:442
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:92
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:204
-#: screens/User/UserDetail/UserDetail.js:68
-#: screens/User/UserList/UserList.js:120
-#: screens/User/UserList/UserList.js:160
-#: screens/User/UserList/UserListItem.js:38
-#: screens/User/shared/UserForm.js:76
-msgid "Username"
-msgstr "Nom d'utilisateur"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:88
-msgid "Username / password"
-msgstr "Nom d'utilisateur / mot de passe"
-
-#: components/AddRole/AddResourceRole.js:178
-#: components/AddRole/AddResourceRole.js:179
-#: routeConfig.js:101
-#: screens/ActivityStream/ActivityStream.js:184
-#: screens/Team/Teams.js:30
-#: screens/User/UserList/UserList.js:110
-#: screens/User/UserList/UserList.js:153
-#: screens/User/Users.js:15
-#: screens/User/Users.js:26
-msgid "Users"
-msgstr "Utilisateurs"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:101
-msgid "VMware vCenter"
-msgstr "VMware vCenter"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:69
-#: components/HostForm/HostForm.js:113
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:115
-#: components/PromptDetail/PromptDetail.js:168
-#: components/PromptDetail/PromptDetail.js:369
-#: components/PromptDetail/PromptJobTemplateDetail.js:286
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:135
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:620
-#: screens/Host/HostDetail/HostDetail.js:93
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:165
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:37
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:88
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:143
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:53
-#: screens/Inventory/shared/InventoryForm.js:108
-#: screens/Inventory/shared/InventoryGroupForm.js:46
-#: screens/Inventory/shared/SmartInventoryForm.js:93
-#: screens/Job/JobDetail/JobDetail.js:546
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:501
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:226
-#: screens/Template/shared/JobTemplateForm.js:402
-#: screens/Template/shared/WorkflowJobTemplateForm.js:212
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:293
-msgid "Variables"
-msgstr "Variables"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:130
-msgid "Variables Prompted"
-msgstr "Variables demandées"
-
-#: screens/Inventory/shared/Inventory.helptext.js:43
-msgid "Variables must be in JSON or YAML syntax. Use the radio button to toggle between the two."
-msgstr "Variables avec la syntaxe JSON ou YAML. Utilisez le bouton radio pour basculer entre les deux."
-
-#: screens/Inventory/shared/Inventory.helptext.js:166
-msgid "Variables used to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>{sourceType}1> plugin configuration guide."
-msgstr "Variables pour configurer la source de l'inventaire. Pour une description détaillée de la configuration de ce plugin, voir les <0>Plugins d’inventaire0> dans la documentation et dans le guide de configuration du Plugin <1>{sourceType}1>."
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:121
-msgid "Vault password"
-msgstr "Mot de passe Archivage sécurisé"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:121
-msgid "Vault password | {credId}"
-msgstr "Mot de passe Archivage sécurisé | {credId}"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:131
-msgid "Verbose"
-msgstr "Verbeux"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:63
-#: components/PromptDetail/PromptDetail.js:260
-#: components/PromptDetail/PromptInventorySourceDetail.js:100
-#: components/PromptDetail/PromptJobTemplateDetail.js:154
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:478
-#: components/VerbositySelectField/VerbositySelectField.js:34
-#: components/VerbositySelectField/VerbositySelectField.js:45
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:232
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:47
-#: screens/Job/JobDetail/JobDetail.js:331
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:271
-msgid "Verbosity"
-msgstr "Verbosité"
-
-#: screens/Setting/AzureAD/AzureAD.js:25
-msgid "View Azure AD settings"
-msgstr "Voir les paramètres Azure AD"
-
-#: screens/Credential/Credential.js:143
-#: screens/Credential/Credential.js:155
-msgid "View Credential Details"
-msgstr "Afficher les détails des informations d'identification"
-
-#: components/Schedule/Schedule.js:151
-msgid "View Details"
-msgstr "Voir les détails"
-
-#: screens/Setting/GitHub/GitHub.js:58
-msgid "View GitHub Settings"
-msgstr "Voir les paramètres de GitHub"
-
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2.js:26
-msgid "View Google OAuth 2.0 settings"
-msgstr "Voir les paramètres de Google OAuth 2.0"
-
-#: screens/Host/Host.js:137
-msgid "View Host Details"
-msgstr "Voir les détails de l'hôte"
-
-#: screens/Instances/Instance.js:78
-msgid "View Instance Details"
-msgstr "Voir les détails de l'instance"
-
-#: screens/Inventory/Inventory.js:192
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:142
-#: screens/Inventory/SmartInventory.js:175
-msgid "View Inventory Details"
-msgstr "Voir les détails de l'inventaire"
-
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:92
-msgid "View Inventory Groups"
-msgstr "Voir les groupes d'inventaire"
-
-#: screens/Inventory/InventoryHost/InventoryHost.js:160
-msgid "View Inventory Host Details"
-msgstr "Voir les détails de l'hôte de l'inventaire"
-
-#: screens/Inventory/shared/Inventory.helptext.js:55
-msgid "View JSON examples at <0>www.json.org0>"
-msgstr "Voir les exemples de JSON sur <0>www.json.org0>"
-
-#: screens/Job/Job.js:212
-msgid "View Job Details"
-msgstr "Voir les détails de Job"
-
-#: screens/Setting/Jobs/Jobs.js:25
-msgid "View Jobs settings"
-msgstr "Voir les paramètres des Jobs"
-
-#: screens/Setting/LDAP/LDAP.js:38
-msgid "View LDAP Settings"
-msgstr "Voir les paramètres LDAP"
-
-#: screens/Setting/Logging/Logging.js:32
-msgid "View Logging settings"
-msgstr "Voir les paramètres d'enregistrement"
-
-#: screens/Setting/MiscAuthentication/MiscAuthentication.js:32
-msgid "View Miscellaneous Authentication settings"
-msgstr "Afficher les paramètres d'authentification divers"
-
-#: screens/Setting/MiscSystem/MiscSystem.js:32
-msgid "View Miscellaneous System settings"
-msgstr "Voir les paramètres divers du système"
-
-#: screens/Setting/OIDC/OIDC.js:25
-msgid "View OIDC settings"
-msgstr "Voir les paramètres de l'OIDC"
-
-#: screens/Organization/Organization.js:225
-msgid "View Organization Details"
-msgstr "Voir les détails de l'organisation"
-
-#: screens/Project/Project.js:200
-msgid "View Project Details"
-msgstr "Voir les détails du projet"
-
-#: screens/Setting/RADIUS/RADIUS.js:25
-msgid "View RADIUS settings"
-msgstr "Voir les paramètres de RADIUS"
-
-#: screens/Setting/SAML/SAML.js:25
-msgid "View SAML settings"
-msgstr "Voir les paramètres SAML"
-
-#: components/Schedule/Schedule.js:83
-#: components/Schedule/Schedule.js:101
-msgid "View Schedules"
-msgstr "Afficher les programmations"
-
-#: screens/Setting/Subscription/Subscription.js:30
-msgid "View Settings"
-msgstr "Afficher les paramètres"
-
-#: screens/Template/Template.js:159
-#: screens/Template/WorkflowJobTemplate.js:145
-msgid "View Survey"
-msgstr "Afficher le questionnaire"
-
-#: screens/Setting/TACACS/TACACS.js:25
-msgid "View TACACS+ settings"
-msgstr "Voir les paramètres TACACS+"
-
-#: screens/Team/Team.js:118
-msgid "View Team Details"
-msgstr "Voir les détails de l'équipe"
-
-#: screens/Template/Template.js:260
-#: screens/Template/WorkflowJobTemplate.js:275
-msgid "View Template Details"
-msgstr "Voir les détails du modèle"
-
-#: screens/User/UserToken/UserToken.js:100
-msgid "View Tokens"
-msgstr "Voir les jetons"
-
-#: screens/User/User.js:141
-msgid "View User Details"
-msgstr "Voir les détails de l'utilisateur"
-
-#: screens/Setting/UI/UI.js:26
-msgid "View User Interface settings"
-msgstr "Voir les paramètres de l'interface utilisateur"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:105
-msgid "View Workflow Approval Details"
-msgstr "Voir les détails pour l'approbation du flux de travail"
-
-#: screens/Inventory/shared/Inventory.helptext.js:66
-msgid "View YAML examples at <0>docs.ansible.com0>"
-msgstr "Voir les exemples YALM sur <0>docs.ansible.com0>"
-
-#: components/ScreenHeader/ScreenHeader.js:65
-#: components/ScreenHeader/ScreenHeader.js:68
-msgid "View activity stream"
-msgstr "Afficher le flux d’activité"
-
-#: screens/Credential/Credential.js:99
-msgid "View all Credentials."
-msgstr "Voir toutes les informations d’identification."
-
-#: screens/Host/Host.js:97
-msgid "View all Hosts."
-msgstr "Voir tous les hôtes."
-
-#: screens/Inventory/Inventory.js:95
-#: screens/Inventory/SmartInventory.js:95
-msgid "View all Inventories."
-msgstr "Voir tous les inventaires."
-
-#: screens/Inventory/InventoryHost/InventoryHost.js:101
-msgid "View all Inventory Hosts."
-msgstr "Voir tous les hôtes de l'inventaire."
-
-#: screens/Job/JobTypeRedirect.js:40
-msgid "View all Jobs"
-msgstr "Voir tous les Jobs"
-
-#: screens/Job/Job.js:162
-msgid "View all Jobs."
-msgstr "Voir tous les Jobs."
-
-#: screens/NotificationTemplate/NotificationTemplate.js:60
-#: screens/NotificationTemplate/NotificationTemplateAdd.js:52
-msgid "View all Notification Templates."
-msgstr "Voir tous les modèles de notification."
-
-#: screens/Organization/Organization.js:155
-msgid "View all Organizations."
-msgstr "Voir toutes les organisations."
-
-#: screens/Project/Project.js:137
-msgid "View all Projects."
-msgstr "Voir tous les projets."
-
-#: screens/Team/Team.js:76
-msgid "View all Teams."
-msgstr "Voir toutes les équipes."
-
-#: screens/Template/Template.js:176
-#: screens/Template/WorkflowJobTemplate.js:176
-msgid "View all Templates."
-msgstr "Voir tous les modèles."
-
-#: screens/User/User.js:97
-msgid "View all Users."
-msgstr "Voir tous les utilisateurs."
-
-#: screens/WorkflowApproval/WorkflowApproval.js:54
-msgid "View all Workflow Approvals."
-msgstr "Voir toutes les approbations de flux de travail."
-
-#: screens/Application/Application/Application.js:96
-msgid "View all applications."
-msgstr "Voir toutes les applications."
-
-#: screens/CredentialType/CredentialType.js:78
-msgid "View all credential types"
-msgstr "Voir tous les types d'informations d'identification"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:85
-msgid "View all execution environments"
-msgstr "Voir tous les environnements d'exécution"
-
-#: screens/InstanceGroup/ContainerGroup.js:86
-#: screens/InstanceGroup/InstanceGroup.js:94
-msgid "View all instance groups"
-msgstr "Voir tous les groupes d'instance"
-
-#: screens/ManagementJob/ManagementJob.js:135
-msgid "View all management jobs"
-msgstr "Voir tous les jobs de gestion"
-
-#: screens/Setting/Settings.js:204
-msgid "View all settings"
-msgstr "Voir tous les paramètres"
-
-#: screens/User/UserToken/UserToken.js:74
-msgid "View all tokens."
-msgstr "Voir tous les jetons."
-
-#: screens/Setting/SettingList.js:133
-msgid "View and edit your subscription information"
-msgstr "Afficher et modifier les informations relatives à votre abonnement"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:25
-#: screens/ActivityStream/ActivityStreamListItem.js:50
-msgid "View event details"
-msgstr "Afficher les détails de l’événement"
-
-#: screens/Inventory/InventorySource/InventorySource.js:167
-msgid "View inventory source details"
-msgstr "Voir les détails de la source de l'inventaire"
-
-#: components/Sparkline/Sparkline.js:44
-msgid "View job {0}"
-msgstr "Voir Job {0}"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:220
-msgid "View node details"
-msgstr "Voir les détails de nœuds"
-
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:85
-msgid "View smart inventory host details"
-msgstr "Voir les détails de l'hôte de l'inventaire smart"
-
-#: routeConfig.js:30
-#: screens/ActivityStream/ActivityStream.js:145
-msgid "Views"
-msgstr "Affichages"
-
-#: components/TemplateList/TemplateListItem.js:198
-#: components/TemplateList/TemplateListItem.js:204
-#: screens/Template/WorkflowJobTemplate.js:137
-msgid "Visualizer"
-msgstr "Visualiseur"
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:44
-msgid "WARNING:"
-msgstr "AVERTISSEMENT :"
-
-#: components/JobList/JobList.js:229
-#: components/StatusLabel/StatusLabel.js:52
-#: components/Workflow/WorkflowNodeHelp.js:96
-msgid "Waiting"
-msgstr "En attente"
-
-#: screens/Job/JobOutput/EmptyOutput.js:35
-msgid "Waiting for job output…"
-msgstr "En attente du résultat du job…"
-
-#: components/Workflow/WorkflowLegend.js:118
-#: screens/Job/JobOutput/JobOutputSearch.js:132
-msgid "Warning"
-msgstr "Avertissement"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:14
-msgid "Warning: Unsaved Changes"
-msgstr "Avertissement : modifications non enregistrées"
-
-#: components/Schedule/shared/ScheduleFormFields.js:43
-msgid "Warning: {selectedValue} is a link to {0} and will be saved as that."
-msgstr "Avertissement : {selectedValue} est un lien vers {0} et sera enregistré comme tel."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:119
-msgid "We were unable to locate licenses associated with this account."
-msgstr "Nous n'avons pas pu localiser les licences associées à ce compte."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:138
-msgid "We were unable to locate subscriptions associated with this account."
-msgstr "Nous n'avons pas pu localiser les abonnements associés à ce compte."
-
-#: components/DetailList/LaunchedByDetail.js:24
-#: components/NotificationList/NotificationList.js:202
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:143
-msgid "Webhook"
-msgstr "Webhook"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:177
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:103
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:333
-#: screens/Template/shared/WebhookSubForm.js:199
-msgid "Webhook Credential"
-msgstr "Informations d'identification du webhook"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:175
-msgid "Webhook Credentials"
-msgstr "Informations d'identification du webhook"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:173
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:92
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:326
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:168
-#: screens/Template/shared/WebhookSubForm.js:173
-msgid "Webhook Key"
-msgstr "Clé du webhook"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:166
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:91
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:311
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:156
-#: screens/Template/shared/WebhookSubForm.js:129
-msgid "Webhook Service"
-msgstr "Service webhook"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:169
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:95
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:319
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:162
-#: screens/Template/shared/WebhookSubForm.js:161
-#: screens/Template/shared/WebhookSubForm.js:167
-msgid "Webhook URL"
-msgstr "URL du webhook"
-
-#: screens/Template/shared/JobTemplateForm.js:646
-#: screens/Template/shared/WorkflowJobTemplateForm.js:271
-msgid "Webhook details"
-msgstr "Détails de webhook"
-
-#: screens/Template/shared/JobTemplate.helptext.js:24
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:17
-msgid "Webhook services can launch jobs with this workflow job template by making a POST request to this URL."
-msgstr "Les services webhook peuvent lancer des tâches avec ce modèle de tâche en effectuant une requête POST à cette URL."
-
-#: screens/Template/shared/JobTemplate.helptext.js:25
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:18
-msgid "Webhook services can use this as a shared secret."
-msgstr "Les services webhook peuvent l'utiliser en tant que secret partagé."
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:78
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:41
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:142
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:63
-msgid "Webhooks"
-msgstr "Webhooks"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:26
-msgid "Webhooks: Enable Webhook for this workflow job template."
-msgstr "Webhooks : activez le webhook pour ce modèle de flux de travail."
-
-#: screens/Template/shared/JobTemplate.helptext.js:42
-msgid "Webhooks: Enable webhook for this template."
-msgstr "Webhooks ; activez le webhook pour ce modèle."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:288
-msgid "Wed"
-msgstr "Mer."
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:79
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:185
-#: components/Schedule/shared/FrequencyDetailSubform.js:293
-#: components/Schedule/shared/FrequencyDetailSubform.js:443
-msgid "Wednesday"
-msgstr "Mercredi"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:185
-#: components/Schedule/shared/FrequencyDetailSubform.js:179
-#: components/Schedule/shared/ScheduleFormFields.js:128
-#: components/Schedule/shared/ScheduleFormFields.js:188
-msgid "Week"
-msgstr "Semaine"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:464
-msgid "Weekday"
-msgstr "Jour de la semaine"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:469
-msgid "Weekend day"
-msgstr "Jour du week-end"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:59
-msgid ""
-"Welcome to Red Hat Ansible Automation Platform!\n"
-"Please complete the steps below to activate your subscription."
-msgstr "Bienvenue sur la plate-forme Red Hat Ansible Automation ! Veuillez compléter les étapes ci-dessous pour activer votre abonnement."
-
-#: screens/Login/Login.js:190
-msgid "Welcome to {brandName}!"
-msgstr "Bienvenue sur {brandName}!"
-
-#: screens/Inventory/shared/Inventory.helptext.js:105
-msgid ""
-"When not checked, a merge will be performed,\n"
-"combining local variables with those found on the\n"
-"external source."
-msgstr "Si non coché, une fusion aura lieu, combinant les variables locales à celles qui se trouvent dans la source externe."
-
-#: screens/Inventory/shared/Inventory.helptext.js:93
-msgid ""
-"When not checked, local child\n"
-"hosts and groups not found on the external source will remain\n"
-"untouched by the inventory update process."
-msgstr "Si non coché, les hôtes et groupes locaux dépendants non trouvés dans la source externe ne seront pas touchés par le processus de mise à jour de l'inventaire."
-
-#: components/Workflow/WorkflowLegend.js:96
-msgid "Workflow"
-msgstr "Flux de travail"
-
-#: components/Workflow/WorkflowNodeHelp.js:75
-msgid "Workflow Approval"
-msgstr "Approbation du flux de travail"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:52
-msgid "Workflow Approval not found."
-msgstr "Approbation du flux de travail non trouvée."
-
-#: routeConfig.js:54
-#: screens/ActivityStream/ActivityStream.js:156
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:118
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:145
-#: screens/WorkflowApproval/WorkflowApprovals.js:13
-#: screens/WorkflowApproval/WorkflowApprovals.js:22
-msgid "Workflow Approvals"
-msgstr "Approbations des flux de travail"
-
-#: components/JobList/JobList.js:216
-#: components/JobList/JobListItem.js:47
-#: components/Schedule/ScheduleList/ScheduleListItem.js:40
-#: screens/Job/JobDetail/JobDetail.js:70
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:224
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:164
-msgid "Workflow Job"
-msgstr "Job de flux de travail"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:76
-msgid "Workflow Job 1/{0}"
-msgstr "Job de flux de travail 1/{0}"
-
-#: components/JobList/JobListItem.js:201
-#: components/Workflow/WorkflowNodeHelp.js:63
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:20
-#: screens/Job/JobDetail/JobDetail.js:244
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:91
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:186
-#: util/getRelatedResourceDeleteDetails.js:105
-msgid "Workflow Job Template"
-msgstr "Modèle de Job de flux de travail"
-
-#: util/getRelatedResourceDeleteDetails.js:115
-#: util/getRelatedResourceDeleteDetails.js:157
-#: util/getRelatedResourceDeleteDetails.js:260
-msgid "Workflow Job Template Nodes"
-msgstr "Nœuds de modèles de Jobs de workflows"
-
-#: util/getRelatedResourceDeleteDetails.js:140
-msgid "Workflow Job Templates"
-msgstr "Modèles de Jobs de flux de travail"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:23
-msgid "Workflow Link"
-msgstr "Lien vers le flux de travail"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:100
-msgid "Workflow Nodes"
-msgstr "Nœuds de flux de travail"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:86
-msgid "Workflow Statuses"
-msgstr "Statuts du flux de travail"
-
-#: components/TemplateList/TemplateList.js:218
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:98
-msgid "Workflow Template"
-msgstr "Modèle de flux de travail"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:519
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:159
-msgid "Workflow approved message"
-msgstr "Message de flux de travail approuvé"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:531
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:168
-msgid "Workflow approved message body"
-msgstr "Corps de message de flux de travail approuvé"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:543
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:177
-msgid "Workflow denied message"
-msgstr "Message de flux de travail refusé"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:555
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:186
-msgid "Workflow denied message body"
-msgstr "Corps de message de flux de travail refusé"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:106
-msgid "Workflow documentation"
-msgstr "Documentation de flux de travail"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:220
-msgid "Workflow job details"
-msgstr "Voir les détails de Job de flux de travail"
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:46
-msgid "Workflow job templates"
-msgstr "Modèles de Jobs de flux de travail"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:24
-msgid "Workflow link modal"
-msgstr "Modal de liaison de flux de travail"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:247
-msgid "Workflow node view modal"
-msgstr "Vue modale du nœud de flux de travail"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:567
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:195
-msgid "Workflow pending message"
-msgstr "Message de flux de travail en attente"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:579
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:204
-msgid "Workflow pending message body"
-msgstr "Corps du message d'exécution de flux de travail"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:591
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:213
-msgid "Workflow timed out message"
-msgstr "Message d'expiration de flux de travail"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:603
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:222
-msgid "Workflow timed out message body"
-msgstr "Corps du message d’expiration de flux de travail"
-
-#: screens/User/shared/UserTokenForm.js:77
-msgid "Write"
-msgstr "Écriture"
-
-#: screens/Inventory/shared/Inventory.helptext.js:52
-msgid "YAML:"
-msgstr "YAML :"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:187
-#: components/Schedule/shared/FrequencyDetailSubform.js:183
-#: components/Schedule/shared/ScheduleFormFields.js:130
-#: components/Schedule/shared/ScheduleFormFields.js:190
-msgid "Year"
-msgstr "Année"
-
-#: components/Search/Search.js:229
-msgid "Yes"
-msgstr "Oui"
-
-#: components/Lookup/MultiCredentialsLookup.js:155
-msgid "You cannot select multiple vault credentials with the same vault ID. Doing so will automatically deselect the other with the same vault ID."
-msgstr "Vous ne pouvez pas sélectionner plusieurs identifiants d’archivage sécurisé (Vault) avec le même identifiant de d’archivage sécurisé. Cela désélectionnerait automatiquement les autres identifiants d’archivage sécurisé."
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:96
-msgid "You do not have permission to delete the following Groups: {itemsUnableToDelete}"
-msgstr "Vous n'avez pas la permission de supprimer les groupes suivants : {itemsUnableToDelete}"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:152
-msgid "You do not have permission to delete {pluralizedItemName}: {itemsUnableToDelete}"
-msgstr "Vous n'avez pas l'autorisation de supprimer : {pluralizedItemName}: {itemsUnableToDelete}"
-
-#: components/DisassociateButton/DisassociateButton.js:66
-msgid "You do not have permission to disassociate the following: {itemsUnableToDisassociate}"
-msgstr "Vous n'avez pas la permission de dissocier les éléments suivants : {itemsUnableToDisassociate}"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:72
-msgid "You do not have permission to remove instances: {itemsUnableToremove}"
-msgstr "Vous n'avez pas de permission pour supprimer les ressources: {itemsUnableToremove}"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:82
-msgid "You have automated against more hosts than your subscription allows."
-msgstr "Vous avez automatisé contre plus d'hôtes que votre abonnement ne le permet."
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:87
-msgid ""
-"You may apply a number of possible variables in the\n"
-"message. For more information, refer to the"
-msgstr "Vous pouvez appliquer un certain nombre de variables possibles dans le message. Pour plus d'informations, reportez-vous au"
-
-#: screens/Login/Login.js:198
-msgid "Your session has expired. Please log in to continue where you left off."
-msgstr "Votre session a expiré. Veuillez vous connecter pour continuer là où vous vous êtes arrêté."
-
-#: components/AppContainer/AppContainer.js:130
-msgid "Your session is about to expire"
-msgstr "Votre session est sur le point d'expirer"
-
-#: components/Workflow/WorkflowTools.js:121
-msgid "Zoom In"
-msgstr "Zoom avant"
-
-#: components/Workflow/WorkflowTools.js:100
-msgid "Zoom Out"
-msgstr "Zoom arrière"
-
-#: screens/TopologyView/Header.js:51
-#: screens/TopologyView/Header.js:54
-msgid "Zoom in"
-msgstr "Zoom avant"
-
-#: screens/TopologyView/Header.js:63
-#: screens/TopologyView/Header.js:66
-msgid "Zoom out"
-msgstr "Zoom arrière"
-
-#: screens/Template/shared/JobTemplateForm.js:754
-#: screens/Template/shared/WebhookSubForm.js:150
-msgid "a new webhook key will be generated on save."
-msgstr "une nouvelle clé webhook sera générée lors de la sauvegarde."
-
-#: screens/Template/shared/JobTemplateForm.js:751
-#: screens/Template/shared/WebhookSubForm.js:140
-msgid "a new webhook url will be generated on save."
-msgstr "une nouvelle url de webhook sera générée lors de la sauvegarde."
-
-#: screens/Inventory/shared/Inventory.helptext.js:123
-#: screens/Inventory/shared/Inventory.helptext.js:142
-msgid "and click on Update Revision on Launch"
-msgstr "et cliquez sur Mise à jour de la révision au lancement"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:505
-msgid "approved"
-msgstr "approuvé"
-
-#: components/AppContainer/AppContainer.js:55
-msgid "brand logo"
-msgstr "logo de la marque"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:279
-#: screens/Template/Survey/SurveyList.js:72
-msgid "cancel delete"
-msgstr "annuler supprimer"
-
-#: screens/Setting/shared/SharedFields.js:341
-msgid "cancel edit login redirect"
-msgstr "annuler modifier connecter rediriger"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:169
-msgid "cancel remove"
-msgstr "annuler la suppression"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:34
-msgid "canceled"
-msgstr "annulé"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:217
-msgid "command"
-msgstr "commande"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:267
-#: screens/Template/Survey/SurveyList.js:63
-msgid "confirm delete"
-msgstr "confirmer supprimer"
-
-#: components/DisassociateButton/DisassociateButton.js:130
-#: screens/Team/TeamRoles/TeamRolesList.js:219
-msgid "confirm disassociate"
-msgstr "confirmer dissocier"
-
-#: screens/Setting/shared/SharedFields.js:330
-msgid "confirm edit login redirect"
-msgstr "confirmer modifier connecter rediriger"
-
-#: screens/TopologyView/ContentLoading.js:32
-msgid "content-loading-in-progress"
-msgstr "chargement-contenu-en-cours"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:189
-msgid "day"
-msgstr "jour"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:151
-msgid "deletion error"
-msgstr "erreur de suppression"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:513
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:37
-msgid "denied"
-msgstr "refusé"
-
-#: screens/Job/JobOutput/EmptyOutput.js:41
-msgid "details."
-msgstr "détails"
-
-#: components/DisassociateButton/DisassociateButton.js:91
-msgid "disassociate"
-msgstr "dissocier"
-
-#: components/Lookup/HostFilterLookup.js:406
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:20
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:39
-#: screens/Template/Survey/SurveyQuestionForm.js:269
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:239
-#: screens/Template/shared/JobTemplate.helptext.js:61
-msgid "documentation"
-msgstr "documentation"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:105
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:117
-#: screens/Host/HostDetail/HostDetail.js:104
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:97
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:109
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:99
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:289
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:161
-#: screens/Project/ProjectDetail/ProjectDetail.js:309
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:195
-#: screens/User/UserDetail/UserDetail.js:92
-msgid "edit"
-msgstr "modifier"
-
-#: screens/Template/Survey/SurveyListItem.js:65
-#: screens/Template/Survey/SurveyReorderModal.js:125
-msgid "encrypted"
-msgstr "crypté"
-
-#: components/Lookup/HostFilterLookup.js:408
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:241
-msgid "for more info."
-msgstr "pour plus d'infos."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:21
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:40
-#: screens/Template/Survey/SurveyQuestionForm.js:271
-#: screens/Template/shared/JobTemplate.helptext.js:63
-msgid "for more information."
-msgstr "pour plus d'informations."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:150
-msgid "here"
-msgstr "ici"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:118
-#: components/AdHocCommands/AdHocDetailsStep.js:170
-#: screens/Job/Job.helptext.js:39
-msgid "here."
-msgstr "ici."
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:49
-msgid "host-description-{0}"
-msgstr "description-hôte-{0}"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:44
-msgid "host-name-{0}"
-msgstr "nom-hôte-{0}"
-
-#: components/Lookup/HostFilterLookup.js:418
-msgid "hosts"
-msgstr "hôtes"
-
-#: components/Pagination/Pagination.js:24
-msgid "items"
-msgstr "éléments"
-
-#: screens/User/UserList/UserListItem.js:44
-msgid "ldap user"
-msgstr "utilisateur ldap"
-
-#: screens/User/UserDetail/UserDetail.js:76
-msgid "login type"
-msgstr "type de connexion"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:203
-msgid "min"
-msgstr "min"
-
-#: screens/Template/Survey/MultipleChoiceField.js:76
-msgid "new choice"
-msgstr "nouveau choix"
-
-#: components/Pagination/Pagination.js:36
-#: components/Schedule/shared/FrequencyDetailSubform.js:480
-msgid "of"
-msgstr "de"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:215
-msgid "option to the"
-msgstr "l'option à la"
-
-#: components/Pagination/Pagination.js:25
-msgid "page"
-msgstr "page"
-
-#: components/Pagination/Pagination.js:26
-msgid "pages"
-msgstr "pages"
-
-#: components/Pagination/Pagination.js:28
-msgid "per page"
-msgstr "par page"
-
-#: components/LaunchButton/ReLaunchDropDown.js:77
-#: components/LaunchButton/ReLaunchDropDown.js:100
-msgid "relaunch jobs"
-msgstr "relancer les Jobs"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:217
-msgid "sec"
-msgstr "sec"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:238
-msgid "seconds"
-msgstr "secondes"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:58
-msgid "select module"
-msgstr "sélectionner un module"
-
-#: screens/User/UserList/UserListItem.js:49
-msgid "social login"
-msgstr "social login"
-
-#: screens/Template/shared/JobTemplateForm.js:346
-#: screens/Template/shared/WorkflowJobTemplateForm.js:188
-msgid "source control branch"
-msgstr "branche du contrôle de la source"
-
-#: screens/ActivityStream/ActivityStreamListItem.js:30
-msgid "system"
-msgstr "système"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:511
-msgid "timed out"
-msgstr "expiré"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:195
-msgid "toggle changes"
-msgstr "basculer les changements"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:516
-msgid "updated"
-msgstr "actualisé"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:190
-msgid "weekday"
-msgstr "jour de la semaine"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:191
-msgid "weekend day"
-msgstr "jour du week-end"
-
-#: screens/Template/shared/WebhookSubForm.js:181
-msgid "workflow job template webhook key"
-msgstr "clé webhook de modèles de tâche flux de travail"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:65
-msgid "{0, plural, one {# source with sync failures.} other {# sources with sync failures.}}"
-msgstr "{0, plural, one {# source with sync failures.} other {# sources with sync failures.}}"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:115
-msgid "{0, plural, one {Are you sure you want delete the group below?} other {Are you sure you want delete the groups below?}}"
-msgstr "{0, plural, one {Are you sure you want delete the group below?} other {Are you sure you want delete the groups below?}}"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:86
-msgid "{0, plural, one {Delete Group?} other {Delete Groups?}}"
-msgstr "{0, plural, one {Delete Group?} other {Delete Groups?}}"
-
-#: util/validators.js:138
-msgid "{0, plural, one {Please enter a valid phone number.} other {Please enter valid phone numbers.}}"
-msgstr "{0, plural, one {Please enter a valid phone number.} other {Please enter valid phone numbers.}}"
-
-#: screens/Inventory/InventoryList/InventoryList.js:247
-msgid "{0, plural, one {The inventory will be in a pending status until the final delete is processed.} other {The inventories will be in a pending status until the final delete is processed.}}"
-msgstr "{0, plural, one {The inventory will be in a pending status until the final delete is processed.} other {The inventories will be in a pending status until the final delete is processed.}}"
-
-#: components/JobList/JobList.js:280
-msgid "{0, plural, one {The selected job cannot be deleted due to insufficient permission or a running job status} other {The selected jobs cannot be deleted due to insufficient permissions or a running job status}}"
-msgstr "{0, plural, one {The selected job cannot be deleted due to insufficient permission or a running job status} other {The selected jobs cannot be deleted due to insufficient permissions or a running job status}}"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:151
-msgid "{0, plural, one {This approval cannot be deleted due to insufficient permissions or a pending job status} other {These approvals cannot be deleted due to insufficient permissions or a pending job status}}"
-msgstr "{0, plural, one {This approval cannot be deleted due to insufficient permissions or a pending job status} other {These approvals cannot be deleted due to insufficient permissions or a pending job status}}"
-
-#: screens/Credential/CredentialList/CredentialList.js:198
-msgid "{0, plural, one {This credential is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these credentials could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This credential is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these credentials could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:164
-msgid "{0, plural, one {This credential type is currently being used by some credentials and cannot be deleted.} other {Credential types that are being used by credentials cannot be deleted. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This credential type is currently being used by some credentials and cannot be deleted.} other {Credential types that are being used by credentials cannot be deleted. Are you sure you want to delete anyway?}}"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:194
-msgid "{0, plural, one {This execution environment is currently being used by other resources. Are you sure you want to delete it?} other {These execution environments could be in use by other resources that rely on them. Are you sure you want to delete them anyway?}}"
-msgstr "{0, plural, one {This execution environment is currently being used by other resources. Are you sure you want to delete it?} other {These execution environments could be in use by other resources that rely on them. Are you sure you want to delete them anyway?}}"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:182
-msgid "{0, plural, one {This instance group is currently being by other resources. Are you sure you want to delete it?} other {Deleting these instance groups could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This instance group is currently being by other resources. Are you sure you want to delete it?} other {Deleting these instance groups could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:85
-msgid "{0, plural, one {This intance is currently being used by other resources. Are you sure you want to delete it?} other {Deprovisioning these instances could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This intance is currently being used by other resources. Are you sure you want to delete it?} other {Deprovisioning these instances could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Inventory/InventoryList/InventoryList.js:240
-msgid "{0, plural, one {This inventory is currently being used by some templates. Are you sure you want to delete it?} other {Deleting these inventories could impact some templates that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This inventory is currently being used by some templates. Are you sure you want to delete it?} other {Deleting these inventories could impact some templates that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:196
-msgid "{0, plural, one {This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?} other {Deleting these inventory sources could impact other resources that rely on them. Are you sure you want to delete anyway}}"
-msgstr "{0, plural, one {This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?} other {Deleting these inventory sources could impact other resources that rely on them. Are you sure you want to delete anyway}}"
-
-#: screens/Organization/OrganizationList/OrganizationList.js:166
-msgid "{0, plural, one {This organization is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these organizations could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This organization is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these organizations could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Project/ProjectList/ProjectList.js:252
-msgid "{0, plural, one {This project is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these projects could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This project is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these projects could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:209
-#: components/TemplateList/TemplateList.js:266
-msgid "{0, plural, one {This template is currently being used by some workflow nodes. Are you sure you want to delete it?} other {Deleting these templates could impact some workflow nodes that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This template is currently being used by some workflow nodes. Are you sure you want to delete it?} other {Deleting these templates could impact some workflow nodes that rely on them. Are you sure you want to delete anyway?}}"
-
-#: components/JobList/JobListCancelButton.js:72
-msgid "{0, plural, one {You cannot cancel the following job because it is not running:} other {You cannot cancel the following jobs because they are not running:}}"
-msgstr "{0, plural, one {You cannot cancel the following job because it is not running:} other {You cannot cancel the following jobs because they are not running:}}"
-
-#: components/JobList/JobListCancelButton.js:56
-msgid "{0, plural, one {You do not have permission to cancel the following job:} other {You do not have permission to cancel the following jobs:}}"
-msgstr "{0, plural, one {You do not have permission to cancel the following job:} other {You do not have permission to cancel the following jobs:}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:143
-msgid "{0, selectordinal, one {The first {dayOfWeek}} two {The second {dayOfWeek}} =3 {The third {dayOfWeek}} =4 {The fourth {dayOfWeek}} =5 {The fifth {dayOfWeek}}}"
-msgstr "{0, selectordinal, one {The first {dayOfWeek}} two {The second {dayOfWeek}} =3 {The third {dayOfWeek}} =4 {The fourth {dayOfWeek}} =5 {The fifth {dayOfWeek}}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:202
-msgid "{0, selectordinal, one {The first {weekday} of {month}} two {The second {weekday} of {month}} =3 {The third {weekday} of {month}} =4 {The fourth {weekday} of {month}} =5 {The fifth {weekday} of {month}}}"
-msgstr "{0, selectordinal, one {The first {weekday} de {month}} two {The second {weekday} de {month}} =3 {The third {weekday} de {month}} =4 {The fourth {weekday} de {month}} =5 {The fifth {weekday} of {month}}}"
-
-#: screens/ActivityStream/ActivityStreamListItem.js:28
-msgid "{0} (deleted)"
-msgstr "{0} (supprimé)"
-
-#: components/ChipGroup/ChipGroup.js:13
-msgid "{0} more"
-msgstr "{0} plus"
-
-#: screens/Job/JobDetail/JobDetail.js:399
-msgid "{0} seconds"
-msgstr "{0} secondes"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:95
-msgid "{automatedInstancesCount} since {automatedInstancesSinceDateTime}"
-msgstr "{automatedInstancesCount} depuis {automatedInstancesSinceDateTime}"
-
-#: components/AppContainer/AppContainer.js:55
-msgid "{brandName} logo"
-msgstr "{brandName} logo"
-
-#: components/DetailList/UserDateDetail.js:23
-msgid "{dateStr} by <0>{username}0>"
-msgstr "{dateStr} par <0>{username}0>"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:231
-#: screens/InstanceGroup/Instances/InstanceListItem.js:148
-#: screens/Instances/InstanceDetail/InstanceDetail.js:274
-#: screens/Instances/InstanceList/InstanceListItem.js:158
-#: screens/TopologyView/Tooltip.js:288
-msgid "{forks, plural, one {# fork} other {# forks}}"
-msgstr "{forks, plural, one {# fork} other {# forks}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:41
-msgid "{interval, plural, one {{interval} day} other {{interval} days}}"
-msgstr "{interval, plural, one {{interval} jour} other {{interval} jours}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:33
-msgid "{interval, plural, one {{interval} hour} other {{interval} hours}}"
-msgstr "{interval, plural, one {{interval} heure} other {{interval} heures}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:25
-msgid "{interval, plural, one {{interval} minute} other {{interval} minutes}}"
-msgstr "{interval, plural, one {{interval} minute} other {{interval} minutes}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:57
-msgid "{interval, plural, one {{interval} month} other {{interval} months}}"
-msgstr "{interval, plural, one {{interval} mois} other {{interval} mois}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:49
-msgid "{interval, plural, one {{interval} week} other {{interval} weeks}}"
-msgstr "{interval, plural, one {{interval} semaine} other {{interval} semaines}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:65
-msgid "{interval, plural, one {{interval} year} other {{interval} years}}"
-msgstr "{interval, plural, one {{interval} année} other {{interval} ans}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:198
-msgid "{intervalValue, plural, one {day} other {days}}"
-msgstr "{intervalValue, plural, one {day} other {days}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:196
-msgid "{intervalValue, plural, one {hour} other {hours}}"
-msgstr "{intervalValue, plural, one {hour} other {hours}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:194
-msgid "{intervalValue, plural, one {minute} other {minutes}}"
-msgstr "{intervalValue, plural, one {minute} other {minutes}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:202
-msgid "{intervalValue, plural, one {month} other {months}}"
-msgstr "{intervalValue, plural, one {month} other {months}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:200
-msgid "{intervalValue, plural, one {week} other {weeks}}"
-msgstr "{intervalValue, plural, one {week} other {weeks}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:204
-msgid "{intervalValue, plural, one {year} other {years}}"
-msgstr "{intervalValue, plural, one {year} other {years}}"
-
-#: components/PromptDetail/PromptDetail.js:44
-msgid "{minutes} min {seconds} sec"
-msgstr "{minutes} min {seconds} sec"
-
-#: components/JobList/JobListCancelButton.js:106
-msgid "{numJobsToCancel, plural, one {Cancel job} other {Cancel jobs}}"
-msgstr "{numJobsToCancel, plural, one {Cancel job} other {Cancel jobs}}"
-
-#: components/JobList/JobListCancelButton.js:168
-msgid "{numJobsToCancel, plural, one {This action will cancel the following job:} other {This action will cancel the following jobs:}}"
-msgstr "{numJobsToCancel, plural, one {This action will cancel the following job:} other {This action will cancel the following jobs:}}"
-
-#: components/JobList/JobListCancelButton.js:91
-msgid "{numJobsToCancel, plural, one {{0}} other {{1}}}"
-msgstr "{numJobsToCancel, plural, one {{0}} other {{1}}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:226
-msgid "{numOccurrences, plural, one {After {numOccurrences} occurrence} other {After {numOccurrences} occurrences}}"
-msgstr "{numOccurrences, plural, one {After {numOccurrences} occurrence} other {After {numOccurrences} occurrences}}"
-
-#: components/PaginatedTable/PaginatedTable.js:79
-msgid "{pluralizedItemName} List"
-msgstr "{pluralizedItemName} Liste"
-
-#: components/HealthCheckButton/HealthCheckButton.js:13
-msgid "{selectedItemsCount, plural, one {Click to run a health check on the selected instance.} other {Click to run a health check on the selected instances.}}"
-msgstr "{selectedItemsCount, plural, one {Click to run a health check on the selected instance.} other {Click to run a health check on the selected instances.}}"
-
-#: components/AppContainer/AppContainer.js:154
-msgid "{sessionCountdown, plural, one {You will be logged out in # second due to inactivity} other {You will be logged out in # seconds due to inactivity}}"
-msgstr "{sessionCountdown, plural, one {You will be logged out in # second due to inactivity} other {You will be logged out in # seconds due to inactivity}}"
diff --git a/awx/ui/src/locales/ja/messages.po b/awx/ui/src/locales/ja/messages.po
deleted file mode 100644
index aa9ea8792d76..000000000000
--- a/awx/ui/src/locales/ja/messages.po
+++ /dev/null
@@ -1,10739 +0,0 @@
-msgid ""
-msgstr ""
-"POT-Creation-Date: 2018-12-10 10:08-0500\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: @lingui/cli\n"
-"Language: en\n"
-"Project-Id-Version: \n"
-"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: \n"
-"Last-Translator: \n"
-"Language-Team: \n"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:43
-msgid "(Limited to first 10)"
-msgstr "(最初の 10 件に制限)"
-
-#: components/TemplateList/TemplateListItem.js:103
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:161
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:90
-msgid "(Prompt on launch)"
-msgstr "(起動プロンプト)"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:283
-msgid "* This field will be retrieved from an external secret management system using the specified credential."
-msgstr "*このフィールドは、指定された認証情報を使用して外部のシークレット管理システムから取得されます。"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:228
-msgid "/ (project root)"
-msgstr "/ (プロジェクト root)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:10
-msgid "0 (Normal)"
-msgstr "0 (正常)"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:38
-msgid "0 (Warning)"
-msgstr "0 (警告)"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:39
-msgid "1 (Info)"
-msgstr "1 (情報)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:11
-msgid "1 (Verbose)"
-msgstr "1 (詳細)"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:40
-msgid "2 (Debug)"
-msgstr "2 (デバッグ)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:12
-msgid "2 (More Verbose)"
-msgstr "2 (より詳細)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:13
-msgid "3 (Debug)"
-msgstr "3 (デバッグ)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:14
-msgid "4 (Connection Debug)"
-msgstr "4 (接続デバッグ)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:15
-msgid "5 (WinRM Debug)"
-msgstr "5 (WinRM デバッグ)"
-
-#: screens/Project/shared/Project.helptext.js:76
-msgid ""
-"A refspec to fetch (passed to the Ansible git\n"
-"module). This parameter allows access to references via\n"
-"the branch field not otherwise available."
-msgstr "取得する refspec (Ansible git モジュールに渡します)。\n"
-"このパラメーターを使用すると、(パラメーターなしでは利用できない) \n"
-"ブランチのフィールド経由で参照にアクセスできるようになります。"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:122
-msgid "A subscription manifest is an export of a Red Hat Subscription. To generate a subscription manifest, go to <0>access.redhat.com0>. For more information, see the <1>User Guide1>."
-msgstr "サブスクリプションマニフェストは、Red Hat サブスクリプションのエクスポートです。サブスクリプションマニフェストを生成するには、<0>access.redhat.com0> にアクセスします。詳細は、『<1>ユーザーガイド1>』を参照してください。"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputNode.js:143
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:326
-msgid "ALL"
-msgstr "すべて"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:284
-msgid "API Service/Integration Key"
-msgstr "API サービス/統合キー"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:291
-msgid "API Token"
-msgstr "API トークン"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:306
-msgid "API service/integration key"
-msgstr "API サービス/統合キー"
-
-#: components/AppContainer/PageHeaderToolbar.js:125
-msgid "About"
-msgstr "情報"
-
-#: routeConfig.js:92
-#: screens/ActivityStream/ActivityStream.js:179
-#: screens/Credential/Credential.js:74
-#: screens/Credential/Credentials.js:29
-#: screens/Inventory/Inventories.js:59
-#: screens/Inventory/Inventory.js:65
-#: screens/Inventory/SmartInventory.js:67
-#: screens/Organization/Organization.js:124
-#: screens/Organization/Organizations.js:31
-#: screens/Project/Project.js:105
-#: screens/Project/Projects.js:27
-#: screens/Team/Team.js:58
-#: screens/Team/Teams.js:31
-#: screens/Template/Template.js:136
-#: screens/Template/Templates.js:45
-#: screens/Template/WorkflowJobTemplate.js:118
-msgid "Access"
-msgstr "アクセス"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:75
-msgid "Access Token Expiration"
-msgstr "アクセストークンの有効期限"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:352
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:421
-msgid "Account SID"
-msgstr "アカウント SID"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:396
-msgid "Account token"
-msgstr "アカウントトークン"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:50
-msgid "Action"
-msgstr "アクション"
-
-#: components/JobList/JobList.js:249
-#: components/JobList/JobListItem.js:103
-#: components/RelatedTemplateList/RelatedTemplateList.js:189
-#: components/Schedule/ScheduleList/ScheduleList.js:172
-#: components/Schedule/ScheduleList/ScheduleListItem.js:128
-#: components/SelectedList/DraggableSelectedList.js:101
-#: components/TemplateList/TemplateList.js:246
-#: components/TemplateList/TemplateListItem.js:195
-#: screens/ActivityStream/ActivityStream.js:266
-#: screens/ActivityStream/ActivityStreamListItem.js:49
-#: screens/Application/ApplicationsList/ApplicationListItem.js:48
-#: screens/Application/ApplicationsList/ApplicationsList.js:160
-#: screens/Credential/CredentialList/CredentialList.js:166
-#: screens/Credential/CredentialList/CredentialListItem.js:66
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:177
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:38
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:168
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:87
-#: screens/Host/HostGroups/HostGroupItem.js:34
-#: screens/Host/HostGroups/HostGroupsList.js:177
-#: screens/Host/HostList/HostList.js:172
-#: screens/Host/HostList/HostListItem.js:70
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:200
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:75
-#: screens/InstanceGroup/Instances/InstanceList.js:271
-#: screens/InstanceGroup/Instances/InstanceListItem.js:171
-#: screens/Instances/InstanceList/InstanceList.js:206
-#: screens/Instances/InstanceList/InstanceListItem.js:183
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:218
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:53
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:39
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:142
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:41
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:187
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:44
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:140
-#: screens/Inventory/InventoryList/InventoryList.js:222
-#: screens/Inventory/InventoryList/InventoryListItem.js:131
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:233
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:44
-#: screens/Inventory/InventorySources/InventorySourceList.js:214
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:101
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:102
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:73
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:181
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:124
-#: screens/Organization/OrganizationList/OrganizationList.js:146
-#: screens/Organization/OrganizationList/OrganizationListItem.js:69
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:86
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:19
-#: screens/Project/ProjectList/ProjectList.js:225
-#: screens/Project/ProjectList/ProjectListItem.js:222
-#: screens/Team/TeamList/TeamList.js:144
-#: screens/Team/TeamList/TeamListItem.js:47
-#: screens/Template/Survey/SurveyList.js:105
-#: screens/Template/Survey/SurveyList.js:105
-#: screens/Template/Survey/SurveyListItem.js:90
-#: screens/User/UserList/UserList.js:164
-#: screens/User/UserList/UserListItem.js:56
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:167
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:78
-msgid "Actions"
-msgstr "アクション"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:98
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:61
-#: components/TemplateList/TemplateListItem.js:277
-#: screens/Host/HostDetail/HostDetail.js:71
-#: screens/Host/HostList/HostListItem.js:95
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:217
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:50
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:76
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:100
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:32
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:115
-msgid "Activity"
-msgstr "アクティビティー"
-
-#: routeConfig.js:49
-#: screens/ActivityStream/ActivityStream.js:43
-#: screens/ActivityStream/ActivityStream.js:121
-#: screens/Setting/Settings.js:44
-msgid "Activity Stream"
-msgstr "アクティビティーストリーム"
-
-#: screens/ActivityStream/ActivityStream.js:124
-msgid "Activity Stream type selector"
-msgstr "アクティビティーストリームのタイプセレクター"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:169
-msgid "Actor"
-msgstr "アクター"
-
-#: components/AddDropDownButton/AddDropDownButton.js:40
-#: components/PaginatedTable/ToolbarAddButton.js:14
-msgid "Add"
-msgstr "追加"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkAddModal.js:14
-msgid "Add Link"
-msgstr "リンクの追加"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.js:78
-msgid "Add Node"
-msgstr "ノードの追加"
-
-#: screens/Template/Templates.js:49
-msgid "Add Question"
-msgstr "質問の追加"
-
-#: components/AddRole/AddResourceRole.js:164
-msgid "Add Roles"
-msgstr "ロールの追加"
-
-#: components/AddRole/AddResourceRole.js:161
-msgid "Add Team Roles"
-msgstr "チームロールの追加"
-
-#: components/AddRole/AddResourceRole.js:158
-msgid "Add User Roles"
-msgstr "ユーザーロールの追加"
-
-#: components/Workflow/WorkflowStartNode.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:238
-msgid "Add a new node"
-msgstr "新規ノードの追加"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:49
-msgid "Add a new node between these two nodes"
-msgstr "これら 2 つのノードの間に新しいノードを追加します"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:108
-msgid "Add container group"
-msgstr "コンテナーグループの追加"
-
-#: components/Schedule/shared/ScheduleFormFields.js:171
-msgid "Add exceptions"
-msgstr "例外の追加"
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:140
-msgid "Add existing group"
-msgstr "既存グループの追加"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:152
-msgid "Add existing host"
-msgstr "既存ホストの追加"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:109
-msgid "Add instance group"
-msgstr "インスタンスグループの追加"
-
-#: screens/Inventory/InventoryList/InventoryList.js:136
-msgid "Add inventory"
-msgstr "インベントリーの追加"
-
-#: components/TemplateList/TemplateList.js:151
-msgid "Add job template"
-msgstr "新規ジョブテンプレートの追加"
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:141
-msgid "Add new group"
-msgstr "新規グループの追加"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:153
-msgid "Add new host"
-msgstr "新規ホストの追加"
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:77
-msgid "Add resource type"
-msgstr "リソースタイプの追加"
-
-#: screens/Inventory/InventoryList/InventoryList.js:137
-msgid "Add smart inventory"
-msgstr "スマートインベントリーの追加"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:202
-msgid "Add team permissions"
-msgstr "チームパーミッションの追加"
-
-#: screens/User/UserRoles/UserRolesList.js:198
-msgid "Add user permissions"
-msgstr "ユーザー権限の追加"
-
-#: components/TemplateList/TemplateList.js:152
-msgid "Add workflow template"
-msgstr "ワークフローテンプレートの追加"
-
-#: screens/TopologyView/Legend.js:269
-msgid "Adding"
-msgstr "追加"
-
-#: routeConfig.js:113
-#: screens/ActivityStream/ActivityStream.js:190
-msgid "Administration"
-msgstr "管理"
-
-#: components/DataListToolbar/DataListToolbar.js:139
-#: screens/Job/JobOutput/JobOutputSearch.js:136
-msgid "Advanced"
-msgstr "詳細"
-
-#: components/Search/AdvancedSearch.js:318
-msgid "Advanced search documentation"
-msgstr "高度な検索に関するドキュメント"
-
-#: components/Search/AdvancedSearch.js:211
-#: components/Search/AdvancedSearch.js:225
-msgid "Advanced search value input"
-msgstr "詳細な検索値の入力"
-
-#: screens/Inventory/shared/Inventory.helptext.js:131
-msgid ""
-"After every project update where the SCM revision\n"
-"changes, refresh the inventory from the selected source\n"
-"before executing job tasks. This is intended for static content,\n"
-"like the Ansible inventory .ini file format."
-msgstr "SCM リビジョンを変更するプロジェクトの毎回の更新後に、\n"
-"選択されたソースのインベントリーを更新してからジョブのタスクを実行します。\n"
-"これは、Ansible インベントリーの .ini ファイル形式のような静的コンテンツが対象であることが\n"
-"意図されています。"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:524
-msgid "After number of occurrences"
-msgstr "指定した実行回数後"
-
-#: components/AlertModal/AlertModal.js:75
-msgid "Alert modal"
-msgstr "アラートモーダル"
-
-#: components/LaunchButton/ReLaunchDropDown.js:48
-#: components/PromptDetail/PromptDetail.js:132
-#: screens/Metrics/Metrics.js:82
-#: screens/Metrics/Metrics.js:82
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:266
-msgid "All"
-msgstr "すべて"
-
-#: screens/Dashboard/DashboardGraph.js:137
-msgid "All job types"
-msgstr "すべてのジョブタイプ"
-
-#: screens/Dashboard/DashboardGraph.js:162
-msgid "All jobs"
-msgstr "すべてのジョブ"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:101
-msgid "Allow Branch Override"
-msgstr "ブランチの上書き許可"
-
-#: components/PromptDetail/PromptProjectDetail.js:66
-#: screens/Project/ProjectDetail/ProjectDetail.js:121
-msgid "Allow branch override"
-msgstr "ブランチの上書き許可"
-
-#: screens/Project/shared/Project.helptext.js:126
-msgid ""
-"Allow changing the Source Control branch or revision in a job\n"
-"template that uses this project."
-msgstr "このプロジェクトを使用するジョブテンプレートで Source Control ブランチまたは\n"
-"リビジョンを変更できるようにします。"
-
-#: screens/Application/shared/Application.helptext.js:6
-msgid "Allowed URIs list, space separated"
-msgstr "許可された URI リスト (スペース区切り)"
-
-#: components/Workflow/WorkflowLegend.js:130
-#: components/Workflow/WorkflowLinkHelp.js:24
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:58
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:46
-msgid "Always"
-msgstr "常時"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:98
-msgid "Amazon EC2"
-msgstr "Amazon EC2"
-
-#: components/Lookup/shared/LookupErrorMessage.js:12
-msgid "An error occurred"
-msgstr "エラーが発生しました"
-
-#: components/LaunchPrompt/steps/useInventoryStep.js:35
-msgid "An inventory must be selected"
-msgstr "インベントリーを選択する必要があります"
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:96
-msgid "Ansible Controller Documentation."
-msgstr "Ansible コントローラーのドキュメント。"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:43
-msgid "Answer type"
-msgstr "回答タイプ"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:177
-msgid "Answer variable name"
-msgstr "回答の変数名"
-
-#: components/PromptDetail/PromptDetail.js:132
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:263
-msgid "Any"
-msgstr "任意"
-
-#: components/Lookup/ApplicationLookup.js:83
-#: screens/User/UserTokenDetail/UserTokenDetail.js:39
-#: screens/User/shared/UserTokenForm.js:48
-msgid "Application"
-msgstr "アプリケーション"
-
-#: screens/User/UserTokenList/UserTokenList.js:187
-msgid "Application Name"
-msgstr "アプリケーション名"
-
-#: screens/Application/Applications.js:67
-#: screens/Application/Applications.js:70
-msgid "Application information"
-msgstr "アプリケーション情報"
-
-#: screens/User/UserTokenList/UserTokenList.js:123
-#: screens/User/UserTokenList/UserTokenList.js:134
-msgid "Application name"
-msgstr "アプリケーション名"
-
-#: screens/Application/Application/Application.js:95
-msgid "Application not found."
-msgstr "アプリケーションが見つかりません。"
-
-#: components/Lookup/ApplicationLookup.js:96
-#: routeConfig.js:142
-#: screens/Application/Applications.js:26
-#: screens/Application/Applications.js:35
-#: screens/Application/ApplicationsList/ApplicationsList.js:113
-#: screens/Application/ApplicationsList/ApplicationsList.js:148
-#: util/getRelatedResourceDeleteDetails.js:209
-msgid "Applications"
-msgstr "アプリケーション"
-
-#: screens/ActivityStream/ActivityStream.js:211
-msgid "Applications & Tokens"
-msgstr "アプリケーションおよびトークン"
-
-#: components/NotificationList/NotificationListItem.js:39
-#: components/NotificationList/NotificationListItem.js:40
-#: components/Workflow/WorkflowLegend.js:114
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:67
-msgid "Approval"
-msgstr "承認"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:82
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:44
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:47
-msgid "Approve"
-msgstr "承認"
-
-#: components/StatusLabel/StatusLabel.js:39
-msgid "Approved"
-msgstr "承認済"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:11
-msgid "Approved - {0}. See the Activity Stream for more information."
-msgstr "承認済み - {0}。詳細は、アクティビティーストリームを参照してください。"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:7
-msgid "Approved by {0} - {1}"
-msgstr "{0} により承認済 - {1}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:162
-#: components/Schedule/shared/FrequencyDetailSubform.js:113
-msgid "April"
-msgstr "4 月"
-
-#: components/JobCancelButton/JobCancelButton.js:104
-msgid "Are you sure you want to cancel this job?"
-msgstr "このジョブを取り消してよろしいですか?"
-
-#: components/DeleteButton/DeleteButton.js:127
-msgid "Are you sure you want to delete:"
-msgstr "次を削除してもよろしいですか:"
-
-#: screens/Setting/shared/SharedFields.js:142
-msgid "Are you sure you want to disable local authentication? Doing so could impact users' ability to log in and the system administrator's ability to reverse this change."
-msgstr "ローカル認証を無効にしてもよろしいですか? これを行うと、ユーザーのログイン機能と、システム管理者がこの変更を元に戻す機能に影響を与える可能性があります。"
-
-#: screens/Setting/shared/SharedFields.js:350
-msgid "Are you sure you want to edit login redirect override URL? Doing so could impact users' ability to log in to the system once local authentication is also disabled."
-msgstr "ログインリダイレクトのオーバーライド URL を編集してもよろしいですか?これを行うと、ローカルの認証情報も無効になると、ユーザーのシステムへのログイン機能に影響があります。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:41
-msgid "Are you sure you want to exit the Workflow Creator without saving your changes?"
-msgstr "変更を保存せずにワークフロークリエーターを終了してもよろしいですか?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:40
-msgid "Are you sure you want to remove all the nodes in this workflow?"
-msgstr "このワークフローのすべてのノードを削除してもよろしいですか?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:56
-msgid "Are you sure you want to remove the node below:"
-msgstr "以下のノードを削除してもよろしいですか?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:43
-msgid "Are you sure you want to remove this link?"
-msgstr "このリンクを削除してもよろしいですか?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:63
-msgid "Are you sure you want to remove this node?"
-msgstr "このノードを削除してもよろしいですか?"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:43
-msgid "Are you sure you want to remove {0} access from {1}? Doing so affects all members of the team."
-msgstr "{1} から {0} のアクセスを削除しますか? これを行うと、チームのすべてのメンバーに影響します。"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:50
-msgid "Are you sure you want to remove {0} access from {username}?"
-msgstr "{username} からの {0} のアクセスを削除してもよろしいですか?"
-
-#: screens/Job/JobOutput/JobOutput.js:826
-msgid "Are you sure you want to submit the request to cancel this job?"
-msgstr "このジョブを取り消す要求を送信してよろしいですか?"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:102
-#: components/AdHocCommands/AdHocDetailsStep.js:104
-msgid "Arguments"
-msgstr "引数"
-
-#: screens/Job/JobDetail/JobDetail.js:559
-msgid "Artifacts"
-msgstr "アーティファクト"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:233
-#: screens/User/UserTeams/UserTeamList.js:208
-msgid "Associate"
-msgstr "関連付け"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:244
-#: screens/User/UserRoles/UserRolesList.js:240
-msgid "Associate role error"
-msgstr "関連付けのロールエラー"
-
-#: components/AssociateModal/AssociateModal.js:98
-msgid "Association modal"
-msgstr "関連付けモーダル"
-
-#: components/LaunchPrompt/steps/SurveyStep.js:168
-msgid "At least one value must be selected for this field."
-msgstr "このフィールドには、少なくとも 1 つの値を選択する必要があります。"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:166
-#: components/Schedule/shared/FrequencyDetailSubform.js:133
-msgid "August"
-msgstr "8 月"
-
-#: screens/Setting/SettingList.js:52
-msgid "Authentication"
-msgstr "認証"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:88
-msgid "Authorization Code Expiration"
-msgstr "認証コードの有効期限"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:81
-#: screens/Application/shared/ApplicationForm.js:85
-msgid "Authorization grant type"
-msgstr "認証付与タイプ"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:215
-#: screens/InstanceGroup/Instances/InstanceListItem.js:204
-#: screens/Instances/InstanceDetail/InstanceDetail.js:209
-#: screens/Instances/InstanceList/InstanceListItem.js:219
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:83
-msgid "Auto"
-msgstr "自動"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:71
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:72
-msgid "Automation Analytics"
-msgstr "自動化アナリティクス"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:111
-msgid "Automation Analytics dashboard"
-msgstr "自動化アナリティクスダッシュボード"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:175
-msgid "Automation controller version"
-msgstr "自動化コントローラーバージョン"
-
-#: screens/Setting/Settings.js:47
-msgid "Azure AD"
-msgstr "Azure AD"
-
-#: screens/Setting/SettingList.js:57
-msgid "Azure AD settings"
-msgstr "Azure AD の設定"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:49
-#: components/AddRole/AddResourceRole.js:267
-#: components/LaunchPrompt/LaunchPrompt.js:158
-#: components/Schedule/shared/SchedulePromptableFields.js:125
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:90
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:70
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:154
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:157
-msgid "Back"
-msgstr "戻る"
-
-#: screens/Credential/Credential.js:65
-msgid "Back to Credentials"
-msgstr "認証情報に戻る"
-
-#: components/ContentError/ContentError.js:43
-msgid "Back to Dashboard."
-msgstr "ダッシュボードに戻る"
-
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:49
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:50
-msgid "Back to Groups"
-msgstr "グループに戻る"
-
-#: screens/Host/Host.js:50
-#: screens/Inventory/InventoryHost/InventoryHost.js:66
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:53
-msgid "Back to Hosts"
-msgstr "ホストに戻る"
-
-#: screens/InstanceGroup/InstanceGroup.js:61
-msgid "Back to Instance Groups"
-msgstr "インスタンスグループに戻る"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:173
-#: screens/Instances/Instance.js:22
-msgid "Back to Instances"
-msgstr "インスタンスに戻る"
-
-#: screens/Inventory/Inventory.js:57
-#: screens/Inventory/SmartInventory.js:60
-msgid "Back to Inventories"
-msgstr "インベントリーに戻る"
-
-#: screens/Job/Job.js:123
-msgid "Back to Jobs"
-msgstr "ジョブに戻る"
-
-#: screens/NotificationTemplate/NotificationTemplate.js:76
-msgid "Back to Notifications"
-msgstr "通知に戻る"
-
-#: screens/Organization/Organization.js:116
-msgid "Back to Organizations"
-msgstr "組織に戻る"
-
-#: screens/Project/Project.js:97
-msgid "Back to Projects"
-msgstr "プロジェクトに戻る"
-
-#: components/Schedule/Schedule.js:64
-msgid "Back to Schedules"
-msgstr "スケジュールに戻る"
-
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:44
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:78
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:44
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:58
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:95
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:69
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:43
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:90
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:44
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:49
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:45
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:34
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:49
-#: screens/Setting/UI/UIDetail/UIDetail.js:59
-msgid "Back to Settings"
-msgstr "設定に戻る"
-
-#: screens/Inventory/InventorySource/InventorySource.js:76
-msgid "Back to Sources"
-msgstr "ソースに戻る"
-
-#: screens/Team/Team.js:50
-msgid "Back to Teams"
-msgstr "チームに戻る"
-
-#: screens/Template/Template.js:128
-#: screens/Template/WorkflowJobTemplate.js:110
-msgid "Back to Templates"
-msgstr "テンプレートに戻る"
-
-#: screens/User/UserToken/UserToken.js:47
-msgid "Back to Tokens"
-msgstr "トークンに戻る"
-
-#: screens/User/User.js:57
-msgid "Back to Users"
-msgstr "ユーザーに戻る"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:69
-msgid "Back to Workflow Approvals"
-msgstr "ワークフローの承認に戻る"
-
-#: screens/Application/Application/Application.js:72
-msgid "Back to applications"
-msgstr "アプリケーションに戻る"
-
-#: screens/CredentialType/CredentialType.js:55
-msgid "Back to credential types"
-msgstr "認証情報タイプに戻る"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:57
-msgid "Back to execution environments"
-msgstr "実行環境に戻る"
-
-#: screens/InstanceGroup/ContainerGroup.js:59
-msgid "Back to instance groups"
-msgstr "インスタンスグループに戻る"
-
-#: screens/ManagementJob/ManagementJob.js:98
-msgid "Back to management jobs"
-msgstr "管理ジョブに戻る"
-
-#: screens/Project/shared/Project.helptext.js:8
-msgid ""
-"Base path used for locating playbooks. Directories\n"
-"found inside this path will be listed in the playbook directory drop-down.\n"
-"Together the base path and selected playbook directory provide the full\n"
-"path used to locate playbooks."
-msgstr "Playbook を見つけるために使用されるベースパスです。\n"
-"このパス内にあるディレクトリーは Playbook ディレクトリーのドロップダウンに一覧表示されます。\n"
-"ベースパスと選択されたPlaybook ディレクトリーは、\n"
-"Playbook を見つけるために使用される完全なパスを提供します。"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:448
-msgid "Basic auth password"
-msgstr "Basic 認証パスワード"
-
-#: screens/Project/shared/Project.helptext.js:104
-msgid ""
-"Branch to checkout. In addition to branches,\n"
-"you can input tags, commit hashes, and arbitrary refs. Some\n"
-"commit hashes and refs may not be available unless you also\n"
-"provide a custom refspec."
-msgstr "チェックアウトするブランチです。\n"
-"ブランチ以外に、タグ、コミットハッシュ値、任意の参照 (refs) を入力できます。\n"
-"カスタムの refspec も指定しない限り、コミットハッシュ値や参照で利用できないものもあります。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:27
-msgid "Branch to use in job run. Project default used if blank. Only allowed if project allow_override field is set to true."
-msgstr "ジョン実行に使用するブランチ。空白の場合はプロジェクトのデフォルト設定が使用されます。プロジェクトの allow_override フィールドが True の場合のみ許可されます。"
-
-#: components/About/About.js:45
-msgid "Brand Image"
-msgstr "ブランドイメージ"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:158
-msgid "Browse"
-msgstr "参照"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:94
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:115
-msgid "Browse…"
-msgstr "参照…"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:36
-msgid "By default, we collect and transmit analytics data on the service usage to Red Hat. There are two categories of data collected by the service. For more information, see <0>this Tower documentation page0>. Uncheck the following boxes to disable this feature."
-msgstr "デフォルトでは、サービスの使用状況に関する解析データを収集して、Red Hat に送信します。サービスが収集するデータにはカテゴリーが 2 種類あります。詳細情報は、<0>Tower のドキュメントページ<0> を参照してください。この機能を無効にするには、以下のボックスのチェックを解除します。"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:228
-#: screens/InstanceGroup/Instances/InstanceListItem.js:145
-#: screens/Instances/InstanceDetail/InstanceDetail.js:271
-#: screens/Instances/InstanceList/InstanceListItem.js:155
-#: screens/TopologyView/Tooltip.js:285
-msgid "CPU {0}"
-msgstr "CPU {0}"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:102
-#: components/PromptDetail/PromptProjectDetail.js:151
-#: screens/Project/ProjectDetail/ProjectDetail.js:268
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:118
-msgid "Cache Timeout"
-msgstr "キャッシュタイムアウト"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:237
-msgid "Cache timeout"
-msgstr "キャッシュタイムアウト"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:106
-msgid "Cache timeout (seconds)"
-msgstr "キャッシュのタイムアウト (秒)"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:50
-#: components/AddRole/AddResourceRole.js:268
-#: components/AssociateModal/AssociateModal.js:114
-#: components/AssociateModal/AssociateModal.js:119
-#: components/DeleteButton/DeleteButton.js:120
-#: components/DeleteButton/DeleteButton.js:123
-#: components/DisassociateButton/DisassociateButton.js:139
-#: components/DisassociateButton/DisassociateButton.js:142
-#: components/FormActionGroup/FormActionGroup.js:23
-#: components/FormActionGroup/FormActionGroup.js:29
-#: components/LaunchPrompt/LaunchPrompt.js:159
-#: components/Lookup/HostFilterLookup.js:388
-#: components/Lookup/Lookup.js:209
-#: components/PaginatedTable/ToolbarDeleteButton.js:282
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:37
-#: components/Schedule/shared/ScheduleForm.js:548
-#: components/Schedule/shared/ScheduleForm.js:553
-#: components/Schedule/shared/SchedulePromptableFields.js:126
-#: components/Schedule/shared/UnsupportedScheduleForm.js:22
-#: components/Schedule/shared/UnsupportedScheduleForm.js:27
-#: screens/Credential/shared/CredentialForm.js:343
-#: screens/Credential/shared/CredentialForm.js:348
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:100
-#: screens/Credential/shared/ExternalTestModal.js:98
-#: screens/Instances/Shared/RemoveInstanceButton.js:174
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:111
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:63
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:66
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:80
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:101
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:107
-#: screens/Setting/shared/RevertAllAlert.js:32
-#: screens/Setting/shared/RevertFormActionGroup.js:31
-#: screens/Setting/shared/RevertFormActionGroup.js:37
-#: screens/Setting/shared/SharedFields.js:133
-#: screens/Setting/shared/SharedFields.js:139
-#: screens/Setting/shared/SharedFields.js:346
-#: screens/Team/TeamRoles/TeamRolesList.js:228
-#: screens/Team/TeamRoles/TeamRolesList.js:231
-#: screens/Template/Survey/SurveyList.js:78
-#: screens/Template/Survey/SurveyReorderModal.js:211
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:31
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:39
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:45
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:50
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:164
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:167
-#: screens/User/UserRoles/UserRolesList.js:224
-#: screens/User/UserRoles/UserRolesList.js:227
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:104
-msgid "Cancel"
-msgstr "取り消し"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:300
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:112
-msgid "Cancel Inventory Source Sync"
-msgstr "インベントリーソース同期の取り消し"
-
-#: components/JobCancelButton/JobCancelButton.js:69
-#: screens/Job/JobOutput/JobOutput.js:802
-#: screens/Job/JobOutput/JobOutput.js:803
-msgid "Cancel Job"
-msgstr "ジョブの取り消し"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:321
-#: screens/Project/ProjectList/ProjectListItem.js:230
-msgid "Cancel Project Sync"
-msgstr "プロジェクトの同期の取り消し"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:302
-#: screens/Project/ProjectDetail/ProjectDetail.js:323
-msgid "Cancel Sync"
-msgstr "同期の取り消し"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:322
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:327
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:95
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:101
-msgid "Cancel Workflow"
-msgstr "ワークフローの取り消し"
-
-#: screens/Job/JobOutput/JobOutput.js:810
-#: screens/Job/JobOutput/JobOutput.js:813
-msgid "Cancel job"
-msgstr "ジョブの取り消し"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:42
-msgid "Cancel link changes"
-msgstr "リンク変更の取り消し"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:34
-msgid "Cancel link removal"
-msgstr "リンク削除の取り消し"
-
-#: components/Lookup/Lookup.js:207
-msgid "Cancel lookup"
-msgstr "ルックアップの取り消し"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:28
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:47
-msgid "Cancel node removal"
-msgstr "ノード削除の取り消し"
-
-#: screens/Setting/shared/RevertAllAlert.js:29
-msgid "Cancel revert"
-msgstr "元に戻すの取り消し"
-
-#: components/JobList/JobListCancelButton.js:93
-msgid "Cancel selected job"
-msgstr "選択したジョブの取り消し"
-
-#: components/JobList/JobListCancelButton.js:94
-msgid "Cancel selected jobs"
-msgstr "選択したジョブの取り消し"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:77
-msgid "Cancel subscription edit"
-msgstr "サブスクリプションの編集の取り消し"
-
-#: components/JobList/JobListItem.js:113
-#: screens/Job/JobDetail/JobDetail.js:600
-#: screens/Job/JobOutput/shared/OutputToolbar.js:137
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:89
-msgid "Cancel {0}"
-msgstr "{0} の取り消し"
-
-#: components/JobList/JobList.js:234
-#: components/StatusLabel/StatusLabel.js:54
-#: components/Workflow/WorkflowNodeHelp.js:111
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:212
-msgid "Canceled"
-msgstr "取り消し済み"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:129
-msgid ""
-"Cannot enable log aggregator without providing\n"
-"logging aggregator host and logging aggregator type."
-msgstr "ログアグリゲーターホストとログアグリゲータータイプを指定せずに、\n"
-"ログアグリゲーターを有効にすることはできません。"
-
-#: screens/Instances/InstanceList/InstanceList.js:199
-#: screens/Instances/InstancePeers/InstancePeerList.js:94
-msgid "Cannot run health check on hop nodes."
-msgstr "ホップノードでは可用性をチェックできません。"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:199
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:74
-#: screens/TopologyView/Tooltip.js:312
-msgid "Capacity"
-msgstr "容量"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:225
-#: screens/InstanceGroup/Instances/InstanceList.js:269
-#: screens/InstanceGroup/Instances/InstanceListItem.js:143
-#: screens/Instances/InstanceDetail/InstanceDetail.js:267
-#: screens/Instances/InstanceList/InstanceList.js:204
-#: screens/Instances/InstanceList/InstanceListItem.js:153
-msgid "Capacity Adjustment"
-msgstr "容量調整"
-
-#: components/Search/LookupTypeInput.js:59
-msgid "Case-insensitive version of contains"
-msgstr "contains で大文字小文字の区別なし。"
-
-#: components/Search/LookupTypeInput.js:87
-msgid "Case-insensitive version of endswith."
-msgstr "endswith で大文字小文字の区別なし。"
-
-#: components/Search/LookupTypeInput.js:45
-msgid "Case-insensitive version of exact."
-msgstr "exact で大文字小文字の区別なし。"
-
-#: components/Search/LookupTypeInput.js:100
-msgid "Case-insensitive version of regex."
-msgstr "regex で大文字小文字の区別なし。"
-
-#: components/Search/LookupTypeInput.js:73
-msgid "Case-insensitive version of startswith."
-msgstr "startswith で大文字小文字の区別なし。"
-
-#: screens/Project/shared/Project.helptext.js:14
-msgid ""
-"Change PROJECTS_ROOT when deploying\n"
-"{brandName} to change this location."
-msgstr "この場所を変更するには {brandName} のデプロイ時に\n"
-" PROJECTS_ROOT を変更します。"
-
-#: components/StatusLabel/StatusLabel.js:55
-#: screens/Job/JobOutput/shared/HostStatusBar.js:43
-msgid "Changed"
-msgstr "変更済み"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:53
-msgid "Changes"
-msgstr "変更"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:258
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:266
-msgid "Channel"
-msgstr "チャネル"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:138
-#: screens/Template/shared/JobTemplateForm.js:218
-msgid "Check"
-msgstr "チェック"
-
-#: components/Search/LookupTypeInput.js:134
-msgid "Check whether the given field or related object is null; expects a boolean value."
-msgstr "特定フィールドもしくは関連オブジェクトが null かどうかをチェック。ブール値を想定。"
-
-#: components/Search/LookupTypeInput.js:140
-msgid "Check whether the given field's value is present in the list provided; expects a comma-separated list of items."
-msgstr "特定フィールドの値が提供されたリストに存在するかどうかをチェック (項目のコンマ区切りのリストを想定)。"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:32
-msgid "Choose a .json file"
-msgstr ".json ファイルの選択"
-
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:78
-msgid "Choose a Notification Type"
-msgstr "通知タイプの選択"
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:25
-msgid "Choose a Playbook Directory"
-msgstr "Playbook ディレクトリーの選択"
-
-#: screens/Project/shared/ProjectForm.js:268
-msgid "Choose a Source Control Type"
-msgstr "ソースコントロールタイプの選択"
-
-#: screens/Template/shared/WebhookSubForm.js:100
-msgid "Choose a Webhook Service"
-msgstr "Webhook サービスの選択"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:131
-#: screens/Template/shared/JobTemplateForm.js:211
-msgid "Choose a job type"
-msgstr "ジョブタイプの選択"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:82
-msgid "Choose a module"
-msgstr "モジュールの選択"
-
-#: screens/Inventory/shared/InventorySourceForm.js:139
-msgid "Choose a source"
-msgstr "ソースの選択"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:490
-msgid "Choose an HTTP method"
-msgstr "HTTP メソッドの選択"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:46
-msgid ""
-"Choose an answer type or format you want as the prompt for the user.\n"
-"Refer to the Ansible Controller Documentation for more additional\n"
-"information about each option."
-msgstr "ユーザーに表示されるプロンプトとして使用する回答タイプまたは形式を選択します。\n"
-"各オプションの詳細については、\n"
-"Ansible Controller のドキュメントを参照してください。"
-
-#: components/AddRole/SelectRoleStep.js:20
-msgid "Choose roles to apply to the selected resources. Note that all selected roles will be applied to all selected resources."
-msgstr "選択済みのリソースに適用するロールを選択します。選択するロールがすべて、選択済みの全リソースに対して適用されることに注意してください。"
-
-#: components/AddRole/SelectResourceStep.js:81
-msgid "Choose the resources that will be receiving new roles. You'll be able to select the roles to apply in the next step. Note that the resources chosen here will receive all roles chosen in the next step."
-msgstr "新しいロールを受け取るリソースを選択します。次のステップで適用するロールを選択できます。ここで選択したリソースは、次のステップで選択したすべてのロールを受け取ることに注意してください。"
-
-#: components/AddRole/AddResourceRole.js:174
-msgid "Choose the type of resource that will be receiving new roles. For example, if you'd like to add new roles to a set of users please choose Users and click Next. You'll be able to select the specific resources in the next step."
-msgstr "新しいロールを受け取るリソースのタイプを選択します。たとえば、一連のユーザーに新しいロールを追加する場合は、ユーザーを選択して次へをクリックしてください。次のステップで特定のリソースを選択できるようになります。"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:74
-msgid "Clean"
-msgstr "クリーニング"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:95
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:116
-msgid "Clear"
-msgstr "消去"
-
-#: components/DataListToolbar/DataListToolbar.js:95
-#: screens/Job/JobOutput/JobOutputSearch.js:144
-msgid "Clear all filters"
-msgstr "すべてのフィルターの解除"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:247
-msgid "Clear subscription"
-msgstr "サブスクリプションの解除"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:252
-msgid "Clear subscription selection"
-msgstr "サブスクリプションの選択解除"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerGraph.js:245
-msgid "Click an available node to create a new link. Click outside the graph to cancel."
-msgstr "使用可能なノードをクリックして、新しいリンクを作成します。キャンセルするには、グラフの外側をクリックしてください。"
-
-#: screens/TopologyView/Tooltip.js:191
-msgid "Click on a node icon to display the details."
-msgstr "ノードアイコンをクリックして詳細を表示します。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:134
-msgid "Click the Edit button below to reconfigure the node."
-msgstr "下の編集ボタンをクリックして、ノードを再構成します。"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:71
-msgid "Click this button to verify connection to the secret management system using the selected credential and specified inputs."
-msgstr "このボタンをクリックして、選択した認証情報と指定した入力を使用してシークレット管理システムへの接続を確認します。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:179
-msgid "Click to create a new link to this node."
-msgstr "クリックして、このノードへの新しいリンクを作成します。"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:251
-msgid "Click to download bundle"
-msgstr "クリックしてバンドルをダウンロードします。"
-
-#: screens/Template/Survey/SurveyToolbar.js:64
-msgid "Click to rearrange the order of the survey questions"
-msgstr "クリックして、 Survey の質問の順序を並べ替えます"
-
-#: screens/Template/Survey/MultipleChoiceField.js:117
-msgid "Click to toggle default value"
-msgstr "クリックしてデフォルト値を切り替えます"
-
-#: components/Workflow/WorkflowNodeHelp.js:202
-msgid "Click to view job details"
-msgstr "クリックしてジョブの詳細を表示"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:89
-#: screens/Application/Applications.js:84
-msgid "Client ID"
-msgstr "クライアント ID"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:289
-msgid "Client Identifier"
-msgstr "クライアント識別子"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:314
-msgid "Client identifier"
-msgstr "クライアント識別子"
-
-#: screens/Application/Applications.js:97
-msgid "Client secret"
-msgstr "クライアントシークレット"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:100
-#: screens/Application/shared/ApplicationForm.js:127
-msgid "Client type"
-msgstr "クライアントタイプ"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:105
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:169
-msgid "Close"
-msgstr "閉じる"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:123
-msgid "Close subscription modal"
-msgstr "サブスクリプションモーダルを閉じる"
-
-#: components/CredentialChip/CredentialChip.js:11
-msgid "Cloud"
-msgstr "クラウド"
-
-#: components/ExpandCollapse/ExpandCollapse.js:41
-msgid "Collapse"
-msgstr "折りたたむ"
-
-#: screens/Job/JobOutput/PageControls.js:50
-msgid "Collapse all job events"
-msgstr "すべてのジョブイベントを折りたたむ"
-
-#: screens/Job/JobOutput/shared/JobEventLineToggle.js:39
-msgid "Collapse section"
-msgstr "セクションを折りたたむ"
-
-#: components/JobList/JobList.js:214
-#: components/JobList/JobListItem.js:45
-#: screens/Job/JobOutput/HostEventModal.js:129
-msgid "Command"
-msgstr "コマンド"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:66
-msgid "Compliant"
-msgstr "有効"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:68
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:36
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:132
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:58
-#: screens/Template/shared/JobTemplateForm.js:591
-msgid "Concurrent Jobs"
-msgstr "同時実行ジョブ"
-
-#: screens/Template/shared/JobTemplate.helptext.js:38
-msgid "Concurrent jobs: If enabled, simultaneous runs of this job template will be allowed."
-msgstr "同時実行ジョブ: 有効な場合は、このジョブテンプレートの同時実行が許可されます。"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:25
-msgid "Concurrent jobs: If enabled, simultaneous runs of this workflow job template will be allowed."
-msgstr "同時実行ジョブ: 有効な場合は、このワークフロージョブテンプレートの同時実行が許可されます。"
-
-#: screens/Setting/shared/SharedFields.js:121
-#: screens/Setting/shared/SharedFields.js:127
-#: screens/Setting/shared/SharedFields.js:336
-msgid "Confirm"
-msgstr "確認"
-
-#: components/DeleteButton/DeleteButton.js:107
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:95
-msgid "Confirm Delete"
-msgstr "削除の確認"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:191
-msgid "Confirm Disable Local Authorization"
-msgstr "ローカル認証の無効化の確認"
-
-#: screens/User/shared/UserForm.js:99
-msgid "Confirm Password"
-msgstr "パスワードの確認"
-
-#: components/JobCancelButton/JobCancelButton.js:86
-msgid "Confirm cancel job"
-msgstr "取り消しジョブの確認"
-
-#: components/JobCancelButton/JobCancelButton.js:90
-msgid "Confirm cancellation"
-msgstr "取り消しの確認"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:26
-msgid "Confirm delete"
-msgstr "削除の確認"
-
-#: screens/User/UserRoles/UserRolesList.js:215
-msgid "Confirm disassociate"
-msgstr "関連付けの解除の確認"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:24
-msgid "Confirm link removal"
-msgstr "リンク削除の確認"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:37
-msgid "Confirm node removal"
-msgstr "ノードの削除の確認"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:18
-msgid "Confirm removal of all nodes"
-msgstr "すべてのノードの削除の確認"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:160
-msgid "Confirm remove"
-msgstr "削除の確認"
-
-#: screens/Setting/shared/RevertAllAlert.js:20
-msgid "Confirm revert all"
-msgstr "すべて元に戻すことを確認"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:91
-msgid "Confirm selection"
-msgstr "選択の確認"
-
-#: screens/Job/JobDetail/JobDetail.js:366
-msgid "Container Group"
-msgstr "コンテナーグループ"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:47
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:57
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:68
-msgid "Container group"
-msgstr "コンテナーグループ"
-
-#: screens/InstanceGroup/ContainerGroup.js:84
-msgid "Container group not found."
-msgstr "コンテナーグループが見つかりません。"
-
-#: components/LaunchPrompt/LaunchPrompt.js:153
-#: components/Schedule/shared/SchedulePromptableFields.js:120
-msgid "Content Loading"
-msgstr "コンテンツの読み込み"
-
-#: components/PromptDetail/PromptProjectDetail.js:130
-#: screens/Project/ProjectDetail/ProjectDetail.js:240
-#: screens/Project/shared/ProjectForm.js:290
-msgid "Content Signature Validation Credential"
-msgstr "コンテンツ署名検証の認証情報"
-
-#: components/AppContainer/AppContainer.js:142
-msgid "Continue"
-msgstr "続行"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:207
-#: screens/Instances/InstanceList/InstanceList.js:151
-msgid "Control"
-msgstr "コントロール"
-
-#: screens/TopologyView/Legend.js:77
-msgid "Control node"
-msgstr "コントロールノード"
-
-#: screens/Inventory/shared/Inventory.helptext.js:79
-msgid ""
-"Control the level of output Ansible\n"
-"will produce for inventory source update jobs."
-msgstr "インベントリーソースの更新ジョブ用に\n"
-" Ansible が生成する出力のレベルを制御します。"
-
-#: screens/Job/Job.helptext.js:15
-#: screens/Template/shared/JobTemplate.helptext.js:16
-msgid "Control the level of output ansible will produce as the playbook executes."
-msgstr "Playbook の実行時に Ansible が生成する出力のレベルを制御します。"
-
-#: screens/Job/JobDetail/JobDetail.js:351
-msgid "Controller Node"
-msgstr "コントローラーノード"
-
-#: components/PromptDetail/PromptDetail.js:130
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:225
-msgid "Convergence"
-msgstr "収束 (コンバージェンス)"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:256
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:257
-msgid "Convergence select"
-msgstr "収束 (コンバージェンス) 選択"
-
-#: components/CopyButton/CopyButton.js:40
-msgid "Copy"
-msgstr "コピー"
-
-#: screens/Credential/CredentialList/CredentialListItem.js:80
-msgid "Copy Credential"
-msgstr "認証情報のコピー"
-
-#: components/CopyButton/CopyButton.js:48
-msgid "Copy Error"
-msgstr "コピーエラー"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:104
-msgid "Copy Execution Environment"
-msgstr "実行環境のコピー"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:154
-msgid "Copy Inventory"
-msgstr "インベントリーのコピー"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:152
-msgid "Copy Notification Template"
-msgstr "通知テンプレートのコピー"
-
-#: screens/Project/ProjectList/ProjectListItem.js:262
-msgid "Copy Project"
-msgstr "プロジェクトのコピー"
-
-#: components/TemplateList/TemplateListItem.js:248
-msgid "Copy Template"
-msgstr "テンプレートのコピー"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:202
-#: screens/Project/ProjectList/ProjectListItem.js:98
-msgid "Copy full revision to clipboard."
-msgstr "完全なリビジョンをクリップボードにコピーします。"
-
-#: components/About/About.js:35
-msgid "Copyright"
-msgstr "著作権"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:231
-#: components/MultiSelect/TagMultiSelect.js:62
-#: screens/Credential/shared/CredentialFormFields/BecomeMethodField.js:66
-#: screens/Inventory/shared/InventoryForm.js:91
-#: screens/Template/shared/JobTemplateForm.js:396
-#: screens/Template/shared/WorkflowJobTemplateForm.js:204
-msgid "Create"
-msgstr "作成"
-
-#: screens/Application/Applications.js:27
-#: screens/Application/Applications.js:36
-msgid "Create New Application"
-msgstr "新規アプリケーションの作成"
-
-#: screens/Credential/Credentials.js:15
-#: screens/Credential/Credentials.js:25
-msgid "Create New Credential"
-msgstr "新規認証情報の作成"
-
-#: screens/Host/Hosts.js:15
-#: screens/Host/Hosts.js:24
-msgid "Create New Host"
-msgstr "新規ホストの作成"
-
-#: screens/Template/Templates.js:18
-msgid "Create New Job Template"
-msgstr "新規ジョブテンプレートの作成"
-
-#: screens/NotificationTemplate/NotificationTemplates.js:15
-#: screens/NotificationTemplate/NotificationTemplates.js:22
-msgid "Create New Notification Template"
-msgstr "新規通知テンプレートの作成"
-
-#: screens/Organization/Organizations.js:17
-#: screens/Organization/Organizations.js:27
-msgid "Create New Organization"
-msgstr "新規組織の作成"
-
-#: screens/Project/Projects.js:13
-#: screens/Project/Projects.js:23
-msgid "Create New Project"
-msgstr "新規プロジェクトの作成"
-
-#: screens/Inventory/Inventories.js:91
-#: screens/ManagementJob/ManagementJobs.js:24
-#: screens/Project/Projects.js:32
-#: screens/Template/Templates.js:52
-msgid "Create New Schedule"
-msgstr "新規スケジュールの作成"
-
-#: screens/Team/Teams.js:16
-#: screens/Team/Teams.js:26
-msgid "Create New Team"
-msgstr "新規チームの作成"
-
-#: screens/User/Users.js:16
-#: screens/User/Users.js:27
-msgid "Create New User"
-msgstr "新規ユーザーの作成"
-
-#: screens/Template/Templates.js:19
-msgid "Create New Workflow Template"
-msgstr "新規ワークフローテンプレートの作成"
-
-#: screens/Host/HostList/SmartInventoryButton.js:26
-msgid "Create a new Smart Inventory with the applied filter"
-msgstr "フィルターを適用して新しいスマートインベントリーを作成"
-
-#: screens/Instances/Instances.js:14
-msgid "Create new Instance"
-msgstr "新規インスタンスの作成"
-
-#: screens/InstanceGroup/InstanceGroups.js:18
-#: screens/InstanceGroup/InstanceGroups.js:28
-msgid "Create new container group"
-msgstr "新規コンテナーグループの作成"
-
-#: screens/CredentialType/CredentialTypes.js:23
-msgid "Create new credential Type"
-msgstr "新規認証情報タイプの作成"
-
-#: screens/CredentialType/CredentialTypes.js:14
-msgid "Create new credential type"
-msgstr "新規認証情報タイプの作成"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:14
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:23
-msgid "Create new execution environment"
-msgstr "新規実行環境の作成"
-
-#: screens/Inventory/Inventories.js:75
-#: screens/Inventory/Inventories.js:82
-msgid "Create new group"
-msgstr "新規グループの作成"
-
-#: screens/Inventory/Inventories.js:66
-#: screens/Inventory/Inventories.js:80
-msgid "Create new host"
-msgstr "新規ホストの作成"
-
-#: screens/InstanceGroup/InstanceGroups.js:17
-#: screens/InstanceGroup/InstanceGroups.js:27
-msgid "Create new instance group"
-msgstr "新規インスタンスグループの作成"
-
-#: screens/Inventory/Inventories.js:18
-msgid "Create new inventory"
-msgstr "新規インベントリーの作成"
-
-#: screens/Inventory/Inventories.js:19
-msgid "Create new smart inventory"
-msgstr "新規スマートインベントリーの作成"
-
-#: screens/Inventory/Inventories.js:85
-msgid "Create new source"
-msgstr "新規ソースの作成"
-
-#: screens/User/Users.js:35
-msgid "Create user token"
-msgstr "ユーザートークンの作成"
-
-#: components/Lookup/ApplicationLookup.js:115
-#: components/PromptDetail/PromptDetail.js:154
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:406
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:105
-#: screens/Credential/CredentialDetail/CredentialDetail.js:257
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:90
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:103
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:151
-#: screens/Host/HostDetail/HostDetail.js:86
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:67
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:93
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:173
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:43
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:81
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:277
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:149
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:46
-#: screens/Job/JobDetail/JobDetail.js:534
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:393
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:115
-#: screens/Project/ProjectDetail/ProjectDetail.js:292
-#: screens/Team/TeamDetail/TeamDetail.js:47
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:353
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:187
-#: screens/User/UserDetail/UserDetail.js:82
-#: screens/User/UserTokenDetail/UserTokenDetail.js:61
-#: screens/User/UserTokenList/UserTokenList.js:150
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:199
-msgid "Created"
-msgstr "作成日時"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:122
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:112
-#: components/AddRole/AddResourceRole.js:57
-#: components/AssociateModal/AssociateModal.js:144
-#: components/LaunchPrompt/steps/CredentialsStep.js:173
-#: components/LaunchPrompt/steps/InventoryStep.js:89
-#: components/Lookup/CredentialLookup.js:194
-#: components/Lookup/InventoryLookup.js:152
-#: components/Lookup/InventoryLookup.js:207
-#: components/Lookup/MultiCredentialsLookup.js:194
-#: components/Lookup/OrganizationLookup.js:134
-#: components/Lookup/ProjectLookup.js:151
-#: components/NotificationList/NotificationList.js:206
-#: components/RelatedTemplateList/RelatedTemplateList.js:166
-#: components/Schedule/ScheduleList/ScheduleList.js:198
-#: components/TemplateList/TemplateList.js:226
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:27
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:58
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:104
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:127
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:173
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:196
-#: screens/Credential/CredentialList/CredentialList.js:150
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:96
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:132
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:102
-#: screens/Host/HostGroups/HostGroupsList.js:164
-#: screens/Host/HostList/HostList.js:157
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:199
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:129
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:174
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:128
-#: screens/Inventory/InventoryList/InventoryList.js:199
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:185
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:94
-#: screens/Organization/OrganizationList/OrganizationList.js:131
-#: screens/Project/ProjectList/ProjectList.js:213
-#: screens/Team/TeamList/TeamList.js:130
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:161
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:112
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:108
-msgid "Created By (Username)"
-msgstr "作成者 (ユーザー名)"
-
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:81
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:147
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:73
-msgid "Created by (username)"
-msgstr "作成者 (ユーザー名)"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:54
-#: components/AdHocCommands/useAdHocCredentialStep.js:24
-#: components/PromptDetail/PromptInventorySourceDetail.js:107
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:40
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:52
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:50
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:258
-#: screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.js:38
-#: screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/GCESubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.js:38
-#: screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:84
-#: screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.js:36
-#: screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.js:38
-#: screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.js:38
-#: util/getRelatedResourceDeleteDetails.js:167
-msgid "Credential"
-msgstr "認証情報"
-
-#: util/getRelatedResourceDeleteDetails.js:74
-msgid "Credential Input Sources"
-msgstr "認証情報の入力ソース"
-
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:83
-#: components/Lookup/InstanceGroupsLookup.js:108
-msgid "Credential Name"
-msgstr "認証情報名"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:241
-#: screens/Credential/CredentialList/CredentialList.js:158
-#: screens/Credential/shared/CredentialForm.js:128
-#: screens/Credential/shared/CredentialForm.js:196
-msgid "Credential Type"
-msgstr "認証情報タイプ"
-
-#: routeConfig.js:117
-#: screens/ActivityStream/ActivityStream.js:192
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:118
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:161
-#: screens/CredentialType/CredentialTypes.js:13
-#: screens/CredentialType/CredentialTypes.js:22
-msgid "Credential Types"
-msgstr "認証情報タイプ"
-
-#: screens/Credential/CredentialList/CredentialList.js:113
-msgid "Credential copied successfully"
-msgstr "認証情報が正常にコピーされました"
-
-#: screens/Credential/Credential.js:98
-msgid "Credential not found."
-msgstr "認証情報が見つかりません。"
-
-#: components/AdHocCommands/useAdHocCredentialPasswordStep.js:23
-#: components/LaunchPrompt/steps/useCredentialPasswordsStep.js:28
-msgid "Credential passwords"
-msgstr "認証情報のパスワード"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:53
-msgid "Credential to authenticate with Kubernetes or OpenShift"
-msgstr "Kubernetes または OpenShift との認証のための認証情報"
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:57
-msgid "Credential to authenticate with Kubernetes or OpenShift. Must be of type \"Kubernetes/OpenShift API Bearer Token\". If left blank, the underlying Pod's service account will be used."
-msgstr "Kubernetes または OpenShift との認証に使用する認証情報。\"Kubernetes/OpenShift API ベアラートークン” のタイプでなければなりません。空白のままにすると、基になる Pod のサービスアカウントが使用されます。"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:21
-msgid "Credential to authenticate with a protected container registry."
-msgstr "保護されたコンテナーレジストリーで認証するための認証情報。"
-
-#: screens/CredentialType/CredentialType.js:76
-msgid "Credential type not found."
-msgstr "認証情報タイプが見つかりません。"
-
-#: components/JobList/JobListItem.js:260
-#: components/LaunchPrompt/steps/CredentialsStep.js:190
-#: components/LaunchPrompt/steps/useCredentialsStep.js:62
-#: components/Lookup/MultiCredentialsLookup.js:138
-#: components/Lookup/MultiCredentialsLookup.js:211
-#: components/PromptDetail/PromptDetail.js:192
-#: components/PromptDetail/PromptJobTemplateDetail.js:191
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:528
-#: components/TemplateList/TemplateListItem.js:323
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:77
-#: routeConfig.js:70
-#: screens/ActivityStream/ActivityStream.js:167
-#: screens/Credential/CredentialList/CredentialList.js:195
-#: screens/Credential/Credentials.js:14
-#: screens/Credential/Credentials.js:24
-#: screens/Job/JobDetail/JobDetail.js:429
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:374
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:49
-#: screens/Template/shared/JobTemplateForm.js:372
-#: util/getRelatedResourceDeleteDetails.js:91
-msgid "Credentials"
-msgstr "認証情報"
-
-#: components/LaunchPrompt/steps/credentialsValidator.js:52
-msgid "Credentials that require passwords on launch are not permitted. Please remove or replace the following credentials with a credential of the same type in order to proceed: {0}"
-msgstr "起動時にパスワードを必要とする認証情報は許可されていません。続行するには、次の認証情報を削除するか、同じ種類の認証情報に置き換えてください: {0}"
-
-#: components/Pagination/Pagination.js:34
-msgid "Current page"
-msgstr "現在のページ"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:85
-msgid "Custom Kubernetes or OpenShift Pod specification."
-msgstr "カスタムの Kubernetes または OpenShift Pod 仕様"
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:79
-msgid "Custom pod spec"
-msgstr "カスタム Pod 仕様"
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:79
-#: screens/Organization/OrganizationList/OrganizationListItem.js:55
-#: screens/Project/ProjectList/ProjectListItem.js:188
-msgid "Custom virtual environment {0} must be replaced by an execution environment."
-msgstr "カスタム仮想環境 {0} は、実行環境に置き換える必要があります。"
-
-#: components/TemplateList/TemplateListItem.js:163
-msgid "Custom virtual environment {0} must be replaced by an execution environment. For more information about migrating to execution environments see <0>the documentation.0>"
-msgstr "カスタム仮想環境 {0} は、実行環境に置き換える必要があります。実行環境への移行の詳細については、<0>ドキュメント0> を参照してください。"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:73
-msgid "Custom virtual environment {virtualEnvironment} must be replaced by an execution environment. For more information about migrating to execution environments see <0>the documentation.0>"
-msgstr "カスタム仮想環境 {virtualEnvironment} は、実行環境に置き換える必要があります。実行環境への移行の詳細については、<0>ドキュメント0> を参照してください。"
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:61
-msgid "Customize messages…"
-msgstr "メッセージのカスタマイズ…"
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:65
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:66
-msgid "Customize pod specification"
-msgstr "Pod 仕様のカスタマイズ"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputNode.js:109
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:212
-msgid "DELETED"
-msgstr "削除済み"
-
-#: routeConfig.js:34
-#: screens/Dashboard/Dashboard.js:74
-msgid "Dashboard"
-msgstr "ダッシュボード"
-
-#: screens/ActivityStream/ActivityStream.js:147
-msgid "Dashboard (all activity)"
-msgstr "ダッシュボード (すべてのアクティビティー)"
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:75
-msgid "Data retention period"
-msgstr "データ保持期間"
-
-#: screens/Dashboard/shared/LineChart.js:168
-msgid "Date"
-msgstr "日付"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:184
-#: components/Schedule/shared/FrequencyDetailSubform.js:177
-#: components/Schedule/shared/FrequencyDetailSubform.js:356
-#: components/Schedule/shared/FrequencyDetailSubform.js:460
-#: components/Schedule/shared/ScheduleFormFields.js:127
-#: components/Schedule/shared/ScheduleFormFields.js:187
-msgid "Day"
-msgstr "日"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:130
-msgid "Day {0}"
-msgstr "日 {0}"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:399
-#: components/Schedule/shared/ScheduleFormFields.js:136
-msgid "Days of Data to Keep"
-msgstr "データの保持日数"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/DaysToKeepStep.js:28
-msgid "Days of data to be retained"
-msgstr "データの保持日数"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:167
-msgid "Days remaining"
-msgstr "残りの日数"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useDaysToKeepStep.js:35
-msgid "Days to keep"
-msgstr "保持する日数"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:102
-msgid "Debug"
-msgstr "デバッグ"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:170
-#: components/Schedule/shared/FrequencyDetailSubform.js:153
-msgid "December"
-msgstr "12 月"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:102
-#: screens/Template/Survey/SurveyList.js:104
-#: screens/Template/Survey/SurveyList.js:104
-#: screens/Template/Survey/SurveyListItem.js:63
-msgid "Default"
-msgstr "デフォルト"
-
-#: screens/Template/Survey/SurveyReorderModal.js:219
-#: screens/Template/Survey/SurveyReorderModal.js:219
-#: screens/Template/Survey/SurveyReorderModal.js:241
-msgid "Default Answer(s)"
-msgstr "デフォルトの応答"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:40
-msgid "Default Execution Environment"
-msgstr "デフォルトの実行環境"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:238
-#: screens/Template/Survey/SurveyQuestionForm.js:246
-#: screens/Template/Survey/SurveyQuestionForm.js:253
-msgid "Default answer"
-msgstr "デフォルトの応答"
-
-#: screens/Setting/SettingList.js:103
-msgid "Define system-level features and functions"
-msgstr "システムレベルの機能および関数の定義"
-
-#: components/DeleteButton/DeleteButton.js:75
-#: components/DeleteButton/DeleteButton.js:80
-#: components/DeleteButton/DeleteButton.js:90
-#: components/DeleteButton/DeleteButton.js:94
-#: components/DeleteButton/DeleteButton.js:114
-#: components/PaginatedTable/ToolbarDeleteButton.js:158
-#: components/PaginatedTable/ToolbarDeleteButton.js:235
-#: components/PaginatedTable/ToolbarDeleteButton.js:246
-#: components/PaginatedTable/ToolbarDeleteButton.js:250
-#: components/PaginatedTable/ToolbarDeleteButton.js:273
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:29
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:646
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:128
-#: screens/Credential/CredentialDetail/CredentialDetail.js:306
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:124
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:134
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:115
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:127
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:201
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:101
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:316
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:174
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:64
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:68
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:73
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:78
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:102
-#: screens/Job/JobDetail/JobDetail.js:612
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:436
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:199
-#: screens/Project/ProjectDetail/ProjectDetail.js:340
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:80
-#: screens/Team/TeamDetail/TeamDetail.js:70
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:545
-#: screens/Template/Survey/SurveyList.js:66
-#: screens/Template/Survey/SurveyToolbar.js:93
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:269
-#: screens/User/UserDetail/UserDetail.js:107
-#: screens/User/UserTokenDetail/UserTokenDetail.js:78
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:340
-msgid "Delete"
-msgstr "削除"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:130
-msgid "Delete All Groups and Hosts"
-msgstr "すべてのグループおよびホストの削除"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:300
-msgid "Delete Credential"
-msgstr "認証情報の削除"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:127
-msgid "Delete Execution Environment"
-msgstr "実行環境の削除"
-
-#: screens/Host/HostDetail/HostDetail.js:114
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:109
-msgid "Delete Host"
-msgstr "ホストの削除"
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:196
-msgid "Delete Inventory"
-msgstr "インベントリーの削除"
-
-#: screens/Job/JobDetail/JobDetail.js:608
-#: screens/Job/JobOutput/shared/OutputToolbar.js:195
-#: screens/Job/JobOutput/shared/OutputToolbar.js:199
-msgid "Delete Job"
-msgstr "ジョブの削除"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:539
-msgid "Delete Job Template"
-msgstr "ジョブテンプレートの削除"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:432
-msgid "Delete Notification"
-msgstr "通知の削除"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:193
-msgid "Delete Organization"
-msgstr "組織の削除"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:334
-msgid "Delete Project"
-msgstr "プロジェクトの削除"
-
-#: screens/Template/Survey/SurveyList.js:52
-msgid "Delete Questions"
-msgstr "質問の削除"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:642
-msgid "Delete Schedule"
-msgstr "スケジュールの削除"
-
-#: screens/Template/Survey/SurveyList.js:52
-msgid "Delete Survey"
-msgstr "Survey の削除"
-
-#: screens/Team/TeamDetail/TeamDetail.js:66
-msgid "Delete Team"
-msgstr "チームの削除"
-
-#: screens/User/UserDetail/UserDetail.js:103
-msgid "Delete User"
-msgstr "ユーザーの削除"
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:74
-msgid "Delete User Token"
-msgstr "ユーザートークンの削除"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:336
-msgid "Delete Workflow Approval"
-msgstr "ワークフロー承認の削除"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:263
-msgid "Delete Workflow Job Template"
-msgstr "新規ワークフロージョブテンプレートの削除"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:138
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:141
-msgid "Delete all nodes"
-msgstr "すべてのノードの削除"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:124
-msgid "Delete application"
-msgstr "アプリケーションの削除"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:116
-msgid "Delete credential type"
-msgstr "認証情報タイプの削除"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:249
-msgid "Delete error"
-msgstr "エラーの削除"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:109
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:121
-msgid "Delete instance group"
-msgstr "インスタンスグループの削除"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:310
-msgid "Delete inventory source"
-msgstr "インベントリーソースの削除"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:170
-msgid "Delete smart inventory"
-msgstr "スマートインベントリーの削除"
-
-#: screens/Template/Survey/SurveyToolbar.js:83
-msgid "Delete survey question"
-msgstr "Survey の質問の削除"
-
-#: screens/Project/shared/Project.helptext.js:114
-msgid ""
-"Delete the local repository in its entirety prior to\n"
-"performing an update. Depending on the size of the\n"
-"repository this may significantly increase the amount\n"
-"of time required to complete an update."
-msgstr "更新の実行前にローカルリポジトリーを完全に削除します。\n"
-"リポジトリーのサイズによっては、\n"
-"更新の完了までにかかる時間が\n"
-"大幅に増大します。"
-
-#: components/PromptDetail/PromptProjectDetail.js:51
-#: screens/Project/ProjectDetail/ProjectDetail.js:100
-msgid "Delete the project before syncing"
-msgstr "プロジェクトを削除してから同期する"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:83
-msgid "Delete this link"
-msgstr "このリンクの削除"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:274
-msgid "Delete this node"
-msgstr "このノードの削除"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:163
-msgid "Delete {pluralizedItemName}?"
-msgstr "{pluralizedItemName} を削除しますか?"
-
-#: components/DetailList/DeletedDetail.js:19
-#: components/Workflow/WorkflowNodeHelp.js:157
-#: components/Workflow/WorkflowNodeHelp.js:193
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:231
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:49
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:60
-msgid "Deleted"
-msgstr "削除済み"
-
-#: components/TemplateList/TemplateList.js:296
-#: screens/Credential/CredentialList/CredentialList.js:211
-#: screens/Inventory/InventoryList/InventoryList.js:284
-#: screens/Project/ProjectList/ProjectList.js:290
-msgid "Deletion Error"
-msgstr "削除エラー"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:202
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:227
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:219
-msgid "Deletion error"
-msgstr "削除エラー"
-
-#: components/StatusLabel/StatusLabel.js:40
-msgid "Denied"
-msgstr "拒否済み"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:21
-msgid "Denied - {0}. See the Activity Stream for more information."
-msgstr "拒否されました - {0}。詳細については、アクティビティーストリームを参照してください。"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:17
-msgid "Denied by {0} - {1}"
-msgstr "{0} - {1} により拒否済み"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:89
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:35
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:42
-msgid "Deny"
-msgstr "拒否"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:103
-msgid "Deprecated"
-msgstr "非推奨"
-
-#: components/StatusLabel/StatusLabel.js:60
-#: screens/TopologyView/Legend.js:164
-msgid "Deprovisioning"
-msgstr "プロビジョニング解除"
-
-#: components/StatusLabel/StatusLabel.js:63
-msgid "Deprovisioning fail"
-msgstr "プロビジョニング解除に失敗"
-
-#: components/HostForm/HostForm.js:104
-#: components/Lookup/ApplicationLookup.js:105
-#: components/Lookup/ApplicationLookup.js:123
-#: components/Lookup/HostFilterLookup.js:423
-#: components/Lookup/HostListItem.js:9
-#: components/NotificationList/NotificationList.js:186
-#: components/PromptDetail/PromptDetail.js:119
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:327
-#: components/Schedule/ScheduleList/ScheduleList.js:194
-#: components/Schedule/shared/ScheduleFormFields.js:80
-#: components/TemplateList/TemplateList.js:210
-#: components/TemplateList/TemplateListItem.js:271
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:65
-#: screens/Application/ApplicationsList/ApplicationsList.js:123
-#: screens/Application/shared/ApplicationForm.js:62
-#: screens/Credential/CredentialDetail/CredentialDetail.js:223
-#: screens/Credential/CredentialList/CredentialList.js:146
-#: screens/Credential/shared/CredentialForm.js:169
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:72
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:128
-#: screens/CredentialType/shared/CredentialTypeForm.js:29
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:60
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:159
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:127
-#: screens/Host/HostDetail/HostDetail.js:75
-#: screens/Host/HostList/HostList.js:153
-#: screens/Host/HostList/HostList.js:170
-#: screens/Host/HostList/HostListItem.js:57
-#: screens/Instances/Shared/InstanceForm.js:26
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:93
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:35
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:216
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:80
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:40
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:124
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:139
-#: screens/Inventory/InventoryList/InventoryList.js:195
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:198
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:104
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:36
-#: screens/Inventory/shared/InventoryForm.js:58
-#: screens/Inventory/shared/InventoryGroupForm.js:40
-#: screens/Inventory/shared/InventorySourceForm.js:108
-#: screens/Inventory/shared/SmartInventoryForm.js:55
-#: screens/Job/JobOutput/HostEventModal.js:112
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:101
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:72
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:109
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:127
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:49
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:95
-#: screens/Organization/OrganizationList/OrganizationList.js:127
-#: screens/Organization/shared/OrganizationForm.js:64
-#: screens/Project/ProjectDetail/ProjectDetail.js:177
-#: screens/Project/ProjectList/ProjectList.js:190
-#: screens/Project/ProjectList/ProjectListItem.js:281
-#: screens/Project/shared/ProjectForm.js:222
-#: screens/Team/TeamDetail/TeamDetail.js:38
-#: screens/Team/TeamList/TeamList.js:122
-#: screens/Team/shared/TeamForm.js:37
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:187
-#: screens/Template/Survey/SurveyQuestionForm.js:171
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:112
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:183
-#: screens/Template/shared/JobTemplateForm.js:251
-#: screens/Template/shared/WorkflowJobTemplateForm.js:117
-#: screens/User/UserOrganizations/UserOrganizationList.js:80
-#: screens/User/UserOrganizations/UserOrganizationListItem.js:18
-#: screens/User/UserTeams/UserTeamList.js:182
-#: screens/User/UserTeams/UserTeamListItem.js:32
-#: screens/User/UserTokenDetail/UserTokenDetail.js:45
-#: screens/User/UserTokenList/UserTokenList.js:128
-#: screens/User/UserTokenList/UserTokenList.js:138
-#: screens/User/UserTokenList/UserTokenList.js:188
-#: screens/User/UserTokenList/UserTokenListItem.js:29
-#: screens/User/shared/UserTokenForm.js:59
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:145
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:128
-msgid "Description"
-msgstr "説明"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:325
-msgid "Destination Channels"
-msgstr "送信先チャネル"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:233
-msgid "Destination Channels or Users"
-msgstr "送信先チャネルまたはユーザー"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:346
-msgid "Destination SMS Number(s)"
-msgstr "送信先 SMS 番号"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:412
-msgid "Destination SMS number(s)"
-msgstr "送信先 SMS 番号"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:364
-msgid "Destination channels"
-msgstr "送信先チャネル"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:231
-msgid "Destination channels or users"
-msgstr "送信先チャネルまたはユーザー"
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:35
-#: components/ErrorDetail/ErrorDetail.js:88
-#: components/Schedule/Schedule.js:71
-#: screens/Application/Application/Application.js:79
-#: screens/Application/Applications.js:39
-#: screens/Credential/Credential.js:72
-#: screens/Credential/Credentials.js:28
-#: screens/CredentialType/CredentialType.js:63
-#: screens/CredentialType/CredentialTypes.js:26
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:65
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:26
-#: screens/Host/Host.js:58
-#: screens/Host/Hosts.js:27
-#: screens/InstanceGroup/ContainerGroup.js:66
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:180
-#: screens/InstanceGroup/InstanceGroup.js:69
-#: screens/InstanceGroup/InstanceGroups.js:30
-#: screens/InstanceGroup/InstanceGroups.js:38
-#: screens/Instances/Instance.js:29
-#: screens/Instances/Instances.js:24
-#: screens/Inventory/Inventories.js:61
-#: screens/Inventory/Inventories.js:87
-#: screens/Inventory/Inventory.js:64
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:57
-#: screens/Inventory/InventoryHost/InventoryHost.js:73
-#: screens/Inventory/InventorySource/InventorySource.js:83
-#: screens/Inventory/SmartInventory.js:66
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:60
-#: screens/Job/Job.js:130
-#: screens/Job/JobOutput/HostEventModal.js:103
-#: screens/Job/Jobs.js:35
-#: screens/ManagementJob/ManagementJobs.js:26
-#: screens/NotificationTemplate/NotificationTemplate.js:84
-#: screens/NotificationTemplate/NotificationTemplates.js:25
-#: screens/Organization/Organization.js:123
-#: screens/Organization/Organizations.js:30
-#: screens/Project/Project.js:104
-#: screens/Project/Projects.js:26
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:51
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:51
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:65
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:76
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:50
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:97
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:51
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:56
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:52
-#: screens/Setting/Settings.js:45
-#: screens/Setting/Settings.js:48
-#: screens/Setting/Settings.js:52
-#: screens/Setting/Settings.js:55
-#: screens/Setting/Settings.js:58
-#: screens/Setting/Settings.js:61
-#: screens/Setting/Settings.js:64
-#: screens/Setting/Settings.js:67
-#: screens/Setting/Settings.js:70
-#: screens/Setting/Settings.js:73
-#: screens/Setting/Settings.js:76
-#: screens/Setting/Settings.js:85
-#: screens/Setting/Settings.js:86
-#: screens/Setting/Settings.js:87
-#: screens/Setting/Settings.js:88
-#: screens/Setting/Settings.js:89
-#: screens/Setting/Settings.js:90
-#: screens/Setting/Settings.js:98
-#: screens/Setting/Settings.js:101
-#: screens/Setting/Settings.js:104
-#: screens/Setting/Settings.js:107
-#: screens/Setting/Settings.js:110
-#: screens/Setting/Settings.js:113
-#: screens/Setting/Settings.js:116
-#: screens/Setting/Settings.js:119
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:56
-#: screens/Setting/UI/UIDetail/UIDetail.js:66
-#: screens/Team/Team.js:57
-#: screens/Team/Teams.js:29
-#: screens/Template/Template.js:135
-#: screens/Template/Templates.js:43
-#: screens/Template/WorkflowJobTemplate.js:117
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:138
-#: screens/TopologyView/Tooltip.js:187
-#: screens/TopologyView/Tooltip.js:213
-#: screens/User/User.js:64
-#: screens/User/UserToken/UserToken.js:54
-#: screens/User/Users.js:30
-#: screens/User/Users.js:36
-#: screens/WorkflowApproval/WorkflowApproval.js:77
-#: screens/WorkflowApproval/WorkflowApprovals.js:24
-msgid "Details"
-msgstr "詳細"
-
-#: screens/Job/JobOutput/HostEventModal.js:100
-msgid "Details tab"
-msgstr "詳細タブ"
-
-#: components/Search/AdvancedSearch.js:271
-msgid "Direct Keys"
-msgstr "ダイレクトキー"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:209
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:268
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:313
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:371
-msgid "Disable SSL Verification"
-msgstr "SSL 検証の無効化"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:187
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:240
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:279
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:350
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:461
-msgid "Disable SSL verification"
-msgstr "SSL 検証の無効化"
-
-#: components/InstanceToggle/InstanceToggle.js:56
-#: components/StatusLabel/StatusLabel.js:53
-#: screens/TopologyView/Legend.js:233
-msgid "Disabled"
-msgstr "無効化"
-
-#: components/DisassociateButton/DisassociateButton.js:73
-#: components/DisassociateButton/DisassociateButton.js:97
-#: components/DisassociateButton/DisassociateButton.js:109
-#: components/DisassociateButton/DisassociateButton.js:113
-#: components/DisassociateButton/DisassociateButton.js:133
-#: screens/Team/TeamRoles/TeamRolesList.js:222
-#: screens/User/UserRoles/UserRolesList.js:218
-msgid "Disassociate"
-msgstr "関連付けの解除"
-
-#: screens/Host/HostGroups/HostGroupsList.js:211
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:229
-msgid "Disassociate group from host?"
-msgstr "グループのホストとの関連付けを解除しますか?"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:247
-msgid "Disassociate host from group?"
-msgstr "ホストのグループとの関連付けを解除しますか?"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:296
-#: screens/InstanceGroup/Instances/InstanceList.js:245
-msgid "Disassociate instance from instance group?"
-msgstr "インスタンスグループへのインスタンスの関連付けを解除しますか?"
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:225
-msgid "Disassociate related group(s)?"
-msgstr "関連するグループの関連付けを解除しますか?"
-
-#: screens/User/UserTeams/UserTeamList.js:216
-msgid "Disassociate related team(s)?"
-msgstr "関連するチームの関連付けを解除しますか?"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:209
-#: screens/User/UserRoles/UserRolesList.js:205
-msgid "Disassociate role"
-msgstr "ロールの関連付けの解除"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:212
-#: screens/User/UserRoles/UserRolesList.js:208
-msgid "Disassociate role!"
-msgstr "ロールの関連付けの解除!"
-
-#: components/DisassociateButton/DisassociateButton.js:18
-msgid "Disassociate?"
-msgstr "関連付けを解除しますか?"
-
-#: components/PromptDetail/PromptProjectDetail.js:46
-#: screens/Project/ProjectDetail/ProjectDetail.js:94
-msgid "Discard local changes before syncing"
-msgstr "同期する前にローカル変更を破棄する"
-
-#: screens/Job/Job.helptext.js:16
-#: screens/Template/shared/JobTemplate.helptext.js:17
-msgid "Divide the work done by this job template into the specified number of job slices, each running the same tasks against a portion of the inventory."
-msgstr "このジョブテンプレートで実施される作業を指定した数のジョブスライスに分割し、それぞれインベントリーの部分に対して同じタスクを実行します。"
-
-#: screens/Project/shared/Project.helptext.js:100
-msgid "Documentation."
-msgstr "ドキュメント。"
-
-#: components/CodeEditor/VariablesDetail.js:117
-#: components/CodeEditor/VariablesDetail.js:123
-#: components/CodeEditor/VariablesField.js:139
-#: components/CodeEditor/VariablesField.js:145
-msgid "Done"
-msgstr "完了"
-
-#: screens/TopologyView/Tooltip.js:251
-msgid "Download Bundle"
-msgstr "バンドルのダウンロード"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:179
-#: screens/Job/JobOutput/shared/OutputToolbar.js:184
-msgid "Download Output"
-msgstr "出力のダウンロード"
-
-#: screens/TopologyView/Tooltip.js:247
-msgid "Download bundle"
-msgstr "バンドルのダウンロード"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:93
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:114
-msgid "Drag a file here or browse to upload"
-msgstr "ここにファイルをドラッグするか、参照してアップロード"
-
-#: components/SelectedList/DraggableSelectedList.js:68
-msgid "Draggable list to reorder and remove selected items."
-msgstr "選択した項目を並べ替えたり削除したりできるドラッグ可能なリスト。"
-
-#: components/SelectedList/DraggableSelectedList.js:43
-msgid "Dragging cancelled. List is unchanged."
-msgstr "ドラッグがキャンセルされました。リストは変更されていません。"
-
-#: components/SelectedList/DraggableSelectedList.js:38
-msgid "Dragging item {id}. Item with index {oldIndex} in now {newIndex}."
-msgstr "項目の {id} をドラッグする。項目のインデックスが {oldIndex} から {newIndex} に変わりました。"
-
-#: components/SelectedList/DraggableSelectedList.js:32
-msgid "Dragging started for item id: {newId}."
-msgstr "項目 ID: {newId} のドラッグが開始しました。"
-
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:81
-msgid "E-mail"
-msgstr "メール"
-
-#: screens/Inventory/shared/Inventory.helptext.js:113
-msgid ""
-"Each time a job runs using this inventory,\n"
-"refresh the inventory from the selected source before\n"
-"executing job tasks."
-msgstr "このインベントリーでジョブを実行する際は常に、\n"
-"選択されたソースのインベントリーを更新してから\n"
-"ジョブのタスクを実行します。"
-
-#: screens/Project/shared/Project.helptext.js:124
-msgid ""
-"Each time a job runs using this project, update the\n"
-"revision of the project prior to starting the job."
-msgstr "このプロジェクトでジョブを実行する際は常に、ジョブの開始前にプロジェクトのリビジョンを更新します。"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:632
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:636
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:115
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:117
-#: screens/Credential/CredentialDetail/CredentialDetail.js:293
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:109
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:121
-#: screens/Host/HostDetail/HostDetail.js:108
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:101
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:113
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:190
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:55
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:62
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:103
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:292
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:127
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:164
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:418
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:420
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:138
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:182
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:186
-#: screens/Project/ProjectDetail/ProjectDetail.js:313
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:85
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:89
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:148
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:152
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:85
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:89
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:96
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:100
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:164
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:168
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:107
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:111
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:84
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:88
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:152
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:156
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:85
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:89
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:99
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:103
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:86
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:90
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:199
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:103
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:108
-#: screens/Setting/UI/UIDetail/UIDetail.js:105
-#: screens/Setting/UI/UIDetail/UIDetail.js:110
-#: screens/Team/TeamDetail/TeamDetail.js:55
-#: screens/Team/TeamDetail/TeamDetail.js:59
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:514
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:516
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:239
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:241
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:260
-#: screens/User/UserDetail/UserDetail.js:96
-msgid "Edit"
-msgstr "編集"
-
-#: screens/Credential/CredentialList/CredentialListItem.js:67
-#: screens/Credential/CredentialList/CredentialListItem.js:71
-msgid "Edit Credential"
-msgstr "認証情報の編集"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:37
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:42
-msgid "Edit Credential Plugin Configuration"
-msgstr "認証情報プラグイン設定の編集"
-
-#: screens/Application/Applications.js:38
-#: screens/Credential/Credentials.js:27
-#: screens/Host/Hosts.js:26
-#: screens/ManagementJob/ManagementJobs.js:27
-#: screens/NotificationTemplate/NotificationTemplates.js:24
-#: screens/Organization/Organizations.js:29
-#: screens/Project/Projects.js:25
-#: screens/Project/Projects.js:35
-#: screens/Setting/Settings.js:46
-#: screens/Setting/Settings.js:49
-#: screens/Setting/Settings.js:53
-#: screens/Setting/Settings.js:56
-#: screens/Setting/Settings.js:59
-#: screens/Setting/Settings.js:62
-#: screens/Setting/Settings.js:65
-#: screens/Setting/Settings.js:68
-#: screens/Setting/Settings.js:71
-#: screens/Setting/Settings.js:74
-#: screens/Setting/Settings.js:77
-#: screens/Setting/Settings.js:91
-#: screens/Setting/Settings.js:92
-#: screens/Setting/Settings.js:93
-#: screens/Setting/Settings.js:94
-#: screens/Setting/Settings.js:95
-#: screens/Setting/Settings.js:96
-#: screens/Setting/Settings.js:99
-#: screens/Setting/Settings.js:102
-#: screens/Setting/Settings.js:105
-#: screens/Setting/Settings.js:108
-#: screens/Setting/Settings.js:111
-#: screens/Setting/Settings.js:114
-#: screens/Setting/Settings.js:117
-#: screens/Setting/Settings.js:120
-#: screens/Team/Teams.js:28
-#: screens/Template/Templates.js:44
-#: screens/User/Users.js:29
-msgid "Edit Details"
-msgstr "詳細の編集"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:90
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:94
-msgid "Edit Execution Environment"
-msgstr "実行環境の編集"
-
-#: screens/Host/HostGroups/HostGroupItem.js:37
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:46
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:46
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:51
-msgid "Edit Group"
-msgstr "グループの編集"
-
-#: screens/Host/HostList/HostListItem.js:74
-#: screens/Host/HostList/HostListItem.js:78
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:61
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:64
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:67
-msgid "Edit Host"
-msgstr "ホストの編集"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:134
-#: screens/Inventory/InventoryList/InventoryListItem.js:139
-msgid "Edit Inventory"
-msgstr "インベントリーの編集"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkEditModal.js:14
-msgid "Edit Link"
-msgstr "リンクの編集"
-
-#: screens/Setting/shared/SharedFields.js:290
-msgid "Edit Login redirect override URL"
-msgstr "ログインリダイレクトのオーバーライド URL"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeEditModal.js:64
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:257
-msgid "Edit Node"
-msgstr "ノードの編集"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:142
-msgid "Edit Notification Template"
-msgstr "通知テンプレートの編集"
-
-#: screens/Template/Survey/SurveyToolbar.js:73
-msgid "Edit Order"
-msgstr "順序の編集"
-
-#: screens/Organization/OrganizationList/OrganizationListItem.js:72
-#: screens/Organization/OrganizationList/OrganizationListItem.js:76
-msgid "Edit Organization"
-msgstr "組織の編集"
-
-#: screens/Project/ProjectList/ProjectListItem.js:248
-#: screens/Project/ProjectList/ProjectListItem.js:253
-msgid "Edit Project"
-msgstr "プロジェクトの編集"
-
-#: screens/Template/Templates.js:50
-msgid "Edit Question"
-msgstr "質問の編集"
-
-#: components/Schedule/ScheduleList/ScheduleListItem.js:132
-#: components/Schedule/ScheduleList/ScheduleListItem.js:136
-#: screens/Template/Templates.js:55
-msgid "Edit Schedule"
-msgstr "スケジュールの編集"
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:131
-msgid "Edit Source"
-msgstr "ソースの編集"
-
-#: screens/Template/Survey/SurveyListItem.js:92
-msgid "Edit Survey"
-msgstr "Survey の編集"
-
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:22
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:26
-#: screens/Team/TeamList/TeamListItem.js:50
-#: screens/Team/TeamList/TeamListItem.js:54
-msgid "Edit Team"
-msgstr "チームの編集"
-
-#: components/TemplateList/TemplateListItem.js:233
-#: components/TemplateList/TemplateListItem.js:239
-msgid "Edit Template"
-msgstr "テンプレートの編集"
-
-#: screens/User/UserList/UserListItem.js:59
-#: screens/User/UserList/UserListItem.js:63
-msgid "Edit User"
-msgstr "ユーザーの編集"
-
-#: screens/Application/ApplicationsList/ApplicationListItem.js:51
-#: screens/Application/ApplicationsList/ApplicationListItem.js:55
-msgid "Edit application"
-msgstr "アプリケーションの編集"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:41
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:45
-msgid "Edit credential type"
-msgstr "認証情報タイプの編集"
-
-#: screens/CredentialType/CredentialTypes.js:25
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:25
-#: screens/InstanceGroup/InstanceGroups.js:35
-#: screens/InstanceGroup/InstanceGroups.js:40
-#: screens/Inventory/Inventories.js:63
-#: screens/Inventory/Inventories.js:68
-#: screens/Inventory/Inventories.js:77
-#: screens/Inventory/Inventories.js:88
-msgid "Edit details"
-msgstr "詳細の編集"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:42
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:44
-msgid "Edit group"
-msgstr "グループの編集"
-
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:48
-msgid "Edit host"
-msgstr "ホストの編集"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:78
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:82
-msgid "Edit instance group"
-msgstr "インスタンスグループの編集"
-
-#: screens/Setting/shared/SharedFields.js:320
-#: screens/Setting/shared/SharedFields.js:322
-msgid "Edit login redirect override URL"
-msgstr "ログインリダイレクトのオーバーライド URL"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:70
-msgid "Edit this link"
-msgstr "このリンクの編集"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:248
-msgid "Edit this node"
-msgstr "このノードの編集"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:97
-msgid "Edit workflow"
-msgstr "ワークフローの編集"
-
-#: components/Workflow/WorkflowNodeHelp.js:170
-#: screens/Job/JobOutput/shared/OutputToolbar.js:125
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:216
-msgid "Elapsed"
-msgstr "経過時間"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:124
-msgid "Elapsed Time"
-msgstr "経過時間"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:126
-msgid "Elapsed time that the job ran"
-msgstr "ジョブ実行の経過時間"
-
-#: components/NotificationList/NotificationList.js:193
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:134
-#: screens/User/UserDetail/UserDetail.js:66
-#: screens/User/UserList/UserList.js:115
-#: screens/User/shared/UserForm.js:73
-msgid "Email"
-msgstr "メール"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:177
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:125
-msgid "Email Options"
-msgstr "メールオプション"
-
-#: screens/Template/shared/WorkflowJobTemplateForm.js:263
-msgid "Enable Concurrent Jobs"
-msgstr "同時実行ジョブの有効化"
-
-#: screens/Template/shared/JobTemplateForm.js:597
-msgid "Enable Fact Storage"
-msgstr "ファクトストレージの有効化"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:192
-msgid "Enable HTTPS certificate verification"
-msgstr "HTTPS 証明書の検証を有効化"
-
-#: screens/Instances/Shared/InstanceForm.js:58
-msgid "Enable Instance"
-msgstr "インスタンスを有効にする"
-
-#: screens/Template/shared/JobTemplateForm.js:573
-#: screens/Template/shared/JobTemplateForm.js:576
-#: screens/Template/shared/WorkflowJobTemplateForm.js:244
-#: screens/Template/shared/WorkflowJobTemplateForm.js:247
-msgid "Enable Webhook"
-msgstr "Webhook の有効化"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:15
-msgid "Enable Webhook for this workflow job template."
-msgstr "このワークフローのジョブテンプレートの Webhook を有効にします。"
-
-#: screens/Project/shared/Project.helptext.js:108
-msgid ""
-"Enable content signing to verify that the content \n"
-"has remained secure when a project is synced. \n"
-"If the content has been tampered with, the \n"
-"job will not run."
-msgstr "コンテンツの署名を有効にして、コンテンツが\n"
-"プロジェクトが同期されても安全です。\n"
-"内容が改ざんされた場合、\n"
-"ジョブは実行されません。"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:136
-msgid "Enable external logging"
-msgstr "外部ログの有効化"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:168
-msgid "Enable log system tracking facts individually"
-msgstr "システムトラッキングファクトを個別に有効化"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:201
-#: components/AdHocCommands/AdHocDetailsStep.js:204
-msgid "Enable privilege escalation"
-msgstr "権限昇格の有効化"
-
-#: screens/Setting/SettingList.js:53
-msgid "Enable simplified login for your {brandName} applications"
-msgstr "{brandName} アプリケーションの簡単ログインの有効化"
-
-#: screens/Template/shared/JobTemplate.helptext.js:31
-msgid "Enable webhook for this template."
-msgstr "このテンプレートの Webhook を有効にします。"
-
-#: components/InstanceToggle/InstanceToggle.js:55
-#: components/Lookup/HostFilterLookup.js:110
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:45
-#: screens/TopologyView/Legend.js:205
-msgid "Enabled"
-msgstr "有効化"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:171
-#: components/PromptDetail/PromptJobTemplateDetail.js:187
-#: components/PromptDetail/PromptProjectDetail.js:145
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:99
-#: screens/Credential/CredentialDetail/CredentialDetail.js:268
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:138
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:265
-#: screens/Project/ProjectDetail/ProjectDetail.js:302
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:365
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:199
-msgid "Enabled Options"
-msgstr "有効なオプション"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:252
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:132
-msgid "Enabled Value"
-msgstr "有効な値"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:247
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:119
-msgid "Enabled Variable"
-msgstr "有効な変数"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:209
-msgid ""
-"Enables creation of a provisioning\n"
-"callback URL. Using the URL a host can contact {brandName}\n"
-"and request a configuration update using this job\n"
-"template"
-msgstr "プロビジョニングコールバック URL の作成を有効にします。ホストは、この URL を使用して {brandName} に接続でき、このジョブテンプレートを使用して接続の更新を要求できます。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:29
-msgid "Enables creation of a provisioning callback URL. Using the URL a host can contact {brandName} and request a configuration update using this job template."
-msgstr "プロビジョニングコールバック URL の作成を有効にします。この URL を使用してホストは {brandName} に接続でき、このジョブテンプレートを使用して接続の更新を要求できます。"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:160
-#: screens/Setting/shared/SettingDetail.js:87
-msgid "Encrypted"
-msgstr "暗号化"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:109
-#: components/Schedule/shared/FrequencyDetailSubform.js:507
-msgid "End"
-msgstr "終了"
-
-#: screens/Setting/Subscription/SubscriptionEdit/EulaStep.js:14
-msgid "End User License Agreement"
-msgstr "使用許諾契約書"
-
-#: components/Schedule/shared/DateTimePicker.js:51
-msgid "End date"
-msgstr "終了日"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:561
-msgid "End date/time"
-msgstr "終了日時"
-
-#: components/Schedule/shared/buildRuleObj.js:110
-msgid "End did not match an expected value ({0})"
-msgstr "終了が期待値と一致しませんでした ({0})"
-
-#: components/Schedule/shared/DateTimePicker.js:61
-msgid "End time"
-msgstr "終了時刻"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:209
-msgid "End user license agreement"
-msgstr "使用許諾契約書"
-
-#: screens/Host/HostList/SmartInventoryButton.js:23
-msgid "Enter at least one search filter to create a new Smart Inventory"
-msgstr "新規スマートインベントリーを作成するために 1 つ以上の検索フィルターを入力してください。"
-
-#: screens/CredentialType/shared/CredentialTypeForm.js:43
-msgid "Enter injectors using either JSON or YAML syntax. Refer to the Ansible Controller documentation for example syntax."
-msgstr "JSON または YAML 構文のいずれかを使用してインジェクターを入力します。構文のサンプルについては Ansible Controller ドキュメントを参照してください。"
-
-#: screens/CredentialType/shared/CredentialTypeForm.js:35
-msgid "Enter inputs using either JSON or YAML syntax. Refer to the Ansible Controller documentation for example syntax."
-msgstr "JSON または YAML 構文のいずれかを使用してインジェクターを入力します。構文のサンプルについては Ansible Controller ドキュメントを参照してください。"
-
-#: screens/Inventory/shared/SmartInventoryForm.js:94
-msgid ""
-"Enter inventory variables using either JSON or YAML syntax.\n"
-"Use the radio button to toggle between the two. Refer to the\n"
-"Ansible Controller documentation for example syntax."
-msgstr "JSON または YAML 構文のいずれかを使用してインベントリー変数を入力します。\n"
-"ラジオボタンを使用して構文間で切り替えを行います。\n"
-"構文のサンプルについては Ansible Controller ドキュメントを参照してください。"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:87
-msgid "Environment variables or extra variables that specify the values a credential type can inject."
-msgstr "認証情報タイプが挿入できる値を指定する環境変数または追加変数。"
-
-#: components/JobList/JobList.js:233
-#: components/StatusLabel/StatusLabel.js:46
-#: components/Workflow/WorkflowNodeHelp.js:108
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:133
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:205
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:143
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:230
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:123
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:135
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:222
-#: screens/Job/JobOutput/JobOutputSearch.js:104
-#: screens/TopologyView/Legend.js:178
-msgid "Error"
-msgstr "エラー"
-
-#: screens/Project/ProjectList/ProjectList.js:302
-msgid "Error fetching updated project"
-msgstr "更新されたプロジェクトの取得エラー"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:501
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:141
-msgid "Error message"
-msgstr "エラーメッセージ"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:510
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:150
-msgid "Error message body"
-msgstr "エラーメッセージボディー"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:709
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:711
-msgid "Error saving the workflow!"
-msgstr "ワークフローの保存中にエラー!"
-
-#: components/AdHocCommands/AdHocCommands.js:104
-#: components/CopyButton/CopyButton.js:51
-#: components/DeleteButton/DeleteButton.js:56
-#: components/HostToggle/HostToggle.js:76
-#: components/InstanceToggle/InstanceToggle.js:67
-#: components/JobList/JobList.js:315
-#: components/JobList/JobList.js:326
-#: components/LaunchButton/LaunchButton.js:185
-#: components/LaunchPrompt/LaunchPrompt.js:96
-#: components/NotificationList/NotificationList.js:246
-#: components/PaginatedTable/ToolbarDeleteButton.js:205
-#: components/RelatedTemplateList/RelatedTemplateList.js:241
-#: components/ResourceAccessList/ResourceAccessList.js:277
-#: components/ResourceAccessList/ResourceAccessList.js:289
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:654
-#: components/Schedule/ScheduleList/ScheduleList.js:239
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:73
-#: components/Schedule/shared/SchedulePromptableFields.js:63
-#: components/TemplateList/TemplateList.js:299
-#: contexts/Config.js:94
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:136
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:155
-#: screens/Application/ApplicationsList/ApplicationsList.js:185
-#: screens/Credential/CredentialDetail/CredentialDetail.js:314
-#: screens/Credential/CredentialList/CredentialList.js:214
-#: screens/Host/HostDetail/HostDetail.js:56
-#: screens/Host/HostDetail/HostDetail.js:123
-#: screens/Host/HostGroups/HostGroupsList.js:244
-#: screens/Host/HostList/HostList.js:233
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:310
-#: screens/InstanceGroup/Instances/InstanceList.js:308
-#: screens/InstanceGroup/Instances/InstanceListItem.js:218
-#: screens/Instances/InstanceDetail/InstanceDetail.js:360
-#: screens/Instances/InstanceDetail/InstanceDetail.js:375
-#: screens/Instances/InstanceList/InstanceList.js:231
-#: screens/Instances/InstanceList/InstanceList.js:243
-#: screens/Instances/InstanceList/InstanceListItem.js:234
-#: screens/Instances/Shared/RemoveInstanceButton.js:104
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:210
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:78
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:285
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:296
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:56
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:118
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:261
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:201
-#: screens/Inventory/InventoryList/InventoryList.js:285
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:264
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:323
-#: screens/Inventory/InventorySources/InventorySourceList.js:239
-#: screens/Inventory/InventorySources/InventorySourceList.js:252
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:183
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:152
-#: screens/Inventory/shared/InventorySourceSyncButton.js:49
-#: screens/Login/Login.js:239
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:125
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:444
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:233
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:169
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:208
-#: screens/Organization/OrganizationList/OrganizationList.js:195
-#: screens/Project/ProjectDetail/ProjectDetail.js:348
-#: screens/Project/ProjectList/ProjectList.js:291
-#: screens/Project/ProjectList/ProjectList.js:303
-#: screens/Project/shared/ProjectSyncButton.js:60
-#: screens/Team/TeamDetail/TeamDetail.js:78
-#: screens/Team/TeamList/TeamList.js:192
-#: screens/Team/TeamRoles/TeamRolesList.js:247
-#: screens/Team/TeamRoles/TeamRolesList.js:258
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:554
-#: screens/Template/TemplateSurvey.js:130
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:277
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:180
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:195
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:337
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:373
-#: screens/TopologyView/MeshGraph.js:405
-#: screens/TopologyView/Tooltip.js:199
-#: screens/User/UserDetail/UserDetail.js:115
-#: screens/User/UserList/UserList.js:189
-#: screens/User/UserRoles/UserRolesList.js:243
-#: screens/User/UserRoles/UserRolesList.js:254
-#: screens/User/UserTeams/UserTeamList.js:259
-#: screens/User/UserTokenDetail/UserTokenDetail.js:85
-#: screens/User/UserTokenList/UserTokenList.js:214
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:348
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:189
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:53
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:48
-msgid "Error!"
-msgstr "エラー!"
-
-#: components/CodeEditor/VariablesDetail.js:105
-msgid "Error:"
-msgstr "エラー:"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:267
-#: screens/Instances/InstanceDetail/InstanceDetail.js:315
-msgid "Errors"
-msgstr "エラー"
-
-#: screens/TopologyView/Legend.js:253
-msgid "Established"
-msgstr "確立済み"
-
-#: screens/ActivityStream/ActivityStream.js:265
-#: screens/ActivityStream/ActivityStreamListItem.js:46
-#: screens/Job/JobOutput/JobOutputSearch.js:99
-msgid "Event"
-msgstr "イベント"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:35
-msgid "Event detail"
-msgstr "イベント詳細"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:36
-msgid "Event detail modal"
-msgstr "イベント詳細モーダル"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:555
-msgid "Event summary not available"
-msgstr "イベントの概要はありません"
-
-#: screens/ActivityStream/ActivityStream.js:234
-msgid "Events"
-msgstr "イベント"
-
-#: screens/Job/JobOutput/JobOutput.js:713
-msgid "Events processing complete."
-msgstr "イベントの処理が完了しました。"
-
-#: components/Search/LookupTypeInput.js:39
-msgid "Exact match (default lookup if not specified)."
-msgstr "完全一致 (指定されない場合のデフォルトのルックアップ)。"
-
-#: components/Search/RelatedLookupTypeInput.js:38
-msgid "Exact search on id field."
-msgstr "id フィールドでの正確な検索。"
-
-#: screens/Project/shared/Project.helptext.js:23
-msgid "Example URLs for GIT Source Control include:"
-msgstr "GIT ソースコントロールの URL の例は次のとおりです。"
-
-#: screens/Project/shared/Project.helptext.js:62
-msgid "Example URLs for Remote Archive Source Control include:"
-msgstr "リモートアーカイブ Source Control のサンプル URL には以下が含まれます:"
-
-#: screens/Project/shared/Project.helptext.js:45
-msgid "Example URLs for Subversion Source Control include:"
-msgstr "Subversion Source Control のサンプル URL には以下が含まれます:"
-
-#: screens/Project/shared/Project.helptext.js:84
-msgid "Examples include:"
-msgstr "以下に例を示します。"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:10
-msgid "Examples:"
-msgstr "例:"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:354
-msgid "Exception Frequency"
-msgstr "例外頻度"
-
-#: components/Schedule/shared/ScheduleFormFields.js:160
-msgid "Exceptions"
-msgstr "例外"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:47
-msgid "Execute regardless of the parent node's final state."
-msgstr "親ノードの最終状態に関係なく実行します。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:40
-msgid "Execute when the parent node results in a failure state."
-msgstr "親ノードが障害状態になったときに実行します。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:33
-msgid "Execute when the parent node results in a successful state."
-msgstr "親ノードが正常な状態になったときに実行します。"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:208
-#: screens/Instances/InstanceList/InstanceList.js:152
-msgid "Execution"
-msgstr "実行"
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:90
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:91
-#: components/AdHocCommands/AdHocPreviewStep.js:58
-#: components/AdHocCommands/useAdHocExecutionEnvironmentStep.js:15
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:41
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:105
-#: components/LaunchPrompt/steps/useExecutionEnvironmentStep.js:29
-#: components/Lookup/ExecutionEnvironmentLookup.js:159
-#: components/Lookup/ExecutionEnvironmentLookup.js:191
-#: components/Lookup/ExecutionEnvironmentLookup.js:208
-#: components/PromptDetail/PromptDetail.js:220
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:451
-msgid "Execution Environment"
-msgstr "実行環境"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:70
-#: components/TemplateList/TemplateListItem.js:160
-msgid "Execution Environment Missing"
-msgstr "実行環境がありません"
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:103
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:107
-#: routeConfig.js:147
-#: screens/ActivityStream/ActivityStream.js:217
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:129
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:191
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:13
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:22
-#: screens/Organization/Organization.js:127
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:78
-#: screens/Organization/Organizations.js:34
-#: util/getRelatedResourceDeleteDetails.js:81
-#: util/getRelatedResourceDeleteDetails.js:188
-msgid "Execution Environments"
-msgstr "実行環境"
-
-#: screens/Job/JobDetail/JobDetail.js:345
-msgid "Execution Node"
-msgstr "実行ノード"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:103
-msgid "Execution environment copied successfully"
-msgstr "実行環境が正常にコピーされました"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:112
-msgid "Execution environment is missing or deleted."
-msgstr "実行環境が存在しないか、削除されています。"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:83
-msgid "Execution environment not found."
-msgstr "実行環境が見つかりません。"
-
-#: screens/TopologyView/Legend.js:86
-msgid "Execution node"
-msgstr "実行ノード"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:23
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:26
-msgid "Exit Without Saving"
-msgstr "保存せずに終了"
-
-#: components/ExpandCollapse/ExpandCollapse.js:52
-msgid "Expand"
-msgstr "展開"
-
-#: components/DataListToolbar/DataListToolbar.js:105
-msgid "Expand all rows"
-msgstr "全列を展開"
-
-#: components/CodeEditor/VariablesDetail.js:212
-#: components/CodeEditor/VariablesField.js:248
-msgid "Expand input"
-msgstr "入力の展開"
-
-#: screens/Job/JobOutput/PageControls.js:50
-msgid "Expand job events"
-msgstr "ジョブイベントの拡張"
-
-#: screens/Job/JobOutput/shared/JobEventLineToggle.js:37
-msgid "Expand section"
-msgstr "セクションの展開"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:46
-msgid "Expected at least one of client_email, project_id or private_key to be present in the file."
-msgstr "client_email、project_id、または private_key の少なくとも 1 つがファイルに存在する必要があります。"
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:137
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:34
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:148
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:172
-#: screens/User/UserTokenDetail/UserTokenDetail.js:56
-#: screens/User/UserTokenList/UserTokenList.js:146
-#: screens/User/UserTokenList/UserTokenList.js:190
-#: screens/User/UserTokenList/UserTokenListItem.js:35
-#: screens/User/UserTokens/UserTokens.js:89
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:151
-msgid "Expires"
-msgstr "有効期限"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:146
-msgid "Expires on"
-msgstr "有効期限:"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:156
-msgid "Expires on UTC"
-msgstr "有効期限 (UTC)"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:50
-msgid "Expires on {0}"
-msgstr "{0} の有効期限"
-
-#: components/JobList/JobListItem.js:307
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:181
-msgid "Explanation"
-msgstr "説明"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:113
-msgid "External Secret Management System"
-msgstr "外部シークレット管理システム"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:272
-#: components/AdHocCommands/AdHocDetailsStep.js:273
-msgid "Extra variables"
-msgstr "追加変数"
-
-#: components/Sparkline/Sparkline.js:35
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:164
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:43
-#: screens/Project/ProjectDetail/ProjectDetail.js:139
-#: screens/Project/ProjectList/ProjectListItem.js:77
-msgid "FINISHED:"
-msgstr "終了日時:"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:73
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:137
-msgid "Fact Storage"
-msgstr "ファクトストレージ"
-
-#: screens/Template/shared/JobTemplate.helptext.js:39
-msgid "Fact storage: If enabled, this will store gathered facts so they can be viewed at the host level. Facts are persisted and injected into the fact cache at runtime.."
-msgstr "ファクトストレージ: 有効にすると、収集されたファクトが保存されるため、ホストレベルで表示できます。ファクトは永続化され、実行時にファクトキャッシュに挿入されます。"
-
-#: screens/Host/Host.js:63
-#: screens/Host/HostFacts/HostFacts.js:45
-#: screens/Host/Hosts.js:28
-#: screens/Inventory/Inventories.js:71
-#: screens/Inventory/InventoryHost/InventoryHost.js:78
-#: screens/Inventory/InventoryHostFacts/InventoryHostFacts.js:39
-msgid "Facts"
-msgstr "ファクト"
-
-#: components/JobList/JobList.js:232
-#: components/StatusLabel/StatusLabel.js:45
-#: components/Workflow/WorkflowNodeHelp.js:105
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:90
-#: screens/Dashboard/shared/ChartTooltip.js:66
-#: screens/Job/JobOutput/shared/HostStatusBar.js:47
-#: screens/Job/JobOutput/shared/OutputToolbar.js:113
-msgid "Failed"
-msgstr "失敗"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:112
-msgid "Failed Host Count"
-msgstr "失敗したホスト数"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:114
-msgid "Failed Hosts"
-msgstr "失敗したホスト"
-
-#: components/LaunchButton/ReLaunchDropDown.js:61
-#: screens/Dashboard/Dashboard.js:87
-msgid "Failed hosts"
-msgstr "失敗したホスト"
-
-#: screens/Dashboard/DashboardGraph.js:170
-msgid "Failed jobs"
-msgstr "失敗したジョブ"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:56
-msgid "Failed to approve {0}."
-msgstr "{0} を承認できませんでした。"
-
-#: components/ResourceAccessList/ResourceAccessList.js:281
-msgid "Failed to assign roles properly"
-msgstr "ロールを正しく割り当てられませんでした"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:250
-#: screens/User/UserRoles/UserRolesList.js:246
-msgid "Failed to associate role"
-msgstr "ロールの関連付けに失敗しました"
-
-#: screens/Host/HostGroups/HostGroupsList.js:248
-#: screens/InstanceGroup/Instances/InstanceList.js:311
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:288
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:265
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:268
-#: screens/User/UserTeams/UserTeamList.js:263
-msgid "Failed to associate."
-msgstr "関連付けに失敗しました。"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:301
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:111
-msgid "Failed to cancel Inventory Source Sync"
-msgstr "インベントリーソースの同期の取り消しに失敗しました。"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:322
-#: screens/Project/ProjectList/ProjectListItem.js:232
-msgid "Failed to cancel Project Sync"
-msgstr "プロジェクトの同期の取り消しに失敗しました。"
-
-#: components/JobList/JobList.js:329
-msgid "Failed to cancel one or more jobs."
-msgstr "1 つ以上のジョブを取り消すことができませんでした。"
-
-#: components/JobList/JobListItem.js:114
-#: screens/Job/JobDetail/JobDetail.js:601
-#: screens/Job/JobOutput/shared/OutputToolbar.js:138
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:90
-msgid "Failed to cancel {0}"
-msgstr "{0} を取り消すことができませんでした。"
-
-#: screens/Credential/CredentialList/CredentialListItem.js:88
-msgid "Failed to copy credential."
-msgstr "認証情報をコピーできませんでした。"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:112
-msgid "Failed to copy execution environment"
-msgstr "実行環境をコピーできませんでした"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:162
-msgid "Failed to copy inventory."
-msgstr "インベントリーをコピーできませんでした。"
-
-#: screens/Project/ProjectList/ProjectListItem.js:270
-msgid "Failed to copy project."
-msgstr "プロジェクトをコピーできませんでした。"
-
-#: components/TemplateList/TemplateListItem.js:253
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:160
-msgid "Failed to copy template."
-msgstr "テンプレートをコピーできませんでした。"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:139
-msgid "Failed to delete application."
-msgstr "アプリケーションを削除できませんでした。"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:317
-msgid "Failed to delete credential."
-msgstr "認証情報を削除できませんでした。"
-
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:82
-msgid "Failed to delete group {0}."
-msgstr "グループ {0} を削除できませんでした。"
-
-#: screens/Host/HostDetail/HostDetail.js:126
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:121
-msgid "Failed to delete host."
-msgstr "ホストを削除できませんでした。"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:327
-msgid "Failed to delete inventory source {name}."
-msgstr "インベントリーソース {name} を削除できませんでした。"
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:213
-msgid "Failed to delete inventory."
-msgstr "インベントリーを削除できませんでした。"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:557
-msgid "Failed to delete job template."
-msgstr "ジョブテンプレートを削除できませんでした。"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:448
-msgid "Failed to delete notification."
-msgstr "通知を削除できませんでした。"
-
-#: screens/Application/ApplicationsList/ApplicationsList.js:188
-msgid "Failed to delete one or more applications."
-msgstr "1 つ以上のアプリケーションを削除できませんでした。"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:208
-msgid "Failed to delete one or more credential types."
-msgstr "1 つ以上の認証情報タイプを削除できませんでした。"
-
-#: screens/Credential/CredentialList/CredentialList.js:217
-msgid "Failed to delete one or more credentials."
-msgstr "1 つ以上の認証情報を削除できませんでした。"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:233
-msgid "Failed to delete one or more execution environments"
-msgstr "1 つ以上の実行環境を削除できませんでした。"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:155
-msgid "Failed to delete one or more groups."
-msgstr "1 つ以上のグループを削除できませんでした。"
-
-#: screens/Host/HostList/HostList.js:236
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:204
-msgid "Failed to delete one or more hosts."
-msgstr "1 つ以上のホストを削除できませんでした。"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:225
-msgid "Failed to delete one or more instance groups."
-msgstr "1 つ以上のインスタンスグループを削除できませんでした。"
-
-#: screens/Inventory/InventoryList/InventoryList.js:288
-msgid "Failed to delete one or more inventories."
-msgstr "1 つ以上のインベントリーを削除できませんでした。"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:255
-msgid "Failed to delete one or more inventory sources."
-msgstr "1 つ以上のインベントリーリソースを削除できませんでした。"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:244
-msgid "Failed to delete one or more job templates."
-msgstr "1 つ以上のジョブテンプレートを削除できませんでした"
-
-#: components/JobList/JobList.js:318
-msgid "Failed to delete one or more jobs."
-msgstr "1 つ以上のジョブを削除できませんでした。"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:236
-msgid "Failed to delete one or more notification template."
-msgstr "1 つ以上の通知テンプレートを削除できませんでした。"
-
-#: screens/Organization/OrganizationList/OrganizationList.js:198
-msgid "Failed to delete one or more organizations."
-msgstr "1 つ以上の組織を削除できませんでした。"
-
-#: screens/Project/ProjectList/ProjectList.js:294
-msgid "Failed to delete one or more projects."
-msgstr "1 つ以上のプロジェクトを削除できませんでした。"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:242
-msgid "Failed to delete one or more schedules."
-msgstr "1 つ以上のスケジュールを削除できませんでした。"
-
-#: screens/Team/TeamList/TeamList.js:195
-msgid "Failed to delete one or more teams."
-msgstr "1 つ以上のチームを削除できませんでした。"
-
-#: components/TemplateList/TemplateList.js:302
-msgid "Failed to delete one or more templates."
-msgstr "1 つ以上のテンプレートを削除できませんでした。"
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:158
-msgid "Failed to delete one or more tokens."
-msgstr "1 つ以上のトークンを削除できませんでした。"
-
-#: screens/User/UserTokenList/UserTokenList.js:217
-msgid "Failed to delete one or more user tokens."
-msgstr "1 つ以上のユーザートークンを削除できませんでした。"
-
-#: screens/User/UserList/UserList.js:192
-msgid "Failed to delete one or more users."
-msgstr "1 人以上のユーザーを削除できませんでした。"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:192
-msgid "Failed to delete one or more workflow approval."
-msgstr "1 つ以上のワークフロー承認を削除できませんでした。"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:211
-msgid "Failed to delete organization."
-msgstr "組織を削除できませんでした。"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:351
-msgid "Failed to delete project."
-msgstr "プロジェクトを削除できませんでした。"
-
-#: components/ResourceAccessList/ResourceAccessList.js:292
-msgid "Failed to delete role"
-msgstr "ロールを削除できませんでした。"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:261
-#: screens/User/UserRoles/UserRolesList.js:257
-msgid "Failed to delete role."
-msgstr "ロールを削除できませんでした。"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:657
-msgid "Failed to delete schedule."
-msgstr "スケジュールを削除できませんでした。"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:186
-msgid "Failed to delete smart inventory."
-msgstr "スマートインベントリーを削除できませんでした。"
-
-#: screens/Team/TeamDetail/TeamDetail.js:81
-msgid "Failed to delete team."
-msgstr "チームを削除できませんでした。"
-
-#: screens/User/UserDetail/UserDetail.js:118
-msgid "Failed to delete user."
-msgstr "ユーザーを削除できませんでした。"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:351
-msgid "Failed to delete workflow approval."
-msgstr "ワークフロー承認を削除できませんでした。"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:280
-msgid "Failed to delete workflow job template."
-msgstr "ワークフロージョブテンプレートを削除できませんでした。"
-
-#: screens/Host/HostDetail/HostDetail.js:59
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:59
-msgid "Failed to delete {name}."
-msgstr "{name} を削除できませんでした。"
-
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:51
-msgid "Failed to deny {0}."
-msgstr "{0} を拒否できませんでした。"
-
-#: screens/Host/HostGroups/HostGroupsList.js:249
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:266
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:269
-msgid "Failed to disassociate one or more groups."
-msgstr "1 つ以上のグループの関連付けを解除できませんでした。"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:299
-msgid "Failed to disassociate one or more hosts."
-msgstr "1 つ以上のホストの関連付けを解除できませんでした。"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:315
-#: screens/InstanceGroup/Instances/InstanceList.js:313
-#: screens/Instances/InstanceDetail/InstanceDetail.js:365
-msgid "Failed to disassociate one or more instances."
-msgstr "1 つ以上のインスタンスの関連付けを解除できませんでした。"
-
-#: screens/User/UserTeams/UserTeamList.js:264
-msgid "Failed to disassociate one or more teams."
-msgstr "1 つ以上のチームの関連付けを解除できませんでした。"
-
-#: screens/Login/Login.js:243
-msgid "Failed to fetch custom login configuration settings. System defaults will be shown instead."
-msgstr "カスタムログイン構成設定を取得できません。代わりに、システムのデフォルトが表示されます。"
-
-#: screens/Project/ProjectList/ProjectList.js:306
-msgid "Failed to fetch the updated project data."
-msgstr "更新されたプロジェクトデータの取得に失敗しました。"
-
-#: screens/TopologyView/MeshGraph.js:409
-msgid "Failed to get instance."
-msgstr "インスタンスを取得できませんでした。"
-
-#: components/AdHocCommands/AdHocCommands.js:112
-#: components/LaunchButton/LaunchButton.js:188
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:128
-msgid "Failed to launch job."
-msgstr "ジョブを起動できませんでした。"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:378
-#: screens/Instances/InstanceList/InstanceList.js:246
-msgid "Failed to remove one or more instances."
-msgstr "1 つ以上のインスタンスを削除できませんでした。"
-
-#: contexts/Config.js:98
-msgid "Failed to retrieve configuration."
-msgstr "構成を取得できませんでした。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:376
-msgid "Failed to retrieve full node resource object."
-msgstr "フルノードリソースオブジェクトを取得できませんでした。"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:315
-#: screens/Instances/InstanceList/InstanceList.js:234
-msgid "Failed to run a health check on one or more instances."
-msgstr "1 つ以上のインスタンスで可用性をチェックできませんでした。"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:172
-msgid "Failed to send test notification."
-msgstr "テスト通知の送信に失敗しました。"
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:52
-msgid "Failed to sync inventory source."
-msgstr "インベントリーソースを同期できませんでした。"
-
-#: screens/Project/shared/ProjectSyncButton.js:63
-msgid "Failed to sync project."
-msgstr "プロジェクトを同期できませんでした。"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:242
-msgid "Failed to sync some or all inventory sources."
-msgstr "一部またはすべてのインベントリーソースを同期できませんでした。"
-
-#: components/HostToggle/HostToggle.js:80
-msgid "Failed to toggle host."
-msgstr "ホストの切り替えに失敗しました。"
-
-#: components/InstanceToggle/InstanceToggle.js:71
-msgid "Failed to toggle instance."
-msgstr "インスタンスの切り替えに失敗しました。"
-
-#: components/NotificationList/NotificationList.js:250
-msgid "Failed to toggle notification."
-msgstr "通知の切り替えに失敗しました。"
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:77
-msgid "Failed to toggle schedule."
-msgstr "スケジュールの切り替えに失敗しました。"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:314
-#: screens/InstanceGroup/Instances/InstanceListItem.js:222
-#: screens/Instances/InstanceDetail/InstanceDetail.js:364
-#: screens/Instances/InstanceList/InstanceListItem.js:238
-msgid "Failed to update capacity adjustment."
-msgstr "容量調整の更新に失敗しました。"
-
-#: screens/TopologyView/Tooltip.js:204
-msgid "Failed to update instance."
-msgstr "インスタンスの更新に失敗しました。"
-
-#: screens/Template/TemplateSurvey.js:133
-msgid "Failed to update survey."
-msgstr "調査の更新に失敗しました。"
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:88
-msgid "Failed to user token."
-msgstr "ユーザートークンに失敗しました。"
-
-#: components/NotificationList/NotificationListItem.js:85
-#: components/NotificationList/NotificationListItem.js:86
-msgid "Failure"
-msgstr "失敗"
-
-#: screens/Job/JobOutput/EmptyOutput.js:45
-msgid "Failure Explanation:"
-msgstr "失敗の説明:"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:66
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:210
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:240
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:270
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:315
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:373
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:142
-msgid "False"
-msgstr "False"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:160
-#: components/Schedule/shared/FrequencyDetailSubform.js:103
-msgid "February"
-msgstr "2 月"
-
-#: components/Search/LookupTypeInput.js:52
-msgid "Field contains value."
-msgstr "値を含むフィールド。"
-
-#: components/Search/LookupTypeInput.js:80
-msgid "Field ends with value."
-msgstr "値で終了するフィールド。"
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:76
-msgid "Field for passing a custom Kubernetes or OpenShift Pod specification."
-msgstr "カスタムの Kubernetes または OpenShift Pod 仕様を渡すためのフィールド。"
-
-#: components/Search/LookupTypeInput.js:94
-msgid "Field matches the given regular expression."
-msgstr "特定の正規表現に一致するフィールド。"
-
-#: components/Search/LookupTypeInput.js:66
-msgid "Field starts with value."
-msgstr "値で開始するフィールド。"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:416
-msgid "Fifth"
-msgstr "第 5"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:105
-msgid "File Difference"
-msgstr "ファイルの相違点"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:72
-msgid "File upload rejected. Please select a single .json file."
-msgstr "ファイルのアップロードが拒否されました。単一の .json ファイルを選択してください。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:96
-msgid "File, directory or script"
-msgstr "ファイル、ディレクトリー、またはスクリプト"
-
-#: components/Search/Search.js:198
-#: components/Search/Search.js:222
-msgid "Filter By {name}"
-msgstr "{name} 別にフィルター"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:88
-msgid "Filter by failed jobs"
-msgstr "失敗したジョブによるフィルター"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:94
-msgid "Filter by successful jobs"
-msgstr "成功ジョブによるフィルター"
-
-#: components/JobList/JobList.js:248
-#: components/JobList/JobListItem.js:100
-msgid "Finish Time"
-msgstr "終了時刻"
-
-#: screens/Job/JobDetail/JobDetail.js:226
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:208
-msgid "Finished"
-msgstr "終了日時"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:404
-msgid "First"
-msgstr "最初"
-
-#: components/AddRole/AddResourceRole.js:28
-#: components/AddRole/AddResourceRole.js:42
-#: components/ResourceAccessList/ResourceAccessList.js:178
-#: screens/User/UserDetail/UserDetail.js:64
-#: screens/User/UserList/UserList.js:124
-#: screens/User/UserList/UserList.js:161
-#: screens/User/UserList/UserListItem.js:53
-#: screens/User/shared/UserForm.js:63
-msgid "First Name"
-msgstr "名"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:332
-msgid "First Run"
-msgstr "初回実行日時"
-
-#: components/ResourceAccessList/ResourceAccessList.js:227
-#: components/ResourceAccessList/ResourceAccessListItem.js:67
-msgid "First name"
-msgstr "名"
-
-#: components/Search/AdvancedSearch.js:213
-#: components/Search/AdvancedSearch.js:227
-msgid "First, select a key"
-msgstr "先にキーを選択"
-
-#: components/Workflow/WorkflowTools.js:88
-msgid "Fit the graph to the available screen size"
-msgstr "グラフを利用可能な画面サイズに合わせます"
-
-#: screens/TopologyView/Header.js:75
-#: screens/TopologyView/Header.js:78
-msgid "Fit to screen"
-msgstr "画面に合わせる"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:94
-msgid "Float"
-msgstr "浮動"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:183
-msgid "Follow"
-msgstr "フォロー"
-
-#: screens/Job/Job.helptext.js:5
-#: screens/Template/shared/JobTemplate.helptext.js:6
-msgid "For job templates, select run to execute the playbook. Select check to only check playbook syntax, test environment setup, and report problems without executing the playbook."
-msgstr "ジョブテンプレートについて、Playbook を実行するために実行を選択します。Playbook を実行せずに、Playbook 構文、テスト環境セットアップ、およびレポートの問題のみを検査するチェックを選択します。"
-
-#: screens/Project/shared/Project.helptext.js:98
-msgid "For more information, refer to the"
-msgstr "詳しい情報は以下の情報を参照してください:"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:160
-#: components/AdHocCommands/AdHocDetailsStep.js:161
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:55
-#: components/PromptDetail/PromptDetail.js:345
-#: components/PromptDetail/PromptJobTemplateDetail.js:150
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:475
-#: screens/Job/JobDetail/JobDetail.js:389
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:259
-#: screens/Template/shared/JobTemplateForm.js:409
-#: screens/TopologyView/Tooltip.js:282
-msgid "Forks"
-msgstr "フォーク"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:414
-msgid "Fourth"
-msgstr "第 4"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:363
-#: components/Schedule/shared/ScheduleFormFields.js:146
-msgid "Frequency Details"
-msgstr "頻度の詳細"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:381
-msgid "Frequency Exception Details"
-msgstr "頻度の例外の詳細"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:72
-#: components/Schedule/shared/FrequencyDetailSubform.js:185
-#: components/Schedule/shared/FrequencyDetailSubform.js:206
-#: components/Schedule/shared/buildRuleObj.js:91
-msgid "Frequency did not match an expected value"
-msgstr "頻度が期待値と一致しませんでした"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:310
-msgid "Fri"
-msgstr "金"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:81
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:187
-#: components/Schedule/shared/FrequencyDetailSubform.js:315
-#: components/Schedule/shared/FrequencyDetailSubform.js:453
-msgid "Friday"
-msgstr "金曜"
-
-#: components/Search/RelatedLookupTypeInput.js:45
-msgid "Fuzzy search on id, name or description fields."
-msgstr "ID、名前、または説明フィールドのあいまい検索。"
-
-#: components/Search/RelatedLookupTypeInput.js:32
-msgid "Fuzzy search on name field."
-msgstr "名前フィールドのあいまい検索。"
-
-#: components/CredentialChip/CredentialChip.js:13
-msgid "GPG Public Key"
-msgstr "GPG 公開鍵"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:153
-#: screens/Organization/shared/OrganizationForm.js:101
-msgid "Galaxy Credentials"
-msgstr "Galaxy 認証情報"
-
-#: screens/Credential/shared/CredentialForm.js:185
-msgid "Galaxy credentials must be owned by an Organization."
-msgstr "Galaxy 認証情報は組織が所有している必要があります。"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:106
-msgid "Gathering Facts"
-msgstr "ファクトの収集"
-
-#: screens/Setting/Settings.js:72
-msgid "Generic OIDC"
-msgstr "汎用 OIDC"
-
-#: screens/Setting/SettingList.js:85
-msgid "Generic OIDC settings"
-msgstr "汎用 OIDC 設定"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:222
-msgid "Get subscription"
-msgstr "サブスクリプションの取得"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:216
-msgid "Get subscriptions"
-msgstr "サブスクリプションの取得"
-
-#: components/Lookup/ProjectLookup.js:136
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:89
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:158
-#: screens/Job/JobDetail/JobDetail.js:75
-#: screens/Project/ProjectList/ProjectList.js:198
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:97
-msgid "Git"
-msgstr "Git"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:312
-#: screens/Template/shared/WebhookSubForm.js:106
-msgid "GitHub"
-msgstr "GitHub"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:85
-#: screens/Setting/Settings.js:51
-msgid "GitHub Default"
-msgstr "GitHub のデフォルト"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:100
-#: screens/Setting/Settings.js:60
-msgid "GitHub Enterprise"
-msgstr "GitHub Enterprise"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:105
-#: screens/Setting/Settings.js:63
-msgid "GitHub Enterprise Organization"
-msgstr "GitHub Enterprise 組織"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:110
-#: screens/Setting/Settings.js:66
-msgid "GitHub Enterprise Team"
-msgstr "GitHub Enterprise チーム"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:90
-#: screens/Setting/Settings.js:54
-msgid "GitHub Organization"
-msgstr "GitHub 組織"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:95
-#: screens/Setting/Settings.js:57
-msgid "GitHub Team"
-msgstr "GitHub チーム"
-
-#: screens/Setting/SettingList.js:61
-msgid "GitHub settings"
-msgstr "GitHub 設定"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:312
-#: screens/Template/shared/WebhookSubForm.js:112
-msgid "GitLab"
-msgstr "GitLab"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:79
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:84
-msgid "Globally Available"
-msgstr "システム全体で利用可能"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:134
-msgid "Globally available execution environment can not be reassigned to a specific Organization"
-msgstr "システム全体で利用可能な実行環境を特定の組織に再割り当てすることはできません"
-
-#: components/Pagination/Pagination.js:29
-msgid "Go to first page"
-msgstr "最初のページに移動"
-
-#: components/Pagination/Pagination.js:31
-msgid "Go to last page"
-msgstr "最後のページに移動"
-
-#: components/Pagination/Pagination.js:32
-msgid "Go to next page"
-msgstr "次のページに移動"
-
-#: components/Pagination/Pagination.js:30
-msgid "Go to previous page"
-msgstr "前のページに移動"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:99
-msgid "Google Compute Engine"
-msgstr "Google Compute Engine"
-
-#: screens/Setting/SettingList.js:65
-msgid "Google OAuth 2 settings"
-msgstr "Google OAuth2 の設定"
-
-#: screens/Setting/Settings.js:69
-msgid "Google OAuth2"
-msgstr "Google OAuth2"
-
-#: components/NotificationList/NotificationList.js:194
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:135
-msgid "Grafana"
-msgstr "Grafana"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:160
-msgid "Grafana API key"
-msgstr "Grafana API キー"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:187
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:151
-msgid "Grafana URL"
-msgstr "Grafana URL"
-
-#: components/Search/LookupTypeInput.js:106
-msgid "Greater than comparison."
-msgstr "Greater than の比較条件"
-
-#: components/Search/LookupTypeInput.js:113
-msgid "Greater than or equal to comparison."
-msgstr "Greater than or equal to の比較条件"
-
-#: components/Lookup/HostFilterLookup.js:102
-msgid "Group"
-msgstr "グループ"
-
-#: screens/Inventory/Inventories.js:78
-msgid "Group details"
-msgstr "グループの詳細"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:124
-msgid "Group type"
-msgstr "グループタイプ"
-
-#: screens/Host/Host.js:68
-#: screens/Host/HostGroups/HostGroupsList.js:231
-#: screens/Host/Hosts.js:29
-#: screens/Inventory/Inventories.js:72
-#: screens/Inventory/Inventories.js:74
-#: screens/Inventory/Inventory.js:66
-#: screens/Inventory/InventoryHost/InventoryHost.js:83
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:248
-#: screens/Inventory/InventoryList/InventoryListItem.js:127
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:251
-#: util/getRelatedResourceDeleteDetails.js:119
-msgid "Groups"
-msgstr "グループ"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:383
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:468
-msgid "HTTP Headers"
-msgstr "HTTP ヘッダー"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:378
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:481
-msgid "HTTP Method"
-msgstr "HTTP メソッド"
-
-#: components/HealthCheckAlert/HealthCheckAlert.js:22
-msgid "Health check request(s) submitted. Please wait and reload the page."
-msgstr "送信されたヘルスチェックリクエスト。ページをリロードしてお待ちください。"
-
-#: components/StatusLabel/StatusLabel.js:42
-msgid "Healthy"
-msgstr "利用可能"
-
-#: components/AppContainer/PageHeaderToolbar.js:116
-msgid "Help"
-msgstr "ヘルプ"
-
-#: components/FormField/PasswordInput.js:35
-msgid "Hide"
-msgstr "非表示"
-
-#: components/LaunchPrompt/LaunchPrompt.js:135
-#: components/Schedule/shared/SchedulePromptableFields.js:102
-msgid "Hide description"
-msgstr "説明の非表示"
-
-#: components/NotificationList/NotificationList.js:195
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:136
-msgid "Hipchat"
-msgstr "Hipchat"
-
-#: screens/Instances/InstanceList/InstanceList.js:154
-msgid "Hop"
-msgstr "ホップ"
-
-#: screens/TopologyView/Legend.js:103
-msgid "Hop node"
-msgstr "ホップノード"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:211
-#: screens/Job/JobOutput/HostEventModal.js:109
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:149
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:78
-msgid "Host"
-msgstr "ホスト"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:107
-msgid "Host Async Failure"
-msgstr "ホストの非同期失敗"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:108
-msgid "Host Async OK"
-msgstr "ホストの非同期 OK"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:159
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:297
-#: screens/Template/shared/JobTemplateForm.js:633
-msgid "Host Config Key"
-msgstr "ホスト設定キー"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:96
-msgid "Host Count"
-msgstr "ホスト数"
-
-#: screens/Job/JobOutput/HostEventModal.js:88
-msgid "Host Details"
-msgstr "ホストの詳細"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:109
-msgid "Host Failed"
-msgstr "ホストの失敗"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:110
-msgid "Host Failure"
-msgstr "ホストの障害"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:242
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:145
-msgid "Host Filter"
-msgstr "ホストフィルター"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:201
-#: screens/Instances/InstanceDetail/InstanceDetail.js:191
-#: screens/Instances/Shared/InstanceForm.js:18
-msgid "Host Name"
-msgstr "ホスト名"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:111
-msgid "Host OK"
-msgstr "ホスト OK"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:112
-msgid "Host Polling"
-msgstr "ホストのポーリング"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:113
-msgid "Host Retry"
-msgstr "ホストの再試行"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:114
-msgid "Host Skipped"
-msgstr "ホストがスキップされました"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:115
-msgid "Host Started"
-msgstr "ホストの開始"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:116
-msgid "Host Unreachable"
-msgstr "ホストに到達できません"
-
-#: screens/Inventory/Inventories.js:69
-msgid "Host details"
-msgstr "ホストの詳細"
-
-#: screens/Job/JobOutput/HostEventModal.js:89
-msgid "Host details modal"
-msgstr "ホストの詳細モーダル"
-
-#: screens/Host/Host.js:96
-#: screens/Inventory/InventoryHost/InventoryHost.js:100
-msgid "Host not found."
-msgstr "ホストが見つかりませんでした。"
-
-#: screens/Job/JobOutput/shared/HostStatusBar.js:76
-msgid "Host status information for this job is unavailable."
-msgstr "このジョブのホストのステータス情報は利用できません。"
-
-#: routeConfig.js:85
-#: screens/ActivityStream/ActivityStream.js:176
-#: screens/Dashboard/Dashboard.js:81
-#: screens/Host/HostList/HostList.js:143
-#: screens/Host/HostList/HostList.js:191
-#: screens/Host/Hosts.js:14
-#: screens/Host/Hosts.js:23
-#: screens/Inventory/Inventories.js:65
-#: screens/Inventory/Inventories.js:79
-#: screens/Inventory/Inventory.js:67
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:67
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:189
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:272
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:112
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:172
-#: screens/Inventory/SmartInventory.js:68
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:71
-#: screens/Job/JobOutput/shared/OutputToolbar.js:97
-#: util/getRelatedResourceDeleteDetails.js:123
-msgid "Hosts"
-msgstr "ホスト"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:92
-msgid "Hosts automated"
-msgstr "自動化されたホスト"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:118
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:125
-msgid "Hosts available"
-msgstr "利用可能なホスト"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:107
-msgid "Hosts imported"
-msgstr "インポートされたホスト"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:112
-msgid "Hosts remaining"
-msgstr "残りのホスト"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:183
-#: components/Schedule/shared/FrequencyDetailSubform.js:175
-#: components/Schedule/shared/ScheduleFormFields.js:126
-#: components/Schedule/shared/ScheduleFormFields.js:186
-msgid "Hour"
-msgstr "時間"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:209
-#: screens/Instances/InstanceList/InstanceList.js:153
-msgid "Hybrid"
-msgstr "ハイブリッド"
-
-#: screens/TopologyView/Legend.js:95
-msgid "Hybrid node"
-msgstr "ハイブリッドノード"
-
-#: components/JobList/JobList.js:200
-#: components/Lookup/HostFilterLookup.js:98
-#: screens/Team/TeamRoles/TeamRolesList.js:155
-msgid "ID"
-msgstr "ID"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:193
-msgid "ID of the Dashboard"
-msgstr "ダッシュボード ID"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:198
-msgid "ID of the Panel"
-msgstr "パネル ID"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:167
-msgid "ID of the dashboard (optional)"
-msgstr "ダッシュボード ID (オプション)"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:173
-msgid "ID of the panel (optional)"
-msgstr "パネル ID (オプション)"
-
-#: screens/TopologyView/Tooltip.js:265
-msgid "IP address"
-msgstr "IP アドレス"
-
-#: components/NotificationList/NotificationList.js:196
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:137
-msgid "IRC"
-msgstr "IRC"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:228
-msgid "IRC Nick"
-msgstr "IRC ニック"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:223
-msgid "IRC Server Address"
-msgstr "IRC サーバーアドレス"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:218
-msgid "IRC Server Port"
-msgstr "IRC サーバーポート"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:223
-msgid "IRC nick"
-msgstr "IRC ニック"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:215
-msgid "IRC server address"
-msgstr "IRC サーバーアドレス"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:201
-msgid "IRC server password"
-msgstr "IRC サーバーパスワード"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:206
-msgid "IRC server port"
-msgstr "IRC サーバーポート"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:263
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:308
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:272
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:343
-msgid "Icon URL"
-msgstr "アイコン URL"
-
-#: screens/Inventory/shared/Inventory.helptext.js:100
-msgid ""
-"If checked, all variables for child groups\n"
-"and hosts will be removed and replaced by those found\n"
-"on the external source."
-msgstr "チェックが付けられている場合、子グループおよびホストのすべての変数が削除され、外部ソースにあるものによって置き換えられます。"
-
-#: screens/Inventory/shared/Inventory.helptext.js:84
-msgid ""
-"If checked, any hosts and groups that were\n"
-"previously present on the external source but are now removed\n"
-"will be removed from the inventory. Hosts and groups\n"
-"that were not managed by the inventory source will be promoted\n"
-"to the next manually created group or if there is no manually\n"
-"created group to promote them into, they will be left in the \"all\"\n"
-"default group for the inventory."
-msgstr "チェックを付けると、以前は外部ソースにあり、現在は削除されているホストおよびグループがインベントリーから削除されます。インベントリーソースで管理されていなかったホストおよびグループは、次に手動で作成したグループにプロモートされます。プロモート先となる、手動で作成したグループがない場合、ホストおよびグループは、インベントリーの \"すべて\" のデフォルトグループに残ります。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:30
-msgid "If enabled, run this playbook as an administrator."
-msgstr "有効な場合は、この Playbook を管理者として実行します。"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:184
-msgid ""
-"If enabled, show the changes made\n"
-"by Ansible tasks, where supported. This is equivalent to Ansible’s\n"
-"--diff mode."
-msgstr "有効で、サポートされている場合は、Ansible タスクで加えられた変更を表示します。これは Ansible の --diff モードに相当します。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:19
-msgid "If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible's --diff mode."
-msgstr "有効で、サポートされている場合は、Ansible タスクで加えられた変更を表示します。これは Ansible の --diff モードに相当します。"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:181
-msgid "If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible’s --diff mode."
-msgstr "有効で、サポートされている場合は、Ansible タスクで加えられた変更を表示します。これは Ansible の --diff モードに相当します。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:32
-msgid "If enabled, simultaneous runs of this job template will be allowed."
-msgstr "有効な場合は、このジョブテンプレートの同時実行が許可されます。"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:16
-msgid "If enabled, simultaneous runs of this workflow job template will be allowed."
-msgstr "有効な場合は、このワークフロージョブテンプレートの同時実行が許可されます。"
-
-#: screens/Inventory/shared/Inventory.helptext.js:194
-msgid ""
-"If enabled, the inventory will prevent adding any organization instance groups to the list of preferred instances groups to run associated job templates on.\n"
-"Note: If this setting is enabled and you provided an empty list, the global instance groups will be applied."
-msgstr "有効にすると、インベントリーは、関連付けられたジョブテンプレートを実行する優先インスタンスグループのリストに組織インスタンスグループを追加することを防ぎます。\n"
-"注: この設定が有効で空のリストを指定した場合は、グローバルインスタンスグループが適用されます。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:33
-msgid ""
-"If enabled, the job template will prevent adding any inventory or organization instance groups to the list of preferred instances groups to run on.\n"
-"Note: If this setting is enabled and you provided an empty list, the global instance groups will be applied."
-msgstr "有効にすると、ジョブテンプレートによって、実行する優先インスタンスグループのリストにインベントリーまたは組織インスタンスグループが追加されなくなります。\n"
-"注: この設定が有効で空のリストを指定した場合は、グローバルインスタンスグループが適用されます。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:35
-msgid "If enabled, this will store gathered facts so they can be viewed at the host level. Facts are persisted and injected into the fact cache at runtime."
-msgstr "有効にすると、収集されたファクトが保存されるため、ホストレベルで表示できます。ファクトは永続化され、実行時にファクトキャッシュに挿入されます。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:275
-msgid "If specified, this field will be shown on the node instead of the resource name when viewing the workflow"
-msgstr "指定した場合に、ワークフローを表示すると、リソース名の代わりにこのフィールドがノードに表示されます"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:180
-msgid "If you are ready to upgrade or renew, please <0>contact us.0>"
-msgstr "アップグレードまたは更新の準備ができましたら、<0>お問い合わせください0>。"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:63
-msgid ""
-"If you do not have a subscription, you can visit\n"
-"Red Hat to obtain a trial subscription."
-msgstr "サブスクリプションをお持ちでない場合は、Red Hat にアクセスしてトライアルサブスクリプションを取得できます。"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:46
-msgid "If you only want to remove access for this particular user, please remove them from the team."
-msgstr "この特定のユーザーのアクセスのみを削除する場合は、チームから削除してください。"
-
-#: screens/Inventory/shared/Inventory.helptext.js:120
-#: screens/Inventory/shared/Inventory.helptext.js:139
-msgid ""
-"If you want the Inventory Source to update on\n"
-"launch and on project update, click on Update on launch, and also go to"
-msgstr "起動時およびプロジェクトの更新時にインベントリーソースを更新する場合は、起動時に更新をクリックして移動します"
-
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:80
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:91
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:101
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:54
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:141
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:147
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:166
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:75
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:98
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:89
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:108
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.js:21
-msgid "Image"
-msgstr "イメージ"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:117
-msgid "Including File"
-msgstr "組み込みファイル"
-
-#: components/HostToggle/HostToggle.js:16
-msgid ""
-"Indicates if a host is available and should be included in running\n"
-"jobs. For hosts that are part of an external inventory, this may be\n"
-"reset by the inventory sync process."
-msgstr "ホストが利用可能かどうか、またホストを実行中のジョブに組み込む必要があるかどうかを示します。外部インベントリーの一部となっているホストについては、これはインベントリー同期プロセスでリセットされる場合があります。"
-
-#: components/AppContainer/PageHeaderToolbar.js:103
-msgid "Info"
-msgstr "情報"
-
-#: screens/ActivityStream/ActivityStreamListItem.js:45
-msgid "Initiated By"
-msgstr "開始ユーザー:"
-
-#: screens/ActivityStream/ActivityStream.js:253
-#: screens/ActivityStream/ActivityStream.js:263
-#: screens/ActivityStream/ActivityStreamDetailButton.js:44
-msgid "Initiated by"
-msgstr "開始ユーザー"
-
-#: screens/ActivityStream/ActivityStream.js:243
-msgid "Initiated by (username)"
-msgstr "開始ユーザー (ユーザー名)"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:82
-#: screens/CredentialType/shared/CredentialTypeForm.js:46
-msgid "Injector configuration"
-msgstr "インジェクターの設定"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:74
-#: screens/CredentialType/shared/CredentialTypeForm.js:38
-msgid "Input configuration"
-msgstr "入力の設定"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:79
-msgid "Input schema which defines a set of ordered fields for that type."
-msgstr "該当タイプの順序付けられたフィールドのセットを定義する入力スキーマ。"
-
-#: screens/Project/shared/ProjectSubForms/InsightsSubForm.js:31
-msgid "Insights Credential"
-msgstr "Insights 認証情報"
-
-#: components/Lookup/HostFilterLookup.js:123
-msgid "Insights system ID"
-msgstr "Insights システム ID"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:249
-msgid "Install Bundle"
-msgstr "バンドルのインストール"
-
-#: components/StatusLabel/StatusLabel.js:58
-#: screens/TopologyView/Legend.js:136
-msgid "Installed"
-msgstr "インストール済み"
-
-#: screens/Metrics/Metrics.js:187
-msgid "Instance"
-msgstr "インスタンス"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:135
-msgid "Instance Filters"
-msgstr "インスタンスフィルター"
-
-#: screens/Job/JobDetail/JobDetail.js:358
-msgid "Instance Group"
-msgstr "インスタンスグループ"
-
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:96
-#: components/LaunchPrompt/steps/useInstanceGroupsStep.js:31
-#: components/Lookup/InstanceGroupsLookup.js:74
-#: components/Lookup/InstanceGroupsLookup.js:121
-#: components/Lookup/InstanceGroupsLookup.js:141
-#: components/Lookup/InstanceGroupsLookup.js:151
-#: components/PromptDetail/PromptDetail.js:227
-#: components/PromptDetail/PromptJobTemplateDetail.js:228
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:499
-#: routeConfig.js:132
-#: screens/ActivityStream/ActivityStream.js:205
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:106
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:179
-#: screens/InstanceGroup/InstanceGroups.js:16
-#: screens/InstanceGroup/InstanceGroups.js:26
-#: screens/Instances/InstanceDetail/InstanceDetail.js:217
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:107
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:127
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:422
-#: util/getRelatedResourceDeleteDetails.js:282
-msgid "Instance Groups"
-msgstr "インスタンスグループ"
-
-#: components/Lookup/HostFilterLookup.js:115
-msgid "Instance ID"
-msgstr "インスタンス ID"
-
-#: screens/Instances/Shared/InstanceForm.js:32
-msgid "Instance State"
-msgstr "インスタンスの状態"
-
-#: screens/Instances/Shared/InstanceForm.js:48
-msgid "Instance Type"
-msgstr "インスタンスタイプ"
-
-#: screens/InstanceGroup/InstanceGroups.js:33
-msgid "Instance details"
-msgstr "インスタンスの詳細"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:58
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:69
-msgid "Instance group"
-msgstr "インスタンスグループ"
-
-#: screens/InstanceGroup/InstanceGroup.js:92
-msgid "Instance group not found."
-msgstr "インスタンスグループが見つかりません。"
-
-#: screens/InstanceGroup/Instances/InstanceListItem.js:165
-#: screens/Instances/InstanceList/InstanceListItem.js:176
-msgid "Instance group used capacity"
-msgstr "インスタンスグループの使用容量"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:122
-#: screens/TopologyView/Tooltip.js:273
-msgid "Instance groups"
-msgstr "インスタンスグループ"
-
-#: screens/TopologyView/Tooltip.js:234
-msgid "Instance status"
-msgstr "インスタンスの状態"
-
-#: screens/TopologyView/Tooltip.js:240
-msgid "Instance type"
-msgstr "インスタンスタイプ"
-
-#: routeConfig.js:137
-#: screens/ActivityStream/ActivityStream.js:203
-#: screens/InstanceGroup/InstanceGroup.js:74
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:198
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:73
-#: screens/InstanceGroup/InstanceGroups.js:31
-#: screens/InstanceGroup/Instances/InstanceList.js:192
-#: screens/InstanceGroup/Instances/InstanceList.js:290
-#: screens/Instances/InstanceList/InstanceList.js:136
-#: screens/Instances/Instances.js:13
-#: screens/Instances/Instances.js:22
-msgid "Instances"
-msgstr "インスタンス"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:93
-msgid "Integer"
-msgstr "整数"
-
-#: util/validators.js:94
-msgid "Invalid email address"
-msgstr "無効なメールアドレス"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:116
-msgid "Invalid file format. Please upload a valid Red Hat Subscription Manifest."
-msgstr "ファイル形式が無効です。有効な Red Hat サブスクリプションマニフェストをアップロードしてください。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:178
-msgid "Invalid link target. Unable to link to children or ancestor nodes. Graph cycles are not supported."
-msgstr "無効なリンクターゲットです。子ノードまたは祖先ノードにリンクできません。グラフサイクルはサポートされていません。"
-
-#: util/validators.js:33
-msgid "Invalid time format"
-msgstr "無効な時間形式"
-
-#: screens/Login/Login.js:153
-msgid "Invalid username or password. Please try again."
-msgstr "無効なユーザー名またはパスワードです。やり直してください。"
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:119
-#: routeConfig.js:80
-#: screens/ActivityStream/ActivityStream.js:173
-#: screens/Dashboard/Dashboard.js:92
-#: screens/Inventory/Inventories.js:17
-#: screens/Inventory/InventoryList/InventoryList.js:174
-#: screens/Inventory/InventoryList/InventoryList.js:237
-#: util/getRelatedResourceDeleteDetails.js:202
-#: util/getRelatedResourceDeleteDetails.js:270
-msgid "Inventories"
-msgstr "インベントリー"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:153
-msgid "Inventories with sources cannot be copied"
-msgstr "ソースを含むインベントリーはコピーできません。"
-
-#: components/HostForm/HostForm.js:48
-#: components/JobList/JobListItem.js:223
-#: components/LaunchPrompt/steps/InventoryStep.js:105
-#: components/LaunchPrompt/steps/useInventoryStep.js:48
-#: components/Lookup/HostFilterLookup.js:424
-#: components/Lookup/HostListItem.js:10
-#: components/Lookup/InventoryLookup.js:119
-#: components/Lookup/InventoryLookup.js:128
-#: components/Lookup/InventoryLookup.js:169
-#: components/Lookup/InventoryLookup.js:184
-#: components/Lookup/InventoryLookup.js:224
-#: components/PromptDetail/PromptDetail.js:214
-#: components/PromptDetail/PromptInventorySourceDetail.js:76
-#: components/PromptDetail/PromptJobTemplateDetail.js:119
-#: components/PromptDetail/PromptJobTemplateDetail.js:129
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:79
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:430
-#: components/TemplateList/TemplateListItem.js:285
-#: components/TemplateList/TemplateListItem.js:295
-#: screens/Host/HostDetail/HostDetail.js:77
-#: screens/Host/HostList/HostList.js:171
-#: screens/Host/HostList/HostListItem.js:61
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:94
-#: screens/Inventory/InventoryList/InventoryList.js:186
-#: screens/Inventory/InventoryList/InventoryListItem.js:117
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:38
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:113
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:41
-#: screens/Job/JobDetail/JobDetail.js:107
-#: screens/Job/JobDetail/JobDetail.js:122
-#: screens/Job/JobDetail/JobDetail.js:129
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:214
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:224
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:141
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:33
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:252
-msgid "Inventory"
-msgstr "インベントリー"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:104
-msgid "Inventory (Name)"
-msgstr "インベントリー (名前)"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:99
-msgid "Inventory File"
-msgstr "インベントリーファイル"
-
-#: components/Lookup/HostFilterLookup.js:106
-msgid "Inventory ID"
-msgstr "インベントリー ID"
-
-#: screens/Job/JobDetail/JobDetail.js:282
-msgid "Inventory Source"
-msgstr "インベントリーソース"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:73
-msgid "Inventory Source Sync"
-msgstr "インベントリーソース同期"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:299
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:110
-msgid "Inventory Source Sync Error"
-msgstr "インベントリーソース同期エラー"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:176
-#: screens/Inventory/InventorySources/InventorySourceList.js:193
-#: util/getRelatedResourceDeleteDetails.js:67
-#: util/getRelatedResourceDeleteDetails.js:147
-msgid "Inventory Sources"
-msgstr "インベントリーソース"
-
-#: components/JobList/JobList.js:212
-#: components/JobList/JobListItem.js:43
-#: components/Schedule/ScheduleList/ScheduleListItem.js:36
-#: components/Workflow/WorkflowLegend.js:100
-#: screens/Job/JobDetail/JobDetail.js:66
-msgid "Inventory Sync"
-msgstr "インベントリー同期"
-
-#: screens/Inventory/InventoryList/InventoryList.js:183
-msgid "Inventory Type"
-msgstr "インベントリーのタイプ"
-
-#: components/Workflow/WorkflowNodeHelp.js:71
-msgid "Inventory Update"
-msgstr "インベントリー更新"
-
-#: screens/Inventory/InventoryList/InventoryList.js:121
-msgid "Inventory copied successfully"
-msgstr "インベントリーが正常にコピーされました"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:226
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:109
-msgid "Inventory file"
-msgstr "インベントリーファイル"
-
-#: screens/Inventory/Inventory.js:94
-msgid "Inventory not found."
-msgstr "インベントリーが見つかりません。"
-
-#: screens/Dashboard/DashboardGraph.js:140
-msgid "Inventory sync"
-msgstr "インベントリーの同期"
-
-#: screens/Dashboard/Dashboard.js:98
-msgid "Inventory sync failures"
-msgstr "インベントリーの同期の失敗"
-
-#: components/DataListToolbar/DataListToolbar.js:110
-msgid "Is expanded"
-msgstr "展開"
-
-#: components/DataListToolbar/DataListToolbar.js:112
-msgid "Is not expanded"
-msgstr "展開なし"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:118
-msgid "Item Failed"
-msgstr "項目の失敗"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:119
-msgid "Item OK"
-msgstr "項目 OK"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:120
-msgid "Item Skipped"
-msgstr "項目のスキップ"
-
-#: components/AssociateModal/AssociateModal.js:20
-#: components/PaginatedTable/PaginatedTable.js:42
-msgid "Items"
-msgstr "項目"
-
-#: components/Pagination/Pagination.js:27
-msgid "Items per page"
-msgstr "項目/ページ"
-
-#: components/Sparkline/Sparkline.js:28
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:157
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:36
-#: screens/Project/ProjectDetail/ProjectDetail.js:132
-#: screens/Project/ProjectList/ProjectListItem.js:70
-msgid "JOB ID:"
-msgstr "ジョブ ID:"
-
-#: screens/Job/JobOutput/HostEventModal.js:136
-msgid "JSON"
-msgstr "JSON"
-
-#: screens/Job/JobOutput/HostEventModal.js:137
-msgid "JSON tab"
-msgstr "JSON タブ"
-
-#: screens/Inventory/shared/Inventory.helptext.js:49
-msgid "JSON:"
-msgstr "JSON:"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:159
-#: components/Schedule/shared/FrequencyDetailSubform.js:98
-msgid "January"
-msgstr "1 月"
-
-#: components/JobList/JobListItem.js:112
-#: screens/Job/JobDetail/JobDetail.js:599
-#: screens/Job/JobOutput/JobOutput.js:845
-#: screens/Job/JobOutput/JobOutput.js:846
-#: screens/Job/JobOutput/shared/OutputToolbar.js:136
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:88
-msgid "Job Cancel Error"
-msgstr "ジョブキャンセルエラー"
-
-#: screens/Job/JobDetail/JobDetail.js:621
-#: screens/Job/JobOutput/JobOutput.js:834
-#: screens/Job/JobOutput/JobOutput.js:835
-msgid "Job Delete Error"
-msgstr "ジョブ削除エラー"
-
-#: screens/Job/JobDetail/JobDetail.js:206
-msgid "Job ID"
-msgstr "ジョブ ID:"
-
-#: screens/Dashboard/shared/LineChart.js:128
-msgid "Job Runs"
-msgstr "ジョブの実行"
-
-#: components/JobList/JobListItem.js:314
-#: screens/Job/JobDetail/JobDetail.js:374
-msgid "Job Slice"
-msgstr "ジョブスライス"
-
-#: components/JobList/JobListItem.js:319
-#: screens/Job/JobDetail/JobDetail.js:382
-msgid "Job Slice Parent"
-msgstr "ジョブスライスの親"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:76
-#: components/PromptDetail/PromptDetail.js:349
-#: components/PromptDetail/PromptJobTemplateDetail.js:158
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:494
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:289
-#: screens/Template/shared/JobTemplateForm.js:453
-msgid "Job Slicing"
-msgstr "ジョブスライス"
-
-#: components/Workflow/WorkflowNodeHelp.js:164
-msgid "Job Status"
-msgstr "ジョブステータス"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:97
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:98
-#: components/PromptDetail/PromptDetail.js:267
-#: components/PromptDetail/PromptJobTemplateDetail.js:247
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:571
-#: screens/Job/JobDetail/JobDetail.js:474
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:449
-#: screens/Template/shared/JobTemplateForm.js:521
-#: screens/Template/shared/WorkflowJobTemplateForm.js:218
-msgid "Job Tags"
-msgstr "ジョブタグ"
-
-#: components/JobList/JobListItem.js:191
-#: components/TemplateList/TemplateList.js:217
-#: components/Workflow/WorkflowLegend.js:92
-#: components/Workflow/WorkflowNodeHelp.js:59
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:97
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:19
-#: screens/Job/JobDetail/JobDetail.js:233
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:79
-msgid "Job Template"
-msgstr "ジョブテンプレート"
-
-#: components/LaunchPrompt/steps/credentialsValidator.js:38
-msgid "Job Template default credentials must be replaced with one of the same type. Please select a credential for the following types in order to proceed: {0}"
-msgstr "ジョブテンプレートのデフォルトの認証情報は、同じタイプの認証情報に置き換える必要があります。続行するには、次のタイプの認証情報を選択してください: {0}"
-
-#: screens/Credential/Credential.js:79
-#: screens/Credential/Credentials.js:30
-#: screens/Inventory/Inventories.js:62
-#: screens/Inventory/Inventory.js:74
-#: screens/Inventory/SmartInventory.js:74
-#: screens/Project/Project.js:107
-#: screens/Project/Projects.js:29
-#: util/getRelatedResourceDeleteDetails.js:56
-#: util/getRelatedResourceDeleteDetails.js:101
-#: util/getRelatedResourceDeleteDetails.js:133
-msgid "Job Templates"
-msgstr "ジョブテンプレート"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.js:25
-msgid "Job Templates with a missing inventory or project cannot be selected when creating or editing nodes. Select another template or fix the missing fields to proceed."
-msgstr "ノードの作成時または編集時に、インベントリーまたはプロジェクトが欠落しているジョブテンプレートは選択できません。別のテンプレートを選択するか、欠落しているフィールドを修正して続行してください。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useWorkflowNodeSteps.js:357
-msgid "Job Templates with credentials that prompt for passwords cannot be selected when creating or editing nodes"
-msgstr "ノードの作成時または編集時に、パスワードの入力を求める認証情報を持つジョブテンプレートを選択できない"
-
-#: components/JobList/JobList.js:208
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:146
-#: components/PromptDetail/PromptDetail.js:185
-#: components/PromptDetail/PromptJobTemplateDetail.js:102
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:423
-#: screens/Job/JobDetail/JobDetail.js:267
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:192
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:138
-#: screens/Template/shared/JobTemplateForm.js:256
-msgid "Job Type"
-msgstr "ジョブタイプ"
-
-#: screens/Dashboard/Dashboard.js:125
-msgid "Job status"
-msgstr "ジョブステータス"
-
-#: screens/Dashboard/Dashboard.js:123
-msgid "Job status graph tab"
-msgstr "ジョブステータスのグラフタブ"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:156
-#: components/RelatedTemplateList/RelatedTemplateList.js:206
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:15
-msgid "Job templates"
-msgstr "ジョブテンプレート"
-
-#: components/JobList/JobList.js:191
-#: components/JobList/JobList.js:274
-#: routeConfig.js:39
-#: screens/ActivityStream/ActivityStream.js:150
-#: screens/Dashboard/shared/LineChart.js:64
-#: screens/Host/Host.js:73
-#: screens/Host/Hosts.js:30
-#: screens/InstanceGroup/ContainerGroup.js:71
-#: screens/InstanceGroup/InstanceGroup.js:79
-#: screens/InstanceGroup/InstanceGroups.js:34
-#: screens/InstanceGroup/InstanceGroups.js:39
-#: screens/Inventory/Inventories.js:60
-#: screens/Inventory/Inventories.js:70
-#: screens/Inventory/Inventory.js:70
-#: screens/Inventory/InventoryHost/InventoryHost.js:88
-#: screens/Inventory/SmartInventory.js:70
-#: screens/Job/Jobs.js:22
-#: screens/Job/Jobs.js:32
-#: screens/Setting/SettingList.js:91
-#: screens/Setting/Settings.js:75
-#: screens/Template/Template.js:155
-#: screens/Template/Templates.js:47
-#: screens/Template/WorkflowJobTemplate.js:141
-msgid "Jobs"
-msgstr "ジョブ"
-
-#: screens/Setting/SettingList.js:96
-msgid "Jobs settings"
-msgstr "ジョブ設定"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:165
-#: components/Schedule/shared/FrequencyDetailSubform.js:128
-msgid "July"
-msgstr "7 月"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:164
-#: components/Schedule/shared/FrequencyDetailSubform.js:123
-msgid "June"
-msgstr "6 月"
-
-#: components/Search/AdvancedSearch.js:262
-msgid "Key"
-msgstr "キー"
-
-#: components/Search/AdvancedSearch.js:253
-msgid "Key select"
-msgstr "キー選択"
-
-#: components/Search/AdvancedSearch.js:256
-msgid "Key typeahead"
-msgstr "キー先行入力"
-
-#: screens/ActivityStream/ActivityStream.js:238
-msgid "Keyword"
-msgstr "キーワード"
-
-#: screens/User/UserDetail/UserDetail.js:56
-#: screens/User/UserList/UserListItem.js:44
-msgid "LDAP"
-msgstr "LDAP"
-
-#: screens/Setting/Settings.js:80
-msgid "LDAP 1"
-msgstr "LDAP 1"
-
-#: screens/Setting/Settings.js:81
-msgid "LDAP 2"
-msgstr "LDAP 2"
-
-#: screens/Setting/Settings.js:82
-msgid "LDAP 3"
-msgstr "LDAP 3"
-
-#: screens/Setting/Settings.js:83
-msgid "LDAP 4"
-msgstr "LDAP 4"
-
-#: screens/Setting/Settings.js:84
-msgid "LDAP 5"
-msgstr "LDAP 5"
-
-#: screens/Setting/Settings.js:79
-msgid "LDAP Default"
-msgstr "LDAP のデフォルト"
-
-#: screens/Setting/SettingList.js:69
-msgid "LDAP settings"
-msgstr "LDAP 設定"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:107
-msgid "LDAP1"
-msgstr "LDAP1"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:112
-msgid "LDAP2"
-msgstr "LDAP2"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:117
-msgid "LDAP3"
-msgstr "LDAP3"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:122
-msgid "LDAP4"
-msgstr "LDAP4"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:127
-msgid "LDAP5"
-msgstr "LDAP5"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:178
-#: components/TemplateList/TemplateList.js:234
-msgid "Label"
-msgstr "ラベル"
-
-#: components/JobList/JobList.js:204
-msgid "Label Name"
-msgstr "ラベル名"
-
-#: components/JobList/JobListItem.js:284
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:223
-#: components/PromptDetail/PromptDetail.js:323
-#: components/PromptDetail/PromptJobTemplateDetail.js:209
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:116
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:551
-#: components/TemplateList/TemplateListItem.js:347
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:148
-#: screens/Inventory/shared/InventoryForm.js:83
-#: screens/Job/JobDetail/JobDetail.js:453
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:401
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:206
-#: screens/Template/shared/JobTemplateForm.js:387
-#: screens/Template/shared/WorkflowJobTemplateForm.js:195
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:273
-msgid "Labels"
-msgstr "ラベル"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:417
-msgid "Last"
-msgstr "最終"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:220
-#: screens/InstanceGroup/Instances/InstanceListItem.js:133
-#: screens/InstanceGroup/Instances/InstanceListItem.js:208
-#: screens/Instances/InstanceDetail/InstanceDetail.js:243
-#: screens/Instances/InstanceList/InstanceListItem.js:138
-#: screens/Instances/InstanceList/InstanceListItem.js:223
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:47
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:87
-msgid "Last Health Check"
-msgstr "最終可用性チェック"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:183
-#: screens/Project/ProjectDetail/ProjectDetail.js:161
-msgid "Last Job Status"
-msgstr "最終ジョブステータス"
-
-#: screens/User/UserDetail/UserDetail.js:80
-msgid "Last Login"
-msgstr "前回のログイン"
-
-#: components/PromptDetail/PromptDetail.js:161
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:411
-#: components/TemplateList/TemplateListItem.js:316
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:106
-#: screens/Application/ApplicationsList/ApplicationListItem.js:45
-#: screens/Application/ApplicationsList/ApplicationsList.js:159
-#: screens/Credential/CredentialDetail/CredentialDetail.js:263
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:95
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:108
-#: screens/Host/HostDetail/HostDetail.js:89
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:72
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:98
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:178
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:45
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:84
-#: screens/Job/JobDetail/JobDetail.js:538
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:398
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:120
-#: screens/Project/ProjectDetail/ProjectDetail.js:297
-#: screens/Team/TeamDetail/TeamDetail.js:48
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:358
-#: screens/User/UserDetail/UserDetail.js:84
-#: screens/User/UserTokenDetail/UserTokenDetail.js:66
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:204
-msgid "Last Modified"
-msgstr "最終変更日時"
-
-#: components/AddRole/AddResourceRole.js:32
-#: components/AddRole/AddResourceRole.js:46
-#: components/ResourceAccessList/ResourceAccessList.js:182
-#: screens/User/UserDetail/UserDetail.js:65
-#: screens/User/UserList/UserList.js:128
-#: screens/User/UserList/UserList.js:162
-#: screens/User/UserList/UserListItem.js:54
-#: screens/User/shared/UserForm.js:69
-msgid "Last Name"
-msgstr "姓"
-
-#: components/TemplateList/TemplateList.js:245
-#: components/TemplateList/TemplateListItem.js:194
-msgid "Last Ran"
-msgstr "最終実行日時"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:341
-msgid "Last Run"
-msgstr "最終実行日時"
-
-#: components/Lookup/HostFilterLookup.js:119
-msgid "Last job"
-msgstr "最後のジョブ"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:280
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:151
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:49
-#: screens/Project/ProjectList/ProjectListItem.js:308
-#: screens/TopologyView/Tooltip.js:331
-msgid "Last modified"
-msgstr "最終変更日時"
-
-#: components/ResourceAccessList/ResourceAccessList.js:228
-#: components/ResourceAccessList/ResourceAccessListItem.js:68
-msgid "Last name"
-msgstr "姓"
-
-#: screens/TopologyView/Tooltip.js:337
-msgid "Last seen"
-msgstr "最終表示"
-
-#: screens/Project/ProjectList/ProjectListItem.js:313
-msgid "Last used"
-msgstr "最終使用日時"
-
-#: components/AdHocCommands/useAdHocPreviewStep.js:22
-#: components/LaunchPrompt/steps/usePreviewStep.js:35
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:54
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:57
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:520
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:529
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:245
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:254
-msgid "Launch"
-msgstr "起動"
-
-#: components/TemplateList/TemplateListItem.js:214
-msgid "Launch Template"
-msgstr "テンプレートの起動"
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:32
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:34
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:46
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:47
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:87
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:90
-msgid "Launch management job"
-msgstr "管理ジョブの起動"
-
-#: components/TemplateList/TemplateListItem.js:222
-msgid "Launch template"
-msgstr "テンプレートの起動"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:119
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:120
-msgid "Launch workflow"
-msgstr "ワークフローの起動"
-
-#: components/LaunchPrompt/LaunchPrompt.js:130
-msgid "Launch | {0}"
-msgstr "起動 | {0}"
-
-#: components/DetailList/LaunchedByDetail.js:54
-msgid "Launched By"
-msgstr "起動者"
-
-#: components/JobList/JobList.js:220
-msgid "Launched By (Username)"
-msgstr "起動者 (ユーザー名)"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:120
-msgid "Learn more about Automation Analytics"
-msgstr "自動化アナリティクスについて"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:69
-msgid "Leave this field blank to make the execution environment globally available."
-msgstr "実行環境をシステム全体で利用できるようにするには、このフィールドを空白のままにします。"
-
-#: components/Workflow/WorkflowLegend.js:86
-#: screens/Metrics/LineChart.js:120
-#: screens/TopologyView/Header.js:102
-#: screens/TopologyView/Legend.js:67
-msgid "Legend"
-msgstr "凡例"
-
-#: components/Search/LookupTypeInput.js:120
-msgid "Less than comparison."
-msgstr "Less than の比較条件"
-
-#: components/Search/LookupTypeInput.js:127
-msgid "Less than or equal to comparison."
-msgstr "Less than or equal to の比較条件"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:140
-#: components/AdHocCommands/AdHocDetailsStep.js:141
-#: components/JobList/JobList.js:238
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:65
-#: components/PromptDetail/PromptDetail.js:255
-#: components/PromptDetail/PromptJobTemplateDetail.js:153
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:90
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:473
-#: screens/Job/JobDetail/JobDetail.js:325
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:265
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:151
-#: screens/Template/shared/JobTemplateForm.js:429
-#: screens/Template/shared/WorkflowJobTemplateForm.js:159
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:238
-msgid "Limit"
-msgstr "制限"
-
-#: screens/TopologyView/Legend.js:237
-msgid "Link state types"
-msgstr "リンク状態のタイプ"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:261
-msgid "Link to an available node"
-msgstr "利用可能なノードへのリンク"
-
-#: screens/Instances/Shared/InstanceForm.js:40
-msgid "Listener Port"
-msgstr "リスナーポート"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:353
-msgid "Loading"
-msgstr "ロード中"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:49
-msgid "Local"
-msgstr "ローカル"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:343
-msgid "Local Time Zone"
-msgstr "ローカルタイムゾーン"
-
-#: components/Schedule/shared/ScheduleFormFields.js:95
-msgid "Local time zone"
-msgstr "ローカルタイムゾーン"
-
-#: screens/Login/Login.js:217
-msgid "Log In"
-msgstr "ログイン"
-
-#: screens/Setting/Settings.js:97
-msgid "Logging"
-msgstr "ロギング"
-
-#: screens/Setting/SettingList.js:115
-msgid "Logging settings"
-msgstr "ロギング設定"
-
-#: components/AppContainer/AppContainer.js:81
-#: components/AppContainer/AppContainer.js:150
-#: components/AppContainer/PageHeaderToolbar.js:169
-msgid "Logout"
-msgstr "ログアウト"
-
-#: components/Lookup/HostFilterLookup.js:367
-#: components/Lookup/Lookup.js:187
-msgid "Lookup modal"
-msgstr "ルックアップモーダル"
-
-#: components/Search/LookupTypeInput.js:22
-msgid "Lookup select"
-msgstr "ルックアップ選択"
-
-#: components/Search/LookupTypeInput.js:31
-msgid "Lookup type"
-msgstr "ルックアップタイプ"
-
-#: components/Search/LookupTypeInput.js:25
-msgid "Lookup typeahead"
-msgstr "ルックアップの先行入力"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:155
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:34
-#: screens/Project/ProjectDetail/ProjectDetail.js:130
-#: screens/Project/ProjectList/ProjectListItem.js:68
-msgid "MOST RECENT SYNC"
-msgstr "直近の同期"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:97
-#: components/AdHocCommands/AdHocCredentialStep.js:98
-#: components/AdHocCommands/AdHocCredentialStep.js:112
-#: screens/Job/JobDetail/JobDetail.js:407
-msgid "Machine Credential"
-msgstr "マシンの認証情報"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:65
-msgid "Managed"
-msgstr "管理"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:147
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:169
-msgid "Managed nodes"
-msgstr "管理ノード"
-
-#: components/JobList/JobList.js:215
-#: components/JobList/JobListItem.js:46
-#: components/Schedule/ScheduleList/ScheduleListItem.js:39
-#: components/Workflow/WorkflowLegend.js:108
-#: components/Workflow/WorkflowNodeHelp.js:79
-#: screens/Job/JobDetail/JobDetail.js:69
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:102
-msgid "Management Job"
-msgstr "管理ジョブ"
-
-#: routeConfig.js:127
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:84
-msgid "Management Jobs"
-msgstr "管理ジョブ"
-
-#: screens/ManagementJob/ManagementJobs.js:20
-msgid "Management job"
-msgstr "管理ジョブ"
-
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:109
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:110
-msgid "Management job launch error"
-msgstr "管理ジョブの起動エラー"
-
-#: screens/ManagementJob/ManagementJob.js:133
-msgid "Management job not found."
-msgstr "管理ジョブが見つかりません。"
-
-#: screens/ManagementJob/ManagementJobs.js:13
-msgid "Management jobs"
-msgstr "管理ジョブ"
-
-#: components/Lookup/ProjectLookup.js:135
-#: components/PromptDetail/PromptProjectDetail.js:98
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:88
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:157
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:215
-#: screens/InstanceGroup/Instances/InstanceListItem.js:204
-#: screens/Instances/InstanceDetail/InstanceDetail.js:209
-#: screens/Instances/InstanceList/InstanceListItem.js:219
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:83
-#: screens/Job/JobDetail/JobDetail.js:74
-#: screens/Project/ProjectDetail/ProjectDetail.js:192
-#: screens/Project/ProjectList/ProjectList.js:197
-#: screens/Project/ProjectList/ProjectListItem.js:219
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:96
-msgid "Manual"
-msgstr "手動"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:161
-#: components/Schedule/shared/FrequencyDetailSubform.js:108
-msgid "March"
-msgstr "3 月"
-
-#: components/NotificationList/NotificationList.js:197
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:138
-msgid "Mattermost"
-msgstr "Mattermost"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:98
-#: screens/Organization/shared/OrganizationForm.js:71
-msgid "Max Hosts"
-msgstr "最大ホスト数"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:220
-msgid "Maximum"
-msgstr "最大"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:204
-msgid "Maximum length"
-msgstr "最大長"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:163
-#: components/Schedule/shared/FrequencyDetailSubform.js:118
-msgid "May"
-msgstr "5 月"
-
-#: screens/Organization/OrganizationList/OrganizationList.js:144
-#: screens/Organization/OrganizationList/OrganizationListItem.js:63
-msgid "Members"
-msgstr "メンバー"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:47
-msgid "Metadata"
-msgstr "メタデータ"
-
-#: screens/Metrics/Metrics.js:207
-msgid "Metric"
-msgstr "メトリクス"
-
-#: screens/Metrics/Metrics.js:179
-msgid "Metrics"
-msgstr "メトリクス"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:100
-msgid "Microsoft Azure Resource Manager"
-msgstr "Microsoft Azure Resource Manager"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:214
-msgid "Minimum"
-msgstr "最小"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:198
-msgid "Minimum length"
-msgstr "最小長"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:65
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:31
-msgid ""
-"Minimum number of instances that will be automatically\n"
-"assigned to this group when new instances come online."
-msgstr "新規インスタンスがオンラインになると、このグループに自動的に最小限割り当てられるインスタンス数"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:71
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:41
-msgid ""
-"Minimum percentage of all instances that will be automatically\n"
-"assigned to this group when new instances come online."
-msgstr "新規インスタンスがオンラインになると、このグループに自動的に最小限割り当てられるインスタンスの割合"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:182
-#: components/Schedule/shared/FrequencyDetailSubform.js:173
-#: components/Schedule/shared/ScheduleFormFields.js:125
-#: components/Schedule/shared/ScheduleFormFields.js:185
-msgid "Minute"
-msgstr "分"
-
-#: screens/Setting/Settings.js:100
-msgid "Miscellaneous Authentication"
-msgstr "その他の認証"
-
-#: screens/Setting/SettingList.js:111
-msgid "Miscellaneous Authentication settings"
-msgstr "その他の認証設定"
-
-#: screens/Setting/Settings.js:103
-msgid "Miscellaneous System"
-msgstr "その他のシステム"
-
-#: screens/Setting/SettingList.js:107
-msgid "Miscellaneous System settings"
-msgstr "その他のシステム設定"
-
-#: components/Workflow/WorkflowNodeHelp.js:120
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:87
-msgid "Missing"
-msgstr "不明"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:66
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:109
-msgid "Missing resource"
-msgstr "不足しているリソース"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:192
-#: screens/User/UserTokenList/UserTokenList.js:154
-msgid "Modified"
-msgstr "変更日時"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:126
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:116
-#: components/AddRole/AddResourceRole.js:61
-#: components/AssociateModal/AssociateModal.js:148
-#: components/LaunchPrompt/steps/CredentialsStep.js:177
-#: components/LaunchPrompt/steps/InventoryStep.js:93
-#: components/Lookup/CredentialLookup.js:198
-#: components/Lookup/InventoryLookup.js:156
-#: components/Lookup/InventoryLookup.js:211
-#: components/Lookup/MultiCredentialsLookup.js:198
-#: components/Lookup/OrganizationLookup.js:138
-#: components/Lookup/ProjectLookup.js:147
-#: components/NotificationList/NotificationList.js:210
-#: components/RelatedTemplateList/RelatedTemplateList.js:170
-#: components/Schedule/ScheduleList/ScheduleList.js:202
-#: components/TemplateList/TemplateList.js:230
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:31
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:62
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:100
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:131
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:169
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:200
-#: screens/Credential/CredentialList/CredentialList.js:154
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:100
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:136
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:106
-#: screens/Host/HostGroups/HostGroupsList.js:168
-#: screens/Host/HostList/HostList.js:161
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:203
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:133
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:178
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:132
-#: screens/Inventory/InventoryList/InventoryList.js:203
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:189
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:98
-#: screens/Organization/OrganizationList/OrganizationList.js:135
-#: screens/Project/ProjectList/ProjectList.js:209
-#: screens/Team/TeamList/TeamList.js:134
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:165
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:108
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:112
-msgid "Modified By (Username)"
-msgstr "変更者 (ユーザー名)"
-
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:85
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:151
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:77
-msgid "Modified by (username)"
-msgstr "変更者 (ユーザー名)"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:59
-#: screens/Job/JobOutput/HostEventModal.js:125
-msgid "Module"
-msgstr "モジュール"
-
-#: screens/Job/JobDetail/JobDetail.js:530
-msgid "Module Arguments"
-msgstr "モジュール引数"
-
-#: screens/Job/JobDetail/JobDetail.js:524
-msgid "Module Name"
-msgstr "モジュール名"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:266
-msgid "Mon"
-msgstr "月"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:77
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:183
-#: components/Schedule/shared/FrequencyDetailSubform.js:271
-#: components/Schedule/shared/FrequencyDetailSubform.js:433
-msgid "Monday"
-msgstr "月曜"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:186
-#: components/Schedule/shared/FrequencyDetailSubform.js:181
-#: components/Schedule/shared/ScheduleFormFields.js:129
-#: components/Schedule/shared/ScheduleFormFields.js:189
-msgid "Month"
-msgstr "月"
-
-#: components/Popover/Popover.js:32
-msgid "More information"
-msgstr "詳細情報"
-
-#: screens/Setting/shared/SharedFields.js:73
-msgid "More information for"
-msgstr "詳細情報: "
-
-#: screens/Template/Survey/SurveyReorderModal.js:162
-#: screens/Template/Survey/SurveyReorderModal.js:163
-msgid "Multi-Select"
-msgstr "複数選択"
-
-#: screens/Template/Survey/SurveyReorderModal.js:146
-#: screens/Template/Survey/SurveyReorderModal.js:147
-msgid "Multiple Choice"
-msgstr "複数選択"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:91
-msgid "Multiple Choice (multiple select)"
-msgstr "多項選択法 (複数の選択可)"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:86
-msgid "Multiple Choice (single select)"
-msgstr "多項選択法 (単一の選択可)"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:258
-msgid "Multiple Choice Options"
-msgstr "多項選択法オプション"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:117
-#: components/AdHocCommands/AdHocCredentialStep.js:132
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:107
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:122
-#: components/AddRole/AddResourceRole.js:52
-#: components/AddRole/AddResourceRole.js:68
-#: components/AssociateModal/AssociateModal.js:139
-#: components/AssociateModal/AssociateModal.js:154
-#: components/HostForm/HostForm.js:96
-#: components/JobList/JobList.js:195
-#: components/JobList/JobList.js:244
-#: components/JobList/JobListItem.js:86
-#: components/LaunchPrompt/steps/CredentialsStep.js:168
-#: components/LaunchPrompt/steps/CredentialsStep.js:183
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:76
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:86
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:97
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:78
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:89
-#: components/LaunchPrompt/steps/InventoryStep.js:84
-#: components/LaunchPrompt/steps/InventoryStep.js:99
-#: components/Lookup/ApplicationLookup.js:100
-#: components/Lookup/ApplicationLookup.js:111
-#: components/Lookup/CredentialLookup.js:189
-#: components/Lookup/CredentialLookup.js:204
-#: components/Lookup/ExecutionEnvironmentLookup.js:177
-#: components/Lookup/ExecutionEnvironmentLookup.js:184
-#: components/Lookup/HostFilterLookup.js:93
-#: components/Lookup/HostFilterLookup.js:422
-#: components/Lookup/HostListItem.js:8
-#: components/Lookup/InstanceGroupsLookup.js:103
-#: components/Lookup/InstanceGroupsLookup.js:114
-#: components/Lookup/InventoryLookup.js:147
-#: components/Lookup/InventoryLookup.js:162
-#: components/Lookup/InventoryLookup.js:202
-#: components/Lookup/InventoryLookup.js:217
-#: components/Lookup/MultiCredentialsLookup.js:189
-#: components/Lookup/MultiCredentialsLookup.js:204
-#: components/Lookup/OrganizationLookup.js:129
-#: components/Lookup/OrganizationLookup.js:144
-#: components/Lookup/ProjectLookup.js:127
-#: components/Lookup/ProjectLookup.js:157
-#: components/NotificationList/NotificationList.js:181
-#: components/NotificationList/NotificationList.js:218
-#: components/NotificationList/NotificationListItem.js:28
-#: components/OptionsList/OptionsList.js:57
-#: components/PaginatedTable/PaginatedTable.js:72
-#: components/PromptDetail/PromptDetail.js:114
-#: components/RelatedTemplateList/RelatedTemplateList.js:161
-#: components/RelatedTemplateList/RelatedTemplateList.js:186
-#: components/ResourceAccessList/ResourceAccessListItem.js:58
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:325
-#: components/Schedule/ScheduleList/ScheduleList.js:168
-#: components/Schedule/ScheduleList/ScheduleList.js:189
-#: components/Schedule/ScheduleList/ScheduleListItem.js:86
-#: components/Schedule/shared/ScheduleFormFields.js:72
-#: components/TemplateList/TemplateList.js:205
-#: components/TemplateList/TemplateList.js:242
-#: components/TemplateList/TemplateListItem.js:142
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:18
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:37
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:49
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:68
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:80
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:110
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:122
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:137
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:149
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:179
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:191
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:206
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:60
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:109
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:135
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:28
-#: screens/Application/Applications.js:81
-#: screens/Application/ApplicationsList/ApplicationListItem.js:33
-#: screens/Application/ApplicationsList/ApplicationsList.js:118
-#: screens/Application/ApplicationsList/ApplicationsList.js:155
-#: screens/Application/shared/ApplicationForm.js:54
-#: screens/Credential/CredentialDetail/CredentialDetail.js:217
-#: screens/Credential/CredentialList/CredentialList.js:141
-#: screens/Credential/CredentialList/CredentialList.js:164
-#: screens/Credential/CredentialList/CredentialListItem.js:58
-#: screens/Credential/shared/CredentialForm.js:161
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:71
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:91
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:68
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:123
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:176
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:33
-#: screens/CredentialType/shared/CredentialTypeForm.js:21
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:49
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:136
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:165
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:69
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:89
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:115
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:12
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:89
-#: screens/Host/HostDetail/HostDetail.js:69
-#: screens/Host/HostGroups/HostGroupItem.js:28
-#: screens/Host/HostGroups/HostGroupsList.js:159
-#: screens/Host/HostGroups/HostGroupsList.js:176
-#: screens/Host/HostList/HostList.js:148
-#: screens/Host/HostList/HostList.js:169
-#: screens/Host/HostList/HostListItem.js:50
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:41
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:49
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:161
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:194
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:61
-#: screens/InstanceGroup/Instances/InstanceList.js:199
-#: screens/InstanceGroup/Instances/InstanceList.js:215
-#: screens/InstanceGroup/Instances/InstanceList.js:266
-#: screens/InstanceGroup/Instances/InstanceList.js:299
-#: screens/InstanceGroup/Instances/InstanceListItem.js:124
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:44
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:19
-#: screens/Instances/InstanceList/InstanceList.js:143
-#: screens/Instances/InstanceList/InstanceList.js:160
-#: screens/Instances/InstanceList/InstanceList.js:201
-#: screens/Instances/InstanceList/InstanceListItem.js:128
-#: screens/Instances/InstancePeers/InstancePeerList.js:80
-#: screens/Instances/InstancePeers/InstancePeerList.js:87
-#: screens/Instances/InstancePeers/InstancePeerList.js:96
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:37
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:89
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:31
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:194
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:209
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:215
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:34
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:119
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:141
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:74
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:36
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:169
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:186
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:33
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:119
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:138
-#: screens/Inventory/InventoryList/InventoryList.js:178
-#: screens/Inventory/InventoryList/InventoryList.js:209
-#: screens/Inventory/InventoryList/InventoryList.js:218
-#: screens/Inventory/InventoryList/InventoryListItem.js:92
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:180
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:195
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:232
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:181
-#: screens/Inventory/InventorySources/InventorySourceList.js:211
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:71
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:98
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:30
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:76
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:111
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:33
-#: screens/Inventory/shared/InventoryForm.js:50
-#: screens/Inventory/shared/InventoryGroupForm.js:32
-#: screens/Inventory/shared/InventorySourceForm.js:100
-#: screens/Inventory/shared/SmartInventoryForm.js:47
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:90
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:100
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:67
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:107
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:122
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:178
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:112
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:41
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:91
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:84
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:107
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.js:16
-#: screens/Organization/OrganizationList/OrganizationList.js:122
-#: screens/Organization/OrganizationList/OrganizationList.js:143
-#: screens/Organization/OrganizationList/OrganizationListItem.js:45
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:68
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:85
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:14
-#: screens/Organization/shared/OrganizationForm.js:56
-#: screens/Project/ProjectDetail/ProjectDetail.js:176
-#: screens/Project/ProjectList/ProjectList.js:185
-#: screens/Project/ProjectList/ProjectList.js:221
-#: screens/Project/ProjectList/ProjectListItem.js:179
-#: screens/Project/shared/ProjectForm.js:214
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:146
-#: screens/Team/TeamDetail/TeamDetail.js:37
-#: screens/Team/TeamList/TeamList.js:117
-#: screens/Team/TeamList/TeamList.js:142
-#: screens/Team/TeamList/TeamListItem.js:33
-#: screens/Team/shared/TeamForm.js:29
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:185
-#: screens/Template/Survey/SurveyList.js:102
-#: screens/Template/Survey/SurveyList.js:102
-#: screens/Template/Survey/SurveyListItem.js:39
-#: screens/Template/Survey/SurveyReorderModal.js:218
-#: screens/Template/Survey/SurveyReorderModal.js:218
-#: screens/Template/Survey/SurveyReorderModal.js:238
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:111
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:69
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:88
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:120
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:152
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:178
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:88
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.js:74
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.js:94
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:75
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:95
-#: screens/Template/shared/JobTemplateForm.js:243
-#: screens/Template/shared/WorkflowJobTemplateForm.js:109
-#: screens/User/UserOrganizations/UserOrganizationList.js:75
-#: screens/User/UserOrganizations/UserOrganizationList.js:79
-#: screens/User/UserOrganizations/UserOrganizationListItem.js:13
-#: screens/User/UserRoles/UserRolesList.js:155
-#: screens/User/UserRoles/UserRolesListItem.js:12
-#: screens/User/UserTeams/UserTeamList.js:180
-#: screens/User/UserTeams/UserTeamList.js:232
-#: screens/User/UserTeams/UserTeamListItem.js:18
-#: screens/User/UserTokenList/UserTokenListItem.js:22
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:140
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:123
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:163
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:43
-msgid "Name"
-msgstr "名前"
-
-#: components/AppContainer/AppContainer.js:95
-msgid "Navigation"
-msgstr "ナビゲーション"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:221
-#: components/Schedule/shared/FrequencyDetailSubform.js:512
-#: screens/Dashboard/shared/ChartTooltip.js:106
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:57
-msgid "Never"
-msgstr "なし"
-
-#: components/Workflow/WorkflowNodeHelp.js:114
-msgid "Never Updated"
-msgstr "未更新"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:47
-msgid "Never expires"
-msgstr "期限切れなし"
-
-#: components/JobList/JobList.js:227
-#: components/Workflow/WorkflowNodeHelp.js:90
-msgid "New"
-msgstr "新規"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:51
-#: components/AdHocCommands/useAdHocCredentialStep.js:29
-#: components/AdHocCommands/useAdHocDetailsStep.js:40
-#: components/AdHocCommands/useAdHocExecutionEnvironmentStep.js:22
-#: components/AddRole/AddResourceRole.js:196
-#: components/AddRole/AddResourceRole.js:231
-#: components/LaunchPrompt/LaunchPrompt.js:160
-#: components/Schedule/shared/SchedulePromptableFields.js:127
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:66
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:59
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:132
-msgid "Next"
-msgstr "次へ"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:337
-#: components/Schedule/ScheduleList/ScheduleList.js:171
-#: components/Schedule/ScheduleList/ScheduleListItem.js:118
-#: components/Schedule/ScheduleList/ScheduleListItem.js:122
-msgid "Next Run"
-msgstr "次回実行日時"
-
-#: components/Search/Search.js:232
-msgid "No"
-msgstr "不可"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:121
-msgid "No Hosts Matched"
-msgstr "一致するホストがありません"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:122
-#: screens/Job/JobOutput/JobOutputSearch.js:123
-msgid "No Hosts Remaining"
-msgstr "残りのホストがありません"
-
-#: screens/Job/JobOutput/HostEventModal.js:150
-msgid "No JSON Available"
-msgstr "JSON は利用できません"
-
-#: screens/Dashboard/shared/ChartTooltip.js:82
-msgid "No Jobs"
-msgstr "ジョブはありません"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:72
-msgid "No inventory sync failures."
-msgstr "インベントリー同期の失敗はありません。"
-
-#: components/ContentEmpty/ContentEmpty.js:20
-msgid "No items found."
-msgstr "項目は見つかりません。"
-
-#: screens/Host/HostList/HostListItem.js:100
-msgid "No job data available"
-msgstr "利用可能なジョブデータがありません"
-
-#: screens/Job/JobOutput/EmptyOutput.js:52
-msgid "No output found for this job."
-msgstr "このジョブの出力は見つかりません"
-
-#: screens/Job/JobOutput/HostEventModal.js:126
-msgid "No result found"
-msgstr "結果が見つかりません"
-
-#: components/LabelSelect/LabelSelect.js:130
-#: components/LaunchPrompt/steps/SurveyStep.js:136
-#: components/LaunchPrompt/steps/SurveyStep.js:195
-#: components/MultiSelect/TagMultiSelect.js:60
-#: components/Search/AdvancedSearch.js:151
-#: components/Search/AdvancedSearch.js:266
-#: components/Search/LookupTypeInput.js:33
-#: components/Search/RelatedLookupTypeInput.js:26
-#: components/Search/Search.js:153
-#: components/Search/Search.js:202
-#: components/Search/Search.js:226
-#: screens/ActivityStream/ActivityStream.js:142
-#: screens/Credential/shared/CredentialForm.js:143
-#: screens/Credential/shared/CredentialFormFields/BecomeMethodField.js:65
-#: screens/Dashboard/DashboardGraph.js:106
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:137
-#: screens/Template/Survey/SurveyReorderModal.js:166
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:260
-#: screens/Template/shared/PlaybookSelect.js:72
-msgid "No results found"
-msgstr "結果が見つかりません"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:116
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:137
-msgid "No subscriptions found"
-msgstr "サブスクリプションが見つかりません"
-
-#: screens/Template/Survey/SurveyList.js:147
-msgid "No survey questions found."
-msgstr "Survey の質問は見つかりません。"
-
-#: screens/Job/JobDetail/JobDetail.js:399
-msgid "No timeout specified"
-msgstr "タイムアウトが指定されていません"
-
-#: components/PaginatedTable/PaginatedTable.js:80
-msgid "No {pluralizedItemName} Found"
-msgstr "{pluralizedItemName} は見つかりません"
-
-#: components/Workflow/WorkflowNodeHelp.js:148
-#: components/Workflow/WorkflowNodeHelp.js:184
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:273
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:274
-msgid "Node Alias"
-msgstr "ノードのエイリアス"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:223
-#: screens/InstanceGroup/Instances/InstanceList.js:204
-#: screens/InstanceGroup/Instances/InstanceList.js:268
-#: screens/InstanceGroup/Instances/InstanceList.js:300
-#: screens/InstanceGroup/Instances/InstanceListItem.js:142
-#: screens/Instances/InstanceDetail/InstanceDetail.js:204
-#: screens/Instances/InstanceList/InstanceList.js:148
-#: screens/Instances/InstanceList/InstanceList.js:203
-#: screens/Instances/InstanceList/InstanceListItem.js:150
-#: screens/Instances/InstancePeers/InstancePeerList.js:98
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:59
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:118
-msgid "Node Type"
-msgstr "ノードタイプ"
-
-#: screens/TopologyView/Legend.js:107
-msgid "Node state types"
-msgstr "ノード状態のタイプ"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.js:75
-msgid "Node type"
-msgstr "ノードタイプ"
-
-#: screens/TopologyView/Legend.js:70
-msgid "Node types"
-msgstr "ノードタイプ"
-
-#: components/Schedule/shared/ScheduleFormFields.js:180
-#: components/Schedule/shared/ScheduleFormFields.js:184
-#: components/Workflow/WorkflowNodeHelp.js:123
-msgid "None"
-msgstr "なし"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:193
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:196
-msgid "None (Run Once)"
-msgstr "なし (1回実行)"
-
-#: components/Schedule/shared/ScheduleFormFields.js:120
-#: components/Schedule/shared/ScheduleFormFields.js:124
-msgid "None (run once)"
-msgstr "なし (1回実行)"
-
-#: screens/User/UserDetail/UserDetail.js:51
-#: screens/User/UserList/UserListItem.js:23
-#: screens/User/shared/UserForm.js:29
-msgid "Normal User"
-msgstr "標準ユーザー"
-
-#: components/ContentError/ContentError.js:37
-msgid "Not Found"
-msgstr "見つかりません"
-
-#: screens/Setting/shared/SettingDetail.js:71
-#: screens/Setting/shared/SettingDetail.js:112
-msgid "Not configured"
-msgstr "設定されていません"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:75
-msgid "Not configured for inventory sync."
-msgstr "インベントリーの同期に設定されていません。"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:248
-msgid ""
-"Note that only hosts directly in this group can\n"
-"be disassociated. Hosts in sub-groups must be disassociated\n"
-"directly from the sub-group level that they belong."
-msgstr "このグループに直接含まれるホストのみの関連付けを解除できることに注意してください。サブグループのホストの関連付けの解除については、それらのホストが属するサブグループのレベルで直接実行する必要があります。"
-
-#: screens/Host/HostGroups/HostGroupsList.js:212
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:230
-msgid ""
-"Note that you may still see the group in the list after\n"
-"disassociating if the host is also a member of that group’s\n"
-"children. This list shows all groups the host is associated\n"
-"with directly and indirectly."
-msgstr "ホストがそのグループの子のメンバーでもある場合は、関連付けを解除した後も一覧にグループが表示される場合があることに注意してください。この一覧には、ホストが直接的および間接的に関連付けられているすべてのグループが表示されます。"
-
-#: components/Lookup/InstanceGroupsLookup.js:90
-msgid "Note: The order in which these are selected sets the execution precedence. Select more than one to enable drag."
-msgstr "注: 選択された順序によって、実行の優先順位が設定されます。ドラッグを有効にするには、1 つ以上選択してください。"
-
-#: screens/Organization/shared/OrganizationForm.js:116
-msgid "Note: The order of these credentials sets precedence for the sync and lookup of the content. Select more than one to enable drag."
-msgstr "注: 資格情報の順序は、コンテンツの同期と検索の優先順位を設定します。ドラッグを有効にするには、1 つ以上選択してください。"
-
-#: screens/Project/shared/Project.helptext.js:81
-msgid "Note: This field assumes the remote name is \"origin\"."
-msgstr "注: このフィールドは、リモート名が \"origin\" であることが前提です。"
-
-#: screens/Project/shared/Project.helptext.js:35
-msgid ""
-"Note: When using SSH protocol for GitHub or\n"
-"Bitbucket, enter an SSH key only, do not enter a username\n"
-"(other than git). Additionally, GitHub and Bitbucket do\n"
-"not support password authentication when using SSH. GIT\n"
-"read only protocol (git://) does not use username or\n"
-"password information."
-msgstr "GitHub または Bitbucket の SSH プロトコルを使用している場合は、SSH キーのみを入力し、ユーザー名 (git 以外) を入力しないでください。また、GitHub および Bitbucket は、SSH の使用時のパスワード認証をサポートしません。GIT の読み取り専用プロトコル (git://) はユーザー名またはパスワード情報を使用しません。"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:331
-msgid "Notification Color"
-msgstr "通知の色"
-
-#: screens/NotificationTemplate/NotificationTemplate.js:58
-#: screens/NotificationTemplate/NotificationTemplateAdd.js:50
-msgid "Notification Template not found."
-msgstr "通知テンプレートテストは見つかりません。"
-
-#: screens/ActivityStream/ActivityStream.js:198
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:117
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:171
-#: screens/NotificationTemplate/NotificationTemplates.js:14
-#: screens/NotificationTemplate/NotificationTemplates.js:21
-#: util/getRelatedResourceDeleteDetails.js:181
-msgid "Notification Templates"
-msgstr "通知テンプレート"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:134
-msgid "Notification Type"
-msgstr "通知タイプ"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:380
-msgid "Notification color"
-msgstr "通知の色"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:193
-msgid "Notification sent successfully"
-msgstr "通知が正常に送信されました"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:449
-msgid "Notification test failed."
-msgstr "通知テストに失敗しました。"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:197
-msgid "Notification timed out"
-msgstr "通知がタイムアウトしました"
-
-#: components/NotificationList/NotificationList.js:190
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:131
-msgid "Notification type"
-msgstr "通知タイプ"
-
-#: components/NotificationList/NotificationList.js:177
-#: routeConfig.js:122
-#: screens/Inventory/Inventories.js:93
-#: screens/Inventory/InventorySource/InventorySource.js:99
-#: screens/ManagementJob/ManagementJob.js:116
-#: screens/ManagementJob/ManagementJobs.js:22
-#: screens/Organization/Organization.js:135
-#: screens/Organization/Organizations.js:33
-#: screens/Project/Project.js:114
-#: screens/Project/Projects.js:28
-#: screens/Template/Template.js:141
-#: screens/Template/Templates.js:46
-#: screens/Template/WorkflowJobTemplate.js:123
-msgid "Notifications"
-msgstr "通知"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:169
-#: components/Schedule/shared/FrequencyDetailSubform.js:148
-msgid "November"
-msgstr "11 月"
-
-#: components/StatusLabel/StatusLabel.js:44
-#: components/Workflow/WorkflowNodeHelp.js:117
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:66
-#: screens/Job/JobOutput/shared/HostStatusBar.js:35
-msgid "OK"
-msgstr "OK"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:42
-#: components/Schedule/shared/FrequencyDetailSubform.js:549
-msgid "Occurrences"
-msgstr "実行回数"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:168
-#: components/Schedule/shared/FrequencyDetailSubform.js:143
-msgid "October"
-msgstr "10 月"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:189
-#: components/HostToggle/HostToggle.js:61
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:192
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:195
-#: components/PromptDetail/PromptDetail.js:362
-#: components/PromptDetail/PromptJobTemplateDetail.js:156
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:489
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:58
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:45
-#: screens/Setting/shared/SettingDetail.js:98
-#: screens/Setting/shared/SharedFields.js:150
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:284
-#: screens/Template/shared/JobTemplateForm.js:504
-msgid "Off"
-msgstr "オフ"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:188
-#: components/HostToggle/HostToggle.js:60
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:192
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:194
-#: components/PromptDetail/PromptDetail.js:362
-#: components/PromptDetail/PromptJobTemplateDetail.js:156
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:489
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:57
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:45
-#: screens/Setting/shared/SettingDetail.js:98
-#: screens/Setting/shared/SharedFields.js:149
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:284
-#: screens/Template/shared/JobTemplateForm.js:504
-msgid "On"
-msgstr "オン"
-
-#: components/Workflow/WorkflowLegend.js:126
-#: components/Workflow/WorkflowLinkHelp.js:30
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:39
-msgid "On Failure"
-msgstr "障害発生時"
-
-#: components/Workflow/WorkflowLegend.js:122
-#: components/Workflow/WorkflowLinkHelp.js:27
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:63
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:32
-msgid "On Success"
-msgstr "成功時"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:536
-msgid "On date"
-msgstr "指定日"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:99
-#: components/Schedule/shared/FrequencyDetailSubform.js:251
-msgid "On days"
-msgstr "曜日"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:18
-msgid ""
-"One Slack channel per line. The pound symbol (#)\n"
-"is required for channels. To respond to or start a thread to a specific message add the parent message Id to the channel where the parent message Id is 16 digits. A dot (.) must be manually inserted after the 10th digit. ie:#destination-channel, 1231257890.006423. See Slack"
-msgstr "それぞれの行に 1 つの Slack チャンネルを入力します。チャンネルにはシャープ記号 (#) が必要です。特定のメッセージに対して応答する、またはスレッドを開始するには、チャンネルに 16 桁の親メッセージ ID を追加します。10 桁目の後にピリオド (.) を手動で挿入する必要があります (例: #destination-channel, 1231257890.006423)。Slack を参照してください。"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:154
-msgid "Only Group By"
-msgstr "グループ化のみ"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:103
-msgid "OpenStack"
-msgstr "OpenStack"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:111
-msgid "Option Details"
-msgstr "オプションの詳細"
-
-#: screens/Inventory/shared/Inventory.helptext.js:25
-msgid ""
-"Optional labels that describe this inventory,\n"
-"such as 'dev' or 'test'. Labels can be used to group and filter\n"
-"inventories and completed jobs."
-msgstr "「dev」、「test」などのこのインベントリーを説明するオプションラベルです。\n"
-"ラベルを使用し、インベントリーおよび完了した\n"
-"ジョブの分類およびフィルターを実行できます。"
-
-#: screens/Job/Job.helptext.js:12
-#: screens/Template/shared/JobTemplate.helptext.js:13
-msgid "Optional labels that describe this job template, such as 'dev' or 'test'. Labels can be used to group and filter job templates and completed jobs."
-msgstr "「dev」、「test」などのこのジョブテンプレートを説明するオプションラベルです。ラベルを使用し、ジョブテンプレートおよび完了したジョブの分類およびフィルターを実行できます。"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:11
-msgid ""
-"Optional labels that describe this workflow job template,\n"
-"such as 'dev' or 'test'. Labels can be used to group and filter\n"
-"workflow job templates and completed jobs."
-msgstr "「dev」、「test」などのこのワークフロージョブテンプレートを説明するオプションラベルです。\n"
-"ラベルを使用し、ワークフロージョブテンプレートおよび完了した\n"
-"ジョブの分類およびフィルターを実行できます。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:26
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:19
-msgid "Optionally select the credential to use to send status updates back to the webhook service."
-msgstr "必要に応じて、ステータスの更新を Webhook サービスに送信しなおすのに使用する認証情報を選択します。"
-
-#: components/NotificationList/NotificationList.js:220
-#: components/NotificationList/NotificationListItem.js:34
-#: screens/Credential/shared/TypeInputsSubForm.js:47
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:61
-#: screens/Instances/Shared/InstanceForm.js:54
-#: screens/Inventory/shared/InventoryForm.js:94
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:69
-#: screens/Template/shared/JobTemplateForm.js:545
-#: screens/Template/shared/WorkflowJobTemplateForm.js:241
-msgid "Options"
-msgstr "オプション"
-
-#: screens/Template/Survey/SurveyReorderModal.js:217
-#: screens/Template/Survey/SurveyReorderModal.js:217
-#: screens/Template/Survey/SurveyReorderModal.js:233
-msgid "Order"
-msgstr "順序"
-
-#: components/Lookup/ApplicationLookup.js:119
-#: components/Lookup/OrganizationLookup.js:101
-#: components/Lookup/OrganizationLookup.js:107
-#: components/Lookup/OrganizationLookup.js:124
-#: components/PromptDetail/PromptInventorySourceDetail.js:62
-#: components/PromptDetail/PromptInventorySourceDetail.js:72
-#: components/PromptDetail/PromptJobTemplateDetail.js:105
-#: components/PromptDetail/PromptJobTemplateDetail.js:115
-#: components/PromptDetail/PromptProjectDetail.js:77
-#: components/PromptDetail/PromptProjectDetail.js:88
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:67
-#: components/TemplateList/TemplateList.js:244
-#: components/TemplateList/TemplateListItem.js:185
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:70
-#: screens/Application/ApplicationsList/ApplicationListItem.js:38
-#: screens/Application/ApplicationsList/ApplicationsList.js:157
-#: screens/Credential/CredentialDetail/CredentialDetail.js:230
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:70
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:155
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:167
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:76
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:96
-#: screens/Inventory/InventoryList/InventoryList.js:191
-#: screens/Inventory/InventoryList/InventoryList.js:221
-#: screens/Inventory/InventoryList/InventoryListItem.js:119
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:202
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:107
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:121
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:131
-#: screens/Project/ProjectDetail/ProjectDetail.js:180
-#: screens/Project/ProjectList/ProjectListItem.js:287
-#: screens/Project/ProjectList/ProjectListItem.js:298
-#: screens/Team/TeamDetail/TeamDetail.js:40
-#: screens/Team/TeamList/TeamList.js:143
-#: screens/Team/TeamList/TeamListItem.js:38
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:199
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:210
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:120
-#: screens/User/UserTeams/UserTeamList.js:181
-#: screens/User/UserTeams/UserTeamList.js:237
-#: screens/User/UserTeams/UserTeamListItem.js:23
-msgid "Organization"
-msgstr "組織"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:100
-msgid "Organization (Name)"
-msgstr "組織 (名前)"
-
-#: screens/Team/TeamList/TeamList.js:126
-msgid "Organization Name"
-msgstr "組織名"
-
-#: screens/Organization/Organization.js:154
-msgid "Organization not found."
-msgstr "組織が見つかりません。"
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:188
-#: routeConfig.js:96
-#: screens/ActivityStream/ActivityStream.js:181
-#: screens/Organization/OrganizationList/OrganizationList.js:117
-#: screens/Organization/OrganizationList/OrganizationList.js:163
-#: screens/Organization/Organizations.js:16
-#: screens/Organization/Organizations.js:26
-#: screens/User/User.js:66
-#: screens/User/UserOrganizations/UserOrganizationList.js:72
-#: screens/User/Users.js:33
-#: util/getRelatedResourceDeleteDetails.js:232
-#: util/getRelatedResourceDeleteDetails.js:266
-msgid "Organizations"
-msgstr "組織"
-
-#: components/LaunchPrompt/steps/useOtherPromptsStep.js:90
-msgid "Other prompts"
-msgstr "他のプロンプト"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:79
-msgid "Out of compliance"
-msgstr "コンプライアンス違反"
-
-#: screens/Job/Job.js:131
-#: screens/Job/JobOutput/HostEventModal.js:156
-#: screens/Job/Jobs.js:34
-msgid "Output"
-msgstr "出力"
-
-#: screens/Job/JobOutput/HostEventModal.js:157
-msgid "Output tab"
-msgstr "出力タブ"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:80
-msgid "Overwrite"
-msgstr "上書き"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:41
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:121
-msgid "Overwrite local groups and hosts from remote inventory source"
-msgstr "リモートインベントリーソースからのローカルグループおよびホストを上書きする"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:46
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:127
-msgid "Overwrite local variables from remote inventory source"
-msgstr "リモートインベントリーソースのローカル変数を上書きする"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:86
-msgid "Overwrite variables"
-msgstr "変数の上書き"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:493
-msgid "POST"
-msgstr "POST"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:494
-msgid "PUT"
-msgstr "PUT"
-
-#: components/NotificationList/NotificationList.js:198
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:139
-msgid "Pagerduty"
-msgstr "Pagerduty"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:279
-msgid "Pagerduty Subdomain"
-msgstr "Pagerduty サブドメイン"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:298
-msgid "Pagerduty subdomain"
-msgstr "Pagerduty サブドメイン"
-
-#: components/Pagination/Pagination.js:35
-msgid "Pagination"
-msgstr "ページネーション"
-
-#: components/Workflow/WorkflowTools.js:165
-msgid "Pan Down"
-msgstr "パンダウン"
-
-#: components/Workflow/WorkflowTools.js:132
-msgid "Pan Left"
-msgstr "パンレフト"
-
-#: components/Workflow/WorkflowTools.js:176
-msgid "Pan Right"
-msgstr "パンライト"
-
-#: components/Workflow/WorkflowTools.js:143
-msgid "Pan Up"
-msgstr "パンアップ"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:243
-msgid "Pass extra command line changes. There are two ansible command line parameters:"
-msgstr "追加のコマンドライン変更を渡します。2 つの Ansible コマンドラインパラメーターがあります。"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:14
-msgid "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. Refer to the Ansible Controller documentation for example syntax."
-msgstr "追加のコマンドライン変数を Playbook に渡します。これは、ansible-playbook の -e または --extra-vars コマンドラインパラメーターです。YAML または JSON のいずれかを使用してキーと値のペアを指定します。構文のサンプルについては Ansible Controller ドキュメントを参照してください。"
-
-#: screens/Job/Job.helptext.js:13
-#: screens/Template/shared/JobTemplate.helptext.js:14
-msgid "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. Refer to the documentation for example syntax."
-msgstr "追加のコマンドライン変数を Playbook に渡します。これは、ansible-playbook の -e または --extra-vars コマンドラインパラメーターです。YAML または JSON のいずれかを使用してキーと値のペアを指定します。構文のサンプルについてはドキュメントを参照してください。"
-
-#: screens/Login/Login.js:227
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:73
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:101
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:212
-#: screens/Template/Survey/SurveyQuestionForm.js:82
-#: screens/User/shared/UserForm.js:88
-msgid "Password"
-msgstr "パスワード"
-
-#: screens/Dashboard/DashboardGraph.js:119
-msgid "Past 24 hours"
-msgstr "過去 24 時間"
-
-#: screens/Dashboard/DashboardGraph.js:110
-msgid "Past month"
-msgstr "過去 1 ヵ月"
-
-#: screens/Dashboard/DashboardGraph.js:113
-msgid "Past two weeks"
-msgstr "過去 2 週間"
-
-#: screens/Dashboard/DashboardGraph.js:116
-msgid "Past week"
-msgstr "過去 1 週間"
-
-#: screens/Instances/Instance.js:51
-#: screens/Instances/InstancePeers/InstancePeerList.js:74
-msgid "Peers"
-msgstr "ピア"
-
-#: components/JobList/JobList.js:228
-#: components/StatusLabel/StatusLabel.js:49
-#: components/Workflow/WorkflowNodeHelp.js:93
-msgid "Pending"
-msgstr "保留中"
-
-#: components/AppContainer/PageHeaderToolbar.js:76
-msgid "Pending Workflow Approvals"
-msgstr "保留中のワークフロー承認"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:128
-msgid "Pending delete"
-msgstr "保留中の削除"
-
-#: components/Lookup/HostFilterLookup.js:370
-msgid "Perform a search to define a host filter"
-msgstr "検索を実行して、ホストフィルターを定義します。"
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:73
-#: screens/User/UserTokenList/UserTokenList.js:105
-msgid "Personal Access Token"
-msgstr "パーソナルアクセストークン"
-
-#: screens/User/UserTokenList/UserTokenListItem.js:26
-msgid "Personal access token"
-msgstr "パーソナルアクセストークン"
-
-#: screens/Job/JobOutput/HostEventModal.js:122
-msgid "Play"
-msgstr "プレイ"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:84
-msgid "Play Count"
-msgstr "再生回数"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:124
-msgid "Play Started"
-msgstr "プレイの開始"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:148
-#: screens/Job/JobDetail/JobDetail.js:319
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:253
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:43
-#: screens/Template/shared/JobTemplateForm.js:357
-msgid "Playbook"
-msgstr "Playbook"
-
-#: components/JobList/JobListItem.js:44
-#: screens/Job/JobDetail/JobDetail.js:67
-msgid "Playbook Check"
-msgstr "Playbook チェック"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:125
-msgid "Playbook Complete"
-msgstr "Playbook の完了"
-
-#: components/PromptDetail/PromptProjectDetail.js:165
-#: screens/Project/ProjectDetail/ProjectDetail.js:288
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:72
-msgid "Playbook Directory"
-msgstr "Playbook ディレクトリー"
-
-#: components/JobList/JobList.js:213
-#: components/JobList/JobListItem.js:44
-#: components/Schedule/ScheduleList/ScheduleListItem.js:37
-#: screens/Job/JobDetail/JobDetail.js:67
-msgid "Playbook Run"
-msgstr "Playbook 実行"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:126
-msgid "Playbook Started"
-msgstr "Playbook の開始"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:174
-#: components/TemplateList/TemplateList.js:222
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:23
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:54
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:157
-msgid "Playbook name"
-msgstr "Playbook 名"
-
-#: screens/Dashboard/DashboardGraph.js:146
-msgid "Playbook run"
-msgstr "Playbook 実行"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:85
-msgid "Plays"
-msgstr "プレイ"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:149
-msgid "Please add a Schedule to populate this list."
-msgstr "スケジュールを追加してこのリストに入力してください。"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:152
-msgid "Please add a Schedule to populate this list. Schedules can be added to a Template, Project, or Inventory Source."
-msgstr "このリストに入力するには、スケジュールを追加してください。スケジュールは、テンプレート、プロジェクト、またはインベントリソースに追加できます。"
-
-#: screens/Template/Survey/SurveyList.js:146
-msgid "Please add survey questions."
-msgstr "Survey の質問を追加してください。"
-
-#: components/PaginatedTable/PaginatedTable.js:93
-msgid "Please add {pluralizedItemName} to populate this list"
-msgstr "{pluralizedItemName} を追加してこのリストに入力してください。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:43
-msgid "Please click the Start button to begin."
-msgstr "開始ボタンをクリックして開始してください。"
-
-#: components/Schedule/shared/ScheduleForm.js:421
-msgid "Please enter a number of occurrences."
-msgstr "出現回数を入力してください。"
-
-#: util/validators.js:160
-msgid "Please enter a valid URL"
-msgstr "有効な URL を入力してください。"
-
-#: screens/User/shared/UserTokenForm.js:20
-msgid "Please enter a value."
-msgstr "値を入力してください。"
-
-#: screens/Login/Login.js:191
-msgid "Please log in"
-msgstr "ログインしてください"
-
-#: components/JobList/JobList.js:190
-msgid "Please run a job to populate this list."
-msgstr "ジョブを実行してこのリストに入力してください。"
-
-#: components/Schedule/shared/ScheduleForm.js:417
-msgid "Please select a day number between 1 and 31."
-msgstr "1 から 31 までの日付を選択してください。"
-
-#: screens/Template/shared/JobTemplateForm.js:174
-msgid "Please select an Inventory or check the Prompt on Launch option"
-msgstr "インベントリーを選択するか、または起動プロンプトオプションにチェックを付けてください。"
-
-#: components/Schedule/shared/ScheduleForm.js:429
-msgid "Please select an end date/time that comes after the start date/time."
-msgstr "開始日時より後の終了日時を選択してください。"
-
-#: components/Lookup/HostFilterLookup.js:359
-msgid "Please select an organization before editing the host filter"
-msgstr "組織を選択してからホストフィルターを編集します。"
-
-#: screens/Job/JobOutput/EmptyOutput.js:32
-msgid "Please try another search using the filter above"
-msgstr "上記のフィルターを使用して別の検索を試してください。"
-
-#: screens/TopologyView/ContentLoading.js:40
-msgid "Please wait until the topology view is populated..."
-msgstr "トポロジービューが反映されるまでお待ちください..."
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:78
-msgid "Pod spec override"
-msgstr "Pod 仕様の上書き"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:214
-#: screens/InstanceGroup/Instances/InstanceListItem.js:203
-#: screens/Instances/InstanceDetail/InstanceDetail.js:208
-#: screens/Instances/InstanceList/InstanceListItem.js:218
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:82
-msgid "Policy Type"
-msgstr "ポリシータイプ"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:63
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:26
-msgid "Policy instance minimum"
-msgstr "ポリシーインスタンスの最小値"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:70
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:36
-msgid "Policy instance percentage"
-msgstr "ポリシーインスタンスの割合"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginField.js:64
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginField.js:70
-msgid "Populate field from an external secret management system"
-msgstr "外部のシークレット管理システムからフィールドにデータを入力します"
-
-#: components/Lookup/HostFilterLookup.js:349
-msgid ""
-"Populate the hosts for this inventory by using a search\n"
-"filter. Example: ansible_facts__ansible_distribution:\"RedHat\".\n"
-"Refer to the documentation for further syntax and\n"
-"examples. Refer to the Ansible Controller documentation for further syntax and\n"
-"examples."
-msgstr "検索フィルターを使用して、このインベントリーのホストにデータを入力します (例: ansible_facts__ansible_distribution:\"RedHat\")。詳細な構文と例については、ドキュメントを参照してください。構文と例の詳細については、Ansible Controller のドキュメントを参照してください。"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:165
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:104
-msgid "Port"
-msgstr "ポート"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:231
-msgid "Preconditions for running this node when there are multiple parents. Refer to the"
-msgstr "複数の親がある場合にこのノードを実行するための前提条件。参照:"
-
-#: screens/Template/Survey/MultipleChoiceField.js:59
-msgid ""
-"Press 'Enter' to add more answer choices. One answer\n"
-"choice per line."
-msgstr "Enter キーを押して、回答の選択肢をさらに追加します。回答の選択肢は、1 行に 1 つです。"
-
-#: components/CodeEditor/CodeEditor.js:181
-msgid "Press Enter to edit. Press ESC to stop editing."
-msgstr "Enter キーを押して編集します。編集を終了するには、ESC キーを押します。"
-
-#: components/SelectedList/DraggableSelectedList.js:85
-msgid ""
-"Press space or enter to begin dragging,\n"
-"and use the arrow keys to navigate up or down.\n"
-"Press enter to confirm the drag, or any other key to\n"
-"cancel the drag operation."
-msgstr "スペースまたは Enter キーを押してドラッグを開始し、矢印キーを使用して上下に移動します。Enter キーを押してドラッグを確認するか、その他のキーを押してドラッグ操作をキャンセルします。"
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:71
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:130
-#: screens/Inventory/shared/InventoryForm.js:99
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:147
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:347
-#: screens/Template/shared/JobTemplateForm.js:603
-msgid "Prevent Instance Group Fallback"
-msgstr "インスタンスグループのフォールバックを防止する"
-
-#: screens/Inventory/shared/Inventory.helptext.js:197
-msgid "Prevent Instance Group Fallback: If enabled, the inventory will prevent adding any organization instance groups to the list of preferred instances groups to run associated job templates on."
-msgstr "インスタンスグループフォールバックの防止: 有効にすると、インベントリーは、関連付けられたジョブテンプレートを実行する優先インスタンスグループのリストに組織インスタンスグループを追加することを防ぎます。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:43
-msgid "Prevent Instance Group Fallback: If enabled, the job template will prevent adding any inventory or organization instance groups to the list of preferred instances groups to run on."
-msgstr "インスタンスグループフォールバックの防止: 有効にすると、ジョブテンプレートは、実行する優先インスタンスグループのリストに組織インスタンスグループを追加することを防ぎます。"
-
-#: components/AdHocCommands/useAdHocPreviewStep.js:17
-#: components/LaunchPrompt/steps/usePreviewStep.js:23
-msgid "Preview"
-msgstr "プレビュー"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:103
-msgid "Private key passphrase"
-msgstr "秘密鍵のパスフレーズ"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:58
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:122
-#: screens/Template/shared/JobTemplateForm.js:551
-msgid "Privilege Escalation"
-msgstr "権限昇格"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:111
-msgid "Privilege escalation password"
-msgstr "権限昇格のパスワード"
-
-#: screens/Template/shared/JobTemplate.helptext.js:40
-msgid "Privilege escalation: If enabled, run this playbook as an administrator."
-msgstr "権限昇格: 有効な場合は、この Playbook を管理者として実行します。"
-
-#: components/JobList/JobListItem.js:239
-#: components/Lookup/ProjectLookup.js:104
-#: components/Lookup/ProjectLookup.js:109
-#: components/Lookup/ProjectLookup.js:166
-#: components/PromptDetail/PromptInventorySourceDetail.js:87
-#: components/PromptDetail/PromptJobTemplateDetail.js:133
-#: components/PromptDetail/PromptJobTemplateDetail.js:141
-#: components/TemplateList/TemplateListItem.js:300
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:216
-#: screens/Job/JobDetail/JobDetail.js:172
-#: screens/Job/JobDetail/JobDetail.js:198
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:229
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:239
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:38
-msgid "Project"
-msgstr "プロジェクト"
-
-#: components/PromptDetail/PromptProjectDetail.js:158
-#: screens/Project/ProjectDetail/ProjectDetail.js:281
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:61
-msgid "Project Base Path"
-msgstr "プロジェクトのベースパス"
-
-#: components/Workflow/WorkflowLegend.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:85
-msgid "Project Sync"
-msgstr "プロジェクトの同期"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:320
-#: screens/Project/ProjectList/ProjectListItem.js:229
-msgid "Project Sync Error"
-msgstr "プロジェクトの同期エラー"
-
-#: components/Workflow/WorkflowNodeHelp.js:67
-msgid "Project Update"
-msgstr "プロジェクトの更新"
-
-#: screens/Job/JobDetail/JobDetail.js:180
-msgid "Project Update Status"
-msgstr "プロジェクトステータスの更新"
-
-#: screens/Job/Job.helptext.js:22
-msgid "Project checkout results"
-msgstr "プロジェクトのチェックアウト結果"
-
-#: screens/Project/ProjectList/ProjectList.js:132
-msgid "Project copied successfully"
-msgstr "プロジェクトが正常にコピーされました"
-
-#: screens/Project/Project.js:136
-msgid "Project not found."
-msgstr "プロジェクトが見つかりません。"
-
-#: screens/Dashboard/Dashboard.js:109
-msgid "Project sync failures"
-msgstr "プロジェクトの同期の失敗"
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:146
-#: routeConfig.js:75
-#: screens/ActivityStream/ActivityStream.js:170
-#: screens/Dashboard/Dashboard.js:103
-#: screens/Project/ProjectList/ProjectList.js:180
-#: screens/Project/ProjectList/ProjectList.js:249
-#: screens/Project/Projects.js:12
-#: screens/Project/Projects.js:22
-#: util/getRelatedResourceDeleteDetails.js:60
-#: util/getRelatedResourceDeleteDetails.js:195
-#: util/getRelatedResourceDeleteDetails.js:225
-msgid "Projects"
-msgstr "プロジェクト"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:139
-msgid "Promote Child Groups and Hosts"
-msgstr "子グループおよびホストのプロモート"
-
-#: components/Schedule/shared/ScheduleForm.js:540
-#: components/Schedule/shared/ScheduleForm.js:543
-msgid "Prompt"
-msgstr "プロンプト"
-
-#: components/PromptDetail/PromptDetail.js:182
-msgid "Prompt Overrides"
-msgstr "プロンプトオーバーライド"
-
-#: components/CodeEditor/VariablesField.js:241
-#: components/FieldWithPrompt/FieldWithPrompt.js:46
-#: screens/Credential/CredentialDetail/CredentialDetail.js:175
-msgid "Prompt on launch"
-msgstr "起動プロンプト"
-
-#: components/Schedule/shared/SchedulePromptableFields.js:97
-msgid "Prompt | {0}"
-msgstr "プロンプト | {0}"
-
-#: components/PromptDetail/PromptDetail.js:180
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:418
-msgid "Prompted Values"
-msgstr "プロンプト値"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:6
-msgid ""
-"Provide a host pattern to further constrain\n"
-"the list of hosts that will be managed or affected by the\n"
-"playbook. Multiple patterns are allowed. Refer to Ansible\n"
-"documentation for more information and examples on patterns."
-msgstr "Playbook によって管理されるか、またはその影響を受けるホストの一覧をさらに制限するためのホストのパターンを指定します。\n"
-"複数のパターンが許可されます。\n"
-"パターンについての詳細およびサンプルについては、Ansible ドキュメントを参照してください。"
-
-#: screens/Job/Job.helptext.js:14
-#: screens/Template/shared/JobTemplate.helptext.js:15
-msgid "Provide a host pattern to further constrain the list of hosts that will be managed or affected by the playbook. Multiple patterns are allowed. Refer to Ansible documentation for more information and examples on patterns."
-msgstr "Playbook によって管理されるか、またはその影響を受けるホストの一覧をさらに制限するためのホストのパターンを指定します。複数のパターンが許可されます。パターンについての詳細およびサンプルについては、Ansible ドキュメントを参照してください。"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:179
-msgid "Provide a value for this field or select the Prompt on launch option."
-msgstr "このフィールドに値を入力するか、起動プロンプトを表示するオプションを選択します。"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:247
-msgid ""
-"Provide key/value pairs using either\n"
-"YAML or JSON."
-msgstr "YAML または JSON のいずれかを使用してキーと値のペアを提供します。"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:191
-msgid ""
-"Provide your Red Hat or Red Hat Satellite credentials\n"
-"below and you can choose from a list of your available subscriptions.\n"
-"The credentials you use will be stored for future use in\n"
-"retrieving renewal or expanded subscriptions."
-msgstr "以下に Red Hat または Red Hat Satellite の認証情報を指定して、利用可能なサブスクリプション一覧から選択してください。使用する認証情報は、将来、更新または拡張されたサブスクリプションを取得する際に使用するために保存されます。"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:83
-msgid "Provide your Red Hat or Red Hat Satellite credentials to enable Automation Analytics."
-msgstr "Red Hat または Red Hat Satellite の認証情報を提供して、自動化アナリティクスを有効にします。"
-
-#: components/StatusLabel/StatusLabel.js:59
-#: screens/TopologyView/Legend.js:150
-msgid "Provisioning"
-msgstr "プロビジョニング"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:162
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:302
-#: screens/Template/shared/JobTemplateForm.js:620
-msgid "Provisioning Callback URL"
-msgstr "プロビジョニングコールバック URL"
-
-#: screens/Template/shared/JobTemplateForm.js:615
-msgid "Provisioning Callback details"
-msgstr "プロビジョニングコールバックの詳細"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:63
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:127
-#: screens/Template/shared/JobTemplateForm.js:555
-#: screens/Template/shared/JobTemplateForm.js:558
-msgid "Provisioning Callbacks"
-msgstr "プロビジョニングコールバック"
-
-#: screens/Template/shared/JobTemplate.helptext.js:41
-msgid "Provisioning callbacks: Enables creation of a provisioning callback URL. Using the URL a host can contact Ansible AWX and request a configuration update using this job template."
-msgstr "プロビジョニングコールバック: プロビジョニングコールバック URL の作成を有効にします。ホストは、この URL を使用して Ansible AWX に接続でき、このジョブテンプレートを使用して設定の更新を要求できます。"
-
-#: components/StatusLabel/StatusLabel.js:62
-msgid "Provisioning fail"
-msgstr "プロビジョニング失敗"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:86
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:114
-msgid "Pull"
-msgstr "プル"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:163
-msgid "Question"
-msgstr "質問"
-
-#: screens/Setting/Settings.js:106
-msgid "RADIUS"
-msgstr "RADIUS"
-
-#: screens/Setting/SettingList.js:73
-msgid "RADIUS settings"
-msgstr "RADIUS 設定"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:244
-#: screens/InstanceGroup/Instances/InstanceListItem.js:161
-#: screens/Instances/InstanceDetail/InstanceDetail.js:287
-#: screens/Instances/InstanceList/InstanceListItem.js:171
-#: screens/TopologyView/Tooltip.js:307
-msgid "RAM {0}"
-msgstr "メモリー {0}"
-
-#: screens/User/shared/UserTokenForm.js:76
-msgid "Read"
-msgstr "読み込み"
-
-#: components/StatusLabel/StatusLabel.js:57
-#: screens/TopologyView/Legend.js:122
-msgid "Ready"
-msgstr "準備"
-
-#: screens/Dashboard/Dashboard.js:133
-msgid "Recent Jobs"
-msgstr "最近のジョブ"
-
-#: screens/Dashboard/Dashboard.js:131
-msgid "Recent Jobs list tab"
-msgstr "最近の求人リストタブ"
-
-#: screens/Dashboard/Dashboard.js:145
-msgid "Recent Templates"
-msgstr "最近のテンプレート"
-
-#: screens/Dashboard/Dashboard.js:143
-msgid "Recent Templates list tab"
-msgstr "最近のテンプレートリストタブ"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:188
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:112
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:38
-msgid "Recent jobs"
-msgstr "最近のジョブ"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:154
-msgid "Recipient List"
-msgstr "受信者リスト"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:86
-msgid "Recipient list"
-msgstr "受信者リスト"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:105
-msgid "Red Hat Ansible Automation Platform"
-msgstr "Red Hat Ansible Automation Platform"
-
-#: components/Lookup/ProjectLookup.js:139
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:92
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:161
-#: screens/Job/JobDetail/JobDetail.js:77
-#: screens/Project/ProjectList/ProjectList.js:201
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:100
-msgid "Red Hat Insights"
-msgstr "Red Hat Insights"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:102
-msgid "Red Hat Satellite 6"
-msgstr "Red Hat Satellite 6"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:104
-msgid "Red Hat Virtualization"
-msgstr "Red Hat Virtualization"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:117
-msgid "Red Hat subscription manifest"
-msgstr "Red Hat サブスクリプションマニュフェスト"
-
-#: components/About/About.js:36
-msgid "Red Hat, Inc."
-msgstr "Red Hat, Inc."
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:94
-#: screens/Application/shared/ApplicationForm.js:107
-msgid "Redirect URIs"
-msgstr "リダイレクト URI"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:259
-msgid "Redirecting to dashboard"
-msgstr "ダッシュボードへのリダイレクト"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:263
-msgid "Redirecting to subscription detail"
-msgstr "サブスクリプションの詳細へのリダイレクト"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:261
-#: screens/Template/shared/JobTemplate.helptext.js:55
-msgid "Refer to the"
-msgstr "参照:"
-
-#: screens/Job/Job.helptext.js:27
-#: screens/Template/shared/JobTemplate.helptext.js:50
-msgid "Refer to the Ansible documentation for details about the configuration file."
-msgstr "設定ファイルの詳細は、Ansible ドキュメントを参照してください。"
-
-#: screens/User/UserTokens/UserTokens.js:77
-msgid "Refresh Token"
-msgstr "トークンの更新"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:81
-msgid "Refresh Token Expiration"
-msgstr "トークンの有効期限の更新"
-
-#: screens/Project/ProjectList/ProjectListItem.js:132
-msgid "Refresh for revision"
-msgstr "リビジョンの更新"
-
-#: screens/Project/ProjectList/ProjectListItem.js:134
-msgid "Refresh project revision"
-msgstr "プロジェクトリビジョンの更新"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:116
-msgid "Regions"
-msgstr "リージョン"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:92
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:143
-msgid "Registry credential"
-msgstr "レジストリーの認証情報"
-
-#: screens/Inventory/shared/Inventory.helptext.js:156
-msgid "Regular expression where only matching host names will be imported. The filter is applied as a post-processing step after any inventory plugin filters are applied."
-msgstr "一致するホスト名のみがインポートされる正規表現。このフィルターは、インベントリープラグインフィルターが適用された後、後処理ステップとして適用されます。"
-
-#: screens/Inventory/Inventories.js:81
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:62
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:175
-msgid "Related Groups"
-msgstr "関連するグループ"
-
-#: components/Search/AdvancedSearch.js:287
-msgid "Related Keys"
-msgstr "関連するキー"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:169
-#: components/Schedule/ScheduleList/ScheduleListItem.js:105
-msgid "Related resource"
-msgstr "関連リソース"
-
-#: components/Search/RelatedLookupTypeInput.js:16
-#: components/Search/RelatedLookupTypeInput.js:24
-msgid "Related search type"
-msgstr "関連する検索タイプ"
-
-#: components/Search/RelatedLookupTypeInput.js:19
-msgid "Related search type typeahead"
-msgstr "関連する検索タイプの先行入力"
-
-#: components/JobList/JobListItem.js:146
-#: components/LaunchButton/ReLaunchDropDown.js:82
-#: screens/Job/JobDetail/JobDetail.js:580
-#: screens/Job/JobDetail/JobDetail.js:588
-#: screens/Job/JobOutput/shared/OutputToolbar.js:167
-msgid "Relaunch"
-msgstr "再起動"
-
-#: components/JobList/JobListItem.js:126
-#: screens/Job/JobOutput/shared/OutputToolbar.js:147
-msgid "Relaunch Job"
-msgstr "ジョブの再起動"
-
-#: components/LaunchButton/ReLaunchDropDown.js:41
-msgid "Relaunch all hosts"
-msgstr "すべてのホストの再起動"
-
-#: components/LaunchButton/ReLaunchDropDown.js:54
-msgid "Relaunch failed hosts"
-msgstr "失敗したホストの再起動"
-
-#: components/LaunchButton/ReLaunchDropDown.js:30
-#: components/LaunchButton/ReLaunchDropDown.js:35
-msgid "Relaunch on"
-msgstr "再起動時"
-
-#: components/JobList/JobListItem.js:125
-#: screens/Job/JobOutput/shared/OutputToolbar.js:146
-msgid "Relaunch using host parameters"
-msgstr "ホストパラメーターを使用した再起動"
-
-#: components/HealthCheckAlert/HealthCheckAlert.js:27
-msgid "Reload"
-msgstr "再読み込み"
-
-#: screens/Job/JobOutput/JobOutput.js:723
-msgid "Reload output"
-msgstr "出力のリロード"
-
-#: components/Lookup/ProjectLookup.js:138
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:91
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:160
-#: screens/Job/JobDetail/JobDetail.js:78
-#: screens/Project/ProjectList/ProjectList.js:200
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:99
-msgid "Remote Archive"
-msgstr "リモートアーカイブ"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:374
-#: screens/Instances/InstanceList/InstanceList.js:242
-msgid "Removal Error"
-msgstr "削除エラー"
-
-#: components/SelectedList/DraggableSelectedList.js:105
-#: screens/Instances/Shared/RemoveInstanceButton.js:75
-#: screens/Instances/Shared/RemoveInstanceButton.js:129
-#: screens/Instances/Shared/RemoveInstanceButton.js:143
-#: screens/Instances/Shared/RemoveInstanceButton.js:163
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:21
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:29
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:40
-msgid "Remove"
-msgstr "削除"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:36
-msgid "Remove All Nodes"
-msgstr "すべてのノードの削除"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:152
-msgid "Remove Instances"
-msgstr "インスタンスの削除"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:17
-msgid "Remove Link"
-msgstr "リンクの削除"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:28
-msgid "Remove Node {nodeName}"
-msgstr "ノード {nodeName} の削除"
-
-#: screens/Project/shared/Project.helptext.js:113
-msgid "Remove any local modifications prior to performing an update."
-msgstr "更新の実行前にローカルの変更を削除します。"
-
-#: components/Search/AdvancedSearch.js:206
-msgid "Remove the current search related to ansible facts to enable another search using this key."
-msgstr "Ansible ファクトに関連する現在の検索を削除して、このキーを使用して別の検索ができるようにします。"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:14
-msgid "Remove {0} Access"
-msgstr "{0} のアクセス権の削除"
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:45
-msgid "Remove {0} chip"
-msgstr "{0} チップの削除"
-
-#: screens/TopologyView/Legend.js:285
-msgid "Removing"
-msgstr "削除"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:48
-msgid "Removing this link will orphan the rest of the branch and cause it to be executed immediately on launch."
-msgstr "このリンクを削除すると、ブランチの残りの部分が孤立し、起動直後に実行します。"
-
-#: components/SelectedList/DraggableSelectedList.js:83
-msgid "Reorder"
-msgstr "並べ替え"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:349
-msgid "Repeat Frequency"
-msgstr "繰り返しの頻度"
-
-#: components/Schedule/shared/ScheduleFormFields.js:113
-msgid "Repeat frequency"
-msgstr "繰り返しの頻度"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:52
-msgid "Replace"
-msgstr "置換"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:60
-msgid "Replace field with new value"
-msgstr "フィールドを新しい値に置き換え"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:67
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:74
-msgid "Request subscription"
-msgstr "サブスクリプションの要求"
-
-#: screens/Template/Survey/SurveyListItem.js:51
-#: screens/Template/Survey/SurveyQuestionForm.js:188
-msgid "Required"
-msgstr "必須"
-
-#: screens/TopologyView/Header.js:87
-#: screens/TopologyView/Header.js:90
-msgid "Reset zoom"
-msgstr "ズームのリセット"
-
-#: components/Workflow/WorkflowNodeHelp.js:154
-#: components/Workflow/WorkflowNodeHelp.js:190
-#: screens/Team/TeamRoles/TeamRoleListItem.js:12
-#: screens/Team/TeamRoles/TeamRolesList.js:180
-msgid "Resource Name"
-msgstr "リソース名"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:246
-msgid "Resource deleted"
-msgstr "リソースが削除されました"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:170
-#: components/Schedule/ScheduleList/ScheduleListItem.js:111
-msgid "Resource type"
-msgstr "リソースタイプ"
-
-#: routeConfig.js:61
-#: screens/ActivityStream/ActivityStream.js:159
-msgid "Resources"
-msgstr "リソース"
-
-#: components/TemplateList/TemplateListItem.js:149
-msgid "Resources are missing from this template."
-msgstr "リソースがこのテンプレートにありません。"
-
-#: screens/Setting/shared/RevertButton.js:43
-msgid "Restore initial value."
-msgstr "初期値を復元します。"
-
-#: screens/Inventory/shared/Inventory.helptext.js:153
-msgid ""
-"Retrieve the enabled state from the given dict of host variables.\n"
-"The enabled variable may be specified using dot notation, e.g: 'foo.bar'"
-msgstr "ホスト変数の指定された辞書から有効な状態を取得します。有効な変数は、ドット表記を使用して指定できます (例: 「foo.bar」)。"
-
-#: components/JobCancelButton/JobCancelButton.js:96
-#: components/JobCancelButton/JobCancelButton.js:100
-#: components/JobList/JobListCancelButton.js:160
-#: components/JobList/JobListCancelButton.js:163
-#: screens/Job/JobOutput/JobOutput.js:819
-#: screens/Job/JobOutput/JobOutput.js:822
-msgid "Return"
-msgstr "戻る"
-
-#: screens/Job/JobOutput/EmptyOutput.js:40
-msgid "Return to"
-msgstr "以下に戻る"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:129
-msgid "Return to subscription management."
-msgstr "サブスクリプション管理へ戻る"
-
-#: components/Search/AdvancedSearch.js:171
-msgid "Returns results that have values other than this one as well as other filters."
-msgstr "このフィルターおよび他のフィルターのいずれにも該当しない結果を返します。"
-
-#: components/Search/AdvancedSearch.js:158
-msgid "Returns results that satisfy this one as well as other filters. This is the default set type if nothing is selected."
-msgstr "このフィルターおよび他のフィルターに該当する結果を返します。何も選択されていない場合は、これがデフォルトのセットタイプです。"
-
-#: components/Search/AdvancedSearch.js:164
-msgid "Returns results that satisfy this one or any other filters."
-msgstr "このフィルターまたは他のフィルターに該当する結果を返します。"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:52
-#: screens/Setting/shared/RevertButton.js:53
-#: screens/Setting/shared/RevertButton.js:62
-msgid "Revert"
-msgstr "戻す"
-
-#: screens/Setting/shared/RevertAllAlert.js:23
-msgid "Revert all"
-msgstr "すべて元に戻す"
-
-#: screens/Setting/shared/RevertFormActionGroup.js:21
-#: screens/Setting/shared/RevertFormActionGroup.js:27
-msgid "Revert all to default"
-msgstr "すべてをデフォルトに戻す"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:59
-msgid "Revert field to previously saved value"
-msgstr "フィールドを以前保存した値に戻す"
-
-#: screens/Setting/shared/RevertAllAlert.js:11
-msgid "Revert settings"
-msgstr "設定を元に戻す"
-
-#: screens/Setting/shared/RevertButton.js:42
-msgid "Revert to factory default."
-msgstr "工場出荷時のデフォルトに戻します。"
-
-#: screens/Job/JobDetail/JobDetail.js:314
-#: screens/Project/ProjectList/ProjectList.js:224
-#: screens/Project/ProjectList/ProjectListItem.js:221
-msgid "Revision"
-msgstr "リビジョン"
-
-#: screens/Project/shared/ProjectSubForms/SvnSubForm.js:22
-msgid "Revision #"
-msgstr "リビジョン #"
-
-#: components/NotificationList/NotificationList.js:199
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:140
-msgid "Rocket.Chat"
-msgstr "Rocket.Chat"
-
-#: screens/Team/TeamRoles/TeamRoleListItem.js:20
-#: screens/Team/TeamRoles/TeamRolesList.js:148
-#: screens/Team/TeamRoles/TeamRolesList.js:182
-#: screens/User/UserList/UserList.js:163
-#: screens/User/UserList/UserListItem.js:55
-#: screens/User/UserRoles/UserRolesList.js:146
-#: screens/User/UserRoles/UserRolesList.js:157
-#: screens/User/UserRoles/UserRolesListItem.js:26
-msgid "Role"
-msgstr "ロール"
-
-#: components/ResourceAccessList/ResourceAccessList.js:189
-#: components/ResourceAccessList/ResourceAccessList.js:202
-#: components/ResourceAccessList/ResourceAccessList.js:229
-#: components/ResourceAccessList/ResourceAccessListItem.js:69
-#: screens/Team/Team.js:59
-#: screens/Team/Teams.js:32
-#: screens/User/User.js:71
-#: screens/User/Users.js:31
-msgid "Roles"
-msgstr "ロール"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:134
-#: components/Workflow/WorkflowLinkHelp.js:39
-#: screens/Credential/shared/ExternalTestModal.js:89
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:49
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:23
-#: screens/Template/shared/JobTemplateForm.js:214
-msgid "Run"
-msgstr "実行"
-
-#: components/AdHocCommands/AdHocCommands.js:131
-#: components/AdHocCommands/AdHocCommands.js:135
-#: components/AdHocCommands/AdHocCommands.js:141
-#: components/AdHocCommands/AdHocCommands.js:145
-#: screens/Job/JobDetail/JobDetail.js:68
-msgid "Run Command"
-msgstr "コマンドの実行"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:277
-#: screens/Instances/InstanceDetail/InstanceDetail.js:335
-msgid "Run a health check on the instance"
-msgstr "インスタンスでの可用性チェック実行"
-
-#: components/AdHocCommands/AdHocCommands.js:125
-msgid "Run ad hoc command"
-msgstr "アドホックコマンドの実行"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:48
-msgid "Run command"
-msgstr "コマンドの実行"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:93
-#: components/Schedule/shared/FrequencyDetailSubform.js:223
-msgid "Run every"
-msgstr "実行する間隔"
-
-#: components/HealthCheckButton/HealthCheckButton.js:32
-#: components/HealthCheckButton/HealthCheckButton.js:45
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:286
-#: screens/Instances/InstanceDetail/InstanceDetail.js:344
-msgid "Run health check"
-msgstr "可用性チェックの実行"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:129
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:138
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:175
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:197
-#: components/Schedule/shared/FrequencyDetailSubform.js:344
-msgid "Run on"
-msgstr "実行:"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useRunTypeStep.js:32
-msgid "Run type"
-msgstr "実行タイプ"
-
-#: components/JobList/JobList.js:230
-#: components/StatusLabel/StatusLabel.js:48
-#: components/TemplateList/TemplateListItem.js:118
-#: components/Workflow/WorkflowNodeHelp.js:99
-msgid "Running"
-msgstr "実行中"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:127
-msgid "Running Handlers"
-msgstr "実行中のハンドラー"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:217
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:196
-#: screens/InstanceGroup/Instances/InstanceListItem.js:194
-#: screens/Instances/InstanceDetail/InstanceDetail.js:212
-#: screens/Instances/InstanceList/InstanceListItem.js:209
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:73
-msgid "Running Jobs"
-msgstr "実行中のジョブ"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:284
-#: screens/Instances/InstanceDetail/InstanceDetail.js:342
-msgid "Running health check"
-msgstr "実行中の可用性チェック"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:71
-msgid "Running jobs"
-msgstr "実行中のジョブ"
-
-#: screens/Setting/Settings.js:109
-msgid "SAML"
-msgstr "SAML"
-
-#: screens/Setting/SettingList.js:77
-msgid "SAML settings"
-msgstr "SAML 設定"
-
-#: screens/Dashboard/DashboardGraph.js:143
-msgid "SCM update"
-msgstr "SCM 更新"
-
-#: screens/User/UserDetail/UserDetail.js:58
-#: screens/User/UserList/UserListItem.js:49
-msgid "SOCIAL"
-msgstr "ソーシャル"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:95
-msgid "SSH password"
-msgstr "SSH パスワード"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:239
-msgid "SSL Connection"
-msgstr "SSL 接続"
-
-#: components/Workflow/WorkflowStartNode.js:60
-#: components/Workflow/workflowReducer.js:419
-msgid "START"
-msgstr "開始"
-
-#: components/Sparkline/Sparkline.js:31
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:160
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:39
-#: screens/Project/ProjectDetail/ProjectDetail.js:135
-#: screens/Project/ProjectList/ProjectListItem.js:73
-msgid "STATUS:"
-msgstr "ステータス:"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:321
-msgid "Sat"
-msgstr "土"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:82
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:188
-#: components/Schedule/shared/FrequencyDetailSubform.js:326
-#: components/Schedule/shared/FrequencyDetailSubform.js:458
-msgid "Saturday"
-msgstr "土曜"
-
-#: components/AddRole/AddResourceRole.js:247
-#: components/AssociateModal/AssociateModal.js:104
-#: components/AssociateModal/AssociateModal.js:110
-#: components/FormActionGroup/FormActionGroup.js:13
-#: components/FormActionGroup/FormActionGroup.js:19
-#: components/Schedule/shared/ScheduleForm.js:526
-#: components/Schedule/shared/ScheduleForm.js:532
-#: components/Schedule/shared/useSchedulePromptSteps.js:49
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:130
-#: screens/Credential/shared/CredentialForm.js:318
-#: screens/Credential/shared/CredentialForm.js:323
-#: screens/Setting/shared/RevertFormActionGroup.js:12
-#: screens/Setting/shared/RevertFormActionGroup.js:18
-#: screens/Template/Survey/SurveyReorderModal.js:205
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:35
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:131
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:158
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:162
-msgid "Save"
-msgstr "保存"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:33
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:36
-msgid "Save & Exit"
-msgstr "保存して終了"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:32
-msgid "Save link changes"
-msgstr "リンクの変更の保存"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:254
-msgid "Save successful!"
-msgstr "正常に保存が実行されました!"
-
-#: components/JobList/JobListItem.js:181
-#: components/JobList/JobListItem.js:187
-msgid "Schedule"
-msgstr "スケジュール"
-
-#: screens/Project/Projects.js:34
-#: screens/Template/Templates.js:54
-msgid "Schedule Details"
-msgstr "スケジュールの詳細"
-
-#: components/Schedule/shared/UnsupportedScheduleForm.js:15
-msgid "Schedule Rules"
-msgstr "スケジュールルール"
-
-#: screens/Inventory/Inventories.js:92
-msgid "Schedule details"
-msgstr "スケジュールの詳細"
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:49
-msgid "Schedule is active"
-msgstr "スケジュールはアクティブです"
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:49
-msgid "Schedule is inactive"
-msgstr "スケジュールは非アクティブです"
-
-#: components/Schedule/shared/ScheduleForm.js:394
-msgid "Schedule is missing rrule"
-msgstr "スケジュールにルールがありません"
-
-#: components/Schedule/Schedule.js:82
-msgid "Schedule not found."
-msgstr "スケジュールが見つかりません。"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:163
-#: components/Schedule/ScheduleList/ScheduleList.js:229
-#: routeConfig.js:44
-#: screens/ActivityStream/ActivityStream.js:153
-#: screens/Inventory/Inventories.js:89
-#: screens/Inventory/InventorySource/InventorySource.js:88
-#: screens/ManagementJob/ManagementJob.js:108
-#: screens/ManagementJob/ManagementJobs.js:23
-#: screens/Project/Project.js:120
-#: screens/Project/Projects.js:31
-#: screens/Schedule/AllSchedules.js:21
-#: screens/Template/Template.js:148
-#: screens/Template/Templates.js:51
-#: screens/Template/WorkflowJobTemplate.js:130
-msgid "Schedules"
-msgstr "スケジュール"
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:136
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:33
-#: screens/User/UserTokenDetail/UserTokenDetail.js:50
-#: screens/User/UserTokenList/UserTokenList.js:142
-#: screens/User/UserTokenList/UserTokenList.js:189
-#: screens/User/UserTokenList/UserTokenListItem.js:32
-#: screens/User/shared/UserTokenForm.js:68
-msgid "Scope"
-msgstr "範囲"
-
-#: screens/User/shared/User.helptext.js:5
-msgid "Scope for the token's access"
-msgstr "トークンのアクセスのスコープ"
-
-#: screens/Job/JobOutput/PageControls.js:79
-msgid "Scroll first"
-msgstr "最初にスクロール"
-
-#: screens/Job/JobOutput/PageControls.js:87
-msgid "Scroll last"
-msgstr "最後にスクロール"
-
-#: screens/Job/JobOutput/PageControls.js:71
-msgid "Scroll next"
-msgstr "次へスクロール"
-
-#: screens/Job/JobOutput/PageControls.js:63
-msgid "Scroll previous"
-msgstr "前にスクロール"
-
-#: components/Lookup/HostFilterLookup.js:290
-#: components/Lookup/Lookup.js:143
-msgid "Search"
-msgstr "検索"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:151
-msgid "Search is disabled while the job is running"
-msgstr "ジョブの実行中は検索が無効になっています"
-
-#: components/Search/AdvancedSearch.js:311
-#: components/Search/Search.js:259
-msgid "Search submit button"
-msgstr "検索送信ボタン"
-
-#: components/Search/Search.js:248
-msgid "Search text input"
-msgstr "テキスト入力の検索"
-
-#: components/Lookup/HostFilterLookup.js:398
-msgid "Searching by ansible_facts requires special syntax. Refer to the"
-msgstr "ansible_facts による検索には特別な構文が必要です。詳細は、以下を参照してください。"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:408
-msgid "Second"
-msgstr "第 2"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:103
-#: components/PromptDetail/PromptProjectDetail.js:153
-#: screens/Project/ProjectDetail/ProjectDetail.js:269
-msgid "Seconds"
-msgstr "秒"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:34
-msgid "See Django"
-msgstr "Django を参照"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:35
-#: components/LaunchPrompt/steps/PreviewStep.js:61
-msgid "See errors on the left"
-msgstr "左側のエラーを参照してください"
-
-#: components/JobList/JobListItem.js:84
-#: components/Lookup/HostFilterLookup.js:380
-#: components/Lookup/Lookup.js:200
-#: components/Pagination/Pagination.js:33
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:98
-msgid "Select"
-msgstr "選択"
-
-#: screens/Credential/shared/CredentialForm.js:129
-msgid "Select Credential Type"
-msgstr "認証情報タイプの選択"
-
-#: screens/Host/HostGroups/HostGroupsList.js:237
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:254
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:257
-msgid "Select Groups"
-msgstr "グループの選択"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:278
-msgid "Select Hosts"
-msgstr "ホストの選択"
-
-#: components/AnsibleSelect/AnsibleSelect.js:38
-msgid "Select Input"
-msgstr "入力の選択"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:295
-msgid "Select Instances"
-msgstr "インスタンスの選択"
-
-#: components/AssociateModal/AssociateModal.js:22
-msgid "Select Items"
-msgstr "アイテムの選択"
-
-#: components/AddRole/AddResourceRole.js:201
-msgid "Select Items from List"
-msgstr "リストからアイテムの選択"
-
-#: components/LabelSelect/LabelSelect.js:127
-msgid "Select Labels"
-msgstr "ラベルの選択"
-
-#: components/AddRole/AddResourceRole.js:236
-msgid "Select Roles to Apply"
-msgstr "適用するロールの選択"
-
-#: screens/User/UserTeams/UserTeamList.js:251
-msgid "Select Teams"
-msgstr "チームの選択"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:25
-msgid "Select a JSON formatted service account key to autopopulate the following fields."
-msgstr "JSON 形式のサービスアカウントキーを選択して、次のフィールドに自動入力します。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:122
-msgid "Select a Node Type"
-msgstr "ノードタイプの選択"
-
-#: components/AddRole/AddResourceRole.js:170
-msgid "Select a Resource Type"
-msgstr "リソースタイプの選択"
-
-#: screens/Job/Job.helptext.js:21
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:10
-msgid "Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch."
-msgstr "ワークフローにブランチを選択してください。このブランチは、ブランチを求めるジョブテンプレートノードすべてに適用されます。"
-
-#: screens/Credential/shared/CredentialForm.js:139
-msgid "Select a credential Type"
-msgstr "認証情報タイプの選択"
-
-#: components/JobList/JobListCancelButton.js:98
-msgid "Select a job to cancel"
-msgstr "取り消すジョブを選択してください"
-
-#: screens/Metrics/Metrics.js:211
-msgid "Select a metric"
-msgstr "メトリクスの選択"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:75
-msgid "Select a module"
-msgstr "モジュールの選択"
-
-#: screens/Template/shared/PlaybookSelect.js:60
-#: screens/Template/shared/PlaybookSelect.js:61
-msgid "Select a playbook"
-msgstr "Playbook の選択"
-
-#: screens/Template/shared/JobTemplateForm.js:323
-msgid "Select a project before editing the execution environment."
-msgstr "実行環境を編集する前にプロジェクトを選択してください。"
-
-#: screens/Template/Survey/SurveyToolbar.js:82
-msgid "Select a question to delete"
-msgstr "削除する質問の選択"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:160
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:103
-msgid "Select a row to delete"
-msgstr "削除する行を選択してください"
-
-#: components/DisassociateButton/DisassociateButton.js:75
-msgid "Select a row to disassociate"
-msgstr "関連付けを解除する行を選択してください"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:77
-msgid "Select a row to remove"
-msgstr "削除する行を選択"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:87
-msgid "Select a subscription"
-msgstr "サブスクリプションの選択"
-
-#: components/HostForm/HostForm.js:39
-#: components/Schedule/shared/FrequencyDetailSubform.js:71
-#: components/Schedule/shared/FrequencyDetailSubform.js:78
-#: components/Schedule/shared/FrequencyDetailSubform.js:88
-#: components/Schedule/shared/ScheduleFormFields.js:33
-#: components/Schedule/shared/ScheduleFormFields.js:37
-#: components/Schedule/shared/ScheduleFormFields.js:61
-#: screens/Credential/shared/CredentialForm.js:44
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:79
-#: screens/Inventory/shared/InventoryForm.js:72
-#: screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.js:46
-#: screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/GCESubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.js:46
-#: screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:97
-#: screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.js:44
-#: screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.js:46
-#: screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.js:46
-#: screens/Inventory/shared/SmartInventoryForm.js:67
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:24
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:61
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:436
-#: screens/Project/shared/ProjectForm.js:234
-#: screens/Project/shared/ProjectSubForms/InsightsSubForm.js:39
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:37
-#: screens/Team/shared/TeamForm.js:49
-#: screens/Template/Survey/SurveyQuestionForm.js:30
-#: screens/Template/shared/WorkflowJobTemplateForm.js:130
-#: screens/User/shared/UserForm.js:139
-#: util/validators.js:201
-msgid "Select a value for this field"
-msgstr "このフィールドの値の選択"
-
-#: screens/Template/shared/JobTemplate.helptext.js:23
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:20
-msgid "Select a webhook service."
-msgstr "Webhook サービスを選択します。"
-
-#: components/DataListToolbar/DataListToolbar.js:121
-#: components/DataListToolbar/DataListToolbar.js:125
-#: screens/Template/Survey/SurveyToolbar.js:49
-msgid "Select all"
-msgstr "すべて選択"
-
-#: screens/ActivityStream/ActivityStream.js:129
-msgid "Select an activity type"
-msgstr "アクティビティータイプの選択"
-
-#: screens/Metrics/Metrics.js:200
-msgid "Select an instance"
-msgstr "インスタンスの選択"
-
-#: screens/Metrics/Metrics.js:242
-msgid "Select an instance and a metric to show chart"
-msgstr "グラフを表示するインスタンスとメトリクスを選択します"
-
-#: components/HealthCheckButton/HealthCheckButton.js:19
-msgid "Select an instance to run a health check."
-msgstr "可用性チェックを実行するインスタンスを選択してください。"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:5
-msgid "Select an inventory for the workflow. This inventory is applied to all workflow nodes that prompt for an inventory."
-msgstr "ワークフローのインベントリーを選択してください。このインベントリーが、インベントリーをプロンプトするすべてのワークフローノードに適用されます。"
-
-#: components/LaunchPrompt/steps/SurveyStep.js:131
-msgid "Select an option"
-msgstr "オプションを選択してください"
-
-#: screens/Project/shared/ProjectForm.js:245
-msgid "Select an organization before editing the default execution environment."
-msgstr "デフォルトの実行環境を編集する前に、組織を選択してください。"
-
-#: screens/Job/Job.helptext.js:11
-#: screens/Template/shared/JobTemplate.helptext.js:12
-msgid "Select credentials for accessing the nodes this job will be ran against. You can only select one credential of each type. For machine credentials (SSH), checking \"Prompt on launch\" without selecting credentials will require you to select a machine credential at run time. If you select credentials and check \"Prompt on launch\", the selected credential(s) become the defaults that can be updated at run time."
-msgstr "このジョブが実行されるノードへのアクセスを許可する認証情報を選択します。各タイプにつき 1 つの認証情報のみを選択できます。マシンの認証情報 (SSH) については、認証情報を選択せずに「起動プロンプト」を選択すると、実行時にマシン認証情報を選択する必要があります。認証情報を選択し、「起動プロンプト」にチェックを付けている場合は、選択した認証情報が実行時に更新できるデフォルトになります。"
-
-#: components/Schedule/shared/ScheduleFormFields.js:120
-#: components/Schedule/shared/ScheduleFormFields.js:179
-msgid "Select frequency"
-msgstr "周波数の選択"
-
-#: screens/Project/shared/Project.helptext.js:18
-msgid ""
-"Select from the list of directories found in\n"
-"the Project Base Path. Together the base path and the playbook\n"
-"directory provide the full path used to locate playbooks."
-msgstr "プロジェクトのベースパスにあるデイレクトリーの一覧から選択します。ベースパスと Playbook ディレクトリーは、Playbook \n"
-"を見つけるために使用される完全なパスを提供します。"
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:98
-msgid "Select items from list"
-msgstr "リストから項目の選択"
-
-#: screens/Dashboard/DashboardGraph.js:124
-#: screens/Dashboard/DashboardGraph.js:125
-msgid "Select job type"
-msgstr "ジョブタイプの選択"
-
-#: components/LaunchPrompt/steps/SurveyStep.js:179
-msgid "Select option(s)"
-msgstr "オプションの選択"
-
-#: screens/Dashboard/DashboardGraph.js:95
-#: screens/Dashboard/DashboardGraph.js:96
-#: screens/Dashboard/DashboardGraph.js:97
-msgid "Select period"
-msgstr "期間の選択"
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:117
-msgid "Select roles to apply"
-msgstr "適用するロールの選択"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:128
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:129
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:130
-msgid "Select source path"
-msgstr "ソースパスの選択"
-
-#: screens/Dashboard/DashboardGraph.js:151
-#: screens/Dashboard/DashboardGraph.js:152
-msgid "Select status"
-msgstr "状態の選択"
-
-#: components/MultiSelect/TagMultiSelect.js:59
-msgid "Select tags"
-msgstr "タグの選択"
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:94
-msgid "Select the Execution Environment you want this command to run inside."
-msgstr "このコマンドを内部で実行する実行環境を選択します。"
-
-#: screens/Inventory/shared/SmartInventoryForm.js:87
-msgid "Select the Instance Groups for this Inventory to run on."
-msgstr "このインベントリーを実行するインスタンスグループを選択します。"
-
-#: screens/Job/Job.helptext.js:18
-#: screens/Template/shared/JobTemplate.helptext.js:20
-msgid "Select the Instance Groups for this Job Template to run on."
-msgstr "このジョブテンプレートが実行されるインスタンスグループを選択します。"
-
-#: screens/Organization/shared/OrganizationForm.js:83
-msgid "Select the Instance Groups for this Organization to run on."
-msgstr "この組織を実行するインスタンスグループを選択します。"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:104
-msgid "Select the credential you want to use when accessing the remote hosts to run the command. Choose the credential containing the username and SSH key or password that Ansible will need to log into the remote hosts."
-msgstr "そのコマンドを実行するためにリモートホストへのアクセス時に使用する認証情報を選択します。Ansible がリモートホストにログインするために必要なユーザー名および SSH キーまたはパスワードが含まれる認証情報を選択してください。"
-
-#: components/Lookup/InventoryLookup.js:123
-msgid ""
-"Select the inventory containing the hosts\n"
-"you want this job to manage."
-msgstr "このジョブで管理するホストが含まれるインベントリーを選択してください。"
-
-#: screens/Job/Job.helptext.js:6
-#: screens/Template/shared/JobTemplate.helptext.js:7
-msgid "Select the inventory containing the hosts you want this job to manage."
-msgstr "このジョブで管理するホストが含まれるインベントリーを選択してください。"
-
-#: components/HostForm/HostForm.js:32
-#: components/HostForm/HostForm.js:51
-msgid "Select the inventory that this host will belong to."
-msgstr "このホストが属するインベントリーを選択します。"
-
-#: screens/Job/Job.helptext.js:10
-#: screens/Template/shared/JobTemplate.helptext.js:11
-msgid "Select the playbook to be executed by this job."
-msgstr "このジョブで実行される Playbook を選択してください。"
-
-#: screens/Instances/Shared/InstanceForm.js:43
-msgid "Select the port that Receptor will listen on for incoming connections. Default is 27199."
-msgstr "Receptor が着信接続をリッスンするポートを選択します。デフォルトは 27199 です。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:8
-msgid "Select the project containing the playbook you want this job to execute."
-msgstr "このジョブで実行する Playbook が含まれるプロジェクトを選択してください。"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:79
-msgid "Select your Ansible Automation Platform subscription to use."
-msgstr "使用する Ansible Automation Platform サブスクリプションを選択します。"
-
-#: components/Lookup/Lookup.js:186
-msgid "Select {0}"
-msgstr "{0} の選択"
-
-#: components/AddRole/AddResourceRole.js:212
-#: components/AddRole/AddResourceRole.js:224
-#: components/AddRole/AddResourceRole.js:242
-#: components/AddRole/SelectRoleStep.js:27
-#: components/CheckboxListItem/CheckboxListItem.js:44
-#: components/Lookup/InstanceGroupsLookup.js:87
-#: components/OptionsList/OptionsList.js:74
-#: components/Schedule/ScheduleList/ScheduleListItem.js:84
-#: components/TemplateList/TemplateListItem.js:140
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:107
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:125
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:26
-#: screens/Application/ApplicationsList/ApplicationListItem.js:31
-#: screens/Credential/CredentialList/CredentialListItem.js:56
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:31
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:65
-#: screens/Host/HostGroups/HostGroupItem.js:26
-#: screens/Host/HostList/HostListItem.js:48
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:59
-#: screens/InstanceGroup/Instances/InstanceListItem.js:122
-#: screens/Instances/InstanceList/InstanceListItem.js:126
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:42
-#: screens/Inventory/InventoryList/InventoryListItem.js:90
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:37
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:110
-#: screens/Organization/OrganizationList/OrganizationListItem.js:43
-#: screens/Organization/shared/OrganizationForm.js:113
-#: screens/Project/ProjectList/ProjectListItem.js:177
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:242
-#: screens/Team/TeamList/TeamListItem.js:31
-#: screens/Template/Survey/SurveyListItem.js:34
-#: screens/User/UserTokenList/UserTokenListItem.js:19
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:41
-msgid "Selected"
-msgstr "選択済み"
-
-#: components/LaunchPrompt/steps/CredentialsStep.js:142
-#: components/LaunchPrompt/steps/CredentialsStep.js:147
-#: components/Lookup/MultiCredentialsLookup.js:162
-#: components/Lookup/MultiCredentialsLookup.js:167
-msgid "Selected Category"
-msgstr "選択したカテゴリー"
-
-#: components/Schedule/shared/ScheduleForm.js:446
-#: components/Schedule/shared/ScheduleForm.js:447
-msgid "Selected date range must have at least 1 schedule occurrence."
-msgstr "選択した日付範囲には、少なくとも 1 つのスケジュールオカレンスが必要です。"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:160
-msgid "Sender Email"
-msgstr "送信者のメール"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:96
-msgid "Sender e-mail"
-msgstr "送信者のメール"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:167
-#: components/Schedule/shared/FrequencyDetailSubform.js:138
-msgid "September"
-msgstr "9 月"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:24
-msgid "Service account JSON file"
-msgstr "サービスアカウント JSON ファイル"
-
-#: screens/Inventory/shared/InventorySourceForm.js:46
-#: screens/Project/shared/ProjectForm.js:112
-msgid "Set a value for this field"
-msgstr "このフィールドに値を設定します"
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:70
-msgid "Set how many days of data should be retained."
-msgstr "データの保持日数を設定します。"
-
-#: screens/Setting/SettingList.js:122
-msgid "Set preferences for data collection, logos, and logins"
-msgstr "データ収集、ロゴ、およびログイン情報の設定"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:131
-msgid "Set source path to"
-msgstr "ソースパスの設定:"
-
-#: components/InstanceToggle/InstanceToggle.js:48
-#: screens/Instances/Shared/InstanceForm.js:59
-msgid "Set the instance enabled or disabled. If disabled, jobs will not be assigned to this instance."
-msgstr "インスタンスを有効または無効に設定します。無効にした場合には、ジョブはこのインスタンスに割り当てられません。"
-
-#: screens/Application/shared/Application.helptext.js:5
-msgid "Set to Public or Confidential depending on how secure the client device is."
-msgstr "クライアントデバイスのセキュリティーレベルに応じて「公開」または「機密」に設定します。"
-
-#: components/Search/AdvancedSearch.js:149
-msgid "Set type"
-msgstr "タイプの設定"
-
-#: components/Search/AdvancedSearch.js:239
-msgid "Set type disabled for related search field fuzzy searches"
-msgstr "関連する検索フィールドのあいまい検索でタイプを無効に設定"
-
-#: components/Search/AdvancedSearch.js:140
-msgid "Set type select"
-msgstr "タイプ選択の設定"
-
-#: components/Search/AdvancedSearch.js:143
-msgid "Set type typeahead"
-msgstr "タイプ先行入力の設定"
-
-#: components/Workflow/WorkflowTools.js:154
-msgid "Set zoom to 100% and center graph"
-msgstr "ズームを 100% に設定し、グラフを中央に配置"
-
-#: screens/Instances/Shared/InstanceForm.js:35
-msgid "Sets the current life cycle stage of this instance. Default is \"installed.\""
-msgstr "このインスタンスの現在のライフサイクルステージを設定します。デフォルトは \"installed\" です。"
-
-#: screens/Instances/Shared/InstanceForm.js:51
-msgid "Sets the role that this instance will play within mesh topology. Default is \"execution.\""
-msgstr "このインスタンスがメッシュトポロジー内で果たすロールを設定します。デフォルトは \"execution\" です。"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:46
-msgid "Setting category"
-msgstr "カテゴリーの設定"
-
-#: screens/Setting/shared/RevertButton.js:46
-msgid "Setting matches factory default."
-msgstr "設定は工場出荷時のデフォルトと一致します。"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:49
-msgid "Setting name"
-msgstr "名前の設定"
-
-#: routeConfig.js:159
-#: routeConfig.js:163
-#: screens/ActivityStream/ActivityStream.js:220
-#: screens/ActivityStream/ActivityStream.js:222
-#: screens/Setting/Settings.js:43
-msgid "Settings"
-msgstr "設定"
-
-#: components/FormField/PasswordInput.js:35
-msgid "Show"
-msgstr "表示"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:182
-#: components/PromptDetail/PromptDetail.js:361
-#: components/PromptDetail/PromptJobTemplateDetail.js:156
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:488
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:283
-#: screens/Template/shared/JobTemplateForm.js:497
-msgid "Show Changes"
-msgstr "変更の表示"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:177
-#: components/AdHocCommands/AdHocDetailsStep.js:178
-msgid "Show changes"
-msgstr "変更の表示"
-
-#: components/LaunchPrompt/LaunchPrompt.js:135
-#: components/Schedule/shared/SchedulePromptableFields.js:102
-msgid "Show description"
-msgstr "説明の表示"
-
-#: components/ChipGroup/ChipGroup.js:12
-msgid "Show less"
-msgstr "簡易表示"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:126
-msgid "Show only root groups"
-msgstr "root グループのみを表示"
-
-#: screens/Login/Login.js:262
-msgid "Sign in with Azure AD"
-msgstr "Azure AD でサインイン"
-
-#: screens/Login/Login.js:276
-msgid "Sign in with GitHub"
-msgstr "GitHub でサインイン"
-
-#: screens/Login/Login.js:318
-msgid "Sign in with GitHub Enterprise"
-msgstr "GitHub Enterprise でサインイン"
-
-#: screens/Login/Login.js:333
-msgid "Sign in with GitHub Enterprise Organizations"
-msgstr "GitHub Enterprise 組織でサインイン"
-
-#: screens/Login/Login.js:349
-msgid "Sign in with GitHub Enterprise Teams"
-msgstr "GitHub Enterprise チームでサインイン"
-
-#: screens/Login/Login.js:290
-msgid "Sign in with GitHub Organizations"
-msgstr "GitHub 組織でサインイン"
-
-#: screens/Login/Login.js:304
-msgid "Sign in with GitHub Teams"
-msgstr "GitHub チームでサインイン"
-
-#: screens/Login/Login.js:364
-msgid "Sign in with Google"
-msgstr "Google でサインイン"
-
-#: screens/Login/Login.js:378
-msgid "Sign in with OIDC"
-msgstr "OIDC でサインイン"
-
-#: screens/Login/Login.js:397
-msgid "Sign in with SAML"
-msgstr "SAML でサインイン"
-
-#: screens/Login/Login.js:396
-msgid "Sign in with SAML {samlIDP}"
-msgstr "SAML {samlIDP} でサインイン"
-
-#: components/Search/Search.js:145
-#: components/Search/Search.js:146
-msgid "Simple key select"
-msgstr "簡易キー選択"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:106
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:107
-#: components/PromptDetail/PromptDetail.js:295
-#: components/PromptDetail/PromptJobTemplateDetail.js:267
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:595
-#: screens/Job/JobDetail/JobDetail.js:500
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:475
-#: screens/Template/shared/JobTemplateForm.js:533
-#: screens/Template/shared/WorkflowJobTemplateForm.js:230
-msgid "Skip Tags"
-msgstr "スキップタグ"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:93
-#: components/Schedule/shared/FrequencyDetailSubform.js:223
-msgid "Skip every"
-msgstr "すべてをスキップ"
-
-#: screens/Job/Job.helptext.js:20
-#: screens/Template/shared/JobTemplate.helptext.js:22
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:22
-msgid "Skip tags are useful when you have a large playbook, and you want to skip specific parts of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags."
-msgstr "スキップタグは、Playbook のサイズが大きい場合にプレイまたはタスクの特定の部分をスキップする必要がある場合に役立ちます。コンマを使用して複数のタグを区切ります。タグの使用方法の詳細については、Ansible Tower のドキュメントを参照してください。"
-
-#: screens/Job/JobOutput/shared/HostStatusBar.js:39
-msgid "Skipped"
-msgstr "スキップ済"
-
-#: components/StatusLabel/StatusLabel.js:50
-msgid "Skipped'"
-msgstr "スキップ済'"
-
-#: components/NotificationList/NotificationList.js:200
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:141
-msgid "Slack"
-msgstr "Slack"
-
-#: screens/Host/HostList/SmartInventoryButton.js:39
-#: screens/Host/HostList/SmartInventoryButton.js:48
-#: screens/Host/HostList/SmartInventoryButton.js:52
-#: screens/Inventory/InventoryList/InventoryList.js:187
-#: screens/Inventory/InventoryList/InventoryListItem.js:117
-msgid "Smart Inventory"
-msgstr "スマートインベントリー"
-
-#: screens/Inventory/SmartInventory.js:94
-msgid "Smart Inventory not found."
-msgstr "スマートインベントリーは見つかりません。"
-
-#: components/Lookup/HostFilterLookup.js:345
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:116
-msgid "Smart host filter"
-msgstr "スマートホストフィルター"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:105
-msgid "Smart inventory"
-msgstr "スマートインベントリー"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:32
-#: components/LaunchPrompt/steps/PreviewStep.js:58
-msgid "Some of the previous step(s) have errors"
-msgstr "前のステップのいくつかにエラーがあります"
-
-#: screens/Host/HostList/SmartInventoryButton.js:17
-msgid "Some search modifiers like not__ and __search are not supported in Smart Inventory host filters. Remove these to create a new Smart Inventory with this filter."
-msgstr "not__、__search などの一部の検索修飾子は、Smart Inventory ホストフィルターではサポートされていません。これらを削除し、このフィルターを使用して新しい Smart Inventory を作成します。"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.js:39
-msgid "Something went wrong with the request to test this credential and metadata."
-msgstr "この認証情報およびメタデータをテストする要求で問題が発生しました。"
-
-#: components/ContentError/ContentError.js:37
-msgid "Something went wrong..."
-msgstr "問題が発生しました..."
-
-#: components/Sort/Sort.js:139
-msgid "Sort"
-msgstr "並び替え"
-
-#: components/JobList/JobListItem.js:169
-#: components/PromptDetail/PromptInventorySourceDetail.js:84
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:199
-#: screens/Inventory/shared/InventorySourceForm.js:130
-#: screens/Job/JobDetail/JobDetail.js:172
-#: screens/Job/JobDetail/JobDetail.js:294
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:93
-msgid "Source"
-msgstr "ソース"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:44
-#: components/PromptDetail/PromptDetail.js:250
-#: components/PromptDetail/PromptJobTemplateDetail.js:147
-#: components/PromptDetail/PromptProjectDetail.js:106
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:89
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:467
-#: screens/Job/JobDetail/JobDetail.js:307
-#: screens/Project/ProjectDetail/ProjectDetail.js:230
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:248
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:133
-#: screens/Template/shared/JobTemplateForm.js:335
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:245
-msgid "Source Control Branch"
-msgstr "ソースコントロールブランチ"
-
-#: screens/Project/shared/ProjectSubForms/GitSubForm.js:29
-msgid "Source Control Branch/Tag/Commit"
-msgstr "ソースコントロールブランチ/タグ/コミット"
-
-#: components/PromptDetail/PromptProjectDetail.js:117
-#: screens/Project/ProjectDetail/ProjectDetail.js:256
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:56
-msgid "Source Control Credential"
-msgstr "ソースコントロール認証情報"
-
-#: components/PromptDetail/PromptProjectDetail.js:111
-#: screens/Project/ProjectDetail/ProjectDetail.js:235
-#: screens/Project/shared/ProjectSubForms/GitSubForm.js:32
-msgid "Source Control Refspec"
-msgstr "ソースコントロールの Refspec"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:195
-msgid "Source Control Revision"
-msgstr "ソースコントロールの改訂"
-
-#: components/PromptDetail/PromptProjectDetail.js:96
-#: screens/Job/JobDetail/JobDetail.js:273
-#: screens/Project/ProjectDetail/ProjectDetail.js:191
-#: screens/Project/shared/ProjectForm.js:259
-msgid "Source Control Type"
-msgstr "ソースコントロールのタイプ"
-
-#: components/Lookup/ProjectLookup.js:143
-#: components/PromptDetail/PromptProjectDetail.js:101
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:96
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:165
-#: screens/Project/ProjectDetail/ProjectDetail.js:225
-#: screens/Project/ProjectList/ProjectList.js:205
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:16
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:104
-msgid "Source Control URL"
-msgstr "ソースコントロールの URL"
-
-#: components/JobList/JobList.js:211
-#: components/JobList/JobListItem.js:42
-#: components/Schedule/ScheduleList/ScheduleListItem.js:38
-#: screens/Job/JobDetail/JobDetail.js:65
-msgid "Source Control Update"
-msgstr "ソースコントロールの更新"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:340
-msgid "Source Phone Number"
-msgstr "発信元の電話番号"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:176
-msgid "Source Variables"
-msgstr "ソース変数"
-
-#: components/JobList/JobListItem.js:213
-#: screens/Job/JobDetail/JobDetail.js:257
-msgid "Source Workflow Job"
-msgstr "ソースワークフローのジョブ"
-
-#: screens/Template/shared/WorkflowJobTemplateForm.js:177
-msgid "Source control branch"
-msgstr "ソースコントロールのブランチ"
-
-#: screens/Inventory/shared/InventorySourceForm.js:152
-msgid "Source details"
-msgstr "ソース詳細"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:403
-msgid "Source phone number"
-msgstr "発信元の電話番号"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:269
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:21
-msgid "Source variables"
-msgstr "ソース変数"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:97
-msgid "Sourced from a project"
-msgstr "プロジェクトから取得"
-
-#: screens/Inventory/Inventories.js:84
-#: screens/Inventory/Inventory.js:68
-msgid "Sources"
-msgstr "ソース"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:30
-msgid ""
-"Specify HTTP Headers in JSON format. Refer to\n"
-"the Ansible Controller documentation for example syntax."
-msgstr "JSON 形式で HTTP ヘッダーを指定します。構文のサンプルについては Ansible Controller ドキュメントを参照してください。"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:24
-msgid ""
-"Specify a notification color. Acceptable colors are hex\n"
-"color code (example: #3af or #789abc)."
-msgstr "通知の色を指定します。使用できる色は、\n"
-"16 進数の色コード (例: #3af または #789abc) です。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:26
-msgid "Specify the conditions under which this node should be executed"
-msgstr "このノードを実行する条件を指定"
-
-#: screens/Job/JobOutput/HostEventModal.js:173
-msgid "Standard Error"
-msgstr "標準エラー"
-
-#: screens/Job/JobOutput/HostEventModal.js:174
-msgid "Standard error tab"
-msgstr "標準エラータブ"
-
-#: components/NotificationList/NotificationListItem.js:57
-#: components/NotificationList/NotificationListItem.js:58
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:47
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:53
-msgid "Start"
-msgstr "開始"
-
-#: components/JobList/JobList.js:247
-#: components/JobList/JobListItem.js:99
-msgid "Start Time"
-msgstr "開始時刻"
-
-#: components/Schedule/shared/DateTimePicker.js:51
-msgid "Start date"
-msgstr "開始日"
-
-#: components/Schedule/shared/ScheduleFormFields.js:87
-msgid "Start date/time"
-msgstr "開始日時"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:465
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:105
-msgid "Start message"
-msgstr "開始メッセージ"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:474
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:114
-msgid "Start message body"
-msgstr "開始メッセージのボディー"
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:33
-msgid "Start sync process"
-msgstr "同期プロセスの開始"
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:37
-msgid "Start sync source"
-msgstr "同期ソースの開始"
-
-#: components/Schedule/shared/DateTimePicker.js:61
-msgid "Start time"
-msgstr "開始時刻"
-
-#: screens/Job/JobDetail/JobDetail.js:220
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:165
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:63
-msgid "Started"
-msgstr "開始"
-
-#: components/JobList/JobList.js:224
-#: components/JobList/JobList.js:245
-#: components/JobList/JobListItem.js:95
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:206
-#: screens/InstanceGroup/Instances/InstanceList.js:267
-#: screens/InstanceGroup/Instances/InstanceListItem.js:129
-#: screens/Instances/InstanceDetail/InstanceDetail.js:196
-#: screens/Instances/InstanceList/InstanceList.js:202
-#: screens/Instances/InstanceList/InstanceListItem.js:134
-#: screens/Instances/InstancePeers/InstancePeerList.js:97
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:43
-#: screens/Inventory/InventoryList/InventoryListItem.js:101
-#: screens/Inventory/InventorySources/InventorySourceList.js:212
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:87
-#: screens/Job/JobDetail/JobDetail.js:210
-#: screens/Job/JobOutput/HostEventModal.js:118
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:115
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:179
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:117
-#: screens/Project/ProjectList/ProjectList.js:222
-#: screens/Project/ProjectList/ProjectListItem.js:197
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:61
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:162
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:166
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:66
-msgid "Status"
-msgstr "Status"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:91
-msgid "Stdout"
-msgstr "Stdout"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:37
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:49
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:212
-msgid "Submit"
-msgstr "送信"
-
-#: screens/Project/shared/Project.helptext.js:118
-msgid ""
-"Submodules will track the latest commit on\n"
-"their master branch (or other branch specified in\n"
-".gitmodules). If no, submodules will be kept at\n"
-"the revision specified by the main project.\n"
-"This is equivalent to specifying the --remote\n"
-"flag to git submodule update."
-msgstr "サブモジュールは、マスターブランチ (または .gitmodules で指定された他のブランチ) の最新のコミットを追跡します。いいえの場合、サブモジュールはメインプロジェクトで指定されたリビジョンで保持されます。これは、git submodule update に --remote フラグを指定するのと同じです。"
-
-#: screens/Setting/SettingList.js:132
-#: screens/Setting/Settings.js:112
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:136
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:195
-msgid "Subscription"
-msgstr "サブスクリプション"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:41
-msgid "Subscription Details"
-msgstr "サブスクリプションの詳細"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:194
-msgid "Subscription Management"
-msgstr "Subscription Management"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:82
-msgid "Subscription manifest"
-msgstr "サブスクリプションマニュフェスト"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:84
-msgid "Subscription selection modal"
-msgstr "サブスクリプション選択モーダル"
-
-#: screens/Setting/SettingList.js:137
-msgid "Subscription settings"
-msgstr "サブスクリプション設定"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:131
-msgid "Subscription type"
-msgstr "サブスクリプションタイプ"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:142
-msgid "Subscriptions table"
-msgstr "サブスクリプションテーブル"
-
-#: components/Lookup/ProjectLookup.js:137
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:90
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:159
-#: screens/Job/JobDetail/JobDetail.js:76
-#: screens/Project/ProjectList/ProjectList.js:199
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:98
-msgid "Subversion"
-msgstr "Subversion"
-
-#: components/NotificationList/NotificationListItem.js:71
-#: components/NotificationList/NotificationListItem.js:72
-#: components/StatusLabel/StatusLabel.js:41
-msgid "Success"
-msgstr "成功"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:483
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:123
-msgid "Success message"
-msgstr "成功メッセージ"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:492
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:132
-msgid "Success message body"
-msgstr "成功メッセージボディー"
-
-#: components/JobList/JobList.js:231
-#: components/StatusLabel/StatusLabel.js:43
-#: components/Workflow/WorkflowNodeHelp.js:102
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:96
-#: screens/Dashboard/shared/ChartTooltip.js:59
-msgid "Successful"
-msgstr "成功"
-
-#: screens/Dashboard/DashboardGraph.js:166
-msgid "Successful jobs"
-msgstr "成功ジョブ"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:28
-msgid "Successfully Approved"
-msgstr "正常に承認されました"
-
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:25
-msgid "Successfully Denied"
-msgstr "正常に拒否されました"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:201
-#: screens/Project/ProjectList/ProjectListItem.js:97
-msgid "Successfully copied to clipboard!"
-msgstr "クリップボードへのコピーに成功しました!"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:255
-msgid "Sun"
-msgstr "日"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:83
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:182
-#: components/Schedule/shared/FrequencyDetailSubform.js:260
-#: components/Schedule/shared/FrequencyDetailSubform.js:428
-msgid "Sunday"
-msgstr "日曜"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:26
-#: screens/Template/Template.js:159
-#: screens/Template/Templates.js:48
-#: screens/Template/WorkflowJobTemplate.js:145
-msgid "Survey"
-msgstr "Survey"
-
-#: screens/Template/Survey/SurveyToolbar.js:105
-msgid "Survey Disabled"
-msgstr "Survey の無効化"
-
-#: screens/Template/Survey/SurveyToolbar.js:104
-msgid "Survey Enabled"
-msgstr "Survey の有効化"
-
-#: screens/Template/Survey/SurveyReorderModal.js:191
-msgid "Survey Question Order"
-msgstr "Survey 質問の順序"
-
-#: screens/Template/Survey/SurveyToolbar.js:102
-msgid "Survey Toggle"
-msgstr "Survey の切り替え"
-
-#: screens/Template/Survey/SurveyReorderModal.js:192
-msgid "Survey preview modal"
-msgstr "Survey プレビューモーダル"
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:120
-#: screens/Inventory/shared/InventorySourceSyncButton.js:41
-#: screens/Project/shared/ProjectSyncButton.js:41
-#: screens/Project/shared/ProjectSyncButton.js:53
-msgid "Sync"
-msgstr "同期"
-
-#: screens/Project/ProjectList/ProjectListItem.js:238
-#: screens/Project/shared/ProjectSyncButton.js:37
-#: screens/Project/shared/ProjectSyncButton.js:48
-msgid "Sync Project"
-msgstr "プロジェクトの同期"
-
-#: screens/Inventory/InventoryList/InventoryList.js:219
-msgid "Sync Status"
-msgstr "同期の状態"
-
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:19
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:29
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:32
-msgid "Sync all"
-msgstr "すべてを同期"
-
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:25
-msgid "Sync all sources"
-msgstr "すべてのソースの同期"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:236
-msgid "Sync error"
-msgstr "同期エラー"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:213
-#: screens/Project/ProjectList/ProjectListItem.js:109
-msgid "Sync for revision"
-msgstr "リビジョンの同期"
-
-#: screens/Project/ProjectList/ProjectListItem.js:122
-msgid "Syncing"
-msgstr "同期"
-
-#: screens/Setting/SettingList.js:102
-#: screens/User/UserRoles/UserRolesListItem.js:18
-msgid "System"
-msgstr "システム"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:128
-#: screens/User/UserDetail/UserDetail.js:47
-#: screens/User/UserList/UserListItem.js:19
-#: screens/User/UserRoles/UserRolesList.js:127
-#: screens/User/shared/UserForm.js:41
-msgid "System Administrator"
-msgstr "システム管理者"
-
-#: screens/User/UserDetail/UserDetail.js:49
-#: screens/User/UserList/UserListItem.js:21
-#: screens/User/shared/UserForm.js:35
-msgid "System Auditor"
-msgstr "システム監査者"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:128
-msgid "System Warning"
-msgstr "システム警告"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:131
-#: screens/User/UserRoles/UserRolesList.js:130
-msgid "System administrators have unrestricted access to all resources."
-msgstr "システム管理者は、すべてのリソースに無制限にアクセスできます。"
-
-#: screens/Setting/Settings.js:115
-msgid "TACACS+"
-msgstr "TACACS+"
-
-#: screens/Setting/SettingList.js:81
-msgid "TACACS+ settings"
-msgstr "TACACS+ 設定"
-
-#: screens/Dashboard/Dashboard.js:117
-#: screens/Job/JobOutput/HostEventModal.js:94
-msgid "Tabs"
-msgstr "タブ"
-
-#: screens/Job/Job.helptext.js:19
-#: screens/Template/shared/JobTemplate.helptext.js:21
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:21
-msgid "Tags are useful when you have a large playbook, and you want to run a specific part of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags."
-msgstr "タグは、Playbook のサイズが大きい場合にプレイまたはタスクの特定の部分を実行する必要がある場合に役立ちます。コンマを使用して複数のタグを区切ります。タグの使用方法の詳細については、Ansible Tower のドキュメントを参照してください。"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:203
-msgid "Tags for the Annotation"
-msgstr "アノテーションのタグ"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:179
-msgid "Tags for the annotation (optional)"
-msgstr "アノテーションのタグ (オプション)"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:248
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:298
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:366
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:252
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:329
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:453
-msgid "Target URL"
-msgstr "ターゲット URL"
-
-#: screens/Job/JobOutput/HostEventModal.js:123
-msgid "Task"
-msgstr "タスク"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:90
-msgid "Task Count"
-msgstr "タスク数"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:129
-msgid "Task Started"
-msgstr "タスクの開始"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:91
-msgid "Tasks"
-msgstr "タスク"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:13
-msgid "Team"
-msgstr "チーム"
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:85
-#: screens/Team/TeamRoles/TeamRolesList.js:144
-msgid "Team Roles"
-msgstr "チームロール"
-
-#: screens/Team/Team.js:75
-msgid "Team not found."
-msgstr "チームが見つかりません。"
-
-#: components/AddRole/AddResourceRole.js:188
-#: components/AddRole/AddResourceRole.js:189
-#: routeConfig.js:106
-#: screens/ActivityStream/ActivityStream.js:187
-#: screens/Organization/Organization.js:125
-#: screens/Organization/OrganizationList/OrganizationList.js:145
-#: screens/Organization/OrganizationList/OrganizationListItem.js:66
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:64
-#: screens/Organization/Organizations.js:32
-#: screens/Team/TeamList/TeamList.js:112
-#: screens/Team/TeamList/TeamList.js:166
-#: screens/Team/Teams.js:15
-#: screens/Team/Teams.js:25
-#: screens/User/User.js:70
-#: screens/User/UserTeams/UserTeamList.js:175
-#: screens/User/UserTeams/UserTeamList.js:246
-#: screens/User/Users.js:32
-#: util/getRelatedResourceDeleteDetails.js:174
-msgid "Teams"
-msgstr "チーム"
-
-#: screens/Setting/Jobs/JobsEdit/JobsEdit.js:130
-msgid "Template"
-msgstr "テンプレート"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:115
-#: components/TemplateList/TemplateList.js:133
-msgid "Template copied successfully"
-msgstr "テンプレートが正常にコピーされました"
-
-#: screens/Template/Template.js:175
-#: screens/Template/WorkflowJobTemplate.js:175
-msgid "Template not found."
-msgstr "テンプレートが見つかりません。"
-
-#: components/TemplateList/TemplateList.js:200
-#: components/TemplateList/TemplateList.js:263
-#: routeConfig.js:65
-#: screens/ActivityStream/ActivityStream.js:164
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:70
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:83
-#: screens/Template/Templates.js:17
-#: util/getRelatedResourceDeleteDetails.js:218
-#: util/getRelatedResourceDeleteDetails.js:275
-msgid "Templates"
-msgstr "テンプレート"
-
-#: screens/Credential/shared/CredentialForm.js:331
-#: screens/Credential/shared/CredentialForm.js:337
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:80
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:426
-msgid "Test"
-msgstr "テスト"
-
-#: screens/Credential/shared/ExternalTestModal.js:77
-msgid "Test External Credential"
-msgstr "外部認証情報のテスト"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:128
-msgid "Test Notification"
-msgstr "テスト通知"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:125
-msgid "Test notification"
-msgstr "テスト通知"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.js:44
-msgid "Test passed"
-msgstr "テストに成功"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:80
-#: screens/Template/Survey/SurveyReorderModal.js:181
-msgid "Text"
-msgstr "テキスト"
-
-#: screens/Template/Survey/SurveyReorderModal.js:135
-msgid "Text Area"
-msgstr "テキストエリア"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:81
-msgid "Textarea"
-msgstr "Textarea"
-
-#: components/Lookup/Lookup.js:63
-msgid "That value was not found. Please enter or select a valid value."
-msgstr "値が見つかりませんでした。有効な値を入力または選択してください。"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:398
-msgid "The"
-msgstr "その"
-
-#: screens/Application/shared/Application.helptext.js:4
-msgid "The Grant type the user must use to acquire tokens for this application"
-msgstr "ユーザーがこのアプリケーションのトークンを取得するために使用する必要のある付与タイプです。"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:128
-msgid "The Instance Groups for this Organization to run on."
-msgstr "この組織を実行するインスタンスグループ。"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:219
-msgid "The Instance Groups to which this instance belongs."
-msgstr "このインスタンスが属するインスタンスグループ。"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:6
-msgid ""
-"The amount of time (in seconds) before the email\n"
-"notification stops trying to reach the host and times out. Ranges\n"
-"from 1 to 120 seconds."
-msgstr "メール通知が、ホストへの到達を試行するのをやめてタイムアウトするまでの時間 (秒単位)。\n"
-"範囲は 1 から 120 秒です。"
-
-#: screens/Job/Job.helptext.js:17
-#: screens/Template/shared/JobTemplate.helptext.js:18
-msgid "The amount of time (in seconds) to run before the job is canceled. Defaults to 0 for no job timeout."
-msgstr "ジョブが取り消される前の実行時間 (秒数)。デフォルト値は 0 で、ジョブのタイムアウトがありません。"
-
-#: screens/User/shared/User.helptext.js:4
-msgid "The application that this token belongs to, or leave this field empty to create a Personal Access Token."
-msgstr "このトークンが属するアプリケーション。あるいは、このフィールドを空欄のままにしてパーソナルアクセストークンを作成します。"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:9
-msgid ""
-"The base URL of the Grafana server - the\n"
-"/api/annotations endpoint will be added automatically to the base\n"
-"Grafana URL."
-msgstr "Grafana サーバーのベース URL。/api/annotations エンドポイントは、ベース Grafana URL に自動的に\n"
-"追加されます。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:9
-msgid "The container image to be used for execution."
-msgstr "実行に使用するコンテナーイメージ。"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:109
-msgid ""
-"The execution environment that will be used for jobs\n"
-"inside of this organization. This will be used a fallback when\n"
-"an execution environment has not been explicitly assigned at the\n"
-"project, job template or workflow level."
-msgstr "この組織内のジョブに使用される実行環境。これは、実行環境がプロジェクト、\n"
-"ジョブテンプレート、またはワークフローレベルで\n"
-"明示的に割り当てられていない場合に\n"
-"フォールバックとして使用されます。"
-
-#: screens/Organization/shared/OrganizationForm.js:93
-msgid "The execution environment that will be used for jobs inside of this organization. This will be used a fallback when an execution environment has not been explicitly assigned at the project, job template or workflow level."
-msgstr "この組織内のジョブに使用される実行環境。これは、実行環境がプロジェクト、ジョブテンプレート、またはワークフローレベルで明示的に割り当てられていない場合にフォールバックとして使用されます。"
-
-#: screens/Project/shared/Project.helptext.js:5
-msgid "The execution environment that will be used for jobs that use this project. This will be used as fallback when an execution environment has not been explicitly assigned at the job template or workflow level."
-msgstr "このプロジェクトを使用するジョブで使用される実行環境。これは、実行環境がジョブテンプレートまたはワークフローレベルで明示的に割り当てられていない場合のフォールバックとして使用されます。"
-
-#: screens/Job/Job.helptext.js:9
-#: screens/Template/shared/JobTemplate.helptext.js:10
-msgid "The execution environment that will be used when launching this job template. The resolved execution environment can be overridden by explicitly assigning a different one to this job template."
-msgstr "このジョブテンプレートを起動するときに使用される実行環境。解決された実行環境は、このジョブテンプレートに別の環境を明示的に割り当てることで上書きできます。"
-
-#: screens/Project/shared/Project.helptext.js:93
-msgid ""
-"The first fetches all references. The second\n"
-"fetches the Github pull request number 62, in this example\n"
-"the branch needs to be \"pull/62/head\"."
-msgstr "1 番目はすべての参照を取得します。2 番目は Github のプル要求の 62 番を取得します。\n"
-"この例では、ブランチは \"pull/62/head\" でなければなりません。"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:7
-msgid "The full image location, including the container registry, image name, and version tag."
-msgstr "コンテナーレジストリー、イメージ名、およびバージョンタグを含む完全なイメージの場所。"
-
-#: screens/Inventory/shared/Inventory.helptext.js:191
-msgid ""
-"The inventory file\n"
-"to be synced by this source. You can select from\n"
-"the dropdown or enter a file within the input."
-msgstr "このソースで同期されるインベントリーファイル。ドロップダウンから選択するか、入力にファイルを指定できます。"
-
-#: screens/Host/HostDetail/HostDetail.js:79
-msgid "The inventory that this host belongs to."
-msgstr "このホストが属するインベントリー。"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:141
-msgid "The last {dayOfWeek}"
-msgstr "最後の {dayOfWeek}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:200
-msgid "The last {weekday} of {month}"
-msgstr "{month} の 最後の {weekday}"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:100
-msgid ""
-"The maximum number of hosts allowed to be managed by\n"
-"this organization. Value defaults to 0 which means no limit.\n"
-"Refer to the Ansible documentation for more details."
-msgstr "この組織で管理可能な最大ホスト数。\n"
-"デフォルト値は 0 で、管理可能な数に制限がありません。\n"
-"詳細は、Ansible のドキュメントを参照してください。"
-
-#: screens/Organization/shared/OrganizationForm.js:72
-msgid ""
-"The maximum number of hosts allowed to be managed by this organization.\n"
-"Value defaults to 0 which means no limit. Refer to the Ansible\n"
-"documentation for more details."
-msgstr "この組織で管理可能な最大ホスト数。デフォルト値は 0 で、管理可能な数に制限がありません。詳細は、Ansible ドキュメントを参照してください。"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:26
-msgid ""
-"The number associated with the \"Messaging\n"
-"Service\" in Twilio with the format +18005550199."
-msgstr "Twilio の \"メッセージングサービス\" に関連付けられた番号 (形式: +18005550199)。"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:69
-msgid "The number of hosts you have automated against is below your subscription count."
-msgstr "自動化したホストの数がサブスクリプション数を下回っています。"
-
-#: screens/Job/Job.helptext.js:25
-#: screens/Template/shared/JobTemplate.helptext.js:48
-msgid "The number of parallel or simultaneous processes to use while executing the playbook. An empty value, or a value less than 1 will use the Ansible default which is usually 5. The default number of forks can be overwritten with a change to"
-msgstr "Playbook の実行中に使用する並列または同時プロセスの数。値が空白または 1 未満の場合は、Ansible のデフォルト値 (通常は 5) を使用します。フォークのデフォルトの数は、以下を変更して上書きできます。"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:164
-msgid "The number of parallel or simultaneous processes to use while executing the playbook. Inputting no value will use the default value from the ansible configuration file. You can find more information"
-msgstr "Playbook の実行中に使用する並列または同時プロセスの数。いずれの値も入力しないと、Ansible 設定ファイルのデフォルト値が使用されます。より多くの情報を確認できます。"
-
-#: components/ContentError/ContentError.js:41
-#: screens/Job/Job.js:161
-msgid "The page you requested could not be found."
-msgstr "要求したページが見つかりませんでした。"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:144
-msgid "The pattern used to target hosts in the inventory. Leaving the field blank, all, and * will all target all hosts in the inventory. You can find more information about Ansible's host patterns"
-msgstr "インベントリー内のホストをターゲットにするために使用されるパターン。フィールドを空白のままにすると、all、および * はすべて、インベントリー内のすべてのホストを対象とします。Ansible のホストパターンに関する詳細情報を確認できます。"
-
-#: screens/Job/Job.helptext.js:7
-msgid "The project containing the playbook this job will execute."
-msgstr "このジョブが実行する Playbook を含むプロジェクト。"
-
-#: screens/Job/Job.helptext.js:8
-msgid "The project from which this inventory update is sourced."
-msgstr "このインベントリーの更新元のプロジェクト。"
-
-#: screens/Project/ProjectList/ProjectListItem.js:120
-msgid "The project is currently syncing and the revision will be available after the sync is complete."
-msgstr "プロジェクトは現在同期中であり、同期が完了するとリビジョンが利用可能になります。"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:211
-#: screens/Project/ProjectList/ProjectListItem.js:107
-msgid "The project must be synced before a revision is available."
-msgstr "リビジョンが利用可能になる前に、プロジェクトを同期する必要があります。"
-
-#: screens/Project/ProjectList/ProjectListItem.js:130
-msgid "The project revision is currently out of date. Please refresh to fetch the most recent revision."
-msgstr "プロジェクトのリビジョンが現在古くなっています。更新して最新のリビジョンを取得してください。"
-
-#: components/Workflow/WorkflowNodeHelp.js:138
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:131
-msgid "The resource associated with this node has been deleted."
-msgstr "このノードに関連付けられているリソースは、削除されました。"
-
-#: screens/Job/JobOutput/EmptyOutput.js:31
-msgid "The search filter did not produce any results…"
-msgstr "検索フィルターで結果が生成されませんでした…"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:180
-msgid ""
-"The suggested format for variable names is lowercase and\n"
-"underscore-separated (for example, foo_bar, user_id, host_name,\n"
-"etc.). Variable names with spaces are not allowed."
-msgstr "アンダースコアで区切った小文字のみを使用する変数名が推奨の形式となっています (foo_bar、user_id、host_name など)。変数名にスペースを含めることはできません。"
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:50
-msgid ""
-"There are no available playbook directories in {project_base_dir}.\n"
-"Either that directory is empty, or all of the contents are already\n"
-"assigned to other projects. Create a new directory there and make\n"
-"sure the playbook files can be read by the \"awx\" system user,\n"
-"or have {brandName} directly retrieve your playbooks from\n"
-"source control using the Source Control Type option above."
-msgstr "{project_base_dir} に利用可能な Playbook ディレクトリーはありません。そのディレクトリーが空であるか、すべてのコンテンツがすでに他のプロジェクトに割り当てられています。そこに新しいディレクトリーを作成し、「awx」システムユーザーが Playbook ファイルを読み取れるか、{brandName} が、上記のソースコントロールタイプオプションを使用して、ソースコントロールから Playbook を直接取得できるようにしてください。"
-
-#: screens/Template/Survey/MultipleChoiceField.js:34
-msgid "There must be a value in at least one input"
-msgstr "少なくとも 1 つの入力に値が必要です"
-
-#: screens/Login/Login.js:155
-msgid "There was a problem logging in. Please try again."
-msgstr "ログインに問題がありました。もう一度やり直してください。"
-
-#: components/ContentError/ContentError.js:42
-msgid "There was an error loading this content. Please reload the page."
-msgstr "このコンテンツの読み込み中にエラーが発生しました。ページを再読み込みしてください。"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:56
-msgid "There was an error parsing the file. Please check the file formatting and try again."
-msgstr "ファイルの解析中にエラーが発生しました。ファイルのフォーマットを確認して、再試行してください。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:713
-msgid "There was an error saving the workflow."
-msgstr "ワークフローの保存中にエラーが発生しました。"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:69
-msgid "These are the modules that {brandName} supports running commands against."
-msgstr "これらは {brandName} がコマンドの実行をサポートするモジュールです。"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:129
-msgid "These are the verbosity levels for standard out of the command run that are supported."
-msgstr "これらは、サポートされているコマンド実行の標準の詳細レベルです。"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:122
-#: screens/Job/Job.helptext.js:43
-msgid "These arguments are used with the specified module."
-msgstr "これらの引数は、指定されたモジュールで使用されます。"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:111
-msgid "These arguments are used with the specified module. You can find information about {0} by clicking"
-msgstr "これらの引数は、指定されたモジュールで使用されます。クリックすると {0} の情報を表示できます。"
-
-#: screens/Job/Job.helptext.js:33
-msgid "These arguments are used with the specified module. You can find information about {moduleName} by clicking"
-msgstr "これらの引数は、指定されたモジュールで使用されます。クリックすると {moduleName} の情報を表示できます。"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:410
-msgid "Third"
-msgstr "第 3"
-
-#: screens/Template/shared/JobTemplateForm.js:157
-msgid "This Project needs to be updated"
-msgstr "このプロジェクトは更新する必要があります"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:286
-#: screens/Template/Survey/SurveyList.js:82
-msgid "This action will delete the following:"
-msgstr "このアクションにより、以下が削除されます。"
-
-#: screens/User/UserTeams/UserTeamList.js:217
-msgid "This action will disassociate all roles for this user from the selected teams."
-msgstr "このアクションにより、このユーザーのすべてのロールと選択したチームの関連付けが解除されます。"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:236
-#: screens/User/UserRoles/UserRolesList.js:232
-msgid "This action will disassociate the following role from {0}:"
-msgstr "このアクションにより、{0} から次のロールの関連付けが解除されます:"
-
-#: components/DisassociateButton/DisassociateButton.js:148
-msgid "This action will disassociate the following:"
-msgstr "このアクションにより、以下の関連付けが解除されます。"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:178
-msgid "This action will remove the following instances:"
-msgstr "この操作により、次のインスタンスが削除されます。"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:113
-msgid "This container group is currently being by other resources. Are you sure you want to delete it?"
-msgstr "このコンテナーグループは、現在他のリソースで使用されています。削除してもよろしいですか?"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:304
-msgid "This credential is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "この認証情報は、現在他のリソースで使用されています。削除してもよろしいですか?"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:121
-msgid "This credential type is currently being used by some credentials and cannot be deleted"
-msgstr "この認証タイプは、現在一部の認証情報で使用されているため、削除できません"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:74
-msgid ""
-"This data is used to enhance\n"
-"future releases of the Software and to provide\n"
-"Automation Analytics."
-msgstr "このデータは、ソフトウェアの今後のリリースを強化し、自動化アナリティクスを提供するために使用されます。"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:62
-msgid ""
-"This data is used to enhance\n"
-"future releases of the Tower Software and help\n"
-"streamline customer experience and success."
-msgstr "このデータは、Tower ソフトウェアの今後のリリースを強化し、顧客へのサービスを効率化し、成功に導くサポートをします。"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:132
-msgid "This execution environment is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "この実行環境は、現在他のリソースで使用されています。削除してもよろしいですか?"
-
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:74
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:79
-msgid "This feature is deprecated and will be removed in a future release."
-msgstr "この機能は非推奨となり、今後のリリースで削除されます。"
-
-#: screens/Inventory/shared/Inventory.helptext.js:155
-msgid "This field is ignored unless an Enabled Variable is set. If the enabled variable matches this value, the host will be enabled on import."
-msgstr "有効な変数が設定されていない限り、このフィールドは無視されます。有効な変数がこの値と一致すると、インポート時にこのホストが有効になります。"
-
-#: components/AdHocCommands/useAdHocCredentialPasswordStep.js:44
-#: components/LaunchPrompt/steps/useCredentialPasswordsStep.js:50
-msgid "This field may not be blank"
-msgstr "このフィールドは空白ではありません"
-
-#: util/validators.js:127
-msgid "This field must be a number"
-msgstr "このフィールドは数字でなければなりません"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:107
-msgid "This field must be a number and have a value between {0} and {1}"
-msgstr "このフィールドは、{0} から {1} の間の値である必要があります"
-
-#: util/validators.js:67
-msgid "This field must be a number and have a value between {min} and {max}"
-msgstr "このフィールドは、{min} から {max} の間の値である必要があります"
-
-#: util/validators.js:64
-msgid "This field must be a number and have a value greater than {min}"
-msgstr "このフィールドの値は、{min} より大きい数字である必要があります"
-
-#: util/validators.js:61
-msgid "This field must be a number and have a value less than {max}"
-msgstr "このフィールドの値は、{max} より小さい値である必要があります"
-
-#: util/validators.js:184
-msgid "This field must be a regular expression"
-msgstr "このフィールドは正規表現である必要があります"
-
-#: util/validators.js:111
-#: util/validators.js:194
-msgid "This field must be an integer"
-msgstr "このフィールドは整数でなければなりません。"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:99
-msgid "This field must be at least {0} characters"
-msgstr "このフィールドは、{0} 文字以上にする必要があります"
-
-#: util/validators.js:52
-msgid "This field must be at least {min} characters"
-msgstr "このフィールドは、{min} 文字以上にする必要があります"
-
-#: util/validators.js:197
-msgid "This field must be greater than 0"
-msgstr "このフィールドは 0 より大きくなければなりません"
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:52
-#: components/LaunchPrompt/steps/useSurveyStep.js:111
-#: screens/Template/shared/JobTemplateForm.js:154
-#: screens/User/shared/UserForm.js:92
-#: screens/User/shared/UserForm.js:103
-#: util/validators.js:5
-#: util/validators.js:76
-msgid "This field must not be blank"
-msgstr "このフィールドを空欄にすることはできません"
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:46
-msgid "This field must not be blank."
-msgstr "このフィールドを空欄にすることはできません。"
-
-#: util/validators.js:101
-msgid "This field must not contain spaces"
-msgstr "このフィールドにスペースを含めることはできません"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:102
-msgid "This field must not exceed {0} characters"
-msgstr "このフィールドは、{0} 文字内にする必要があります"
-
-#: util/validators.js:43
-msgid "This field must not exceed {max} characters"
-msgstr "このフィールドは、{max} 文字内にする必要があります"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:51
-msgid "This field will be retrieved from an external secret management system using the specified credential."
-msgstr "このフィールドは、指定された認証情報を使用して外部のシークレット管理システムから取得されます。"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:82
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:89
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:104
-msgid "This has already been acted on"
-msgstr "これはすでに処理されています"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:125
-msgid "This instance group is currently being by other resources. Are you sure you want to delete it?"
-msgstr "このインスタンスグループは、現在他のリソースで使用されています。削除してもよろしいですか?"
-
-#: components/LaunchPrompt/steps/useInventoryStep.js:59
-msgid "This inventory is applied to all workflow nodes within this workflow ({0}) that prompt for an inventory."
-msgstr "このインベントリーが、このワークフロー ({0}) 内の、インベントリーをプロンプトするすべてのワークフローノードに適用されます。"
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:199
-msgid "This inventory is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "このインベントリーは、現在他のリソースで使用されています。削除してもよろしいですか?"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:313
-msgid "This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?"
-msgstr "このインベントリーソースは、現在それに依存している他のリソースで使用されています。削除してもよろしいですか?"
-
-#: screens/Application/Applications.js:77
-msgid "This is the only time the client secret will be shown."
-msgstr "クライアントシークレットが表示されるのはこれだけです。"
-
-#: screens/User/UserTokens/UserTokens.js:59
-msgid "This is the only time the token value and associated refresh token value will be shown."
-msgstr "この時だけ唯一、トークンの値と、関連する更新トークンの値が表示されます。"
-
-#: screens/Job/JobOutput/EmptyOutput.js:37
-msgid "This job failed and has no output."
-msgstr "このジョブは失敗し、出力がありません。"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:543
-msgid "This job template is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "このジョブテンプレートは、現在他のリソースで使用されています。削除してもよろしいですか?"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:197
-msgid "This organization is currently being by other resources. Are you sure you want to delete it?"
-msgstr "この組織は、現在他のリソースで使用されています。削除してもよろしいですか?"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:338
-msgid "This project is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "このプロジェクトは、現在他のリソースで使用されています。削除してもよろしいですか?"
-
-#: screens/Project/shared/Project.helptext.js:59
-msgid "This project is currently on sync and cannot be clicked until sync process completed"
-msgstr "このプロジェクトは現在同期中で、同期プロセスが完了するまでクリックできません"
-
-#: components/Schedule/shared/ScheduleForm.js:460
-msgid "This schedule has no occurrences due to the selected exceptions."
-msgstr "選択した例外により、このスケジュールには発生がありません。"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:122
-msgid "This schedule is missing an Inventory"
-msgstr "このスケジュールにはインベントリーがありません"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:147
-msgid "This schedule is missing required survey values"
-msgstr "このスケジュールには、必要な Survey 値がありません"
-
-#: components/Schedule/shared/UnsupportedScheduleForm.js:12
-msgid ""
-"This schedule uses complex rules that are not supported in the\n"
-"UI. Please use the API to manage this schedule."
-msgstr "このスケジュールは、でサポートされていない複雑なルールを使用しています\n"
-"UI。このスケジュールを管理するには API を使用してください。"
-
-#: components/LaunchPrompt/steps/StepName.js:26
-msgid "This step contains errors"
-msgstr "このステップにはエラーが含まれています"
-
-#: screens/User/shared/UserForm.js:150
-msgid "This value does not match the password you entered previously. Please confirm that password."
-msgstr "この値は、以前に入力されたパスワードと一致しません。パスワードを確認してください。"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:106
-msgid "This will cancel all subsequent nodes in this workflow"
-msgstr "これにより、このワークフローの後続のノードがすべてキャンセルされます"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:328
-msgid "This will cancel all subsequent nodes in this workflow."
-msgstr "これにより、このワークフローの後続のノードがすべてキャンセルされます。"
-
-#: screens/Setting/shared/RevertAllAlert.js:36
-msgid ""
-"This will revert all configuration values on this page to\n"
-"their factory defaults. Are you sure you want to proceed?"
-msgstr "これにより、このページのすべての設定値が出荷時の設定に戻ります。\n"
-"続行してもよろしいですか?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:40
-msgid "This workflow does not have any nodes configured."
-msgstr "このワークフローには、ノードが構成されていません。"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:43
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:35
-msgid "This workflow has already been acted on"
-msgstr "このワークフローはすでに処理されています"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:267
-msgid "This workflow job template is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "このワークフロージョブテンプレートは、現在他のリソースによって使用されています。削除してもよろしいですか?"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:299
-msgid "Thu"
-msgstr "木"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:80
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:186
-#: components/Schedule/shared/FrequencyDetailSubform.js:304
-#: components/Schedule/shared/FrequencyDetailSubform.js:448
-msgid "Thursday"
-msgstr "木曜"
-
-#: screens/ActivityStream/ActivityStream.js:249
-#: screens/ActivityStream/ActivityStream.js:261
-#: screens/ActivityStream/ActivityStreamDetailButton.js:41
-#: screens/ActivityStream/ActivityStreamListItem.js:42
-msgid "Time"
-msgstr "日時"
-
-#: screens/Project/shared/Project.helptext.js:128
-msgid ""
-"Time in seconds to consider a project\n"
-"to be current. During job runs and callbacks the task\n"
-"system will evaluate the timestamp of the latest project\n"
-"update. If it is older than Cache Timeout, it is not\n"
-"considered current, and a new project update will be\n"
-"performed."
-msgstr "プロジェクトが最新であることを判別するための時間 (秒単位) です。ジョブ実行およびコールバック時に、タスクシステムは最新のプロジェクト更新のタイムスタンプを評価します。これがキャッシュタイムアウトよりも古い場合には、最新とは見なされず、新規のプロジェクト更新が実行されます。"
-
-#: screens/Inventory/shared/Inventory.helptext.js:147
-msgid ""
-"Time in seconds to consider an inventory sync\n"
-"to be current. During job runs and callbacks the task system will\n"
-"evaluate the timestamp of the latest sync. If it is older than\n"
-"Cache Timeout, it is not considered current, and a new\n"
-"inventory sync will be performed."
-msgstr "インベントリーの同期が最新の状態であることを判別するために使用される時間 (秒単位) です。ジョブの実行およびコールバック時に、タスクシステムは最新の同期のタイムスタンプを評価します。これがキャッシュタイムアウトよりも古い場合は最新とは見なされず、インベントリーの同期が新たに実行されます。"
-
-#: components/StatusLabel/StatusLabel.js:51
-msgid "Timed out"
-msgstr "タイムアウト"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:86
-#: components/PromptDetail/PromptDetail.js:136
-#: components/PromptDetail/PromptDetail.js:355
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:484
-#: screens/Job/JobDetail/JobDetail.js:397
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:170
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:114
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:277
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:186
-#: screens/Template/shared/JobTemplateForm.js:475
-msgid "Timeout"
-msgstr "タイムアウト"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:193
-msgid "Timeout minutes"
-msgstr "タイムアウト (分)"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:207
-msgid "Timeout seconds"
-msgstr "タイムアウトの秒数"
-
-#: screens/Host/HostList/SmartInventoryButton.js:20
-msgid "To create a smart inventory using ansible facts, go to the smart inventory screen."
-msgstr "Ansible ファクトを使用してスマートインベントリーを作成するには、スマートインベントリー画面に移動します。"
-
-#: screens/Template/Survey/SurveyReorderModal.js:194
-msgid "To reorder the survey questions drag and drop them in the desired location."
-msgstr "Survey の質問を並べ替えるには、目的の場所にドラッグアンドドロップします。"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:106
-msgid "Toggle Legend"
-msgstr "凡例の表示/非表示"
-
-#: components/FormField/PasswordInput.js:39
-msgid "Toggle Password"
-msgstr "パスワードの切り替え"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:116
-msgid "Toggle Tools"
-msgstr "ツールの切り替え"
-
-#: components/HostToggle/HostToggle.js:70
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:56
-msgid "Toggle host"
-msgstr "ホストの切り替え"
-
-#: components/InstanceToggle/InstanceToggle.js:61
-msgid "Toggle instance"
-msgstr "インスタンスの切り替え"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:80
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:82
-#: screens/TopologyView/Header.js:99
-msgid "Toggle legend"
-msgstr "凡例の表示/非表示"
-
-#: components/NotificationList/NotificationListItem.js:50
-msgid "Toggle notification approvals"
-msgstr "通知承認の切り替え"
-
-#: components/NotificationList/NotificationListItem.js:92
-msgid "Toggle notification failure"
-msgstr "通知失敗の切り替え"
-
-#: components/NotificationList/NotificationListItem.js:64
-msgid "Toggle notification start"
-msgstr "通知開始の切り替え"
-
-#: components/NotificationList/NotificationListItem.js:78
-msgid "Toggle notification success"
-msgstr "通知成功の切り替え"
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:66
-msgid "Toggle schedule"
-msgstr "スケジュールの切り替え"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:92
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:94
-msgid "Toggle tools"
-msgstr "ツールの切り替え"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:373
-#: screens/User/UserTokens/UserTokens.js:64
-msgid "Token"
-msgstr "トークン"
-
-#: screens/User/UserTokens/UserTokens.js:50
-#: screens/User/UserTokens/UserTokens.js:53
-msgid "Token information"
-msgstr "トークン情報"
-
-#: screens/User/UserToken/UserToken.js:73
-msgid "Token not found."
-msgstr "ジョブが見つかりません。"
-
-#: screens/Application/Application/Application.js:80
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:105
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:128
-#: screens/Application/Applications.js:40
-#: screens/User/User.js:76
-#: screens/User/UserTokenList/UserTokenList.js:118
-#: screens/User/Users.js:34
-msgid "Tokens"
-msgstr "トークン"
-
-#: components/Workflow/WorkflowTools.js:83
-msgid "Tools"
-msgstr "ツール"
-
-#: routeConfig.js:152
-#: screens/TopologyView/TopologyView.js:40
-msgid "Topology View"
-msgstr "トポロジービュー"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:218
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:197
-#: screens/InstanceGroup/Instances/InstanceListItem.js:199
-#: screens/Instances/InstanceDetail/InstanceDetail.js:213
-#: screens/Instances/InstanceList/InstanceListItem.js:214
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:78
-msgid "Total Jobs"
-msgstr "ジョブの合計"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:76
-msgid "Total Nodes"
-msgstr "ノードの合計"
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:103
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:119
-msgid "Total hosts"
-msgstr "ホストの合計"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:72
-msgid "Total jobs"
-msgstr "ジョブの合計"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:87
-msgid "Track submodules"
-msgstr "サブモジュールを追跡する"
-
-#: components/PromptDetail/PromptProjectDetail.js:56
-#: screens/Project/ProjectDetail/ProjectDetail.js:109
-msgid "Track submodules latest commit on branch"
-msgstr "ブランチでのサブモジュールの最新のコミットを追跡する"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:141
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:168
-msgid "Trial"
-msgstr "トライアル"
-
-#: components/JobList/JobListItem.js:319
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:66
-#: screens/Job/JobDetail/JobDetail.js:383
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:210
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:240
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:270
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:315
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:373
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:142
-msgid "True"
-msgstr "True"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:277
-msgid "Tue"
-msgstr "火"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:78
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:184
-#: components/Schedule/shared/FrequencyDetailSubform.js:282
-#: components/Schedule/shared/FrequencyDetailSubform.js:438
-msgid "Tuesday"
-msgstr "火曜"
-
-#: components/NotificationList/NotificationList.js:201
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:142
-msgid "Twilio"
-msgstr "Twilio"
-
-#: components/JobList/JobList.js:246
-#: components/JobList/JobListItem.js:98
-#: components/Lookup/ProjectLookup.js:132
-#: components/NotificationList/NotificationList.js:219
-#: components/NotificationList/NotificationListItem.js:33
-#: components/PromptDetail/PromptDetail.js:124
-#: components/RelatedTemplateList/RelatedTemplateList.js:187
-#: components/TemplateList/TemplateList.js:214
-#: components/TemplateList/TemplateList.js:243
-#: components/TemplateList/TemplateListItem.js:184
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:85
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:154
-#: components/Workflow/WorkflowNodeHelp.js:160
-#: components/Workflow/WorkflowNodeHelp.js:196
-#: screens/Credential/CredentialList/CredentialList.js:165
-#: screens/Credential/CredentialList/CredentialListItem.js:63
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:94
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:116
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:17
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:46
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:54
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:195
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:66
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:94
-#: screens/Inventory/InventoryList/InventoryList.js:220
-#: screens/Inventory/InventoryList/InventoryListItem.js:116
-#: screens/Inventory/InventorySources/InventorySourceList.js:213
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:100
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:105
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:180
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:120
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:68
-#: screens/Project/ProjectList/ProjectList.js:194
-#: screens/Project/ProjectList/ProjectList.js:223
-#: screens/Project/ProjectList/ProjectListItem.js:218
-#: screens/Team/TeamRoles/TeamRoleListItem.js:17
-#: screens/Team/TeamRoles/TeamRolesList.js:181
-#: screens/Template/Survey/SurveyList.js:103
-#: screens/Template/Survey/SurveyList.js:103
-#: screens/Template/Survey/SurveyListItem.js:60
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:93
-#: screens/User/UserDetail/UserDetail.js:75
-#: screens/User/UserRoles/UserRolesList.js:156
-#: screens/User/UserRoles/UserRolesListItem.js:21
-msgid "Type"
-msgstr "タイプ"
-
-#: screens/Credential/shared/TypeInputsSubForm.js:25
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:47
-#: screens/Project/shared/ProjectForm.js:298
-msgid "Type Details"
-msgstr "タイプの詳細"
-
-#: screens/Template/Survey/MultipleChoiceField.js:56
-msgid ""
-"Type answer then click checkbox on right to select answer as\n"
-"default."
-msgstr "回答を入力し、右側のチェックボックスをクリックして、回答をデフォルトとして選択します。"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:50
-msgid "UTC"
-msgstr "UTC"
-
-#: components/HostForm/HostForm.js:62
-msgid "Unable to change inventory on a host"
-msgstr "ホストのインベントリーを変更できません。"
-
-#: screens/Project/ProjectList/ProjectListItem.js:211
-msgid "Unable to load last job update"
-msgstr "最後のジョブ更新を読み込めません"
-
-#: components/StatusLabel/StatusLabel.js:61
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:260
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:87
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:46
-#: screens/InstanceGroup/Instances/InstanceListItem.js:78
-#: screens/Instances/InstanceDetail/InstanceDetail.js:306
-#: screens/Instances/InstanceList/InstanceListItem.js:77
-#: screens/TopologyView/Tooltip.js:121
-msgid "Unavailable"
-msgstr "利用不可"
-
-#: screens/Setting/shared/RevertButton.js:53
-#: screens/Setting/shared/RevertButton.js:62
-msgid "Undo"
-msgstr "元に戻す"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:183
-msgid "Unfollow"
-msgstr "フォロー解除"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:126
-msgid "Unlimited"
-msgstr "制限なし"
-
-#: components/StatusLabel/StatusLabel.js:47
-#: screens/Job/JobOutput/shared/HostStatusBar.js:51
-#: screens/Job/JobOutput/shared/OutputToolbar.js:103
-msgid "Unreachable"
-msgstr "到達不能"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:102
-msgid "Unreachable Host Count"
-msgstr "到達不能なホスト数"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:104
-msgid "Unreachable Hosts"
-msgstr "到達不能なホスト"
-
-#: util/dates.js:74
-msgid "Unrecognized day string"
-msgstr "認識されない日付の文字列"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:15
-msgid "Unsaved changes modal"
-msgstr "保存されていない変更モーダル"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:94
-msgid "Update Revision on Launch"
-msgstr "起動時のリビジョン更新"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:51
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:133
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:92
-msgid "Update on launch"
-msgstr "起動時の更新"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:75
-msgid "Update options"
-msgstr "オプションの更新"
-
-#: components/PromptDetail/PromptProjectDetail.js:61
-#: screens/Project/ProjectDetail/ProjectDetail.js:115
-msgid "Update revision on job launch"
-msgstr "ジョブ起動時のリビジョン更新"
-
-#: screens/Setting/SettingList.js:92
-msgid "Update settings pertaining to Jobs within {brandName}"
-msgstr "{brandName} 内のジョブを含む設定の更新"
-
-#: screens/Template/shared/WebhookSubForm.js:188
-msgid "Update webhook key"
-msgstr "Webhook キーの更新"
-
-#: components/Workflow/WorkflowNodeHelp.js:126
-msgid "Updating"
-msgstr "更新中"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:118
-msgid "Upload a .zip file"
-msgstr ".zip ファイルをアップロードする"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:97
-msgid "Upload a Red Hat Subscription Manifest containing your subscription. To generate your subscription manifest, go to <0>subscription allocations0> on the Red Hat Customer Portal."
-msgstr "サブスクリプションを含む Red Hat Subscription Manifest をアップロードします。サブスクリプションマニフェストを生成するには、Red Hat カスタマーポータルの <0>サブスクリプション割り当て0> にアクセスします。"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:53
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:132
-msgid "Use SSL"
-msgstr "SSL の使用"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:58
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:137
-msgid "Use TLS"
-msgstr "TLS の使用"
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:72
-msgid ""
-"Use custom messages to change the content of\n"
-"notifications sent when a job starts, succeeds, or fails. Use\n"
-"curly braces to access information about the job:"
-msgstr "カスタムメッセージを使用して、ジョブの開始時、成功時、または失敗時に送信する通知内容を変更します。波括弧を使用してジョブに関する情報にアクセスします:"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:12
-msgid "Use one Annotation Tag per line, without commas."
-msgstr "コンマで区切らずに、1 行ごとに 1 つのアノテーションタグを指定します。"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:13
-msgid ""
-"Use one IRC channel or username per line. The pound\n"
-"symbol (#) for channels, and the at (@) symbol for users, are not\n"
-"required."
-msgstr "1 行ごとに 1 つの IRC チャンネルまたはユーザー名を指定します。チャンネルのシャープ記号 (#) およびユーザーのアットマーク (@) 記号は不要です。"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:5
-msgid "Use one email address per line to create a recipient list for this type of notification."
-msgstr "1 行ごとに 1 つのメールアドレスを指定して、この通知タイプの受信者リストを作成します。"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:28
-msgid ""
-"Use one phone number per line to specify where to\n"
-"route SMS messages. Phone numbers should be formatted +11231231234. For more information see Twilio documentation"
-msgstr "1 行ごとに 1 つの電話番号を指定して、SMS メッセージのルーティング先を指定します。電話番号は +11231231234 の形式にする必要があります。詳細は、Twilio のドキュメントを参照してください"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:249
-#: screens/InstanceGroup/Instances/InstanceList.js:270
-#: screens/Instances/InstanceDetail/InstanceDetail.js:292
-#: screens/Instances/InstanceList/InstanceList.js:205
-msgid "Used Capacity"
-msgstr "使用済み容量"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:253
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:257
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:78
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:86
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:42
-#: screens/InstanceGroup/Instances/InstanceListItem.js:74
-#: screens/Instances/InstanceDetail/InstanceDetail.js:297
-#: screens/Instances/InstanceDetail/InstanceDetail.js:303
-#: screens/Instances/InstanceList/InstanceListItem.js:73
-#: screens/TopologyView/Tooltip.js:117
-msgid "Used capacity"
-msgstr "使用済み容量"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:13
-msgid "User"
-msgstr "ユーザー"
-
-#: components/AppContainer/PageHeaderToolbar.js:160
-msgid "User Details"
-msgstr "ユーザーの詳細"
-
-#: screens/Setting/SettingList.js:121
-#: screens/Setting/Settings.js:118
-msgid "User Interface"
-msgstr "ユーザーインターフェース"
-
-#: screens/Setting/SettingList.js:126
-msgid "User Interface settings"
-msgstr "ユーザーインターフェースの設定"
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:72
-#: screens/User/UserRoles/UserRolesList.js:142
-msgid "User Roles"
-msgstr "ユーザーロール"
-
-#: screens/User/UserDetail/UserDetail.js:72
-#: screens/User/shared/UserForm.js:119
-msgid "User Type"
-msgstr "ユーザータイプ"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:59
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:60
-msgid "User analytics"
-msgstr "ユーザーアナリティクス"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:34
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:202
-msgid "User and Automation Analytics"
-msgstr "ユーザーおよび自動化アナリティクス"
-
-#: components/AppContainer/PageHeaderToolbar.js:154
-msgid "User details"
-msgstr "ユーザーの詳細"
-
-#: screens/User/User.js:96
-msgid "User not found."
-msgstr "ジョブが見つかりません。"
-
-#: screens/User/UserTokenList/UserTokenList.js:180
-msgid "User tokens"
-msgstr "ユーザートークン"
-
-#: components/AddRole/AddResourceRole.js:23
-#: components/AddRole/AddResourceRole.js:38
-#: components/ResourceAccessList/ResourceAccessList.js:173
-#: components/ResourceAccessList/ResourceAccessList.js:226
-#: screens/Login/Login.js:230
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:144
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:253
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:303
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:361
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:67
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:260
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:337
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:442
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:92
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:204
-#: screens/User/UserDetail/UserDetail.js:68
-#: screens/User/UserList/UserList.js:120
-#: screens/User/UserList/UserList.js:160
-#: screens/User/UserList/UserListItem.js:38
-#: screens/User/shared/UserForm.js:76
-msgid "Username"
-msgstr "ユーザー名"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:88
-msgid "Username / password"
-msgstr "ユーザー名 / パスワード"
-
-#: components/AddRole/AddResourceRole.js:178
-#: components/AddRole/AddResourceRole.js:179
-#: routeConfig.js:101
-#: screens/ActivityStream/ActivityStream.js:184
-#: screens/Team/Teams.js:30
-#: screens/User/UserList/UserList.js:110
-#: screens/User/UserList/UserList.js:153
-#: screens/User/Users.js:15
-#: screens/User/Users.js:26
-msgid "Users"
-msgstr "ユーザー"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:101
-msgid "VMware vCenter"
-msgstr "VMware vCenter"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:69
-#: components/HostForm/HostForm.js:113
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:115
-#: components/PromptDetail/PromptDetail.js:168
-#: components/PromptDetail/PromptDetail.js:369
-#: components/PromptDetail/PromptJobTemplateDetail.js:286
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:135
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:620
-#: screens/Host/HostDetail/HostDetail.js:93
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:165
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:37
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:88
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:143
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:53
-#: screens/Inventory/shared/InventoryForm.js:108
-#: screens/Inventory/shared/InventoryGroupForm.js:46
-#: screens/Inventory/shared/SmartInventoryForm.js:93
-#: screens/Job/JobDetail/JobDetail.js:546
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:501
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:226
-#: screens/Template/shared/JobTemplateForm.js:402
-#: screens/Template/shared/WorkflowJobTemplateForm.js:212
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:293
-msgid "Variables"
-msgstr "変数"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:130
-msgid "Variables Prompted"
-msgstr "提示される変数"
-
-#: screens/Inventory/shared/Inventory.helptext.js:43
-msgid "Variables must be in JSON or YAML syntax. Use the radio button to toggle between the two."
-msgstr "変数は JSON または YAML 構文にする必要があります。ラジオボタンを使用してこの構文を切り替えます。"
-
-#: screens/Inventory/shared/Inventory.helptext.js:166
-msgid "Variables used to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>{sourceType}1> plugin configuration guide."
-msgstr "インベントリーソースの設定に使用する変数。このプラグインの設定方法の詳細については、ドキュメントの <0>インベントリープラグイン0> および <1>{sourceType}1> プラグイン設定ガイドを参照してください。"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:121
-msgid "Vault password"
-msgstr "Vault パスワード"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:121
-msgid "Vault password | {credId}"
-msgstr "Vault パスワード | {credId}"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:131
-msgid "Verbose"
-msgstr "詳細"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:63
-#: components/PromptDetail/PromptDetail.js:260
-#: components/PromptDetail/PromptInventorySourceDetail.js:100
-#: components/PromptDetail/PromptJobTemplateDetail.js:154
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:478
-#: components/VerbositySelectField/VerbositySelectField.js:34
-#: components/VerbositySelectField/VerbositySelectField.js:45
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:232
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:47
-#: screens/Job/JobDetail/JobDetail.js:331
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:271
-msgid "Verbosity"
-msgstr "詳細"
-
-#: screens/Setting/AzureAD/AzureAD.js:25
-msgid "View Azure AD settings"
-msgstr "Azure AD 設定の表示"
-
-#: screens/Credential/Credential.js:143
-#: screens/Credential/Credential.js:155
-msgid "View Credential Details"
-msgstr "認証情報の詳細の表示"
-
-#: components/Schedule/Schedule.js:151
-msgid "View Details"
-msgstr "詳細の表示"
-
-#: screens/Setting/GitHub/GitHub.js:58
-msgid "View GitHub Settings"
-msgstr "GitHub 設定の表示"
-
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2.js:26
-msgid "View Google OAuth 2.0 settings"
-msgstr "Google OAuth 2.0 設定の表示"
-
-#: screens/Host/Host.js:137
-msgid "View Host Details"
-msgstr "ホストの詳細の表示"
-
-#: screens/Instances/Instance.js:78
-msgid "View Instance Details"
-msgstr "インスタンスの詳細の表示"
-
-#: screens/Inventory/Inventory.js:192
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:142
-#: screens/Inventory/SmartInventory.js:175
-msgid "View Inventory Details"
-msgstr "インベントリーの詳細の表示"
-
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:92
-msgid "View Inventory Groups"
-msgstr "インベントリーグループの表示"
-
-#: screens/Inventory/InventoryHost/InventoryHost.js:160
-msgid "View Inventory Host Details"
-msgstr "インベントリーホストの詳細の表示"
-
-#: screens/Inventory/shared/Inventory.helptext.js:55
-msgid "View JSON examples at <0>www.json.org0>"
-msgstr "JSON のサンプルについては、<0>www.json.org0> を参照してください。"
-
-#: screens/Job/Job.js:212
-msgid "View Job Details"
-msgstr "ジョブの詳細の表示"
-
-#: screens/Setting/Jobs/Jobs.js:25
-msgid "View Jobs settings"
-msgstr "ジョブ設定の表示"
-
-#: screens/Setting/LDAP/LDAP.js:38
-msgid "View LDAP Settings"
-msgstr "LDAP 設定の表示"
-
-#: screens/Setting/Logging/Logging.js:32
-msgid "View Logging settings"
-msgstr "ロギング設定の表示"
-
-#: screens/Setting/MiscAuthentication/MiscAuthentication.js:32
-msgid "View Miscellaneous Authentication settings"
-msgstr "その他の認証設定の表示"
-
-#: screens/Setting/MiscSystem/MiscSystem.js:32
-msgid "View Miscellaneous System settings"
-msgstr "その他のシステム設定の表示"
-
-#: screens/Setting/OIDC/OIDC.js:25
-msgid "View OIDC settings"
-msgstr "OIDC 設定の表示"
-
-#: screens/Organization/Organization.js:225
-msgid "View Organization Details"
-msgstr "組織の詳細の表示"
-
-#: screens/Project/Project.js:200
-msgid "View Project Details"
-msgstr "プロジェクトの詳細の表示"
-
-#: screens/Setting/RADIUS/RADIUS.js:25
-msgid "View RADIUS settings"
-msgstr "RADIUS 設定の表示"
-
-#: screens/Setting/SAML/SAML.js:25
-msgid "View SAML settings"
-msgstr "SAML 設定の表示"
-
-#: components/Schedule/Schedule.js:83
-#: components/Schedule/Schedule.js:101
-msgid "View Schedules"
-msgstr "スケジュールの表示"
-
-#: screens/Setting/Subscription/Subscription.js:30
-msgid "View Settings"
-msgstr "設定の表示"
-
-#: screens/Template/Template.js:159
-#: screens/Template/WorkflowJobTemplate.js:145
-msgid "View Survey"
-msgstr "Survey の表示"
-
-#: screens/Setting/TACACS/TACACS.js:25
-msgid "View TACACS+ settings"
-msgstr "TACACS+ 設定の表示"
-
-#: screens/Team/Team.js:118
-msgid "View Team Details"
-msgstr "チームの詳細の表示"
-
-#: screens/Template/Template.js:260
-#: screens/Template/WorkflowJobTemplate.js:275
-msgid "View Template Details"
-msgstr "テンプレートの詳細の表示"
-
-#: screens/User/UserToken/UserToken.js:100
-msgid "View Tokens"
-msgstr "トークンの表示"
-
-#: screens/User/User.js:141
-msgid "View User Details"
-msgstr "ユーザーの詳細の表示"
-
-#: screens/Setting/UI/UI.js:26
-msgid "View User Interface settings"
-msgstr "ユーザーインターフェース設定の表示"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:105
-msgid "View Workflow Approval Details"
-msgstr "ワークフロー承認の詳細の表示"
-
-#: screens/Inventory/shared/Inventory.helptext.js:66
-msgid "View YAML examples at <0>docs.ansible.com0>"
-msgstr "<0>docs.ansible.com0> での YAML サンプルの表示"
-
-#: components/ScreenHeader/ScreenHeader.js:65
-#: components/ScreenHeader/ScreenHeader.js:68
-msgid "View activity stream"
-msgstr "アクティビティーストリームの表示"
-
-#: screens/Credential/Credential.js:99
-msgid "View all Credentials."
-msgstr "すべての認証情報を表示します。"
-
-#: screens/Host/Host.js:97
-msgid "View all Hosts."
-msgstr "すべてのホストを表示します。"
-
-#: screens/Inventory/Inventory.js:95
-#: screens/Inventory/SmartInventory.js:95
-msgid "View all Inventories."
-msgstr "すべてのインベントリーを表示します。"
-
-#: screens/Inventory/InventoryHost/InventoryHost.js:101
-msgid "View all Inventory Hosts."
-msgstr "すべてのインベントリーホストを表示します。"
-
-#: screens/Job/JobTypeRedirect.js:40
-msgid "View all Jobs"
-msgstr "すべてのジョブを表示"
-
-#: screens/Job/Job.js:162
-msgid "View all Jobs."
-msgstr "すべてのジョブを表示します。"
-
-#: screens/NotificationTemplate/NotificationTemplate.js:60
-#: screens/NotificationTemplate/NotificationTemplateAdd.js:52
-msgid "View all Notification Templates."
-msgstr "すべての通知テンプレートを表示します。"
-
-#: screens/Organization/Organization.js:155
-msgid "View all Organizations."
-msgstr "すべての組織を表示します。"
-
-#: screens/Project/Project.js:137
-msgid "View all Projects."
-msgstr "すべてのプロジェクトを表示します。"
-
-#: screens/Team/Team.js:76
-msgid "View all Teams."
-msgstr "すべてのチームを表示します。"
-
-#: screens/Template/Template.js:176
-#: screens/Template/WorkflowJobTemplate.js:176
-msgid "View all Templates."
-msgstr "すべてのテンプレートを表示します。"
-
-#: screens/User/User.js:97
-msgid "View all Users."
-msgstr "すべてのユーザーを表示します。"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:54
-msgid "View all Workflow Approvals."
-msgstr "すべてのワークフロー承認を表示します。"
-
-#: screens/Application/Application/Application.js:96
-msgid "View all applications."
-msgstr "すべてのアプリケーションを表示します。"
-
-#: screens/CredentialType/CredentialType.js:78
-msgid "View all credential types"
-msgstr "すべての認証情報タイプの表示"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:85
-msgid "View all execution environments"
-msgstr "すべての実行環境の表示"
-
-#: screens/InstanceGroup/ContainerGroup.js:86
-#: screens/InstanceGroup/InstanceGroup.js:94
-msgid "View all instance groups"
-msgstr "すべてのインスタンスグループの表示"
-
-#: screens/ManagementJob/ManagementJob.js:135
-msgid "View all management jobs"
-msgstr "すべての管理ジョブの表示"
-
-#: screens/Setting/Settings.js:204
-msgid "View all settings"
-msgstr "すべての設定の表示"
-
-#: screens/User/UserToken/UserToken.js:74
-msgid "View all tokens."
-msgstr "すべてのトークンを表示します。"
-
-#: screens/Setting/SettingList.js:133
-msgid "View and edit your subscription information"
-msgstr "サブスクリプション情報の表示および編集"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:25
-#: screens/ActivityStream/ActivityStreamListItem.js:50
-msgid "View event details"
-msgstr "イベント詳細の表示"
-
-#: screens/Inventory/InventorySource/InventorySource.js:167
-msgid "View inventory source details"
-msgstr "インベントリソース詳細の表示"
-
-#: components/Sparkline/Sparkline.js:44
-msgid "View job {0}"
-msgstr "ジョブ {0} の表示"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:220
-msgid "View node details"
-msgstr "ノードの詳細の表示"
-
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:85
-msgid "View smart inventory host details"
-msgstr "スマートインベントリーホストの詳細の表示"
-
-#: routeConfig.js:30
-#: screens/ActivityStream/ActivityStream.js:145
-msgid "Views"
-msgstr "ビュー"
-
-#: components/TemplateList/TemplateListItem.js:198
-#: components/TemplateList/TemplateListItem.js:204
-#: screens/Template/WorkflowJobTemplate.js:137
-msgid "Visualizer"
-msgstr "ビジュアライザー"
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:44
-msgid "WARNING:"
-msgstr "警告:"
-
-#: components/JobList/JobList.js:229
-#: components/StatusLabel/StatusLabel.js:52
-#: components/Workflow/WorkflowNodeHelp.js:96
-msgid "Waiting"
-msgstr "待機中"
-
-#: screens/Job/JobOutput/EmptyOutput.js:35
-msgid "Waiting for job output…"
-msgstr "ジョブの出力を待機中…"
-
-#: components/Workflow/WorkflowLegend.js:118
-#: screens/Job/JobOutput/JobOutputSearch.js:132
-msgid "Warning"
-msgstr "警告"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:14
-msgid "Warning: Unsaved Changes"
-msgstr "警告: 変更が保存されていません"
-
-#: components/Schedule/shared/ScheduleFormFields.js:43
-msgid "Warning: {selectedValue} is a link to {0} and will be saved as that."
-msgstr "警告:{selectedValue} は {0} へのリンクで、そのように保存されます。"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:119
-msgid "We were unable to locate licenses associated with this account."
-msgstr "このアカウントに関連するライセンスを見つけることができませんでした。"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:138
-msgid "We were unable to locate subscriptions associated with this account."
-msgstr "このアカウントに関連するサブスクリプションを見つけることができませんでした。"
-
-#: components/DetailList/LaunchedByDetail.js:24
-#: components/NotificationList/NotificationList.js:202
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:143
-msgid "Webhook"
-msgstr "Webhook"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:177
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:103
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:333
-#: screens/Template/shared/WebhookSubForm.js:199
-msgid "Webhook Credential"
-msgstr "Webhook の認証情報"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:175
-msgid "Webhook Credentials"
-msgstr "Webhook の認証情報"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:173
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:92
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:326
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:168
-#: screens/Template/shared/WebhookSubForm.js:173
-msgid "Webhook Key"
-msgstr "Webhook キー"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:166
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:91
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:311
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:156
-#: screens/Template/shared/WebhookSubForm.js:129
-msgid "Webhook Service"
-msgstr "Webhook サービス"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:169
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:95
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:319
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:162
-#: screens/Template/shared/WebhookSubForm.js:161
-#: screens/Template/shared/WebhookSubForm.js:167
-msgid "Webhook URL"
-msgstr "Webhook URL"
-
-#: screens/Template/shared/JobTemplateForm.js:646
-#: screens/Template/shared/WorkflowJobTemplateForm.js:271
-msgid "Webhook details"
-msgstr "Webhook の詳細"
-
-#: screens/Template/shared/JobTemplate.helptext.js:24
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:17
-msgid "Webhook services can launch jobs with this workflow job template by making a POST request to this URL."
-msgstr "Webhook サービスは、この URL への POST 要求を作成してこのワークフロージョブテンプレートでジョブを起動できます。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:25
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:18
-msgid "Webhook services can use this as a shared secret."
-msgstr "Webhook サービスは、これを共有シークレットとして使用できます。"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:78
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:41
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:142
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:63
-msgid "Webhooks"
-msgstr "Webhook"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:26
-msgid "Webhooks: Enable Webhook for this workflow job template."
-msgstr "Webhook: このワークフローのジョブテンプレートの Webhook を有効にします。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:42
-msgid "Webhooks: Enable webhook for this template."
-msgstr "Webhook: このテンプレートの Webhook を有効にします。"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:288
-msgid "Wed"
-msgstr "水"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:79
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:185
-#: components/Schedule/shared/FrequencyDetailSubform.js:293
-#: components/Schedule/shared/FrequencyDetailSubform.js:443
-msgid "Wednesday"
-msgstr "水曜"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:185
-#: components/Schedule/shared/FrequencyDetailSubform.js:179
-#: components/Schedule/shared/ScheduleFormFields.js:128
-#: components/Schedule/shared/ScheduleFormFields.js:188
-msgid "Week"
-msgstr "週"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:464
-msgid "Weekday"
-msgstr "平日"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:469
-msgid "Weekend day"
-msgstr "週末"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:59
-msgid ""
-"Welcome to Red Hat Ansible Automation Platform!\n"
-"Please complete the steps below to activate your subscription."
-msgstr "Red Hat Ansible Automation Platform へようこそ! サブスクリプションをアクティブにするには、以下の手順を実行してください。"
-
-#: screens/Login/Login.js:190
-msgid "Welcome to {brandName}!"
-msgstr "{brandName} へようこそ"
-
-#: screens/Inventory/shared/Inventory.helptext.js:105
-msgid ""
-"When not checked, a merge will be performed,\n"
-"combining local variables with those found on the\n"
-"external source."
-msgstr "チェックが付けられていない場合は、ローカル変数と外部ソースにあるものを組み合わせるマージが実行されます。"
-
-#: screens/Inventory/shared/Inventory.helptext.js:93
-msgid ""
-"When not checked, local child\n"
-"hosts and groups not found on the external source will remain\n"
-"untouched by the inventory update process."
-msgstr "チェックが付けられていない場合、外部ソースにないローカルの子ホストおよびグループは、インベントリーの更新プロセスによって処理されないままになります。"
-
-#: components/Workflow/WorkflowLegend.js:96
-msgid "Workflow"
-msgstr "ワークフロー"
-
-#: components/Workflow/WorkflowNodeHelp.js:75
-msgid "Workflow Approval"
-msgstr "ワークフローの承認"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:52
-msgid "Workflow Approval not found."
-msgstr "ワークフローの承認が見つかりません。"
-
-#: routeConfig.js:54
-#: screens/ActivityStream/ActivityStream.js:156
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:118
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:145
-#: screens/WorkflowApproval/WorkflowApprovals.js:13
-#: screens/WorkflowApproval/WorkflowApprovals.js:22
-msgid "Workflow Approvals"
-msgstr "ワークフローの承認"
-
-#: components/JobList/JobList.js:216
-#: components/JobList/JobListItem.js:47
-#: components/Schedule/ScheduleList/ScheduleListItem.js:40
-#: screens/Job/JobDetail/JobDetail.js:70
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:224
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:164
-msgid "Workflow Job"
-msgstr "ワークフロージョブ"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:76
-msgid "Workflow Job 1/{0}"
-msgstr "ワークフロージョブ 1/{0}"
-
-#: components/JobList/JobListItem.js:201
-#: components/Workflow/WorkflowNodeHelp.js:63
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:20
-#: screens/Job/JobDetail/JobDetail.js:244
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:91
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:186
-#: util/getRelatedResourceDeleteDetails.js:105
-msgid "Workflow Job Template"
-msgstr "ワークフロージョブテンプレート"
-
-#: util/getRelatedResourceDeleteDetails.js:115
-#: util/getRelatedResourceDeleteDetails.js:157
-#: util/getRelatedResourceDeleteDetails.js:260
-msgid "Workflow Job Template Nodes"
-msgstr "ワークフロージョブテンプレートのノード"
-
-#: util/getRelatedResourceDeleteDetails.js:140
-msgid "Workflow Job Templates"
-msgstr "ワークフロージョブテンプレート"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:23
-msgid "Workflow Link"
-msgstr "ワークフローのリンク"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:100
-msgid "Workflow Nodes"
-msgstr "ワークフローノード"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:86
-msgid "Workflow Statuses"
-msgstr "ワークフローのステータス"
-
-#: components/TemplateList/TemplateList.js:218
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:98
-msgid "Workflow Template"
-msgstr "ワークフローテンプレート"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:519
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:159
-msgid "Workflow approved message"
-msgstr "ワークフロー承認メッセージ"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:531
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:168
-msgid "Workflow approved message body"
-msgstr "ワークフロー承認メッセージのボディー"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:543
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:177
-msgid "Workflow denied message"
-msgstr "ワークフロー拒否メッセージ"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:555
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:186
-msgid "Workflow denied message body"
-msgstr "ワークフロー拒否メッセージのボディー"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:106
-msgid "Workflow documentation"
-msgstr "ワークフロードキュメント"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:220
-msgid "Workflow job details"
-msgstr "ワークフロージョブの詳細"
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:46
-msgid "Workflow job templates"
-msgstr "ワークフロージョブテンプレート"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:24
-msgid "Workflow link modal"
-msgstr "ワークフローリンクモーダル"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:247
-msgid "Workflow node view modal"
-msgstr "ワークフローノード表示モーダル"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:567
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:195
-msgid "Workflow pending message"
-msgstr "ワークフロー保留メッセージ"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:579
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:204
-msgid "Workflow pending message body"
-msgstr "ワークフロー保留メッセージのボディー"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:591
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:213
-msgid "Workflow timed out message"
-msgstr "ワークフローのタイムアウトメッセージ"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:603
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:222
-msgid "Workflow timed out message body"
-msgstr "ワークフローのタイムアウトメッセージのボディー"
-
-#: screens/User/shared/UserTokenForm.js:77
-msgid "Write"
-msgstr "書き込み"
-
-#: screens/Inventory/shared/Inventory.helptext.js:52
-msgid "YAML:"
-msgstr "YAML:"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:187
-#: components/Schedule/shared/FrequencyDetailSubform.js:183
-#: components/Schedule/shared/ScheduleFormFields.js:130
-#: components/Schedule/shared/ScheduleFormFields.js:190
-msgid "Year"
-msgstr "年"
-
-#: components/Search/Search.js:229
-msgid "Yes"
-msgstr "はい"
-
-#: components/Lookup/MultiCredentialsLookup.js:155
-msgid "You cannot select multiple vault credentials with the same vault ID. Doing so will automatically deselect the other with the same vault ID."
-msgstr "同じ Vault ID を持つ複数の Vault 認証情報を選択することはできません。これを行うと、同じ Vault ID を持つもう一方の選択が自動的に解除されます。"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:96
-msgid "You do not have permission to delete the following Groups: {itemsUnableToDelete}"
-msgstr "次のグループを削除する権限がありません: {itemsUnableToDelete}"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:152
-msgid "You do not have permission to delete {pluralizedItemName}: {itemsUnableToDelete}"
-msgstr "{pluralizedItemName} を削除するパーミッションがありません: {itemsUnableToDelete}"
-
-#: components/DisassociateButton/DisassociateButton.js:66
-msgid "You do not have permission to disassociate the following: {itemsUnableToDisassociate}"
-msgstr "以下の関連付けを解除する権限がありません: {itemsUnableToDisassociate}"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:72
-msgid "You do not have permission to remove instances: {itemsUnableToremove}"
-msgstr "インスタンスを削除する権限がありません: {itemsUnableToremove}"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:82
-msgid "You have automated against more hosts than your subscription allows."
-msgstr "サブスクリプションで許可されているよりも多くのホストに対して自動化しました。"
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:87
-msgid ""
-"You may apply a number of possible variables in the\n"
-"message. For more information, refer to the"
-msgstr "メッセージにはいくつかの可能な変数を適用できます。\n"
-"詳細の参照:"
-
-#: screens/Login/Login.js:198
-msgid "Your session has expired. Please log in to continue where you left off."
-msgstr "セッションの期限が切れました。中断したところから続行するには、ログインしてください。"
-
-#: components/AppContainer/AppContainer.js:130
-msgid "Your session is about to expire"
-msgstr "セッションの有効期限が近づいています"
-
-#: components/Workflow/WorkflowTools.js:121
-msgid "Zoom In"
-msgstr "ズームイン"
-
-#: components/Workflow/WorkflowTools.js:100
-msgid "Zoom Out"
-msgstr "ズームアウト"
-
-#: screens/TopologyView/Header.js:51
-#: screens/TopologyView/Header.js:54
-msgid "Zoom in"
-msgstr "ズームイン"
-
-#: screens/TopologyView/Header.js:63
-#: screens/TopologyView/Header.js:66
-msgid "Zoom out"
-msgstr "ズームアウト"
-
-#: screens/Template/shared/JobTemplateForm.js:754
-#: screens/Template/shared/WebhookSubForm.js:150
-msgid "a new webhook key will be generated on save."
-msgstr "新規 Webhook キーは保存時に生成されます。"
-
-#: screens/Template/shared/JobTemplateForm.js:751
-#: screens/Template/shared/WebhookSubForm.js:140
-msgid "a new webhook url will be generated on save."
-msgstr "新規 Webhook URL は保存時に生成されます。"
-
-#: screens/Inventory/shared/Inventory.helptext.js:123
-#: screens/Inventory/shared/Inventory.helptext.js:142
-msgid "and click on Update Revision on Launch"
-msgstr "そして、起動時のリビジョン更新をクリックします"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:505
-msgid "approved"
-msgstr "承認"
-
-#: components/AppContainer/AppContainer.js:55
-msgid "brand logo"
-msgstr "ブランドロゴ"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:279
-#: screens/Template/Survey/SurveyList.js:72
-msgid "cancel delete"
-msgstr "削除のキャンセル"
-
-#: screens/Setting/shared/SharedFields.js:341
-msgid "cancel edit login redirect"
-msgstr "ログインリダイレクトの編集をキャンセルする"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:169
-msgid "cancel remove"
-msgstr "削除をキャンセルする"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:34
-msgid "canceled"
-msgstr "キャンセル済み"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:217
-msgid "command"
-msgstr "コマンド"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:267
-#: screens/Template/Survey/SurveyList.js:63
-msgid "confirm delete"
-msgstr "削除の確認"
-
-#: components/DisassociateButton/DisassociateButton.js:130
-#: screens/Team/TeamRoles/TeamRolesList.js:219
-msgid "confirm disassociate"
-msgstr "関連付けの解除の確認"
-
-#: screens/Setting/shared/SharedFields.js:330
-msgid "confirm edit login redirect"
-msgstr "ログインリダイレクトの編集の確認"
-
-#: screens/TopologyView/ContentLoading.js:32
-msgid "content-loading-in-progress"
-msgstr "コンテンツの読み込みが進行中"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:189
-msgid "day"
-msgstr "日"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:151
-msgid "deletion error"
-msgstr "削除エラー"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:513
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:37
-msgid "denied"
-msgstr "拒否"
-
-#: screens/Job/JobOutput/EmptyOutput.js:41
-msgid "details."
-msgstr "詳細。"
-
-#: components/DisassociateButton/DisassociateButton.js:91
-msgid "disassociate"
-msgstr "関連付けの解除"
-
-#: components/Lookup/HostFilterLookup.js:406
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:20
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:39
-#: screens/Template/Survey/SurveyQuestionForm.js:269
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:239
-#: screens/Template/shared/JobTemplate.helptext.js:61
-msgid "documentation"
-msgstr "ドキュメント"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:105
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:117
-#: screens/Host/HostDetail/HostDetail.js:104
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:97
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:109
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:99
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:289
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:161
-#: screens/Project/ProjectDetail/ProjectDetail.js:309
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:195
-#: screens/User/UserDetail/UserDetail.js:92
-msgid "edit"
-msgstr "編集"
-
-#: screens/Template/Survey/SurveyListItem.js:65
-#: screens/Template/Survey/SurveyReorderModal.js:125
-msgid "encrypted"
-msgstr "暗号化"
-
-#: components/Lookup/HostFilterLookup.js:408
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:241
-msgid "for more info."
-msgstr "(詳細情報)"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:21
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:40
-#: screens/Template/Survey/SurveyQuestionForm.js:271
-#: screens/Template/shared/JobTemplate.helptext.js:63
-msgid "for more information."
-msgstr "(詳細情報)"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:150
-msgid "here"
-msgstr "ここ"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:118
-#: components/AdHocCommands/AdHocDetailsStep.js:170
-#: screens/Job/Job.helptext.js:39
-msgid "here."
-msgstr "ここ。"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:49
-msgid "host-description-{0}"
-msgstr "host-description-{0}"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:44
-msgid "host-name-{0}"
-msgstr "host-name-{0}"
-
-#: components/Lookup/HostFilterLookup.js:418
-msgid "hosts"
-msgstr "ホスト"
-
-#: components/Pagination/Pagination.js:24
-msgid "items"
-msgstr "項目"
-
-#: screens/User/UserList/UserListItem.js:44
-msgid "ldap user"
-msgstr "LDAP ユーザー"
-
-#: screens/User/UserDetail/UserDetail.js:76
-msgid "login type"
-msgstr "ログインタイプ"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:203
-msgid "min"
-msgstr "分"
-
-#: screens/Template/Survey/MultipleChoiceField.js:76
-msgid "new choice"
-msgstr "新しい選択"
-
-#: components/Pagination/Pagination.js:36
-#: components/Schedule/shared/FrequencyDetailSubform.js:480
-msgid "of"
-msgstr "/"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:215
-msgid "option to the"
-msgstr "以下へのオプション:"
-
-#: components/Pagination/Pagination.js:25
-msgid "page"
-msgstr "ページ"
-
-#: components/Pagination/Pagination.js:26
-msgid "pages"
-msgstr "ページ"
-
-#: components/Pagination/Pagination.js:28
-msgid "per page"
-msgstr "項目/ページ"
-
-#: components/LaunchButton/ReLaunchDropDown.js:77
-#: components/LaunchButton/ReLaunchDropDown.js:100
-msgid "relaunch jobs"
-msgstr "ジョブの再起動"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:217
-msgid "sec"
-msgstr "秒"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:238
-msgid "seconds"
-msgstr "秒"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:58
-msgid "select module"
-msgstr "モジュールの選択"
-
-#: screens/User/UserList/UserListItem.js:49
-msgid "social login"
-msgstr "ソーシャルログイン"
-
-#: screens/Template/shared/JobTemplateForm.js:346
-#: screens/Template/shared/WorkflowJobTemplateForm.js:188
-msgid "source control branch"
-msgstr "ソースコントロールのブランチ"
-
-#: screens/ActivityStream/ActivityStreamListItem.js:30
-msgid "system"
-msgstr "システム"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:511
-msgid "timed out"
-msgstr "タイムアウト"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:195
-msgid "toggle changes"
-msgstr "変更の切り替え"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:516
-msgid "updated"
-msgstr "更新"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:190
-msgid "weekday"
-msgstr "平日"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:191
-msgid "weekend day"
-msgstr "週末"
-
-#: screens/Template/shared/WebhookSubForm.js:181
-msgid "workflow job template webhook key"
-msgstr "ワークフロージョブテンプレートの Wbhook キー"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:65
-msgid "{0, plural, one {# source with sync failures.} other {# sources with sync failures.}}"
-msgstr "{0, plural, one {# source with sync failures.} other {# sources with sync failures.}}"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:115
-msgid "{0, plural, one {Are you sure you want delete the group below?} other {Are you sure you want delete the groups below?}}"
-msgstr "{0, plural, one {Are you sure you want delete the group below?} other {Are you sure you want delete the groups below?}}"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:86
-msgid "{0, plural, one {Delete Group?} other {Delete Groups?}}"
-msgstr "{0, plural, one {Delete Group?} other {Delete Groups?}}"
-
-#: util/validators.js:138
-msgid "{0, plural, one {Please enter a valid phone number.} other {Please enter valid phone numbers.}}"
-msgstr "{0, plural, one {Please enter a valid phone number.} other {Please enter valid phone numbers.}}"
-
-#: screens/Inventory/InventoryList/InventoryList.js:247
-msgid "{0, plural, one {The inventory will be in a pending status until the final delete is processed.} other {The inventories will be in a pending status until the final delete is processed.}}"
-msgstr "{0, plural, one {The inventory will be in a pending status until the final delete is processed.} other {The inventories will be in a pending status until the final delete is processed.}}"
-
-#: components/JobList/JobList.js:280
-msgid "{0, plural, one {The selected job cannot be deleted due to insufficient permission or a running job status} other {The selected jobs cannot be deleted due to insufficient permissions or a running job status}}"
-msgstr "{0, plural, one {The selected job cannot be deleted due to insufficient permission or a running job status} other {The selected jobs cannot be deleted due to insufficient permissions or a running job status}}"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:151
-msgid "{0, plural, one {This approval cannot be deleted due to insufficient permissions or a pending job status} other {These approvals cannot be deleted due to insufficient permissions or a pending job status}}"
-msgstr "{0, plural, one {This approval cannot be deleted due to insufficient permissions or a pending job status} other {These approvals cannot be deleted due to insufficient permissions or a pending job status}}"
-
-#: screens/Credential/CredentialList/CredentialList.js:198
-msgid "{0, plural, one {This credential is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these credentials could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This credential is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these credentials could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:164
-msgid "{0, plural, one {This credential type is currently being used by some credentials and cannot be deleted.} other {Credential types that are being used by credentials cannot be deleted. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This credential type is currently being used by some credentials and cannot be deleted.} other {Credential types that are being used by credentials cannot be deleted. Are you sure you want to delete anyway?}}"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:194
-msgid "{0, plural, one {This execution environment is currently being used by other resources. Are you sure you want to delete it?} other {These execution environments could be in use by other resources that rely on them. Are you sure you want to delete them anyway?}}"
-msgstr "{0, plural, one {This execution environment is currently being used by other resources. Are you sure you want to delete it?} other {These execution environments could be in use by other resources that rely on them. Are you sure you want to delete them anyway?}}"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:182
-msgid "{0, plural, one {This instance group is currently being by other resources. Are you sure you want to delete it?} other {Deleting these instance groups could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This instance group is currently being by other resources. Are you sure you want to delete it?} other {Deleting these instance groups could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:85
-msgid "{0, plural, one {This intance is currently being used by other resources. Are you sure you want to delete it?} other {Deprovisioning these instances could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This intance is currently being used by other resources. Are you sure you want to delete it?} other {Deprovisioning these instances could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Inventory/InventoryList/InventoryList.js:240
-msgid "{0, plural, one {This inventory is currently being used by some templates. Are you sure you want to delete it?} other {Deleting these inventories could impact some templates that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This inventory is currently being used by some templates. Are you sure you want to delete it?} other {Deleting these inventories could impact some templates that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:196
-msgid "{0, plural, one {This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?} other {Deleting these inventory sources could impact other resources that rely on them. Are you sure you want to delete anyway}}"
-msgstr "{0, plural, one {This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?} other {Deleting these inventory sources could impact other resources that rely on them. Are you sure you want to delete anyway}}"
-
-#: screens/Organization/OrganizationList/OrganizationList.js:166
-msgid "{0, plural, one {This organization is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these organizations could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This organization is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these organizations could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Project/ProjectList/ProjectList.js:252
-msgid "{0, plural, one {This project is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these projects could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This project is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these projects could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:209
-#: components/TemplateList/TemplateList.js:266
-msgid "{0, plural, one {This template is currently being used by some workflow nodes. Are you sure you want to delete it?} other {Deleting these templates could impact some workflow nodes that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This template is currently being used by some workflow nodes. Are you sure you want to delete it?} other {Deleting these templates could impact some workflow nodes that rely on them. Are you sure you want to delete anyway?}}"
-
-#: components/JobList/JobListCancelButton.js:72
-msgid "{0, plural, one {You cannot cancel the following job because it is not running:} other {You cannot cancel the following jobs because they are not running:}}"
-msgstr "{0, plural, one {You cannot cancel the following job because it is not running:} other {You cannot cancel the following jobs because they are not running:}}"
-
-#: components/JobList/JobListCancelButton.js:56
-msgid "{0, plural, one {You do not have permission to cancel the following job:} other {You do not have permission to cancel the following jobs:}}"
-msgstr "{0, plural, one {You do not have permission to cancel the following job:} other {You do not have permission to cancel the following jobs:}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:143
-msgid "{0, selectordinal, one {The first {dayOfWeek}} two {The second {dayOfWeek}} =3 {The third {dayOfWeek}} =4 {The fourth {dayOfWeek}} =5 {The fifth {dayOfWeek}}}"
-msgstr "{0, selectordinal, one {The first {dayOfWeek}} two {The second {dayOfWeek}} =3 {The third {dayOfWeek}} =4 {The fourth {dayOfWeek}} =5 {The fifth {dayOfWeek}}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:202
-msgid "{0, selectordinal, one {The first {weekday} of {month}} two {The second {weekday} of {month}} =3 {The third {weekday} of {month}} =4 {The fourth {weekday} of {month}} =5 {The fifth {weekday} of {month}}}"
-msgstr "{0, selectordinal, one {The first {weekday} of {month}} two {The second {weekday} of {month}} =3 {The third {weekday} of {month}} =4 {The fourth {weekday} of {month}} =5 {The fifth {weekday} of {month}}}"
-
-#: screens/ActivityStream/ActivityStreamListItem.js:28
-msgid "{0} (deleted)"
-msgstr "{0} (削除済み)"
-
-#: components/ChipGroup/ChipGroup.js:13
-msgid "{0} more"
-msgstr "{0} 以上"
-
-#: screens/Job/JobDetail/JobDetail.js:399
-msgid "{0} seconds"
-msgstr "{0} 秒"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:95
-msgid "{automatedInstancesCount} since {automatedInstancesSinceDateTime}"
-msgstr "{automatedInstancesSinceDateTime} 以来 {automatedInstancesCount}"
-
-#: components/AppContainer/AppContainer.js:55
-msgid "{brandName} logo"
-msgstr "{brandName} ロゴ"
-
-#: components/DetailList/UserDateDetail.js:23
-msgid "{dateStr} by <0>{username}0>"
-msgstr "{dateStr} (<0>{username}0> による)"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:231
-#: screens/InstanceGroup/Instances/InstanceListItem.js:148
-#: screens/Instances/InstanceDetail/InstanceDetail.js:274
-#: screens/Instances/InstanceList/InstanceListItem.js:158
-#: screens/TopologyView/Tooltip.js:288
-msgid "{forks, plural, one {# fork} other {# forks}}"
-msgstr "{forks, plural, one {# fork} other {# forks}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:41
-msgid "{interval, plural, one {{interval} day} other {{interval} days}}"
-msgstr "{interval, plural, one {{interval} day} other {{interval} days}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:33
-msgid "{interval, plural, one {{interval} hour} other {{interval} hours}}"
-msgstr "{interval, plural, one {{interval} hour} other {{interval} hours}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:25
-msgid "{interval, plural, one {{interval} minute} other {{interval} minutes}}"
-msgstr "{interval, plural, one {{interval} minute} other {{interval} minutes}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:57
-msgid "{interval, plural, one {{interval} month} other {{interval} months}}"
-msgstr "{interval, plural, one {{interval} month} other {{interval} months}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:49
-msgid "{interval, plural, one {{interval} week} other {{interval} weeks}}"
-msgstr "{interval, plural, one {{interval} week} other {{interval} weeks}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:65
-msgid "{interval, plural, one {{interval} year} other {{interval} years}}"
-msgstr "{interval, plural, one {{interval} year} other {{interval} years}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:198
-msgid "{intervalValue, plural, one {day} other {days}}"
-msgstr "{intervalValue, plural, one {day} other {days}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:196
-msgid "{intervalValue, plural, one {hour} other {hours}}"
-msgstr "{intervalValue, plural, one {hour} other {hours}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:194
-msgid "{intervalValue, plural, one {minute} other {minutes}}"
-msgstr "{intervalValue, plural, one {minute} other {minutes}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:202
-msgid "{intervalValue, plural, one {month} other {months}}"
-msgstr "{intervalValue, plural, one {month} other {months}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:200
-msgid "{intervalValue, plural, one {week} other {weeks}}"
-msgstr "{intervalValue, plural, one {week} other {weeks}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:204
-msgid "{intervalValue, plural, one {year} other {years}}"
-msgstr "{intervalValue, plural, one {year} other {years}}"
-
-#: components/PromptDetail/PromptDetail.js:44
-msgid "{minutes} min {seconds} sec"
-msgstr "{minutes} 分 {seconds} 秒"
-
-#: components/JobList/JobListCancelButton.js:106
-msgid "{numJobsToCancel, plural, one {Cancel job} other {Cancel jobs}}"
-msgstr "{numJobsToCancel, plural, one {Cancel job} other {Cancel jobs}}"
-
-#: components/JobList/JobListCancelButton.js:168
-msgid "{numJobsToCancel, plural, one {This action will cancel the following job:} other {This action will cancel the following jobs:}}"
-msgstr "{numJobsToCancel, plural, one {This action will cancel the following job:} other {This action will cancel the following jobs:}}"
-
-#: components/JobList/JobListCancelButton.js:91
-msgid "{numJobsToCancel, plural, one {{0}} other {{1}}}"
-msgstr "{numJobsToCancel, plural, one {{0}} other {{1}}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:226
-msgid "{numOccurrences, plural, one {After {numOccurrences} occurrence} other {After {numOccurrences} occurrences}}"
-msgstr "{numOccurrences, plural, one {After {numOccurrences} occurrence} other {After {numOccurrences} occurrences}}"
-
-#: components/PaginatedTable/PaginatedTable.js:79
-msgid "{pluralizedItemName} List"
-msgstr "{pluralizedItemName} 一覧"
-
-#: components/HealthCheckButton/HealthCheckButton.js:13
-msgid "{selectedItemsCount, plural, one {Click to run a health check on the selected instance.} other {Click to run a health check on the selected instances.}}"
-msgstr "{selectedItemsCount, plural, one {Click to run a health check on the selected instance.} other {Click to run a health check on the selected instances.}}"
-
-#: components/AppContainer/AppContainer.js:154
-msgid "{sessionCountdown, plural, one {You will be logged out in # second due to inactivity} other {You will be logged out in # seconds due to inactivity}}"
-msgstr "{sessionCountdown, plural, one {You will be logged out in # second due to inactivity} other {You will be logged out in # seconds due to inactivity}}"
-
diff --git a/awx/ui/src/locales/ko/messages.po b/awx/ui/src/locales/ko/messages.po
deleted file mode 100644
index 00ee18f22b5b..000000000000
--- a/awx/ui/src/locales/ko/messages.po
+++ /dev/null
@@ -1,10699 +0,0 @@
-msgid ""
-msgstr ""
-"POT-Creation-Date: 2018-12-10 10:08-0500\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: @lingui/cli\n"
-"Language: en\n"
-"Project-Id-Version: \n"
-"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: \n"
-"Last-Translator: \n"
-"Language-Team: \n"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:43
-msgid "(Limited to first 10)"
-msgstr "(상위 10개로 제한)"
-
-#: components/TemplateList/TemplateListItem.js:103
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:161
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:90
-msgid "(Prompt on launch)"
-msgstr "(실행 시 프롬프트)"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:283
-msgid "* This field will be retrieved from an external secret management system using the specified credential."
-msgstr "*이 필드는 지정된 인증 정보를 사용하여 외부 보안 관리 시스템에서 검색됩니다."
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:228
-msgid "/ (project root)"
-msgstr "/ (프로젝트 root)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:10
-msgid "0 (Normal)"
-msgstr "0 (정상)"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:38
-msgid "0 (Warning)"
-msgstr "0 (경고)"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:39
-msgid "1 (Info)"
-msgstr "1 (정보)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:11
-msgid "1 (Verbose)"
-msgstr "1 (상세 정보)"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:40
-msgid "2 (Debug)"
-msgstr "2 (디버그)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:12
-msgid "2 (More Verbose)"
-msgstr "2 (자세한 내용)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:13
-msgid "3 (Debug)"
-msgstr "3 (디버그)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:14
-msgid "4 (Connection Debug)"
-msgstr "4 (연결 디버그)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:15
-msgid "5 (WinRM Debug)"
-msgstr "5 (WinRM 디버그)"
-
-#: screens/Project/shared/Project.helptext.js:76
-msgid ""
-"A refspec to fetch (passed to the Ansible git\n"
-"module). This parameter allows access to references via\n"
-"the branch field not otherwise available."
-msgstr "가져올 refspec(Ansible git 모듈에 전달됨)입니다. 이 매개변수를 사용하면 분기 필드를 통해 다른 방법으로는 사용할 수 없는 참조에 액세스할 수 있습니다."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:122
-msgid "A subscription manifest is an export of a Red Hat Subscription. To generate a subscription manifest, go to <0>access.redhat.com0>. For more information, see the <1>User Guide1>."
-msgstr "서브스크립션 목록은 Red Hat 서브스크립션의 내보내기입니다. 서브스크립션 목록을 생성하려면 <0>access.redhat.com0>로 이동하십시오. 자세한 내용은 <1>사용자 가이드1> 를 참조하십시오."
-
-#: screens/Job/WorkflowOutput/WorkflowOutputNode.js:143
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:326
-msgid "ALL"
-msgstr "전체"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:284
-msgid "API Service/Integration Key"
-msgstr "API 서비스/통합 키"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:291
-msgid "API Token"
-msgstr "API 토큰"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:306
-msgid "API service/integration key"
-msgstr "API 서비스/통합 키"
-
-#: components/AppContainer/PageHeaderToolbar.js:125
-msgid "About"
-msgstr "정보"
-
-#: routeConfig.js:92
-#: screens/ActivityStream/ActivityStream.js:179
-#: screens/Credential/Credential.js:74
-#: screens/Credential/Credentials.js:29
-#: screens/Inventory/Inventories.js:59
-#: screens/Inventory/Inventory.js:65
-#: screens/Inventory/SmartInventory.js:67
-#: screens/Organization/Organization.js:124
-#: screens/Organization/Organizations.js:31
-#: screens/Project/Project.js:105
-#: screens/Project/Projects.js:27
-#: screens/Team/Team.js:58
-#: screens/Team/Teams.js:31
-#: screens/Template/Template.js:136
-#: screens/Template/Templates.js:45
-#: screens/Template/WorkflowJobTemplate.js:118
-msgid "Access"
-msgstr "액세스"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:75
-msgid "Access Token Expiration"
-msgstr "액세스 토큰 만료"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:352
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:421
-msgid "Account SID"
-msgstr "계정 SID"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:396
-msgid "Account token"
-msgstr "계정 토큰"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:50
-msgid "Action"
-msgstr "동작"
-
-#: components/JobList/JobList.js:249
-#: components/JobList/JobListItem.js:103
-#: components/RelatedTemplateList/RelatedTemplateList.js:189
-#: components/Schedule/ScheduleList/ScheduleList.js:172
-#: components/Schedule/ScheduleList/ScheduleListItem.js:128
-#: components/SelectedList/DraggableSelectedList.js:101
-#: components/TemplateList/TemplateList.js:246
-#: components/TemplateList/TemplateListItem.js:195
-#: screens/ActivityStream/ActivityStream.js:266
-#: screens/ActivityStream/ActivityStreamListItem.js:49
-#: screens/Application/ApplicationsList/ApplicationListItem.js:48
-#: screens/Application/ApplicationsList/ApplicationsList.js:160
-#: screens/Credential/CredentialList/CredentialList.js:166
-#: screens/Credential/CredentialList/CredentialListItem.js:66
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:177
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:38
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:168
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:87
-#: screens/Host/HostGroups/HostGroupItem.js:34
-#: screens/Host/HostGroups/HostGroupsList.js:177
-#: screens/Host/HostList/HostList.js:172
-#: screens/Host/HostList/HostListItem.js:70
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:200
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:75
-#: screens/InstanceGroup/Instances/InstanceList.js:271
-#: screens/InstanceGroup/Instances/InstanceListItem.js:171
-#: screens/Instances/InstanceList/InstanceList.js:206
-#: screens/Instances/InstanceList/InstanceListItem.js:183
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:218
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:53
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:39
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:142
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:41
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:187
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:44
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:140
-#: screens/Inventory/InventoryList/InventoryList.js:222
-#: screens/Inventory/InventoryList/InventoryListItem.js:131
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:233
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:44
-#: screens/Inventory/InventorySources/InventorySourceList.js:214
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:101
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:102
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:73
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:181
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:124
-#: screens/Organization/OrganizationList/OrganizationList.js:146
-#: screens/Organization/OrganizationList/OrganizationListItem.js:69
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:86
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:19
-#: screens/Project/ProjectList/ProjectList.js:225
-#: screens/Project/ProjectList/ProjectListItem.js:222
-#: screens/Team/TeamList/TeamList.js:144
-#: screens/Team/TeamList/TeamListItem.js:47
-#: screens/Template/Survey/SurveyList.js:105
-#: screens/Template/Survey/SurveyList.js:105
-#: screens/Template/Survey/SurveyListItem.js:90
-#: screens/User/UserList/UserList.js:164
-#: screens/User/UserList/UserListItem.js:56
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:167
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:78
-msgid "Actions"
-msgstr "동작"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:98
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:61
-#: components/TemplateList/TemplateListItem.js:277
-#: screens/Host/HostDetail/HostDetail.js:71
-#: screens/Host/HostList/HostListItem.js:95
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:217
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:50
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:76
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:100
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:32
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:115
-msgid "Activity"
-msgstr "활동"
-
-#: routeConfig.js:49
-#: screens/ActivityStream/ActivityStream.js:43
-#: screens/ActivityStream/ActivityStream.js:121
-#: screens/Setting/Settings.js:44
-msgid "Activity Stream"
-msgstr "활동 스트림"
-
-#: screens/ActivityStream/ActivityStream.js:124
-msgid "Activity Stream type selector"
-msgstr "활동 스트림 유형 선택기"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:169
-msgid "Actor"
-msgstr "작업자"
-
-#: components/AddDropDownButton/AddDropDownButton.js:40
-#: components/PaginatedTable/ToolbarAddButton.js:14
-msgid "Add"
-msgstr "추가"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkAddModal.js:14
-msgid "Add Link"
-msgstr "링크 추가"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.js:78
-msgid "Add Node"
-msgstr "노드 추가"
-
-#: screens/Template/Templates.js:49
-msgid "Add Question"
-msgstr "질문 추가"
-
-#: components/AddRole/AddResourceRole.js:164
-msgid "Add Roles"
-msgstr "역할 추가"
-
-#: components/AddRole/AddResourceRole.js:161
-msgid "Add Team Roles"
-msgstr "팀 역할 추가"
-
-#: components/AddRole/AddResourceRole.js:158
-msgid "Add User Roles"
-msgstr "사용자 역할 추가"
-
-#: components/Workflow/WorkflowStartNode.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:238
-msgid "Add a new node"
-msgstr "새 노드 추가"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:49
-msgid "Add a new node between these two nodes"
-msgstr "두 노드 사이에 새 노드 추가"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:108
-msgid "Add container group"
-msgstr "컨테이너 그룹 추가"
-
-#: components/Schedule/shared/ScheduleFormFields.js:171
-msgid "Add exceptions"
-msgstr "예외 추가"
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:140
-msgid "Add existing group"
-msgstr "기존 그룹 추가"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:152
-msgid "Add existing host"
-msgstr "기존 호스트 추가"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:109
-msgid "Add instance group"
-msgstr "인스턴스 그룹 추가"
-
-#: screens/Inventory/InventoryList/InventoryList.js:136
-msgid "Add inventory"
-msgstr "인벤토리 추가"
-
-#: components/TemplateList/TemplateList.js:151
-msgid "Add job template"
-msgstr "작업 템플릿 추가"
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:141
-msgid "Add new group"
-msgstr "새 그룹 추가"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:153
-msgid "Add new host"
-msgstr "새 호스트 추가"
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:77
-msgid "Add resource type"
-msgstr "리소스 유형 추가"
-
-#: screens/Inventory/InventoryList/InventoryList.js:137
-msgid "Add smart inventory"
-msgstr "스마트 인벤토리 추가"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:202
-msgid "Add team permissions"
-msgstr "팀 권한 추가"
-
-#: screens/User/UserRoles/UserRolesList.js:198
-msgid "Add user permissions"
-msgstr "사용자 권한 추가"
-
-#: components/TemplateList/TemplateList.js:152
-msgid "Add workflow template"
-msgstr "워크플로우 템플릿 추가"
-
-#: screens/TopologyView/Legend.js:269
-msgid "Adding"
-msgstr "추가 중"
-
-#: routeConfig.js:113
-#: screens/ActivityStream/ActivityStream.js:190
-msgid "Administration"
-msgstr "관리"
-
-#: components/DataListToolbar/DataListToolbar.js:139
-#: screens/Job/JobOutput/JobOutputSearch.js:136
-msgid "Advanced"
-msgstr "고급"
-
-#: components/Search/AdvancedSearch.js:318
-msgid "Advanced search documentation"
-msgstr "고급 검색 설명서"
-
-#: components/Search/AdvancedSearch.js:211
-#: components/Search/AdvancedSearch.js:225
-msgid "Advanced search value input"
-msgstr "고급 검색 값 입력"
-
-#: screens/Inventory/shared/Inventory.helptext.js:131
-msgid ""
-"After every project update where the SCM revision\n"
-"changes, refresh the inventory from the selected source\n"
-"before executing job tasks. This is intended for static content,\n"
-"like the Ansible inventory .ini file format."
-msgstr "SCM 버전 변경으로 인한 프로젝트를 업데이트한 후 작업 작업을 실행하기 전에 선택한 소스에서 인벤토리를 새로 고칩니다. 이는 Ansible 인벤토리 .ini 파일 형식과 같은 정적 콘텐츠를 위한 것입니다."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:524
-msgid "After number of occurrences"
-msgstr "발생 횟수 이후"
-
-#: components/AlertModal/AlertModal.js:75
-msgid "Alert modal"
-msgstr "경고 모달"
-
-#: components/LaunchButton/ReLaunchDropDown.js:48
-#: components/PromptDetail/PromptDetail.js:132
-#: screens/Metrics/Metrics.js:82
-#: screens/Metrics/Metrics.js:82
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:266
-msgid "All"
-msgstr "모두"
-
-#: screens/Dashboard/DashboardGraph.js:137
-msgid "All job types"
-msgstr "모든 작업 유형"
-
-#: screens/Dashboard/DashboardGraph.js:162
-msgid "All jobs"
-msgstr "모든 작업"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:101
-msgid "Allow Branch Override"
-msgstr "분기 덮어쓰기 허용"
-
-#: components/PromptDetail/PromptProjectDetail.js:66
-#: screens/Project/ProjectDetail/ProjectDetail.js:121
-msgid "Allow branch override"
-msgstr "분기 덮어쓰기 허용"
-
-#: screens/Project/shared/Project.helptext.js:126
-msgid ""
-"Allow changing the Source Control branch or revision in a job\n"
-"template that uses this project."
-msgstr "이 프로젝트를 사용하는 작업 템플릿에서 소스 제어 분기 또는 버전 변경을 허용합니다."
-
-#: screens/Application/shared/Application.helptext.js:6
-msgid "Allowed URIs list, space separated"
-msgstr "공백으로 구분된 허용된 URI 목록"
-
-#: components/Workflow/WorkflowLegend.js:130
-#: components/Workflow/WorkflowLinkHelp.js:24
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:58
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:46
-msgid "Always"
-msgstr "항상"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:98
-msgid "Amazon EC2"
-msgstr "Amazon EC2"
-
-#: components/Lookup/shared/LookupErrorMessage.js:12
-msgid "An error occurred"
-msgstr "오류가 발생했습니다."
-
-#: components/LaunchPrompt/steps/useInventoryStep.js:35
-msgid "An inventory must be selected"
-msgstr "인벤토리를 선택해야 함"
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:96
-msgid "Ansible Controller Documentation."
-msgstr "Ansible 컨트롤러 설명서"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:43
-msgid "Answer type"
-msgstr "응답 유형"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:177
-msgid "Answer variable name"
-msgstr "응답 변수 이름"
-
-#: components/PromptDetail/PromptDetail.js:132
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:263
-msgid "Any"
-msgstr "모든"
-
-#: components/Lookup/ApplicationLookup.js:83
-#: screens/User/UserTokenDetail/UserTokenDetail.js:39
-#: screens/User/shared/UserTokenForm.js:48
-msgid "Application"
-msgstr "애플리케이션"
-
-#: screens/User/UserTokenList/UserTokenList.js:187
-msgid "Application Name"
-msgstr "애플리케이션 이름"
-
-#: screens/Application/Applications.js:67
-#: screens/Application/Applications.js:70
-msgid "Application information"
-msgstr "애플리케이션 정보"
-
-#: screens/User/UserTokenList/UserTokenList.js:123
-#: screens/User/UserTokenList/UserTokenList.js:134
-msgid "Application name"
-msgstr "애플리케이션 이름"
-
-#: screens/Application/Application/Application.js:95
-msgid "Application not found."
-msgstr "애플리케이션을 찾을 수 없습니다."
-
-#: components/Lookup/ApplicationLookup.js:96
-#: routeConfig.js:142
-#: screens/Application/Applications.js:26
-#: screens/Application/Applications.js:35
-#: screens/Application/ApplicationsList/ApplicationsList.js:113
-#: screens/Application/ApplicationsList/ApplicationsList.js:148
-#: util/getRelatedResourceDeleteDetails.js:209
-msgid "Applications"
-msgstr "애플리케이션"
-
-#: screens/ActivityStream/ActivityStream.js:211
-msgid "Applications & Tokens"
-msgstr "애플리케이션 및 토큰"
-
-#: components/NotificationList/NotificationListItem.js:39
-#: components/NotificationList/NotificationListItem.js:40
-#: components/Workflow/WorkflowLegend.js:114
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:67
-msgid "Approval"
-msgstr "승인"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:82
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:44
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:47
-msgid "Approve"
-msgstr "승인"
-
-#: components/StatusLabel/StatusLabel.js:39
-msgid "Approved"
-msgstr "승인됨"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:11
-msgid "Approved - {0}. See the Activity Stream for more information."
-msgstr "승인 - {0} 자세한 내용은 활동 스트림을 참조하십시오."
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:7
-msgid "Approved by {0} - {1}"
-msgstr "{0} - {1}에 승인"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:162
-#: components/Schedule/shared/FrequencyDetailSubform.js:113
-msgid "April"
-msgstr "4월"
-
-#: components/JobCancelButton/JobCancelButton.js:104
-msgid "Are you sure you want to cancel this job?"
-msgstr "이 작업을 취소하시겠습니까?"
-
-#: components/DeleteButton/DeleteButton.js:127
-msgid "Are you sure you want to delete:"
-msgstr "삭제하시겠습니까"
-
-#: screens/Setting/shared/SharedFields.js:142
-msgid "Are you sure you want to disable local authentication? Doing so could impact users' ability to log in and the system administrator's ability to reverse this change."
-msgstr "로컬 인증을 비활성화하시겠습니까? 이렇게 하면 로그인할 수 있는 사용자와 시스템 관리자가 이러한 변경을 취소할 수 있습니다."
-
-#: screens/Setting/shared/SharedFields.js:350
-msgid "Are you sure you want to edit login redirect override URL? Doing so could impact users' ability to log in to the system once local authentication is also disabled."
-msgstr "로그인 리디렉션 재정의 URL을 편집하시겠습니까? 편집하는 경우 로컬 인증이 비활성화되어 있는 동안 사용자가 시스템에 로그인하는 데 영향을 미칠 수 있습니다."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:41
-msgid "Are you sure you want to exit the Workflow Creator without saving your changes?"
-msgstr "변경 사항을 저장하지 않고 Workflow Creator를 종료하시겠습니까?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:40
-msgid "Are you sure you want to remove all the nodes in this workflow?"
-msgstr "이 워크플로우에서 모든 노드를 제거하시겠습니까?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:56
-msgid "Are you sure you want to remove the node below:"
-msgstr "아래 노드를 삭제하시겠습니까."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:43
-msgid "Are you sure you want to remove this link?"
-msgstr "이 링크를 삭제하시겠습니까?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:63
-msgid "Are you sure you want to remove this node?"
-msgstr "이 노드를 삭제하시겠습니까?"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:43
-msgid "Are you sure you want to remove {0} access from {1}? Doing so affects all members of the team."
-msgstr "{1}에서 {0} 액세스 권한을 삭제하시겠습니까? 이렇게 하면 팀의 모든 구성원에게 영향을 미칩니다."
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:50
-msgid "Are you sure you want to remove {0} access from {username}?"
-msgstr "{username} 에서 {0} 액세스 권한을 삭제하시겠습니까?"
-
-#: screens/Job/JobOutput/JobOutput.js:826
-msgid "Are you sure you want to submit the request to cancel this job?"
-msgstr "이 작업을 취소하기 위한 요청을 제출하시겠습니까?"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:102
-#: components/AdHocCommands/AdHocDetailsStep.js:104
-msgid "Arguments"
-msgstr "인수"
-
-#: screens/Job/JobDetail/JobDetail.js:559
-msgid "Artifacts"
-msgstr "아티팩트"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:233
-#: screens/User/UserTeams/UserTeamList.js:208
-msgid "Associate"
-msgstr "연결"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:244
-#: screens/User/UserRoles/UserRolesList.js:240
-msgid "Associate role error"
-msgstr "역할 연결 오류"
-
-#: components/AssociateModal/AssociateModal.js:98
-msgid "Association modal"
-msgstr "연결 모달"
-
-#: components/LaunchPrompt/steps/SurveyStep.js:168
-msgid "At least one value must be selected for this field."
-msgstr "이 필드에 대해 하나 이상의 값을 선택해야 합니다."
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:166
-#: components/Schedule/shared/FrequencyDetailSubform.js:133
-msgid "August"
-msgstr "8월"
-
-#: screens/Setting/SettingList.js:52
-msgid "Authentication"
-msgstr "인증"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:88
-msgid "Authorization Code Expiration"
-msgstr "인증 코드 만료"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:81
-#: screens/Application/shared/ApplicationForm.js:85
-msgid "Authorization grant type"
-msgstr "인증 권한 부여 유형"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:215
-#: screens/InstanceGroup/Instances/InstanceListItem.js:204
-#: screens/Instances/InstanceDetail/InstanceDetail.js:209
-#: screens/Instances/InstanceList/InstanceListItem.js:219
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:83
-msgid "Auto"
-msgstr "Auto"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:71
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:72
-msgid "Automation Analytics"
-msgstr "자동화 분석"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:111
-msgid "Automation Analytics dashboard"
-msgstr "자동화 분석 대시보드"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:175
-msgid "Automation controller version"
-msgstr "Automation Controller 버전"
-
-#: screens/Setting/Settings.js:47
-msgid "Azure AD"
-msgstr "Azure AD"
-
-#: screens/Setting/SettingList.js:57
-msgid "Azure AD settings"
-msgstr "Azure AD 설정"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:49
-#: components/AddRole/AddResourceRole.js:267
-#: components/LaunchPrompt/LaunchPrompt.js:158
-#: components/Schedule/shared/SchedulePromptableFields.js:125
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:90
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:70
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:154
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:157
-msgid "Back"
-msgstr "뒤로"
-
-#: screens/Credential/Credential.js:65
-msgid "Back to Credentials"
-msgstr "인증 정보로 돌아가기"
-
-#: components/ContentError/ContentError.js:43
-msgid "Back to Dashboard."
-msgstr "대시보드로 돌아가기"
-
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:49
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:50
-msgid "Back to Groups"
-msgstr "그룹으로 돌아가기"
-
-#: screens/Host/Host.js:50
-#: screens/Inventory/InventoryHost/InventoryHost.js:66
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:53
-msgid "Back to Hosts"
-msgstr "호스트로 돌아가기"
-
-#: screens/InstanceGroup/InstanceGroup.js:61
-msgid "Back to Instance Groups"
-msgstr "인스턴스 그룹으로 돌아가기"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:173
-#: screens/Instances/Instance.js:22
-msgid "Back to Instances"
-msgstr "인스턴스로 돌아가기"
-
-#: screens/Inventory/Inventory.js:57
-#: screens/Inventory/SmartInventory.js:60
-msgid "Back to Inventories"
-msgstr "인벤토리로 돌아가기"
-
-#: screens/Job/Job.js:123
-msgid "Back to Jobs"
-msgstr "작업으로 돌아가기"
-
-#: screens/NotificationTemplate/NotificationTemplate.js:76
-msgid "Back to Notifications"
-msgstr "알림으로 돌아가기"
-
-#: screens/Organization/Organization.js:116
-msgid "Back to Organizations"
-msgstr "조직으로 돌아가기"
-
-#: screens/Project/Project.js:97
-msgid "Back to Projects"
-msgstr "프로젝트로 돌아가기"
-
-#: components/Schedule/Schedule.js:64
-msgid "Back to Schedules"
-msgstr "일정으로 돌아가기"
-
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:44
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:78
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:44
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:58
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:95
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:69
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:43
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:90
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:44
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:49
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:45
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:34
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:49
-#: screens/Setting/UI/UIDetail/UIDetail.js:59
-msgid "Back to Settings"
-msgstr "설정으로 돌아가기"
-
-#: screens/Inventory/InventorySource/InventorySource.js:76
-msgid "Back to Sources"
-msgstr "출처로 돌아가기"
-
-#: screens/Team/Team.js:50
-msgid "Back to Teams"
-msgstr "팀으로 돌아가기"
-
-#: screens/Template/Template.js:128
-#: screens/Template/WorkflowJobTemplate.js:110
-msgid "Back to Templates"
-msgstr "템플릿으로 돌아가기"
-
-#: screens/User/UserToken/UserToken.js:47
-msgid "Back to Tokens"
-msgstr "토큰으로 돌아가기"
-
-#: screens/User/User.js:57
-msgid "Back to Users"
-msgstr "사용자로 돌아가기"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:69
-msgid "Back to Workflow Approvals"
-msgstr "워크플로우 승인으로 돌아가기"
-
-#: screens/Application/Application/Application.js:72
-msgid "Back to applications"
-msgstr "애플리케이션으로 돌아가기"
-
-#: screens/CredentialType/CredentialType.js:55
-msgid "Back to credential types"
-msgstr "인증 정보 유형으로 돌아가기"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:57
-msgid "Back to execution environments"
-msgstr "실행 환경으로 돌아가기"
-
-#: screens/InstanceGroup/ContainerGroup.js:59
-msgid "Back to instance groups"
-msgstr "인스턴스 그룹으로 돌아가기"
-
-#: screens/ManagementJob/ManagementJob.js:98
-msgid "Back to management jobs"
-msgstr "관리 작업으로 돌아가기"
-
-#: screens/Project/shared/Project.helptext.js:8
-msgid ""
-"Base path used for locating playbooks. Directories\n"
-"found inside this path will be listed in the playbook directory drop-down.\n"
-"Together the base path and selected playbook directory provide the full\n"
-"path used to locate playbooks."
-msgstr "플레이북을 찾는 데 사용되는 기본 경로입니다. 이 경로 내에 있는 디렉터리가 플레이북 디렉터리 드롭다운에 나열됩니다. 기본 경로 및 선택한 플레이북 디렉터리를 사용하면 플레이북을 찾는 데 사용되는 전체 경로가 제공됩니다."
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:448
-msgid "Basic auth password"
-msgstr "기본 인증 암호"
-
-#: screens/Project/shared/Project.helptext.js:104
-msgid ""
-"Branch to checkout. In addition to branches,\n"
-"you can input tags, commit hashes, and arbitrary refs. Some\n"
-"commit hashes and refs may not be available unless you also\n"
-"provide a custom refspec."
-msgstr "체크아웃할 분기입니다. 분기 외에도 태그, 커밋 해시 및 임의의 refs를 입력할 수 있습니다. 사용자 정의 refspec을 제공하지 않는 한 일부 커밋 해시 및 ref를 사용할 수 없습니다."
-
-#: screens/Template/shared/JobTemplate.helptext.js:27
-msgid "Branch to use in job run. Project default used if blank. Only allowed if project allow_override field is set to true."
-msgstr "작업 실행에 사용할 분기입니다. 비어 있는 경우 프로젝트 기본값이 사용됩니다. 프로젝트 allow_override 필드가 true로 설정된 경우에만 허용됩니다."
-
-#: components/About/About.js:45
-msgid "Brand Image"
-msgstr "브랜드 이미지"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:158
-msgid "Browse"
-msgstr "검색"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:94
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:115
-msgid "Browse…"
-msgstr "검색 중..."
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:36
-msgid "By default, we collect and transmit analytics data on the service usage to Red Hat. There are two categories of data collected by the service. For more information, see <0>this Tower documentation page0>. Uncheck the following boxes to disable this feature."
-msgstr "기본적으로 Red Hat은 서비스 사용에 대한 분석 데이터를 수집하여 전송합니다. 서비스에서 수집하는 데이터에는 두 가지 카테고리가 있습니다. 자세한 내용은 <0>Tower 설명서 페이지0>를 참조하십시오. 이 기능을 비활성화하려면 다음 확인란을 선택 해제하십시오."
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:228
-#: screens/InstanceGroup/Instances/InstanceListItem.js:145
-#: screens/Instances/InstanceDetail/InstanceDetail.js:271
-#: screens/Instances/InstanceList/InstanceListItem.js:155
-#: screens/TopologyView/Tooltip.js:285
-msgid "CPU {0}"
-msgstr "CPU {0}"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:102
-#: components/PromptDetail/PromptProjectDetail.js:151
-#: screens/Project/ProjectDetail/ProjectDetail.js:268
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:118
-msgid "Cache Timeout"
-msgstr "캐시 제한 시간"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:237
-msgid "Cache timeout"
-msgstr "캐시 제한 시간"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:106
-msgid "Cache timeout (seconds)"
-msgstr "캐시 제한 시간 (초)"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:50
-#: components/AddRole/AddResourceRole.js:268
-#: components/AssociateModal/AssociateModal.js:114
-#: components/AssociateModal/AssociateModal.js:119
-#: components/DeleteButton/DeleteButton.js:120
-#: components/DeleteButton/DeleteButton.js:123
-#: components/DisassociateButton/DisassociateButton.js:139
-#: components/DisassociateButton/DisassociateButton.js:142
-#: components/FormActionGroup/FormActionGroup.js:23
-#: components/FormActionGroup/FormActionGroup.js:29
-#: components/LaunchPrompt/LaunchPrompt.js:159
-#: components/Lookup/HostFilterLookup.js:388
-#: components/Lookup/Lookup.js:209
-#: components/PaginatedTable/ToolbarDeleteButton.js:282
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:37
-#: components/Schedule/shared/ScheduleForm.js:548
-#: components/Schedule/shared/ScheduleForm.js:553
-#: components/Schedule/shared/SchedulePromptableFields.js:126
-#: components/Schedule/shared/UnsupportedScheduleForm.js:22
-#: components/Schedule/shared/UnsupportedScheduleForm.js:27
-#: screens/Credential/shared/CredentialForm.js:343
-#: screens/Credential/shared/CredentialForm.js:348
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:100
-#: screens/Credential/shared/ExternalTestModal.js:98
-#: screens/Instances/Shared/RemoveInstanceButton.js:174
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:111
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:63
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:66
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:80
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:101
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:107
-#: screens/Setting/shared/RevertAllAlert.js:32
-#: screens/Setting/shared/RevertFormActionGroup.js:31
-#: screens/Setting/shared/RevertFormActionGroup.js:37
-#: screens/Setting/shared/SharedFields.js:133
-#: screens/Setting/shared/SharedFields.js:139
-#: screens/Setting/shared/SharedFields.js:346
-#: screens/Team/TeamRoles/TeamRolesList.js:228
-#: screens/Team/TeamRoles/TeamRolesList.js:231
-#: screens/Template/Survey/SurveyList.js:78
-#: screens/Template/Survey/SurveyReorderModal.js:211
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:31
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:39
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:45
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:50
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:164
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:167
-#: screens/User/UserRoles/UserRolesList.js:224
-#: screens/User/UserRoles/UserRolesList.js:227
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:104
-msgid "Cancel"
-msgstr "취소"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:300
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:112
-msgid "Cancel Inventory Source Sync"
-msgstr "인벤토리 소스 동기화 취소"
-
-#: components/JobCancelButton/JobCancelButton.js:69
-#: screens/Job/JobOutput/JobOutput.js:802
-#: screens/Job/JobOutput/JobOutput.js:803
-msgid "Cancel Job"
-msgstr "작업 취소"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:321
-#: screens/Project/ProjectList/ProjectListItem.js:230
-msgid "Cancel Project Sync"
-msgstr "프로젝트 동기화 취소"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:302
-#: screens/Project/ProjectDetail/ProjectDetail.js:323
-msgid "Cancel Sync"
-msgstr "동기화 취소"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:322
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:327
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:95
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:101
-msgid "Cancel Workflow"
-msgstr "워크플로우 취소"
-
-#: screens/Job/JobOutput/JobOutput.js:810
-#: screens/Job/JobOutput/JobOutput.js:813
-msgid "Cancel job"
-msgstr "작업 취소"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:42
-msgid "Cancel link changes"
-msgstr "링크 변경 취소"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:34
-msgid "Cancel link removal"
-msgstr "링크 삭제 취소"
-
-#: components/Lookup/Lookup.js:207
-msgid "Cancel lookup"
-msgstr "검색 취소"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:28
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:47
-msgid "Cancel node removal"
-msgstr "노드 제거 취소"
-
-#: screens/Setting/shared/RevertAllAlert.js:29
-msgid "Cancel revert"
-msgstr "되돌리기 취소"
-
-#: components/JobList/JobListCancelButton.js:93
-msgid "Cancel selected job"
-msgstr "선택한 작업 취소"
-
-#: components/JobList/JobListCancelButton.js:94
-msgid "Cancel selected jobs"
-msgstr "선택한 작업 취소"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:77
-msgid "Cancel subscription edit"
-msgstr "서브스크립션 편집 취소"
-
-#: components/JobList/JobListItem.js:113
-#: screens/Job/JobDetail/JobDetail.js:600
-#: screens/Job/JobOutput/shared/OutputToolbar.js:137
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:89
-msgid "Cancel {0}"
-msgstr "취소 {0}"
-
-#: components/JobList/JobList.js:234
-#: components/StatusLabel/StatusLabel.js:54
-#: components/Workflow/WorkflowNodeHelp.js:111
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:212
-msgid "Canceled"
-msgstr "취소됨"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:129
-msgid ""
-"Cannot enable log aggregator without providing\n"
-"logging aggregator host and logging aggregator type."
-msgstr "로깅 수집기 호스트 및 로깅 수집기 유형을 제공하지 않고 로그 수집기를 활성화할 수 없습니다."
-
-#: screens/Instances/InstanceList/InstanceList.js:199
-#: screens/Instances/InstancePeers/InstancePeerList.js:94
-msgid "Cannot run health check on hop nodes."
-msgstr "홉 노드에서 상태 점검을 실행할 수 없습니다."
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:199
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:74
-#: screens/TopologyView/Tooltip.js:312
-msgid "Capacity"
-msgstr "용량"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:225
-#: screens/InstanceGroup/Instances/InstanceList.js:269
-#: screens/InstanceGroup/Instances/InstanceListItem.js:143
-#: screens/Instances/InstanceDetail/InstanceDetail.js:267
-#: screens/Instances/InstanceList/InstanceList.js:204
-#: screens/Instances/InstanceList/InstanceListItem.js:153
-msgid "Capacity Adjustment"
-msgstr "용량 조정"
-
-#: components/Search/LookupTypeInput.js:59
-msgid "Case-insensitive version of contains"
-msgstr "대소문자를 구분하지 않는 버전을 포함합니다."
-
-#: components/Search/LookupTypeInput.js:87
-msgid "Case-insensitive version of endswith."
-msgstr "마지막에 대소문자를 구분하지 않는 버전입니다."
-
-#: components/Search/LookupTypeInput.js:45
-msgid "Case-insensitive version of exact."
-msgstr "대소문자를 구분하지 않는 동일한 버전입니다."
-
-#: components/Search/LookupTypeInput.js:100
-msgid "Case-insensitive version of regex."
-msgstr "대소문자를 구분하지 않는 정규식 버전입니다."
-
-#: components/Search/LookupTypeInput.js:73
-msgid "Case-insensitive version of startswith."
-msgstr "처음에 대소문자를 구분하지 않는 버전입니다."
-
-#: screens/Project/shared/Project.helptext.js:14
-msgid ""
-"Change PROJECTS_ROOT when deploying\n"
-"{brandName} to change this location."
-msgstr "PROJECTS_ROOT를 변경하여 {brandName} 배포 시 이 위치를 변경합니다."
-
-#: components/StatusLabel/StatusLabel.js:55
-#: screens/Job/JobOutput/shared/HostStatusBar.js:43
-msgid "Changed"
-msgstr "변경됨"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:53
-msgid "Changes"
-msgstr "변경 사항"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:258
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:266
-msgid "Channel"
-msgstr "채널"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:138
-#: screens/Template/shared/JobTemplateForm.js:218
-msgid "Check"
-msgstr "확인"
-
-#: components/Search/LookupTypeInput.js:134
-msgid "Check whether the given field or related object is null; expects a boolean value."
-msgstr "지정된 필드 또는 관련 개체가 null인지 여부를 확인합니다. 부울 값이 필요합니다."
-
-#: components/Search/LookupTypeInput.js:140
-msgid "Check whether the given field's value is present in the list provided; expects a comma-separated list of items."
-msgstr "지정된 필드의 값이 제공된 목록에 있는지 확인합니다. 쉼표로 구분된 항목 목록이 있어야 합니다."
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:32
-msgid "Choose a .json file"
-msgstr ".json 파일 선택"
-
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:78
-msgid "Choose a Notification Type"
-msgstr "알림 유형 선택"
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:25
-msgid "Choose a Playbook Directory"
-msgstr "Playbook 디렉토리 선택"
-
-#: screens/Project/shared/ProjectForm.js:268
-msgid "Choose a Source Control Type"
-msgstr "소스 제어 유형 선택"
-
-#: screens/Template/shared/WebhookSubForm.js:100
-msgid "Choose a Webhook Service"
-msgstr "Webhook 서비스 선택"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:131
-#: screens/Template/shared/JobTemplateForm.js:211
-msgid "Choose a job type"
-msgstr "작업 유형 선택"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:82
-msgid "Choose a module"
-msgstr "모듈 선택"
-
-#: screens/Inventory/shared/InventorySourceForm.js:139
-msgid "Choose a source"
-msgstr "소스 선택"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:490
-msgid "Choose an HTTP method"
-msgstr "HTTP 방법 선택"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:46
-msgid ""
-"Choose an answer type or format you want as the prompt for the user.\n"
-"Refer to the Ansible Controller Documentation for more additional\n"
-"information about each option."
-msgstr "사용자에 대한 프롬프트로 원하는 응답 유형 또는 형식을 선택합니다. 각 옵션에 대한 자세한 내용은 Ansible Controller 설명서를 참조하십시오."
-
-#: components/AddRole/SelectRoleStep.js:20
-msgid "Choose roles to apply to the selected resources. Note that all selected roles will be applied to all selected resources."
-msgstr "선택한 리소스에 적용할 역할을 선택합니다. 선택한 모든 역할이 선택한 모든 리소스에 적용됩니다."
-
-#: components/AddRole/SelectResourceStep.js:81
-msgid "Choose the resources that will be receiving new roles. You'll be able to select the roles to apply in the next step. Note that the resources chosen here will receive all roles chosen in the next step."
-msgstr "새 역할을 받을 리소스를 선택합니다. 다음 단계에서 적용할 역할을 선택할 수 있습니다. 여기에서 선택한 리소스에는 다음 단계에서 선택한 모든 역할이 수신됩니다."
-
-#: components/AddRole/AddResourceRole.js:174
-msgid "Choose the type of resource that will be receiving new roles. For example, if you'd like to add new roles to a set of users please choose Users and click Next. You'll be able to select the specific resources in the next step."
-msgstr "새 역할을 받을 리소스 유형을 선택합니다. 예를 들어 사용자 집합에 새 역할을 추가하려면 사용자를 선택하고 다음을 클릭합니다. 다음 단계에서 특정 리소스를 선택할 수 있습니다."
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:74
-msgid "Clean"
-msgstr "정리"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:95
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:116
-msgid "Clear"
-msgstr "지우기"
-
-#: components/DataListToolbar/DataListToolbar.js:95
-#: screens/Job/JobOutput/JobOutputSearch.js:144
-msgid "Clear all filters"
-msgstr "모든 필터 지우기"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:247
-msgid "Clear subscription"
-msgstr "서브스크립션 지우기"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:252
-msgid "Clear subscription selection"
-msgstr "서브스크립션 선택 지우기"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerGraph.js:245
-msgid "Click an available node to create a new link. Click outside the graph to cancel."
-msgstr "사용 가능한 노드를 클릭하여 새 링크를 생성합니다. 취소하려면 그래프 외부를 클릭합니다."
-
-#: screens/TopologyView/Tooltip.js:191
-msgid "Click on a node icon to display the details."
-msgstr "노드 아이콘을 클릭하여 세부 정보를 표시합니다."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:134
-msgid "Click the Edit button below to reconfigure the node."
-msgstr "노드를 재구성하려면 아래의 편집 버튼을 클릭합니다."
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:71
-msgid "Click this button to verify connection to the secret management system using the selected credential and specified inputs."
-msgstr "이 버튼을 클릭하여 선택한 인증 정보 및 지정된 입력을 사용하여 시크릿 관리 시스템에 대한 연결을 확인합니다."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:179
-msgid "Click to create a new link to this node."
-msgstr "이 노드에 대한 새 링크를 생성하려면 클릭합니다."
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:251
-msgid "Click to download bundle"
-msgstr "클릭하여 번들을 다운로드합니다"
-
-#: screens/Template/Survey/SurveyToolbar.js:64
-msgid "Click to rearrange the order of the survey questions"
-msgstr "클릭하여 설문조사 질문의 순서를 다시 정렬합니다."
-
-#: screens/Template/Survey/MultipleChoiceField.js:117
-msgid "Click to toggle default value"
-msgstr "기본값을 토글하려면 클릭합니다."
-
-#: components/Workflow/WorkflowNodeHelp.js:202
-msgid "Click to view job details"
-msgstr "작업 세부 정보를 보려면 클릭합니다."
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:89
-#: screens/Application/Applications.js:84
-msgid "Client ID"
-msgstr "클라이언트 ID"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:289
-msgid "Client Identifier"
-msgstr "클라이언트 식별자"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:314
-msgid "Client identifier"
-msgstr "클라이언트 식별자"
-
-#: screens/Application/Applications.js:97
-msgid "Client secret"
-msgstr "클라이언트 시크릿"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:100
-#: screens/Application/shared/ApplicationForm.js:127
-msgid "Client type"
-msgstr "클라이언트 유형"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:105
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:169
-msgid "Close"
-msgstr "닫기"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:123
-msgid "Close subscription modal"
-msgstr "서브스크립션 모달 닫기"
-
-#: components/CredentialChip/CredentialChip.js:11
-msgid "Cloud"
-msgstr "클라우드"
-
-#: components/ExpandCollapse/ExpandCollapse.js:41
-msgid "Collapse"
-msgstr "접기"
-
-#: screens/Job/JobOutput/PageControls.js:50
-msgid "Collapse all job events"
-msgstr "모든 작업 이벤트 축소"
-
-#: screens/Job/JobOutput/shared/JobEventLineToggle.js:39
-msgid "Collapse section"
-msgstr "섹션 축소"
-
-#: components/JobList/JobList.js:214
-#: components/JobList/JobListItem.js:45
-#: screens/Job/JobOutput/HostEventModal.js:129
-msgid "Command"
-msgstr "명령"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:66
-msgid "Compliant"
-msgstr "준수"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:68
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:36
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:132
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:58
-#: screens/Template/shared/JobTemplateForm.js:591
-msgid "Concurrent Jobs"
-msgstr "동시 작업"
-
-#: screens/Template/shared/JobTemplate.helptext.js:38
-msgid "Concurrent jobs: If enabled, simultaneous runs of this job template will be allowed."
-msgstr "동시 작업: 활성화하면 이 작업 템플릿을 동시에 실행할 수 있습니다."
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:25
-msgid "Concurrent jobs: If enabled, simultaneous runs of this workflow job template will be allowed."
-msgstr "동시 작업: 활성화하면 이 워크플로 작업 템플릿을 동시에 실행할 수 있습니다."
-
-#: screens/Setting/shared/SharedFields.js:121
-#: screens/Setting/shared/SharedFields.js:127
-#: screens/Setting/shared/SharedFields.js:336
-msgid "Confirm"
-msgstr "확인"
-
-#: components/DeleteButton/DeleteButton.js:107
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:95
-msgid "Confirm Delete"
-msgstr "삭제 확인"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:191
-msgid "Confirm Disable Local Authorization"
-msgstr "로컬 인증 비활성화 확인"
-
-#: screens/User/shared/UserForm.js:99
-msgid "Confirm Password"
-msgstr "암호 확인"
-
-#: components/JobCancelButton/JobCancelButton.js:86
-msgid "Confirm cancel job"
-msgstr "작업 취소 확인"
-
-#: components/JobCancelButton/JobCancelButton.js:90
-msgid "Confirm cancellation"
-msgstr "취소 확인"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:26
-msgid "Confirm delete"
-msgstr "삭제 확인"
-
-#: screens/User/UserRoles/UserRolesList.js:215
-msgid "Confirm disassociate"
-msgstr "연결 해제 확인"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:24
-msgid "Confirm link removal"
-msgstr "링크 삭제 확인"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:37
-msgid "Confirm node removal"
-msgstr "노드 제거 확인"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:18
-msgid "Confirm removal of all nodes"
-msgstr "모든 노드 제거 확인"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:160
-msgid "Confirm remove"
-msgstr "제거 확인"
-
-#: screens/Setting/shared/RevertAllAlert.js:20
-msgid "Confirm revert all"
-msgstr "모두 되돌리기 확인"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:91
-msgid "Confirm selection"
-msgstr "선택 확인"
-
-#: screens/Job/JobDetail/JobDetail.js:366
-msgid "Container Group"
-msgstr "컨테이너 그룹"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:47
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:57
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:68
-msgid "Container group"
-msgstr "컨테이너 그룹"
-
-#: screens/InstanceGroup/ContainerGroup.js:84
-msgid "Container group not found."
-msgstr "컨테이너 그룹을 찾을 수 없습니다."
-
-#: components/LaunchPrompt/LaunchPrompt.js:153
-#: components/Schedule/shared/SchedulePromptableFields.js:120
-msgid "Content Loading"
-msgstr "콘텐츠 로딩 중"
-
-#: components/PromptDetail/PromptProjectDetail.js:130
-#: screens/Project/ProjectDetail/ProjectDetail.js:240
-#: screens/Project/shared/ProjectForm.js:290
-msgid "Content Signature Validation Credential"
-msgstr "콘텐츠 서명 확인 인증 정보"
-
-#: components/AppContainer/AppContainer.js:142
-msgid "Continue"
-msgstr "계속"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:207
-#: screens/Instances/InstanceList/InstanceList.js:151
-msgid "Control"
-msgstr "컨트롤"
-
-#: screens/TopologyView/Legend.js:77
-msgid "Control node"
-msgstr "컨트롤 노드"
-
-#: screens/Inventory/shared/Inventory.helptext.js:79
-msgid ""
-"Control the level of output Ansible\n"
-"will produce for inventory source update jobs."
-msgstr "인벤토리 소스 업데이트 작업에 대해 Ansible에서 생성할 출력 수준을 제어합니다."
-
-#: screens/Job/Job.helptext.js:15
-#: screens/Template/shared/JobTemplate.helptext.js:16
-msgid "Control the level of output ansible will produce as the playbook executes."
-msgstr "플레이북이 실행되면 ansible이 생성되는 출력 수준을 제어합니다."
-
-#: screens/Job/JobDetail/JobDetail.js:351
-msgid "Controller Node"
-msgstr "컨트롤러 노드"
-
-#: components/PromptDetail/PromptDetail.js:130
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:225
-msgid "Convergence"
-msgstr "통합"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:256
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:257
-msgid "Convergence select"
-msgstr "통합 선택"
-
-#: components/CopyButton/CopyButton.js:40
-msgid "Copy"
-msgstr "복사"
-
-#: screens/Credential/CredentialList/CredentialListItem.js:80
-msgid "Copy Credential"
-msgstr "인증 정보 복사"
-
-#: components/CopyButton/CopyButton.js:48
-msgid "Copy Error"
-msgstr "복사 오류"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:104
-msgid "Copy Execution Environment"
-msgstr "실행 환경 복사"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:154
-msgid "Copy Inventory"
-msgstr "인벤토리 복사"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:152
-msgid "Copy Notification Template"
-msgstr "알림 템플릿 복사"
-
-#: screens/Project/ProjectList/ProjectListItem.js:262
-msgid "Copy Project"
-msgstr "프로젝트 복사"
-
-#: components/TemplateList/TemplateListItem.js:248
-msgid "Copy Template"
-msgstr "템플릿 복사"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:202
-#: screens/Project/ProjectList/ProjectListItem.js:98
-msgid "Copy full revision to clipboard."
-msgstr "클립보드에 전체 버전을 복사합니다."
-
-#: components/About/About.js:35
-msgid "Copyright"
-msgstr "저작권"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:231
-#: components/MultiSelect/TagMultiSelect.js:62
-#: screens/Credential/shared/CredentialFormFields/BecomeMethodField.js:66
-#: screens/Inventory/shared/InventoryForm.js:91
-#: screens/Template/shared/JobTemplateForm.js:396
-#: screens/Template/shared/WorkflowJobTemplateForm.js:204
-msgid "Create"
-msgstr "만들기"
-
-#: screens/Application/Applications.js:27
-#: screens/Application/Applications.js:36
-msgid "Create New Application"
-msgstr "새 애플리케이션 만들기"
-
-#: screens/Credential/Credentials.js:15
-#: screens/Credential/Credentials.js:25
-msgid "Create New Credential"
-msgstr "새 인증 정보 만들기"
-
-#: screens/Host/Hosts.js:15
-#: screens/Host/Hosts.js:24
-msgid "Create New Host"
-msgstr "새 호스트 만들기"
-
-#: screens/Template/Templates.js:18
-msgid "Create New Job Template"
-msgstr "새 작업 템플릿 만들기"
-
-#: screens/NotificationTemplate/NotificationTemplates.js:15
-#: screens/NotificationTemplate/NotificationTemplates.js:22
-msgid "Create New Notification Template"
-msgstr "새 알림 템플릿 만들기"
-
-#: screens/Organization/Organizations.js:17
-#: screens/Organization/Organizations.js:27
-msgid "Create New Organization"
-msgstr "새 조직 만들기"
-
-#: screens/Project/Projects.js:13
-#: screens/Project/Projects.js:23
-msgid "Create New Project"
-msgstr "새 프로젝트 만들기"
-
-#: screens/Inventory/Inventories.js:91
-#: screens/ManagementJob/ManagementJobs.js:24
-#: screens/Project/Projects.js:32
-#: screens/Template/Templates.js:52
-msgid "Create New Schedule"
-msgstr "새 일정 만들기"
-
-#: screens/Team/Teams.js:16
-#: screens/Team/Teams.js:26
-msgid "Create New Team"
-msgstr "새 팀 만들기"
-
-#: screens/User/Users.js:16
-#: screens/User/Users.js:27
-msgid "Create New User"
-msgstr "새 사용자 만들기"
-
-#: screens/Template/Templates.js:19
-msgid "Create New Workflow Template"
-msgstr "새 워크플로 템플릿 만들기"
-
-#: screens/Host/HostList/SmartInventoryButton.js:26
-msgid "Create a new Smart Inventory with the applied filter"
-msgstr "적용된 필터를 사용하여 새 스마트 인벤토리 만들기"
-
-#: screens/Instances/Instances.js:14
-msgid "Create new Instance"
-msgstr "새 인스턴스 만들기"
-
-#: screens/InstanceGroup/InstanceGroups.js:18
-#: screens/InstanceGroup/InstanceGroups.js:28
-msgid "Create new container group"
-msgstr "새 컨테이너 그룹 만들기"
-
-#: screens/CredentialType/CredentialTypes.js:23
-msgid "Create new credential Type"
-msgstr "새 인증 정보 유형 만들기"
-
-#: screens/CredentialType/CredentialTypes.js:14
-msgid "Create new credential type"
-msgstr "새 인증 정보 유형 만들기"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:14
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:23
-msgid "Create new execution environment"
-msgstr "새로운 실행 환경 만들기"
-
-#: screens/Inventory/Inventories.js:75
-#: screens/Inventory/Inventories.js:82
-msgid "Create new group"
-msgstr "새 그룹 만들기"
-
-#: screens/Inventory/Inventories.js:66
-#: screens/Inventory/Inventories.js:80
-msgid "Create new host"
-msgstr "새 호스트 만들기"
-
-#: screens/InstanceGroup/InstanceGroups.js:17
-#: screens/InstanceGroup/InstanceGroups.js:27
-msgid "Create new instance group"
-msgstr "새 인스턴스 그룹 만들기"
-
-#: screens/Inventory/Inventories.js:18
-msgid "Create new inventory"
-msgstr "새 인벤토리 만들기"
-
-#: screens/Inventory/Inventories.js:19
-msgid "Create new smart inventory"
-msgstr "새 스마트 인벤토리 만들기"
-
-#: screens/Inventory/Inventories.js:85
-msgid "Create new source"
-msgstr "새 소스 만들기"
-
-#: screens/User/Users.js:35
-msgid "Create user token"
-msgstr "사용자 토큰 만들기"
-
-#: components/Lookup/ApplicationLookup.js:115
-#: components/PromptDetail/PromptDetail.js:154
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:406
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:105
-#: screens/Credential/CredentialDetail/CredentialDetail.js:257
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:90
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:103
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:151
-#: screens/Host/HostDetail/HostDetail.js:86
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:67
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:93
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:173
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:43
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:81
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:277
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:149
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:46
-#: screens/Job/JobDetail/JobDetail.js:534
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:393
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:115
-#: screens/Project/ProjectDetail/ProjectDetail.js:292
-#: screens/Team/TeamDetail/TeamDetail.js:47
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:353
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:187
-#: screens/User/UserDetail/UserDetail.js:82
-#: screens/User/UserTokenDetail/UserTokenDetail.js:61
-#: screens/User/UserTokenList/UserTokenList.js:150
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:199
-msgid "Created"
-msgstr "생성됨"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:122
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:112
-#: components/AddRole/AddResourceRole.js:57
-#: components/AssociateModal/AssociateModal.js:144
-#: components/LaunchPrompt/steps/CredentialsStep.js:173
-#: components/LaunchPrompt/steps/InventoryStep.js:89
-#: components/Lookup/CredentialLookup.js:194
-#: components/Lookup/InventoryLookup.js:152
-#: components/Lookup/InventoryLookup.js:207
-#: components/Lookup/MultiCredentialsLookup.js:194
-#: components/Lookup/OrganizationLookup.js:134
-#: components/Lookup/ProjectLookup.js:151
-#: components/NotificationList/NotificationList.js:206
-#: components/RelatedTemplateList/RelatedTemplateList.js:166
-#: components/Schedule/ScheduleList/ScheduleList.js:198
-#: components/TemplateList/TemplateList.js:226
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:27
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:58
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:104
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:127
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:173
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:196
-#: screens/Credential/CredentialList/CredentialList.js:150
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:96
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:132
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:102
-#: screens/Host/HostGroups/HostGroupsList.js:164
-#: screens/Host/HostList/HostList.js:157
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:199
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:129
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:174
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:128
-#: screens/Inventory/InventoryList/InventoryList.js:199
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:185
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:94
-#: screens/Organization/OrganizationList/OrganizationList.js:131
-#: screens/Project/ProjectList/ProjectList.js:213
-#: screens/Team/TeamList/TeamList.js:130
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:161
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:112
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:108
-msgid "Created By (Username)"
-msgstr "(사용자 이름)에 의해 생성됨"
-
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:81
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:147
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:73
-msgid "Created by (username)"
-msgstr "(사용자 이름)에 의해 생성됨"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:54
-#: components/AdHocCommands/useAdHocCredentialStep.js:24
-#: components/PromptDetail/PromptInventorySourceDetail.js:107
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:40
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:52
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:50
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:258
-#: screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.js:38
-#: screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/GCESubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.js:38
-#: screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:84
-#: screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.js:36
-#: screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.js:38
-#: screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.js:38
-#: util/getRelatedResourceDeleteDetails.js:167
-msgid "Credential"
-msgstr "인증 정보"
-
-#: util/getRelatedResourceDeleteDetails.js:74
-msgid "Credential Input Sources"
-msgstr "인증 입력 소스"
-
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:83
-#: components/Lookup/InstanceGroupsLookup.js:108
-msgid "Credential Name"
-msgstr "인증 정보 이름"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:241
-#: screens/Credential/CredentialList/CredentialList.js:158
-#: screens/Credential/shared/CredentialForm.js:128
-#: screens/Credential/shared/CredentialForm.js:196
-msgid "Credential Type"
-msgstr "인증 정보 유형"
-
-#: routeConfig.js:117
-#: screens/ActivityStream/ActivityStream.js:192
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:118
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:161
-#: screens/CredentialType/CredentialTypes.js:13
-#: screens/CredentialType/CredentialTypes.js:22
-msgid "Credential Types"
-msgstr "인증 정보 유형"
-
-#: screens/Credential/CredentialList/CredentialList.js:113
-msgid "Credential copied successfully"
-msgstr "인증 정보가 성공적으로 복사됨"
-
-#: screens/Credential/Credential.js:98
-msgid "Credential not found."
-msgstr "인증 정보를 찾을 수 없습니다."
-
-#: components/AdHocCommands/useAdHocCredentialPasswordStep.js:23
-#: components/LaunchPrompt/steps/useCredentialPasswordsStep.js:28
-msgid "Credential passwords"
-msgstr "인증 정보 암호"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:53
-msgid "Credential to authenticate with Kubernetes or OpenShift"
-msgstr "Kubernetes 또는 OpenShift로 인증하는 인증 정보"
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:57
-msgid "Credential to authenticate with Kubernetes or OpenShift. Must be of type \"Kubernetes/OpenShift API Bearer Token\". If left blank, the underlying Pod's service account will be used."
-msgstr "Kubernetes 또는 OpenShift로 인증하는 인증 정보입니다. \"Kubernetes/OpenShift API Bearer Token\" 유형이어야 합니다. 정보를 입력하지 않는 경우 기본 Pod의 서비스 계정이 사용됩니다."
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:21
-msgid "Credential to authenticate with a protected container registry."
-msgstr "보안 컨테이너 레지스트리로 인증하기 위한 인증 정보."
-
-#: screens/CredentialType/CredentialType.js:76
-msgid "Credential type not found."
-msgstr "인증 정보 유형을 찾을 수 없습니다."
-
-#: components/JobList/JobListItem.js:260
-#: components/LaunchPrompt/steps/CredentialsStep.js:190
-#: components/LaunchPrompt/steps/useCredentialsStep.js:62
-#: components/Lookup/MultiCredentialsLookup.js:138
-#: components/Lookup/MultiCredentialsLookup.js:211
-#: components/PromptDetail/PromptDetail.js:192
-#: components/PromptDetail/PromptJobTemplateDetail.js:191
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:528
-#: components/TemplateList/TemplateListItem.js:323
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:77
-#: routeConfig.js:70
-#: screens/ActivityStream/ActivityStream.js:167
-#: screens/Credential/CredentialList/CredentialList.js:195
-#: screens/Credential/Credentials.js:14
-#: screens/Credential/Credentials.js:24
-#: screens/Job/JobDetail/JobDetail.js:429
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:374
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:49
-#: screens/Template/shared/JobTemplateForm.js:372
-#: util/getRelatedResourceDeleteDetails.js:91
-msgid "Credentials"
-msgstr "인증 정보"
-
-#: components/LaunchPrompt/steps/credentialsValidator.js:52
-msgid "Credentials that require passwords on launch are not permitted. Please remove or replace the following credentials with a credential of the same type in order to proceed: {0}"
-msgstr "시작 시 암호가 필요한 인증 정보는 허용되지 않습니다. 계속하려면 삭제하거나 동일한 유형의 인증 정보로 교체하십시오. {0}"
-
-#: components/Pagination/Pagination.js:34
-msgid "Current page"
-msgstr "현재 페이지"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:85
-msgid "Custom Kubernetes or OpenShift Pod specification."
-msgstr "사용자 정의 Kubernetes 또는 OpenShift Pod 사양"
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:79
-msgid "Custom pod spec"
-msgstr "사용자 정의 Pod 사양"
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:79
-#: screens/Organization/OrganizationList/OrganizationListItem.js:55
-#: screens/Project/ProjectList/ProjectListItem.js:188
-msgid "Custom virtual environment {0} must be replaced by an execution environment."
-msgstr "사용자 지정 가상 환경 {0} 은 실행 환경으로 교체해야 합니다."
-
-#: components/TemplateList/TemplateListItem.js:163
-msgid "Custom virtual environment {0} must be replaced by an execution environment. For more information about migrating to execution environments see <0>the documentation.0>"
-msgstr "사용자 지정 가상 환경 {0} 은 실행 환경으로 교체해야 합니다. 실행 환경으로 마이그레이션하는 방법에 대한 자세한 내용은 해당 <0>문서0>를 참조하십시오."
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:73
-msgid "Custom virtual environment {virtualEnvironment} must be replaced by an execution environment. For more information about migrating to execution environments see <0>the documentation.0>"
-msgstr "사용자 지정 가상 환경 {virtualEnvironment} 은 실행 환경으로 교체해야 합니다. 실행 환경으로 마이그레이션하는 방법에 대한 자세한 내용은 해당 <0>문서0>를 참조하십시오."
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:61
-msgid "Customize messages…"
-msgstr "메시지 사용자 정의..."
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:65
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:66
-msgid "Customize pod specification"
-msgstr "Pod 사양 사용자 정의"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputNode.js:109
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:212
-msgid "DELETED"
-msgstr "삭제됨"
-
-#: routeConfig.js:34
-#: screens/Dashboard/Dashboard.js:74
-msgid "Dashboard"
-msgstr "대시보드"
-
-#: screens/ActivityStream/ActivityStream.js:147
-msgid "Dashboard (all activity)"
-msgstr "대시보드(모든 활동)"
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:75
-msgid "Data retention period"
-msgstr "데이터 보존 기간"
-
-#: screens/Dashboard/shared/LineChart.js:168
-msgid "Date"
-msgstr "날짜"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:184
-#: components/Schedule/shared/FrequencyDetailSubform.js:177
-#: components/Schedule/shared/FrequencyDetailSubform.js:356
-#: components/Schedule/shared/FrequencyDetailSubform.js:460
-#: components/Schedule/shared/ScheduleFormFields.js:127
-#: components/Schedule/shared/ScheduleFormFields.js:187
-msgid "Day"
-msgstr "일"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:130
-msgid "Day {0}"
-msgstr "{0} 일"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:399
-#: components/Schedule/shared/ScheduleFormFields.js:136
-msgid "Days of Data to Keep"
-msgstr "데이터 보관 일수"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/DaysToKeepStep.js:28
-msgid "Days of data to be retained"
-msgstr "데이터 유지 일수"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:167
-msgid "Days remaining"
-msgstr "남은 일수"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useDaysToKeepStep.js:35
-msgid "Days to keep"
-msgstr "보관 일수"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:102
-msgid "Debug"
-msgstr "디버그"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:170
-#: components/Schedule/shared/FrequencyDetailSubform.js:153
-msgid "December"
-msgstr "12월"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:102
-#: screens/Template/Survey/SurveyList.js:104
-#: screens/Template/Survey/SurveyList.js:104
-#: screens/Template/Survey/SurveyListItem.js:63
-msgid "Default"
-msgstr "기본값"
-
-#: screens/Template/Survey/SurveyReorderModal.js:219
-#: screens/Template/Survey/SurveyReorderModal.js:219
-#: screens/Template/Survey/SurveyReorderModal.js:241
-msgid "Default Answer(s)"
-msgstr "기본 답변"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:40
-msgid "Default Execution Environment"
-msgstr "기본 실행 환경"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:238
-#: screens/Template/Survey/SurveyQuestionForm.js:246
-#: screens/Template/Survey/SurveyQuestionForm.js:253
-msgid "Default answer"
-msgstr "기본 응답"
-
-#: screens/Setting/SettingList.js:103
-msgid "Define system-level features and functions"
-msgstr "시스템 수준 기능 및 함수 정의"
-
-#: components/DeleteButton/DeleteButton.js:75
-#: components/DeleteButton/DeleteButton.js:80
-#: components/DeleteButton/DeleteButton.js:90
-#: components/DeleteButton/DeleteButton.js:94
-#: components/DeleteButton/DeleteButton.js:114
-#: components/PaginatedTable/ToolbarDeleteButton.js:158
-#: components/PaginatedTable/ToolbarDeleteButton.js:235
-#: components/PaginatedTable/ToolbarDeleteButton.js:246
-#: components/PaginatedTable/ToolbarDeleteButton.js:250
-#: components/PaginatedTable/ToolbarDeleteButton.js:273
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:29
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:646
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:128
-#: screens/Credential/CredentialDetail/CredentialDetail.js:306
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:124
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:134
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:115
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:127
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:201
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:101
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:316
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:174
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:64
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:68
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:73
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:78
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:102
-#: screens/Job/JobDetail/JobDetail.js:612
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:436
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:199
-#: screens/Project/ProjectDetail/ProjectDetail.js:340
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:80
-#: screens/Team/TeamDetail/TeamDetail.js:70
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:545
-#: screens/Template/Survey/SurveyList.js:66
-#: screens/Template/Survey/SurveyToolbar.js:93
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:269
-#: screens/User/UserDetail/UserDetail.js:107
-#: screens/User/UserTokenDetail/UserTokenDetail.js:78
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:340
-msgid "Delete"
-msgstr "삭제"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:130
-msgid "Delete All Groups and Hosts"
-msgstr "모든 그룹 및 호스트 삭제"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:300
-msgid "Delete Credential"
-msgstr "인증 정보 삭제"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:127
-msgid "Delete Execution Environment"
-msgstr "실행 환경 삭제"
-
-#: screens/Host/HostDetail/HostDetail.js:114
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:109
-msgid "Delete Host"
-msgstr "호스트 삭제"
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:196
-msgid "Delete Inventory"
-msgstr "인벤토리 삭제"
-
-#: screens/Job/JobDetail/JobDetail.js:608
-#: screens/Job/JobOutput/shared/OutputToolbar.js:195
-#: screens/Job/JobOutput/shared/OutputToolbar.js:199
-msgid "Delete Job"
-msgstr "작업 삭제"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:539
-msgid "Delete Job Template"
-msgstr "작업 템플릿 삭제"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:432
-msgid "Delete Notification"
-msgstr "알림 삭제"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:193
-msgid "Delete Organization"
-msgstr "조직 삭제"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:334
-msgid "Delete Project"
-msgstr "프로젝트 삭제"
-
-#: screens/Template/Survey/SurveyList.js:52
-msgid "Delete Questions"
-msgstr "질문 삭제"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:642
-msgid "Delete Schedule"
-msgstr "일정 삭제"
-
-#: screens/Template/Survey/SurveyList.js:52
-msgid "Delete Survey"
-msgstr "설문 조사 삭제"
-
-#: screens/Team/TeamDetail/TeamDetail.js:66
-msgid "Delete Team"
-msgstr "팀 삭제"
-
-#: screens/User/UserDetail/UserDetail.js:103
-msgid "Delete User"
-msgstr "사용자 삭제"
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:74
-msgid "Delete User Token"
-msgstr "사용자 토큰 삭제"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:336
-msgid "Delete Workflow Approval"
-msgstr "워크플로우 승인 삭제"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:263
-msgid "Delete Workflow Job Template"
-msgstr "워크플로우 작업 템플릿 삭제"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:138
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:141
-msgid "Delete all nodes"
-msgstr "모든 노드 삭제"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:124
-msgid "Delete application"
-msgstr "애플리케이션 삭제"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:116
-msgid "Delete credential type"
-msgstr "인증 정보 유형 삭제"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:249
-msgid "Delete error"
-msgstr "오류 삭제"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:109
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:121
-msgid "Delete instance group"
-msgstr "인스턴스 그룹 삭제"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:310
-msgid "Delete inventory source"
-msgstr "인벤토리 소스 삭제"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:170
-msgid "Delete smart inventory"
-msgstr "스마트 인벤토리 삭제"
-
-#: screens/Template/Survey/SurveyToolbar.js:83
-msgid "Delete survey question"
-msgstr "설문 조사 질문 삭제"
-
-#: screens/Project/shared/Project.helptext.js:114
-msgid ""
-"Delete the local repository in its entirety prior to\n"
-"performing an update. Depending on the size of the\n"
-"repository this may significantly increase the amount\n"
-"of time required to complete an update."
-msgstr "업데이트를 수행하기 전에 전체 로컬 리포지토리를 삭제합니다. 리포지토리 크기에 따라 업데이트를 완료하는 데 필요한 시간이 크게 증가할 수 있습니다."
-
-#: components/PromptDetail/PromptProjectDetail.js:51
-#: screens/Project/ProjectDetail/ProjectDetail.js:100
-msgid "Delete the project before syncing"
-msgstr "동기화 전에 프로젝트 삭제"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:83
-msgid "Delete this link"
-msgstr "이 링크 삭제"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:274
-msgid "Delete this node"
-msgstr "이 노드 삭제"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:163
-msgid "Delete {pluralizedItemName}?"
-msgstr "{pluralizedItemName} 을/를 삭제하시겠습니까?"
-
-#: components/DetailList/DeletedDetail.js:19
-#: components/Workflow/WorkflowNodeHelp.js:157
-#: components/Workflow/WorkflowNodeHelp.js:193
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:231
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:49
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:60
-msgid "Deleted"
-msgstr "삭제됨"
-
-#: components/TemplateList/TemplateList.js:296
-#: screens/Credential/CredentialList/CredentialList.js:211
-#: screens/Inventory/InventoryList/InventoryList.js:284
-#: screens/Project/ProjectList/ProjectList.js:290
-msgid "Deletion Error"
-msgstr "삭제 오류"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:202
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:227
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:219
-msgid "Deletion error"
-msgstr "삭제 오류"
-
-#: components/StatusLabel/StatusLabel.js:40
-msgid "Denied"
-msgstr "거부됨"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:21
-msgid "Denied - {0}. See the Activity Stream for more information."
-msgstr "거부됨 - {0} 자세한 내용은 활동 스트림을 참조하십시오."
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:17
-msgid "Denied by {0} - {1}"
-msgstr "{0} - {1} 거부됨"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:89
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:35
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:42
-msgid "Deny"
-msgstr "거부"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:103
-msgid "Deprecated"
-msgstr "더 이상 사용되지 않음"
-
-#: components/StatusLabel/StatusLabel.js:60
-#: screens/TopologyView/Legend.js:164
-msgid "Deprovisioning"
-msgstr "프로비저닝 해제 중"
-
-#: components/StatusLabel/StatusLabel.js:63
-msgid "Deprovisioning fail"
-msgstr "프로비저닝 해제 실패"
-
-#: components/HostForm/HostForm.js:104
-#: components/Lookup/ApplicationLookup.js:105
-#: components/Lookup/ApplicationLookup.js:123
-#: components/Lookup/HostFilterLookup.js:423
-#: components/Lookup/HostListItem.js:9
-#: components/NotificationList/NotificationList.js:186
-#: components/PromptDetail/PromptDetail.js:119
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:327
-#: components/Schedule/ScheduleList/ScheduleList.js:194
-#: components/Schedule/shared/ScheduleFormFields.js:80
-#: components/TemplateList/TemplateList.js:210
-#: components/TemplateList/TemplateListItem.js:271
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:65
-#: screens/Application/ApplicationsList/ApplicationsList.js:123
-#: screens/Application/shared/ApplicationForm.js:62
-#: screens/Credential/CredentialDetail/CredentialDetail.js:223
-#: screens/Credential/CredentialList/CredentialList.js:146
-#: screens/Credential/shared/CredentialForm.js:169
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:72
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:128
-#: screens/CredentialType/shared/CredentialTypeForm.js:29
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:60
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:159
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:127
-#: screens/Host/HostDetail/HostDetail.js:75
-#: screens/Host/HostList/HostList.js:153
-#: screens/Host/HostList/HostList.js:170
-#: screens/Host/HostList/HostListItem.js:57
-#: screens/Instances/Shared/InstanceForm.js:26
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:93
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:35
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:216
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:80
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:40
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:124
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:139
-#: screens/Inventory/InventoryList/InventoryList.js:195
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:198
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:104
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:36
-#: screens/Inventory/shared/InventoryForm.js:58
-#: screens/Inventory/shared/InventoryGroupForm.js:40
-#: screens/Inventory/shared/InventorySourceForm.js:108
-#: screens/Inventory/shared/SmartInventoryForm.js:55
-#: screens/Job/JobOutput/HostEventModal.js:112
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:101
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:72
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:109
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:127
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:49
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:95
-#: screens/Organization/OrganizationList/OrganizationList.js:127
-#: screens/Organization/shared/OrganizationForm.js:64
-#: screens/Project/ProjectDetail/ProjectDetail.js:177
-#: screens/Project/ProjectList/ProjectList.js:190
-#: screens/Project/ProjectList/ProjectListItem.js:281
-#: screens/Project/shared/ProjectForm.js:222
-#: screens/Team/TeamDetail/TeamDetail.js:38
-#: screens/Team/TeamList/TeamList.js:122
-#: screens/Team/shared/TeamForm.js:37
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:187
-#: screens/Template/Survey/SurveyQuestionForm.js:171
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:112
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:183
-#: screens/Template/shared/JobTemplateForm.js:251
-#: screens/Template/shared/WorkflowJobTemplateForm.js:117
-#: screens/User/UserOrganizations/UserOrganizationList.js:80
-#: screens/User/UserOrganizations/UserOrganizationListItem.js:18
-#: screens/User/UserTeams/UserTeamList.js:182
-#: screens/User/UserTeams/UserTeamListItem.js:32
-#: screens/User/UserTokenDetail/UserTokenDetail.js:45
-#: screens/User/UserTokenList/UserTokenList.js:128
-#: screens/User/UserTokenList/UserTokenList.js:138
-#: screens/User/UserTokenList/UserTokenList.js:188
-#: screens/User/UserTokenList/UserTokenListItem.js:29
-#: screens/User/shared/UserTokenForm.js:59
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:145
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:128
-msgid "Description"
-msgstr "설명"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:325
-msgid "Destination Channels"
-msgstr "대상 채널"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:233
-msgid "Destination Channels or Users"
-msgstr "대상 채널 또는 사용자"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:346
-msgid "Destination SMS Number(s)"
-msgstr "대상 SMS 번호"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:412
-msgid "Destination SMS number(s)"
-msgstr "대상 SMS 번호"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:364
-msgid "Destination channels"
-msgstr "대상 채널"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:231
-msgid "Destination channels or users"
-msgstr "대상 채널 또는 사용자"
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:35
-#: components/ErrorDetail/ErrorDetail.js:88
-#: components/Schedule/Schedule.js:71
-#: screens/Application/Application/Application.js:79
-#: screens/Application/Applications.js:39
-#: screens/Credential/Credential.js:72
-#: screens/Credential/Credentials.js:28
-#: screens/CredentialType/CredentialType.js:63
-#: screens/CredentialType/CredentialTypes.js:26
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:65
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:26
-#: screens/Host/Host.js:58
-#: screens/Host/Hosts.js:27
-#: screens/InstanceGroup/ContainerGroup.js:66
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:180
-#: screens/InstanceGroup/InstanceGroup.js:69
-#: screens/InstanceGroup/InstanceGroups.js:30
-#: screens/InstanceGroup/InstanceGroups.js:38
-#: screens/Instances/Instance.js:29
-#: screens/Instances/Instances.js:24
-#: screens/Inventory/Inventories.js:61
-#: screens/Inventory/Inventories.js:87
-#: screens/Inventory/Inventory.js:64
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:57
-#: screens/Inventory/InventoryHost/InventoryHost.js:73
-#: screens/Inventory/InventorySource/InventorySource.js:83
-#: screens/Inventory/SmartInventory.js:66
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:60
-#: screens/Job/Job.js:130
-#: screens/Job/JobOutput/HostEventModal.js:103
-#: screens/Job/Jobs.js:35
-#: screens/ManagementJob/ManagementJobs.js:26
-#: screens/NotificationTemplate/NotificationTemplate.js:84
-#: screens/NotificationTemplate/NotificationTemplates.js:25
-#: screens/Organization/Organization.js:123
-#: screens/Organization/Organizations.js:30
-#: screens/Project/Project.js:104
-#: screens/Project/Projects.js:26
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:51
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:51
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:65
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:76
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:50
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:97
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:51
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:56
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:52
-#: screens/Setting/Settings.js:45
-#: screens/Setting/Settings.js:48
-#: screens/Setting/Settings.js:52
-#: screens/Setting/Settings.js:55
-#: screens/Setting/Settings.js:58
-#: screens/Setting/Settings.js:61
-#: screens/Setting/Settings.js:64
-#: screens/Setting/Settings.js:67
-#: screens/Setting/Settings.js:70
-#: screens/Setting/Settings.js:73
-#: screens/Setting/Settings.js:76
-#: screens/Setting/Settings.js:85
-#: screens/Setting/Settings.js:86
-#: screens/Setting/Settings.js:87
-#: screens/Setting/Settings.js:88
-#: screens/Setting/Settings.js:89
-#: screens/Setting/Settings.js:90
-#: screens/Setting/Settings.js:98
-#: screens/Setting/Settings.js:101
-#: screens/Setting/Settings.js:104
-#: screens/Setting/Settings.js:107
-#: screens/Setting/Settings.js:110
-#: screens/Setting/Settings.js:113
-#: screens/Setting/Settings.js:116
-#: screens/Setting/Settings.js:119
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:56
-#: screens/Setting/UI/UIDetail/UIDetail.js:66
-#: screens/Team/Team.js:57
-#: screens/Team/Teams.js:29
-#: screens/Template/Template.js:135
-#: screens/Template/Templates.js:43
-#: screens/Template/WorkflowJobTemplate.js:117
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:138
-#: screens/TopologyView/Tooltip.js:187
-#: screens/TopologyView/Tooltip.js:213
-#: screens/User/User.js:64
-#: screens/User/UserToken/UserToken.js:54
-#: screens/User/Users.js:30
-#: screens/User/Users.js:36
-#: screens/WorkflowApproval/WorkflowApproval.js:77
-#: screens/WorkflowApproval/WorkflowApprovals.js:24
-msgid "Details"
-msgstr "세부 정보"
-
-#: screens/Job/JobOutput/HostEventModal.js:100
-msgid "Details tab"
-msgstr "세부 정보 탭"
-
-#: components/Search/AdvancedSearch.js:271
-msgid "Direct Keys"
-msgstr "직접 키"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:209
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:268
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:313
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:371
-msgid "Disable SSL Verification"
-msgstr "SSL 확인 비활성화"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:187
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:240
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:279
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:350
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:461
-msgid "Disable SSL verification"
-msgstr "SSL 확인 비활성화"
-
-#: components/InstanceToggle/InstanceToggle.js:56
-#: components/StatusLabel/StatusLabel.js:53
-#: screens/TopologyView/Legend.js:233
-msgid "Disabled"
-msgstr "비활성화됨"
-
-#: components/DisassociateButton/DisassociateButton.js:73
-#: components/DisassociateButton/DisassociateButton.js:97
-#: components/DisassociateButton/DisassociateButton.js:109
-#: components/DisassociateButton/DisassociateButton.js:113
-#: components/DisassociateButton/DisassociateButton.js:133
-#: screens/Team/TeamRoles/TeamRolesList.js:222
-#: screens/User/UserRoles/UserRolesList.js:218
-msgid "Disassociate"
-msgstr "연결 해제"
-
-#: screens/Host/HostGroups/HostGroupsList.js:211
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:229
-msgid "Disassociate group from host?"
-msgstr "호스트에서 그룹을 분리하시겠습니까?"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:247
-msgid "Disassociate host from group?"
-msgstr "그룹에서 호스트를 분리하시겠습니까?"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:296
-#: screens/InstanceGroup/Instances/InstanceList.js:245
-msgid "Disassociate instance from instance group?"
-msgstr "인스턴스를 인스턴스 그룹에서 분리하시겠습니까?"
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:225
-msgid "Disassociate related group(s)?"
-msgstr "관련 그룹을 분리하시겠습니까?"
-
-#: screens/User/UserTeams/UserTeamList.js:216
-msgid "Disassociate related team(s)?"
-msgstr "관련 팀을 분리하시겠습니까?"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:209
-#: screens/User/UserRoles/UserRolesList.js:205
-msgid "Disassociate role"
-msgstr "역할 연결 해제"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:212
-#: screens/User/UserRoles/UserRolesList.js:208
-msgid "Disassociate role!"
-msgstr "역할 연결 해제!"
-
-#: components/DisassociateButton/DisassociateButton.js:18
-msgid "Disassociate?"
-msgstr "연결 해제하시겠습니까?"
-
-#: components/PromptDetail/PromptProjectDetail.js:46
-#: screens/Project/ProjectDetail/ProjectDetail.js:94
-msgid "Discard local changes before syncing"
-msgstr "동기화 전에 로컬 변경 사항 삭제"
-
-#: screens/Job/Job.helptext.js:16
-#: screens/Template/shared/JobTemplate.helptext.js:17
-msgid "Divide the work done by this job template into the specified number of job slices, each running the same tasks against a portion of the inventory."
-msgstr "이 작업 템플릿으로 수행한 작업을 지정된 수의 작업 슬라이스로 나눕니다. 각각 인벤토리의 일부에 대해 동일한 작업을 실행합니다."
-
-#: screens/Project/shared/Project.helptext.js:100
-msgid "Documentation."
-msgstr "문서."
-
-#: components/CodeEditor/VariablesDetail.js:117
-#: components/CodeEditor/VariablesDetail.js:123
-#: components/CodeEditor/VariablesField.js:139
-#: components/CodeEditor/VariablesField.js:145
-msgid "Done"
-msgstr "완료"
-
-#: screens/TopologyView/Tooltip.js:251
-msgid "Download Bundle"
-msgstr "번들 다운로드"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:179
-#: screens/Job/JobOutput/shared/OutputToolbar.js:184
-msgid "Download Output"
-msgstr "출력 다운로드"
-
-#: screens/TopologyView/Tooltip.js:247
-msgid "Download bundle"
-msgstr "번들 다운로드"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:93
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:114
-msgid "Drag a file here or browse to upload"
-msgstr "여기에 파일을 드래그하거나 업로드할 파일을 찾습니다."
-
-#: components/SelectedList/DraggableSelectedList.js:68
-msgid "Draggable list to reorder and remove selected items."
-msgstr "선택한 항목을 다시 정렬하고 제거하기 위한 드래그 가능한 목록입니다."
-
-#: components/SelectedList/DraggableSelectedList.js:43
-msgid "Dragging cancelled. List is unchanged."
-msgstr "드래그 앤 드롭이 취소되었습니다. 목록은 변경되지 않습니다."
-
-#: components/SelectedList/DraggableSelectedList.js:38
-msgid "Dragging item {id}. Item with index {oldIndex} in now {newIndex}."
-msgstr "{id} 항목을 드래그합니다. 색인이 {oldIndex} 인 항목은 이제 {newIndex}입니다."
-
-#: components/SelectedList/DraggableSelectedList.js:32
-msgid "Dragging started for item id: {newId}."
-msgstr "드래그 앤 드롭 항목 ID: {newId} 가 시작되었습니다."
-
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:81
-msgid "E-mail"
-msgstr "이메일"
-
-#: screens/Inventory/shared/Inventory.helptext.js:113
-msgid ""
-"Each time a job runs using this inventory,\n"
-"refresh the inventory from the selected source before\n"
-"executing job tasks."
-msgstr "이 인벤토리를 사용하여 작업을 실행할 때마다 작업 작업을 실행하기 전에 선택한 소스에서 인벤토리를 새로 고칩니다."
-
-#: screens/Project/shared/Project.helptext.js:124
-msgid ""
-"Each time a job runs using this project, update the\n"
-"revision of the project prior to starting the job."
-msgstr "이 프로젝트를 사용하여 작업을 실행할 때마다 작업을 시작하기 전에 프로젝트의 버전을 업데이트합니다."
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:632
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:636
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:115
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:117
-#: screens/Credential/CredentialDetail/CredentialDetail.js:293
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:109
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:121
-#: screens/Host/HostDetail/HostDetail.js:108
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:101
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:113
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:190
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:55
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:62
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:103
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:292
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:127
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:164
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:418
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:420
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:138
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:182
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:186
-#: screens/Project/ProjectDetail/ProjectDetail.js:313
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:85
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:89
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:148
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:152
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:85
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:89
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:96
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:100
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:164
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:168
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:107
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:111
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:84
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:88
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:152
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:156
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:85
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:89
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:99
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:103
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:86
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:90
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:199
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:103
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:108
-#: screens/Setting/UI/UIDetail/UIDetail.js:105
-#: screens/Setting/UI/UIDetail/UIDetail.js:110
-#: screens/Team/TeamDetail/TeamDetail.js:55
-#: screens/Team/TeamDetail/TeamDetail.js:59
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:514
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:516
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:239
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:241
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:260
-#: screens/User/UserDetail/UserDetail.js:96
-msgid "Edit"
-msgstr "편집"
-
-#: screens/Credential/CredentialList/CredentialListItem.js:67
-#: screens/Credential/CredentialList/CredentialListItem.js:71
-msgid "Edit Credential"
-msgstr "인증 정보 편집"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:37
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:42
-msgid "Edit Credential Plugin Configuration"
-msgstr "인증 정보 플러그인 설정 편집"
-
-#: screens/Application/Applications.js:38
-#: screens/Credential/Credentials.js:27
-#: screens/Host/Hosts.js:26
-#: screens/ManagementJob/ManagementJobs.js:27
-#: screens/NotificationTemplate/NotificationTemplates.js:24
-#: screens/Organization/Organizations.js:29
-#: screens/Project/Projects.js:25
-#: screens/Project/Projects.js:35
-#: screens/Setting/Settings.js:46
-#: screens/Setting/Settings.js:49
-#: screens/Setting/Settings.js:53
-#: screens/Setting/Settings.js:56
-#: screens/Setting/Settings.js:59
-#: screens/Setting/Settings.js:62
-#: screens/Setting/Settings.js:65
-#: screens/Setting/Settings.js:68
-#: screens/Setting/Settings.js:71
-#: screens/Setting/Settings.js:74
-#: screens/Setting/Settings.js:77
-#: screens/Setting/Settings.js:91
-#: screens/Setting/Settings.js:92
-#: screens/Setting/Settings.js:93
-#: screens/Setting/Settings.js:94
-#: screens/Setting/Settings.js:95
-#: screens/Setting/Settings.js:96
-#: screens/Setting/Settings.js:99
-#: screens/Setting/Settings.js:102
-#: screens/Setting/Settings.js:105
-#: screens/Setting/Settings.js:108
-#: screens/Setting/Settings.js:111
-#: screens/Setting/Settings.js:114
-#: screens/Setting/Settings.js:117
-#: screens/Setting/Settings.js:120
-#: screens/Team/Teams.js:28
-#: screens/Template/Templates.js:44
-#: screens/User/Users.js:29
-msgid "Edit Details"
-msgstr "세부 정보 편집"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:90
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:94
-msgid "Edit Execution Environment"
-msgstr "실행 환경 편집"
-
-#: screens/Host/HostGroups/HostGroupItem.js:37
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:46
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:46
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:51
-msgid "Edit Group"
-msgstr "그룹 편집"
-
-#: screens/Host/HostList/HostListItem.js:74
-#: screens/Host/HostList/HostListItem.js:78
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:61
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:64
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:67
-msgid "Edit Host"
-msgstr "호스트 편집"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:134
-#: screens/Inventory/InventoryList/InventoryListItem.js:139
-msgid "Edit Inventory"
-msgstr "인벤토리 편집"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkEditModal.js:14
-msgid "Edit Link"
-msgstr "링크 편집"
-
-#: screens/Setting/shared/SharedFields.js:290
-msgid "Edit Login redirect override URL"
-msgstr "로그인 리디렉션 덮어쓰기 URL 편집"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeEditModal.js:64
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:257
-msgid "Edit Node"
-msgstr "노드 편집"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:142
-msgid "Edit Notification Template"
-msgstr "알림 템플릿 편집"
-
-#: screens/Template/Survey/SurveyToolbar.js:73
-msgid "Edit Order"
-msgstr "순서 편집"
-
-#: screens/Organization/OrganizationList/OrganizationListItem.js:72
-#: screens/Organization/OrganizationList/OrganizationListItem.js:76
-msgid "Edit Organization"
-msgstr "조직 편집"
-
-#: screens/Project/ProjectList/ProjectListItem.js:248
-#: screens/Project/ProjectList/ProjectListItem.js:253
-msgid "Edit Project"
-msgstr "프로젝트 편집"
-
-#: screens/Template/Templates.js:50
-msgid "Edit Question"
-msgstr "질문 편집"
-
-#: components/Schedule/ScheduleList/ScheduleListItem.js:132
-#: components/Schedule/ScheduleList/ScheduleListItem.js:136
-#: screens/Template/Templates.js:55
-msgid "Edit Schedule"
-msgstr "일정 편집"
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:131
-msgid "Edit Source"
-msgstr "소스 편집"
-
-#: screens/Template/Survey/SurveyListItem.js:92
-msgid "Edit Survey"
-msgstr "설문조사 편집"
-
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:22
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:26
-#: screens/Team/TeamList/TeamListItem.js:50
-#: screens/Team/TeamList/TeamListItem.js:54
-msgid "Edit Team"
-msgstr "팀 편집"
-
-#: components/TemplateList/TemplateListItem.js:233
-#: components/TemplateList/TemplateListItem.js:239
-msgid "Edit Template"
-msgstr "템플릿 편집"
-
-#: screens/User/UserList/UserListItem.js:59
-#: screens/User/UserList/UserListItem.js:63
-msgid "Edit User"
-msgstr "사용자 편집"
-
-#: screens/Application/ApplicationsList/ApplicationListItem.js:51
-#: screens/Application/ApplicationsList/ApplicationListItem.js:55
-msgid "Edit application"
-msgstr "애플리케이션 편집"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:41
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:45
-msgid "Edit credential type"
-msgstr "인증 정보 유형 편집"
-
-#: screens/CredentialType/CredentialTypes.js:25
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:25
-#: screens/InstanceGroup/InstanceGroups.js:35
-#: screens/InstanceGroup/InstanceGroups.js:40
-#: screens/Inventory/Inventories.js:63
-#: screens/Inventory/Inventories.js:68
-#: screens/Inventory/Inventories.js:77
-#: screens/Inventory/Inventories.js:88
-msgid "Edit details"
-msgstr "세부 정보 편집"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:42
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:44
-msgid "Edit group"
-msgstr "그룹 편집"
-
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:48
-msgid "Edit host"
-msgstr "호스트 편집"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:78
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:82
-msgid "Edit instance group"
-msgstr "인스턴스 그룹 편집"
-
-#: screens/Setting/shared/SharedFields.js:320
-#: screens/Setting/shared/SharedFields.js:322
-msgid "Edit login redirect override URL"
-msgstr "로그인 리디렉션 덮어쓰기 URL 편집"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:70
-msgid "Edit this link"
-msgstr "이 링크 편집"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:248
-msgid "Edit this node"
-msgstr "이 노드 편집"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:97
-msgid "Edit workflow"
-msgstr "워크플로우 편집"
-
-#: components/Workflow/WorkflowNodeHelp.js:170
-#: screens/Job/JobOutput/shared/OutputToolbar.js:125
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:216
-msgid "Elapsed"
-msgstr "경과됨"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:124
-msgid "Elapsed Time"
-msgstr "경과된 시간"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:126
-msgid "Elapsed time that the job ran"
-msgstr "작업이 실행되는 데 경과된 시간"
-
-#: components/NotificationList/NotificationList.js:193
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:134
-#: screens/User/UserDetail/UserDetail.js:66
-#: screens/User/UserList/UserList.js:115
-#: screens/User/shared/UserForm.js:73
-msgid "Email"
-msgstr "이메일"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:177
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:125
-msgid "Email Options"
-msgstr "이메일 옵션"
-
-#: screens/Template/shared/WorkflowJobTemplateForm.js:263
-msgid "Enable Concurrent Jobs"
-msgstr "동시 작업 활성화"
-
-#: screens/Template/shared/JobTemplateForm.js:597
-msgid "Enable Fact Storage"
-msgstr "실제 스토리지 활성화"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:192
-msgid "Enable HTTPS certificate verification"
-msgstr "HTTPS 인증서 확인 활성화"
-
-#: screens/Instances/Shared/InstanceForm.js:58
-msgid "Enable Instance"
-msgstr "인스턴스 활성화"
-
-#: screens/Template/shared/JobTemplateForm.js:573
-#: screens/Template/shared/JobTemplateForm.js:576
-#: screens/Template/shared/WorkflowJobTemplateForm.js:244
-#: screens/Template/shared/WorkflowJobTemplateForm.js:247
-msgid "Enable Webhook"
-msgstr "Webhook 활성화"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:15
-msgid "Enable Webhook for this workflow job template."
-msgstr "이 워크플로 작업 템플릿에 대한 Webhook을 활성화합니다."
-
-#: screens/Project/shared/Project.helptext.js:108
-msgid ""
-"Enable content signing to verify that the content \n"
-"has remained secure when a project is synced. \n"
-"If the content has been tampered with, the \n"
-"job will not run."
-msgstr "콘텐츠 서명을 활성화하여 프로젝트가 동기화될 때 \n"
-" 콘텐츠가 안전하게 유지되는지 확인합니다. \n"
-" 컨텐츠가 변조된 경우, \n"
-" 작업이 실행되지 않습니다."
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:136
-msgid "Enable external logging"
-msgstr "외부 로깅 활성화"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:168
-msgid "Enable log system tracking facts individually"
-msgstr "로그 시스템 추적 사실을 개별적으로 활성화"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:201
-#: components/AdHocCommands/AdHocDetailsStep.js:204
-msgid "Enable privilege escalation"
-msgstr "권한 에스컬레이션 활성화"
-
-#: screens/Setting/SettingList.js:53
-msgid "Enable simplified login for your {brandName} applications"
-msgstr "{brandName} 애플리케이션에 대한 간편 로그인 활성화"
-
-#: screens/Template/shared/JobTemplate.helptext.js:31
-msgid "Enable webhook for this template."
-msgstr "이 템플릿에 대한 Webhook을 활성화합니다."
-
-#: components/InstanceToggle/InstanceToggle.js:55
-#: components/Lookup/HostFilterLookup.js:110
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:45
-#: screens/TopologyView/Legend.js:205
-msgid "Enabled"
-msgstr "활성화됨"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:171
-#: components/PromptDetail/PromptJobTemplateDetail.js:187
-#: components/PromptDetail/PromptProjectDetail.js:145
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:99
-#: screens/Credential/CredentialDetail/CredentialDetail.js:268
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:138
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:265
-#: screens/Project/ProjectDetail/ProjectDetail.js:302
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:365
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:199
-msgid "Enabled Options"
-msgstr "활성화된 옵션"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:252
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:132
-msgid "Enabled Value"
-msgstr "활성화된 값"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:247
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:119
-msgid "Enabled Variable"
-msgstr "활성화된 변수"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:209
-msgid ""
-"Enables creation of a provisioning\n"
-"callback URL. Using the URL a host can contact {brandName}\n"
-"and request a configuration update using this job\n"
-"template"
-msgstr "프로비저닝 콜백 URL 생성을 활성화합니다. 호스트에서 URL을 사용하면 {brandName} 에 액세스하고 이 작업 템플릿을 사용하여 구성 업데이트를 요청할 수 있습니다."
-
-#: screens/Template/shared/JobTemplate.helptext.js:29
-msgid "Enables creation of a provisioning callback URL. Using the URL a host can contact {brandName} and request a configuration update using this job template."
-msgstr "프로비저닝 콜백 URL 생성을 활성화합니다. 호스트에서 URL을 사용하면 {brandName} 에 연락하여 이 작업 템플릿을 사용하여 구성 업데이트를 요청할 수 있습니다."
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:160
-#: screens/Setting/shared/SettingDetail.js:87
-msgid "Encrypted"
-msgstr "암호화"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:109
-#: components/Schedule/shared/FrequencyDetailSubform.js:507
-msgid "End"
-msgstr "종료"
-
-#: screens/Setting/Subscription/SubscriptionEdit/EulaStep.js:14
-msgid "End User License Agreement"
-msgstr "최종 사용자 라이센스 계약"
-
-#: components/Schedule/shared/DateTimePicker.js:51
-msgid "End date"
-msgstr "종료일"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:561
-msgid "End date/time"
-msgstr "종료일/시간"
-
-#: components/Schedule/shared/buildRuleObj.js:110
-msgid "End did not match an expected value ({0})"
-msgstr "종료일이 예상 값({0})과 일치하지 않음"
-
-#: components/Schedule/shared/DateTimePicker.js:61
-msgid "End time"
-msgstr "종료 시간"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:209
-msgid "End user license agreement"
-msgstr "최종 사용자 라이센스 계약"
-
-#: screens/Host/HostList/SmartInventoryButton.js:23
-msgid "Enter at least one search filter to create a new Smart Inventory"
-msgstr "새 스마트 인벤토리를 생성하려면 하나 이상의 검색 필터를 입력합니다."
-
-#: screens/CredentialType/shared/CredentialTypeForm.js:43
-msgid "Enter injectors using either JSON or YAML syntax. Refer to the Ansible Controller documentation for example syntax."
-msgstr "JSON 또는 YAML 구문을 사용하여 인젝터를 입력합니다. 구문 예제는 Ansible Controller 설명서를 참조하십시오."
-
-#: screens/CredentialType/shared/CredentialTypeForm.js:35
-msgid "Enter inputs using either JSON or YAML syntax. Refer to the Ansible Controller documentation for example syntax."
-msgstr "JSON 또는 YAML 구문을 사용하여 입력합니다. 구문 예제는 Ansible Controller 설명서를 참조하십시오."
-
-#: screens/Inventory/shared/SmartInventoryForm.js:94
-msgid ""
-"Enter inventory variables using either JSON or YAML syntax.\n"
-"Use the radio button to toggle between the two. Refer to the\n"
-"Ansible Controller documentation for example syntax."
-msgstr "JSON 또는 YAML 구문을 사용하여 인벤토리 변수를 입력합니다. 라디오 버튼을 사용하여 두 항목 사이를 전환합니다. 구문 예제는 Ansible Controller 설명서를 참조하십시오."
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:87
-msgid "Environment variables or extra variables that specify the values a credential type can inject."
-msgstr "인증 정보 유형에서 삽입할 수 있는 값을 지정하는 환경 변수 또는 추가 변수입니다."
-
-#: components/JobList/JobList.js:233
-#: components/StatusLabel/StatusLabel.js:46
-#: components/Workflow/WorkflowNodeHelp.js:108
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:133
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:205
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:143
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:230
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:123
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:135
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:222
-#: screens/Job/JobOutput/JobOutputSearch.js:104
-#: screens/TopologyView/Legend.js:178
-msgid "Error"
-msgstr "오류"
-
-#: screens/Project/ProjectList/ProjectList.js:302
-msgid "Error fetching updated project"
-msgstr "업데이트된 프로젝트를 가져오는 동안 오류 발생"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:501
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:141
-msgid "Error message"
-msgstr "오류 메시지"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:510
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:150
-msgid "Error message body"
-msgstr "오류 메시지 본문"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:709
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:711
-msgid "Error saving the workflow!"
-msgstr "워크플로우를 저장하는 동안 오류가 발생했습니다!"
-
-#: components/AdHocCommands/AdHocCommands.js:104
-#: components/CopyButton/CopyButton.js:51
-#: components/DeleteButton/DeleteButton.js:56
-#: components/HostToggle/HostToggle.js:76
-#: components/InstanceToggle/InstanceToggle.js:67
-#: components/JobList/JobList.js:315
-#: components/JobList/JobList.js:326
-#: components/LaunchButton/LaunchButton.js:185
-#: components/LaunchPrompt/LaunchPrompt.js:96
-#: components/NotificationList/NotificationList.js:246
-#: components/PaginatedTable/ToolbarDeleteButton.js:205
-#: components/RelatedTemplateList/RelatedTemplateList.js:241
-#: components/ResourceAccessList/ResourceAccessList.js:277
-#: components/ResourceAccessList/ResourceAccessList.js:289
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:654
-#: components/Schedule/ScheduleList/ScheduleList.js:239
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:73
-#: components/Schedule/shared/SchedulePromptableFields.js:63
-#: components/TemplateList/TemplateList.js:299
-#: contexts/Config.js:94
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:136
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:155
-#: screens/Application/ApplicationsList/ApplicationsList.js:185
-#: screens/Credential/CredentialDetail/CredentialDetail.js:314
-#: screens/Credential/CredentialList/CredentialList.js:214
-#: screens/Host/HostDetail/HostDetail.js:56
-#: screens/Host/HostDetail/HostDetail.js:123
-#: screens/Host/HostGroups/HostGroupsList.js:244
-#: screens/Host/HostList/HostList.js:233
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:310
-#: screens/InstanceGroup/Instances/InstanceList.js:308
-#: screens/InstanceGroup/Instances/InstanceListItem.js:218
-#: screens/Instances/InstanceDetail/InstanceDetail.js:360
-#: screens/Instances/InstanceDetail/InstanceDetail.js:375
-#: screens/Instances/InstanceList/InstanceList.js:231
-#: screens/Instances/InstanceList/InstanceList.js:243
-#: screens/Instances/InstanceList/InstanceListItem.js:234
-#: screens/Instances/Shared/RemoveInstanceButton.js:104
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:210
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:78
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:285
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:296
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:56
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:118
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:261
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:201
-#: screens/Inventory/InventoryList/InventoryList.js:285
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:264
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:323
-#: screens/Inventory/InventorySources/InventorySourceList.js:239
-#: screens/Inventory/InventorySources/InventorySourceList.js:252
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:183
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:152
-#: screens/Inventory/shared/InventorySourceSyncButton.js:49
-#: screens/Login/Login.js:239
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:125
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:444
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:233
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:169
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:208
-#: screens/Organization/OrganizationList/OrganizationList.js:195
-#: screens/Project/ProjectDetail/ProjectDetail.js:348
-#: screens/Project/ProjectList/ProjectList.js:291
-#: screens/Project/ProjectList/ProjectList.js:303
-#: screens/Project/shared/ProjectSyncButton.js:60
-#: screens/Team/TeamDetail/TeamDetail.js:78
-#: screens/Team/TeamList/TeamList.js:192
-#: screens/Team/TeamRoles/TeamRolesList.js:247
-#: screens/Team/TeamRoles/TeamRolesList.js:258
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:554
-#: screens/Template/TemplateSurvey.js:130
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:277
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:180
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:195
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:337
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:373
-#: screens/TopologyView/MeshGraph.js:405
-#: screens/TopologyView/Tooltip.js:199
-#: screens/User/UserDetail/UserDetail.js:115
-#: screens/User/UserList/UserList.js:189
-#: screens/User/UserRoles/UserRolesList.js:243
-#: screens/User/UserRoles/UserRolesList.js:254
-#: screens/User/UserTeams/UserTeamList.js:259
-#: screens/User/UserTokenDetail/UserTokenDetail.js:85
-#: screens/User/UserTokenList/UserTokenList.js:214
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:348
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:189
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:53
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:48
-msgid "Error!"
-msgstr "오류!"
-
-#: components/CodeEditor/VariablesDetail.js:105
-msgid "Error:"
-msgstr "오류:"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:267
-#: screens/Instances/InstanceDetail/InstanceDetail.js:315
-msgid "Errors"
-msgstr "오류"
-
-#: screens/TopologyView/Legend.js:253
-msgid "Established"
-msgstr "설립되었습니다"
-
-#: screens/ActivityStream/ActivityStream.js:265
-#: screens/ActivityStream/ActivityStreamListItem.js:46
-#: screens/Job/JobOutput/JobOutputSearch.js:99
-msgid "Event"
-msgstr "이벤트"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:35
-msgid "Event detail"
-msgstr "이벤트 세부 정보"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:36
-msgid "Event detail modal"
-msgstr "이벤트 세부 정보 모달"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:555
-msgid "Event summary not available"
-msgstr "이벤트 요약을 사용할 수 없음"
-
-#: screens/ActivityStream/ActivityStream.js:234
-msgid "Events"
-msgstr "이벤트"
-
-#: screens/Job/JobOutput/JobOutput.js:713
-msgid "Events processing complete."
-msgstr "이벤트 처리가 완료되었습니다."
-
-#: components/Search/LookupTypeInput.js:39
-msgid "Exact match (default lookup if not specified)."
-msgstr "정확한 일치(지정되지 않은 경우 기본 조회)."
-
-#: components/Search/RelatedLookupTypeInput.js:38
-msgid "Exact search on id field."
-msgstr "id 필드에서 정확한 검색"
-
-#: screens/Project/shared/Project.helptext.js:23
-msgid "Example URLs for GIT Source Control include:"
-msgstr "GIT 소스 제어용 URL의 예는 다음과 같습니다."
-
-#: screens/Project/shared/Project.helptext.js:62
-msgid "Example URLs for Remote Archive Source Control include:"
-msgstr "원격 아카이브 소스 제어에 대한 URL의 예는 다음과 같습니다."
-
-#: screens/Project/shared/Project.helptext.js:45
-msgid "Example URLs for Subversion Source Control include:"
-msgstr "하위 버전 소스 제어(Subversion Source Control)의 URL의 예는 다음과 같습니다."
-
-#: screens/Project/shared/Project.helptext.js:84
-msgid "Examples include:"
-msgstr "예를 들면 다음과 같습니다."
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:10
-msgid "Examples:"
-msgstr "예:"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:354
-msgid "Exception Frequency"
-msgstr "예외 빈도"
-
-#: components/Schedule/shared/ScheduleFormFields.js:160
-msgid "Exceptions"
-msgstr "예외"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:47
-msgid "Execute regardless of the parent node's final state."
-msgstr "부모 노드의 최종 상태에 관계없이 실행합니다."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:40
-msgid "Execute when the parent node results in a failure state."
-msgstr "부모 노드가 실패 상태가 되면 실행합니다."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:33
-msgid "Execute when the parent node results in a successful state."
-msgstr "부모 노드가 성공하면 실행됩니다."
-
-#: screens/InstanceGroup/Instances/InstanceList.js:208
-#: screens/Instances/InstanceList/InstanceList.js:152
-msgid "Execution"
-msgstr "실행"
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:90
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:91
-#: components/AdHocCommands/AdHocPreviewStep.js:58
-#: components/AdHocCommands/useAdHocExecutionEnvironmentStep.js:15
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:41
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:105
-#: components/LaunchPrompt/steps/useExecutionEnvironmentStep.js:29
-#: components/Lookup/ExecutionEnvironmentLookup.js:159
-#: components/Lookup/ExecutionEnvironmentLookup.js:191
-#: components/Lookup/ExecutionEnvironmentLookup.js:208
-#: components/PromptDetail/PromptDetail.js:220
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:451
-msgid "Execution Environment"
-msgstr "실행 환경"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:70
-#: components/TemplateList/TemplateListItem.js:160
-msgid "Execution Environment Missing"
-msgstr "실행 환경이 없습니다"
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:103
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:107
-#: routeConfig.js:147
-#: screens/ActivityStream/ActivityStream.js:217
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:129
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:191
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:13
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:22
-#: screens/Organization/Organization.js:127
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:78
-#: screens/Organization/Organizations.js:34
-#: util/getRelatedResourceDeleteDetails.js:81
-#: util/getRelatedResourceDeleteDetails.js:188
-msgid "Execution Environments"
-msgstr "실행 환경"
-
-#: screens/Job/JobDetail/JobDetail.js:345
-msgid "Execution Node"
-msgstr "실행 노드"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:103
-msgid "Execution environment copied successfully"
-msgstr "실행 환경이 성공적으로 복사되었습니다"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:112
-msgid "Execution environment is missing or deleted."
-msgstr "실행 환경이 없거나 삭제되었습니다."
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:83
-msgid "Execution environment not found."
-msgstr "실행 환경을 찾을 수 없습니다."
-
-#: screens/TopologyView/Legend.js:86
-msgid "Execution node"
-msgstr "실행 노드"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:23
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:26
-msgid "Exit Without Saving"
-msgstr "저장하지 않고 종료"
-
-#: components/ExpandCollapse/ExpandCollapse.js:52
-msgid "Expand"
-msgstr "확장"
-
-#: components/DataListToolbar/DataListToolbar.js:105
-msgid "Expand all rows"
-msgstr "모든 줄 확장"
-
-#: components/CodeEditor/VariablesDetail.js:212
-#: components/CodeEditor/VariablesField.js:248
-msgid "Expand input"
-msgstr "입력 확장"
-
-#: screens/Job/JobOutput/PageControls.js:50
-msgid "Expand job events"
-msgstr "작업 이벤트 확장"
-
-#: screens/Job/JobOutput/shared/JobEventLineToggle.js:37
-msgid "Expand section"
-msgstr "섹션 확장"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:46
-msgid "Expected at least one of client_email, project_id or private_key to be present in the file."
-msgstr "파일에 client_email, project_id 또는 private_key 중 하나가 있어야 합니다."
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:137
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:34
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:148
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:172
-#: screens/User/UserTokenDetail/UserTokenDetail.js:56
-#: screens/User/UserTokenList/UserTokenList.js:146
-#: screens/User/UserTokenList/UserTokenList.js:190
-#: screens/User/UserTokenList/UserTokenListItem.js:35
-#: screens/User/UserTokens/UserTokens.js:89
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:151
-msgid "Expires"
-msgstr "만료"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:146
-msgid "Expires on"
-msgstr "만료일"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:156
-msgid "Expires on UTC"
-msgstr "UTC에서 만료"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:50
-msgid "Expires on {0}"
-msgstr "{0}에 만료"
-
-#: components/JobList/JobListItem.js:307
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:181
-msgid "Explanation"
-msgstr "설명"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:113
-msgid "External Secret Management System"
-msgstr "외부 시크릿 관리 시스템"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:272
-#: components/AdHocCommands/AdHocDetailsStep.js:273
-msgid "Extra variables"
-msgstr "추가 변수"
-
-#: components/Sparkline/Sparkline.js:35
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:164
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:43
-#: screens/Project/ProjectDetail/ProjectDetail.js:139
-#: screens/Project/ProjectList/ProjectListItem.js:77
-msgid "FINISHED:"
-msgstr "완료:"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:73
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:137
-msgid "Fact Storage"
-msgstr "실제 스토리지"
-
-#: screens/Template/shared/JobTemplate.helptext.js:39
-msgid "Fact storage: If enabled, this will store gathered facts so they can be viewed at the host level. Facts are persisted and injected into the fact cache at runtime.."
-msgstr "팩트 스토리지: 활성화하면 수집된 사실이 저장되어 호스트 수준에서 볼 수 있습니다. 팩트는 지속되며 런타임 시 팩트 캐시에 주입됩니다."
-
-#: screens/Host/Host.js:63
-#: screens/Host/HostFacts/HostFacts.js:45
-#: screens/Host/Hosts.js:28
-#: screens/Inventory/Inventories.js:71
-#: screens/Inventory/InventoryHost/InventoryHost.js:78
-#: screens/Inventory/InventoryHostFacts/InventoryHostFacts.js:39
-msgid "Facts"
-msgstr "팩트"
-
-#: components/JobList/JobList.js:232
-#: components/StatusLabel/StatusLabel.js:45
-#: components/Workflow/WorkflowNodeHelp.js:105
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:90
-#: screens/Dashboard/shared/ChartTooltip.js:66
-#: screens/Job/JobOutput/shared/HostStatusBar.js:47
-#: screens/Job/JobOutput/shared/OutputToolbar.js:113
-msgid "Failed"
-msgstr "실패"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:112
-msgid "Failed Host Count"
-msgstr "실패한 호스트 수"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:114
-msgid "Failed Hosts"
-msgstr "실패한 호스트"
-
-#: components/LaunchButton/ReLaunchDropDown.js:61
-#: screens/Dashboard/Dashboard.js:87
-msgid "Failed hosts"
-msgstr "실패한 호스트"
-
-#: screens/Dashboard/DashboardGraph.js:170
-msgid "Failed jobs"
-msgstr "실패한 작업"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:56
-msgid "Failed to approve {0}."
-msgstr "승인하지 못했습니다 {0}."
-
-#: components/ResourceAccessList/ResourceAccessList.js:281
-msgid "Failed to assign roles properly"
-msgstr "역할을 적절하게 할당하지 못했습니다."
-
-#: screens/Team/TeamRoles/TeamRolesList.js:250
-#: screens/User/UserRoles/UserRolesList.js:246
-msgid "Failed to associate role"
-msgstr "역할을 연결하지 못했습니다."
-
-#: screens/Host/HostGroups/HostGroupsList.js:248
-#: screens/InstanceGroup/Instances/InstanceList.js:311
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:288
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:265
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:268
-#: screens/User/UserTeams/UserTeamList.js:263
-msgid "Failed to associate."
-msgstr "연결에 실패했습니다."
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:301
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:111
-msgid "Failed to cancel Inventory Source Sync"
-msgstr "인벤토리 소스 동기화를 취소하지 못했습니다."
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:322
-#: screens/Project/ProjectList/ProjectListItem.js:232
-msgid "Failed to cancel Project Sync"
-msgstr "프로젝트 동기화 취소 실패"
-
-#: components/JobList/JobList.js:329
-msgid "Failed to cancel one or more jobs."
-msgstr "하나 이상의 작업을 취소하지 못했습니다."
-
-#: components/JobList/JobListItem.js:114
-#: screens/Job/JobDetail/JobDetail.js:601
-#: screens/Job/JobOutput/shared/OutputToolbar.js:138
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:90
-msgid "Failed to cancel {0}"
-msgstr "{0} 취소 실패"
-
-#: screens/Credential/CredentialList/CredentialListItem.js:88
-msgid "Failed to copy credential."
-msgstr "인증 정보를 복사하지 못했습니다."
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:112
-msgid "Failed to copy execution environment"
-msgstr "실행 환경을 복사하지 못했습니다."
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:162
-msgid "Failed to copy inventory."
-msgstr "인벤토리를 복사하지 못했습니다."
-
-#: screens/Project/ProjectList/ProjectListItem.js:270
-msgid "Failed to copy project."
-msgstr "프로젝트를 복사하지 못했습니다."
-
-#: components/TemplateList/TemplateListItem.js:253
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:160
-msgid "Failed to copy template."
-msgstr "템플릿을 복사하지 못했습니다."
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:139
-msgid "Failed to delete application."
-msgstr "애플리케이션을 삭제하지 못했습니다."
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:317
-msgid "Failed to delete credential."
-msgstr "인증 정보를 삭제하지 못했습니다."
-
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:82
-msgid "Failed to delete group {0}."
-msgstr "그룹 {0} 을/를 삭제하지 못했습니다."
-
-#: screens/Host/HostDetail/HostDetail.js:126
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:121
-msgid "Failed to delete host."
-msgstr "호스트를 삭제하지 못했습니다."
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:327
-msgid "Failed to delete inventory source {name}."
-msgstr "인벤토리 소스 {name} 삭제에 실패했습니다."
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:213
-msgid "Failed to delete inventory."
-msgstr "인벤토리를 삭제하지 못했습니다."
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:557
-msgid "Failed to delete job template."
-msgstr "작업 템플릿을 삭제하지 못했습니다."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:448
-msgid "Failed to delete notification."
-msgstr "알림을 삭제하지 못했습니다."
-
-#: screens/Application/ApplicationsList/ApplicationsList.js:188
-msgid "Failed to delete one or more applications."
-msgstr "하나 이상의 애플리케이션을 삭제하지 못했습니다."
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:208
-msgid "Failed to delete one or more credential types."
-msgstr "하나 이상의 인증 정보 유형을 삭제하지 못했습니다."
-
-#: screens/Credential/CredentialList/CredentialList.js:217
-msgid "Failed to delete one or more credentials."
-msgstr "하나 이상의 인증 정보를 삭제하지 못했습니다."
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:233
-msgid "Failed to delete one or more execution environments"
-msgstr "하나 이상의 실행 환경을 삭제하지 못했습니다."
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:155
-msgid "Failed to delete one or more groups."
-msgstr "하나 이상의 그룹을 삭제하지 못했습니다."
-
-#: screens/Host/HostList/HostList.js:236
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:204
-msgid "Failed to delete one or more hosts."
-msgstr "하나 이상의 호스트를 삭제하지 못했습니다."
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:225
-msgid "Failed to delete one or more instance groups."
-msgstr "하나 이상의 인스턴스 그룹을 삭제하지 못했습니다."
-
-#: screens/Inventory/InventoryList/InventoryList.js:288
-msgid "Failed to delete one or more inventories."
-msgstr "하나 이상의 인벤토리를 삭제하지 못했습니다."
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:255
-msgid "Failed to delete one or more inventory sources."
-msgstr "하나 이상의 인벤토리 소스를 삭제하지 못했습니다."
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:244
-msgid "Failed to delete one or more job templates."
-msgstr "하나 이상의 작업 템플릿을 삭제하지 못했습니다."
-
-#: components/JobList/JobList.js:318
-msgid "Failed to delete one or more jobs."
-msgstr "하나 이상의 작업을 삭제하지 못했습니다."
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:236
-msgid "Failed to delete one or more notification template."
-msgstr "하나 이상의 알림 템플릿을 삭제하지 못했습니다."
-
-#: screens/Organization/OrganizationList/OrganizationList.js:198
-msgid "Failed to delete one or more organizations."
-msgstr "하나 이상의 조직을 삭제하지 못했습니다."
-
-#: screens/Project/ProjectList/ProjectList.js:294
-msgid "Failed to delete one or more projects."
-msgstr "하나 이상의 프로젝트를 삭제하지 못했습니다."
-
-#: components/Schedule/ScheduleList/ScheduleList.js:242
-msgid "Failed to delete one or more schedules."
-msgstr "하나 이상의 일정을 삭제하지 못했습니다."
-
-#: screens/Team/TeamList/TeamList.js:195
-msgid "Failed to delete one or more teams."
-msgstr "하나 이상의 팀을 삭제하지 못했습니다."
-
-#: components/TemplateList/TemplateList.js:302
-msgid "Failed to delete one or more templates."
-msgstr "하나 이상의 템플릿을 삭제하지 못했습니다."
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:158
-msgid "Failed to delete one or more tokens."
-msgstr "하나 이상의 토큰을 삭제하지 못했습니다."
-
-#: screens/User/UserTokenList/UserTokenList.js:217
-msgid "Failed to delete one or more user tokens."
-msgstr "하나 이상의 사용자 토큰을 삭제하지 못했습니다."
-
-#: screens/User/UserList/UserList.js:192
-msgid "Failed to delete one or more users."
-msgstr "하나 이상의 사용자를 삭제하지 못했습니다."
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:192
-msgid "Failed to delete one or more workflow approval."
-msgstr "하나 이상의 워크플로우 승인을 삭제하지 못했습니다."
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:211
-msgid "Failed to delete organization."
-msgstr "조직을 삭제하지 못했습니다."
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:351
-msgid "Failed to delete project."
-msgstr "프로젝트를 삭제하지 못했습니다."
-
-#: components/ResourceAccessList/ResourceAccessList.js:292
-msgid "Failed to delete role"
-msgstr "역할을 삭제하지 못했습니다"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:261
-#: screens/User/UserRoles/UserRolesList.js:257
-msgid "Failed to delete role."
-msgstr "역할을 삭제하지 못했습니다."
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:657
-msgid "Failed to delete schedule."
-msgstr "일정을 삭제하지 못했습니다."
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:186
-msgid "Failed to delete smart inventory."
-msgstr "스마트 인벤토리를 삭제하지 못했습니다."
-
-#: screens/Team/TeamDetail/TeamDetail.js:81
-msgid "Failed to delete team."
-msgstr "팀을 삭제하지 못했습니다."
-
-#: screens/User/UserDetail/UserDetail.js:118
-msgid "Failed to delete user."
-msgstr "사용자를 삭제하지 못했습니다."
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:351
-msgid "Failed to delete workflow approval."
-msgstr "워크플로우 승인을 삭제하지 못했습니다."
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:280
-msgid "Failed to delete workflow job template."
-msgstr "워크플로 작업 템플릿을 삭제하지 못했습니다."
-
-#: screens/Host/HostDetail/HostDetail.js:59
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:59
-msgid "Failed to delete {name}."
-msgstr "{name} 을/를 삭제하지 못했습니다."
-
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:51
-msgid "Failed to deny {0}."
-msgstr "{0} 을/를 거부하지 못했습니다."
-
-#: screens/Host/HostGroups/HostGroupsList.js:249
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:266
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:269
-msgid "Failed to disassociate one or more groups."
-msgstr "하나 이상의 그룹을 연결 해제하지 못했습니다."
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:299
-msgid "Failed to disassociate one or more hosts."
-msgstr "하나 이상의 호스트를 연결 해제하지 못했습니다."
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:315
-#: screens/InstanceGroup/Instances/InstanceList.js:313
-#: screens/Instances/InstanceDetail/InstanceDetail.js:365
-msgid "Failed to disassociate one or more instances."
-msgstr "하나 이상의 인스턴스를 연결 해제하지 못했습니다."
-
-#: screens/User/UserTeams/UserTeamList.js:264
-msgid "Failed to disassociate one or more teams."
-msgstr "하나 이상의 팀을 연결 해제하지 못했습니다."
-
-#: screens/Login/Login.js:243
-msgid "Failed to fetch custom login configuration settings. System defaults will be shown instead."
-msgstr "사용자 정의 로그인 구성 설정을 가져오지 못했습니다. 시스템 기본 설정이 대신 표시됩니다."
-
-#: screens/Project/ProjectList/ProjectList.js:306
-msgid "Failed to fetch the updated project data."
-msgstr "업데이트된 프로젝트 데이터를 가져오지 못했습니다."
-
-#: screens/TopologyView/MeshGraph.js:409
-msgid "Failed to get instance."
-msgstr "인스턴스를 가져오지 못했습니다."
-
-#: components/AdHocCommands/AdHocCommands.js:112
-#: components/LaunchButton/LaunchButton.js:188
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:128
-msgid "Failed to launch job."
-msgstr "작업을 시작하지 못했습니다."
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:378
-#: screens/Instances/InstanceList/InstanceList.js:246
-msgid "Failed to remove one or more instances."
-msgstr "하나 이상의 인스턴스를 제거하지 못했습니다."
-
-#: contexts/Config.js:98
-msgid "Failed to retrieve configuration."
-msgstr "구성을 검색하지 못했습니다."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:376
-msgid "Failed to retrieve full node resource object."
-msgstr "전체 노드 리소스 오브젝트를 검색하지 못했습니다."
-
-#: screens/InstanceGroup/Instances/InstanceList.js:315
-#: screens/Instances/InstanceList/InstanceList.js:234
-msgid "Failed to run a health check on one or more instances."
-msgstr "하나 이상의 인스턴스에서 상태 확인을 실행하지 못했습니다."
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:172
-msgid "Failed to send test notification."
-msgstr "테스트 알림을 발송하지 못했습니다."
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:52
-msgid "Failed to sync inventory source."
-msgstr "인벤토리 소스를 동기화하지 못했습니다."
-
-#: screens/Project/shared/ProjectSyncButton.js:63
-msgid "Failed to sync project."
-msgstr "프로젝트를 동기화하지 못했습니다."
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:242
-msgid "Failed to sync some or all inventory sources."
-msgstr "일부 또는 모든 인벤토리 소스를 동기화하지 못했습니다."
-
-#: components/HostToggle/HostToggle.js:80
-msgid "Failed to toggle host."
-msgstr "호스트를 전환하지 못했습니다."
-
-#: components/InstanceToggle/InstanceToggle.js:71
-msgid "Failed to toggle instance."
-msgstr "인스턴스를 전환하지 못했습니다."
-
-#: components/NotificationList/NotificationList.js:250
-msgid "Failed to toggle notification."
-msgstr "알림을 전환하지 못했습니다."
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:77
-msgid "Failed to toggle schedule."
-msgstr "일정을 전환하지 못했습니다."
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:314
-#: screens/InstanceGroup/Instances/InstanceListItem.js:222
-#: screens/Instances/InstanceDetail/InstanceDetail.js:364
-#: screens/Instances/InstanceList/InstanceListItem.js:238
-msgid "Failed to update capacity adjustment."
-msgstr "크기 조정을 업데이트하지 못했습니다."
-
-#: screens/TopologyView/Tooltip.js:204
-msgid "Failed to update instance."
-msgstr "인스턴스를 업데이트하지 못했습니다."
-
-#: screens/Template/TemplateSurvey.js:133
-msgid "Failed to update survey."
-msgstr "설문 조사를 업데이트하지 못했습니다."
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:88
-msgid "Failed to user token."
-msgstr "사용자 토큰에 실패했습니다."
-
-#: components/NotificationList/NotificationListItem.js:85
-#: components/NotificationList/NotificationListItem.js:86
-msgid "Failure"
-msgstr "실패"
-
-#: screens/Job/JobOutput/EmptyOutput.js:45
-msgid "Failure Explanation:"
-msgstr "실패 설명:"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:66
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:210
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:240
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:270
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:315
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:373
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:142
-msgid "False"
-msgstr "False"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:160
-#: components/Schedule/shared/FrequencyDetailSubform.js:103
-msgid "February"
-msgstr "2월"
-
-#: components/Search/LookupTypeInput.js:52
-msgid "Field contains value."
-msgstr "필드에 값이 있습니다."
-
-#: components/Search/LookupTypeInput.js:80
-msgid "Field ends with value."
-msgstr "필드는 값으로 끝납니다."
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:76
-msgid "Field for passing a custom Kubernetes or OpenShift Pod specification."
-msgstr "사용자 정의 Kubernetes 또는 OpenShift Pod 사양을 전달하는 필드입니다."
-
-#: components/Search/LookupTypeInput.js:94
-msgid "Field matches the given regular expression."
-msgstr "필드는 지정된 정규식과 일치합니다."
-
-#: components/Search/LookupTypeInput.js:66
-msgid "Field starts with value."
-msgstr "필드는 값으로 시작합니다."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:416
-msgid "Fifth"
-msgstr "다섯 번째"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:105
-msgid "File Difference"
-msgstr "파일 차이점"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:72
-msgid "File upload rejected. Please select a single .json file."
-msgstr "파일 업로드가 거부되었습니다. 단일 .json 파일을 선택하십시오."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:96
-msgid "File, directory or script"
-msgstr "파일, 디렉터리 또는 스크립트"
-
-#: components/Search/Search.js:198
-#: components/Search/Search.js:222
-msgid "Filter By {name}"
-msgstr "{name}으로 필터링"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:88
-msgid "Filter by failed jobs"
-msgstr "실패한 작업으로 필터링"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:94
-msgid "Filter by successful jobs"
-msgstr "성공한 작업으로 필터링"
-
-#: components/JobList/JobList.js:248
-#: components/JobList/JobListItem.js:100
-msgid "Finish Time"
-msgstr "완료 시간"
-
-#: screens/Job/JobDetail/JobDetail.js:226
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:208
-msgid "Finished"
-msgstr "완료"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:404
-msgid "First"
-msgstr "첫 번째"
-
-#: components/AddRole/AddResourceRole.js:28
-#: components/AddRole/AddResourceRole.js:42
-#: components/ResourceAccessList/ResourceAccessList.js:178
-#: screens/User/UserDetail/UserDetail.js:64
-#: screens/User/UserList/UserList.js:124
-#: screens/User/UserList/UserList.js:161
-#: screens/User/UserList/UserListItem.js:53
-#: screens/User/shared/UserForm.js:63
-msgid "First Name"
-msgstr "이름"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:332
-msgid "First Run"
-msgstr "첫 번째 실행"
-
-#: components/ResourceAccessList/ResourceAccessList.js:227
-#: components/ResourceAccessList/ResourceAccessListItem.js:67
-msgid "First name"
-msgstr "이름"
-
-#: components/Search/AdvancedSearch.js:213
-#: components/Search/AdvancedSearch.js:227
-msgid "First, select a key"
-msgstr "먼저 키 선택"
-
-#: components/Workflow/WorkflowTools.js:88
-msgid "Fit the graph to the available screen size"
-msgstr "그래프를 사용 가능한 화면 크기에 맞춥니다."
-
-#: screens/TopologyView/Header.js:75
-#: screens/TopologyView/Header.js:78
-msgid "Fit to screen"
-msgstr "화면에 맞추기"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:94
-msgid "Float"
-msgstr "부동 값"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:183
-msgid "Follow"
-msgstr "팔로우"
-
-#: screens/Job/Job.helptext.js:5
-#: screens/Template/shared/JobTemplate.helptext.js:6
-msgid "For job templates, select run to execute the playbook. Select check to only check playbook syntax, test environment setup, and report problems without executing the playbook."
-msgstr "작업 템플릿의 경우 실행을 선택하여 플레이북을 실행합니다. 플레이북을 실행하지 않고 플레이북 구문만 확인하고, 환경 설정을 테스트하고, 문제를 보고하려면 확인을 선택합니다."
-
-#: screens/Project/shared/Project.helptext.js:98
-msgid "For more information, refer to the"
-msgstr "자세한 내용은 다음을 참조하십시오."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:160
-#: components/AdHocCommands/AdHocDetailsStep.js:161
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:55
-#: components/PromptDetail/PromptDetail.js:345
-#: components/PromptDetail/PromptJobTemplateDetail.js:150
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:475
-#: screens/Job/JobDetail/JobDetail.js:389
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:259
-#: screens/Template/shared/JobTemplateForm.js:409
-#: screens/TopologyView/Tooltip.js:282
-msgid "Forks"
-msgstr "포크"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:414
-msgid "Fourth"
-msgstr "네 번째"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:363
-#: components/Schedule/shared/ScheduleFormFields.js:146
-msgid "Frequency Details"
-msgstr "빈도 세부 정보"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:381
-msgid "Frequency Exception Details"
-msgstr "빈도 예외 세부 정보"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:72
-#: components/Schedule/shared/FrequencyDetailSubform.js:185
-#: components/Schedule/shared/FrequencyDetailSubform.js:206
-#: components/Schedule/shared/buildRuleObj.js:91
-msgid "Frequency did not match an expected value"
-msgstr "빈도가 예상 값과 일치하지 않음"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:310
-msgid "Fri"
-msgstr "금요일"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:81
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:187
-#: components/Schedule/shared/FrequencyDetailSubform.js:315
-#: components/Schedule/shared/FrequencyDetailSubform.js:453
-msgid "Friday"
-msgstr "금요일"
-
-#: components/Search/RelatedLookupTypeInput.js:45
-msgid "Fuzzy search on id, name or description fields."
-msgstr "id, 이름 또는 설명 필드에서 퍼지 검색"
-
-#: components/Search/RelatedLookupTypeInput.js:32
-msgid "Fuzzy search on name field."
-msgstr "이름 필드에서 퍼지 검색"
-
-#: components/CredentialChip/CredentialChip.js:13
-msgid "GPG Public Key"
-msgstr "GPG 공개 키"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:153
-#: screens/Organization/shared/OrganizationForm.js:101
-msgid "Galaxy Credentials"
-msgstr "Galaxy 인증 정보"
-
-#: screens/Credential/shared/CredentialForm.js:185
-msgid "Galaxy credentials must be owned by an Organization."
-msgstr "Galaxy 인증 정보는 조직에 속해 있어야 합니다."
-
-#: screens/Job/JobOutput/JobOutputSearch.js:106
-msgid "Gathering Facts"
-msgstr "팩트 수집"
-
-#: screens/Setting/Settings.js:72
-msgid "Generic OIDC"
-msgstr "일반 OIDC"
-
-#: screens/Setting/SettingList.js:85
-msgid "Generic OIDC settings"
-msgstr "일반 OIDC 설정"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:222
-msgid "Get subscription"
-msgstr "서브스크립션 받기"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:216
-msgid "Get subscriptions"
-msgstr "서브스크립션 가져오기"
-
-#: components/Lookup/ProjectLookup.js:136
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:89
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:158
-#: screens/Job/JobDetail/JobDetail.js:75
-#: screens/Project/ProjectList/ProjectList.js:198
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:97
-msgid "Git"
-msgstr "Git"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:312
-#: screens/Template/shared/WebhookSubForm.js:106
-msgid "GitHub"
-msgstr "GitHub"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:85
-#: screens/Setting/Settings.js:51
-msgid "GitHub Default"
-msgstr "GitHub 기본값"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:100
-#: screens/Setting/Settings.js:60
-msgid "GitHub Enterprise"
-msgstr "GitHub Enterprise"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:105
-#: screens/Setting/Settings.js:63
-msgid "GitHub Enterprise Organization"
-msgstr "GitHub Enterprise 조직"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:110
-#: screens/Setting/Settings.js:66
-msgid "GitHub Enterprise Team"
-msgstr "GitHub Enterprise 팀"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:90
-#: screens/Setting/Settings.js:54
-msgid "GitHub Organization"
-msgstr "GitHub 조직"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:95
-#: screens/Setting/Settings.js:57
-msgid "GitHub Team"
-msgstr "GitHub 팀"
-
-#: screens/Setting/SettingList.js:61
-msgid "GitHub settings"
-msgstr "GitHub 설정"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:312
-#: screens/Template/shared/WebhookSubForm.js:112
-msgid "GitLab"
-msgstr "GitLab"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:79
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:84
-msgid "Globally Available"
-msgstr "전역적으로 사용 가능"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:134
-msgid "Globally available execution environment can not be reassigned to a specific Organization"
-msgstr "전역적으로 사용 가능한 실행 환경을 특정 조직에 다시 할당할 수 없습니다."
-
-#: components/Pagination/Pagination.js:29
-msgid "Go to first page"
-msgstr "첫 페이지로 이동"
-
-#: components/Pagination/Pagination.js:31
-msgid "Go to last page"
-msgstr "마지막 페이지로 이동"
-
-#: components/Pagination/Pagination.js:32
-msgid "Go to next page"
-msgstr "다음 페이지로 이동"
-
-#: components/Pagination/Pagination.js:30
-msgid "Go to previous page"
-msgstr "이전 페이지로 이동"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:99
-msgid "Google Compute Engine"
-msgstr "Google Compute Engine"
-
-#: screens/Setting/SettingList.js:65
-msgid "Google OAuth 2 settings"
-msgstr "Google OAuth 2 설정"
-
-#: screens/Setting/Settings.js:69
-msgid "Google OAuth2"
-msgstr "Google OAuth2"
-
-#: components/NotificationList/NotificationList.js:194
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:135
-msgid "Grafana"
-msgstr "Grafana"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:160
-msgid "Grafana API key"
-msgstr "Grafana API 키"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:187
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:151
-msgid "Grafana URL"
-msgstr "Grafana URL"
-
-#: components/Search/LookupTypeInput.js:106
-msgid "Greater than comparison."
-msgstr "비교보다 큽니다."
-
-#: components/Search/LookupTypeInput.js:113
-msgid "Greater than or equal to comparison."
-msgstr "비교보다 크거나 같습니다."
-
-#: components/Lookup/HostFilterLookup.js:102
-msgid "Group"
-msgstr "그룹"
-
-#: screens/Inventory/Inventories.js:78
-msgid "Group details"
-msgstr "그룹 세부 정보"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:124
-msgid "Group type"
-msgstr "그룹 유형"
-
-#: screens/Host/Host.js:68
-#: screens/Host/HostGroups/HostGroupsList.js:231
-#: screens/Host/Hosts.js:29
-#: screens/Inventory/Inventories.js:72
-#: screens/Inventory/Inventories.js:74
-#: screens/Inventory/Inventory.js:66
-#: screens/Inventory/InventoryHost/InventoryHost.js:83
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:248
-#: screens/Inventory/InventoryList/InventoryListItem.js:127
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:251
-#: util/getRelatedResourceDeleteDetails.js:119
-msgid "Groups"
-msgstr "그룹"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:383
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:468
-msgid "HTTP Headers"
-msgstr "HTTP 헤더"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:378
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:481
-msgid "HTTP Method"
-msgstr "HTTP 방법"
-
-#: components/HealthCheckAlert/HealthCheckAlert.js:22
-msgid "Health check request(s) submitted. Please wait and reload the page."
-msgstr "상태 점검 요청이 제출되었습니다. 잠시 기다렸다가 페이지를 다시 로드하십시오."
-
-#: components/StatusLabel/StatusLabel.js:42
-msgid "Healthy"
-msgstr "상태 양호"
-
-#: components/AppContainer/PageHeaderToolbar.js:116
-msgid "Help"
-msgstr "도움말"
-
-#: components/FormField/PasswordInput.js:35
-msgid "Hide"
-msgstr "숨기기"
-
-#: components/LaunchPrompt/LaunchPrompt.js:135
-#: components/Schedule/shared/SchedulePromptableFields.js:102
-msgid "Hide description"
-msgstr "설명 숨기기"
-
-#: components/NotificationList/NotificationList.js:195
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:136
-msgid "Hipchat"
-msgstr "Hipchat"
-
-#: screens/Instances/InstanceList/InstanceList.js:154
-msgid "Hop"
-msgstr "홉"
-
-#: screens/TopologyView/Legend.js:103
-msgid "Hop node"
-msgstr "홉 노드"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:211
-#: screens/Job/JobOutput/HostEventModal.js:109
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:149
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:78
-msgid "Host"
-msgstr "호스트"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:107
-msgid "Host Async Failure"
-msgstr "호스트 동기화 실패"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:108
-msgid "Host Async OK"
-msgstr "호스트 동기화 확인"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:159
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:297
-#: screens/Template/shared/JobTemplateForm.js:633
-msgid "Host Config Key"
-msgstr "호스트 구성 키"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:96
-msgid "Host Count"
-msgstr "호스트 수"
-
-#: screens/Job/JobOutput/HostEventModal.js:88
-msgid "Host Details"
-msgstr "호스트 세부 정보"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:109
-msgid "Host Failed"
-msgstr "호스트 실패"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:110
-msgid "Host Failure"
-msgstr "호스트 실패"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:242
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:145
-msgid "Host Filter"
-msgstr "호스트 필터"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:201
-#: screens/Instances/InstanceDetail/InstanceDetail.js:191
-#: screens/Instances/Shared/InstanceForm.js:18
-msgid "Host Name"
-msgstr "호스트 이름"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:111
-msgid "Host OK"
-msgstr "호스트 확인"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:112
-msgid "Host Polling"
-msgstr "호스트 폴링"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:113
-msgid "Host Retry"
-msgstr "호스트 재시도"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:114
-msgid "Host Skipped"
-msgstr "호스트 건너뜀"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:115
-msgid "Host Started"
-msgstr "호스트 시작됨"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:116
-msgid "Host Unreachable"
-msgstr "호스트에 연결할 수 없음"
-
-#: screens/Inventory/Inventories.js:69
-msgid "Host details"
-msgstr "호스트 세부 정보"
-
-#: screens/Job/JobOutput/HostEventModal.js:89
-msgid "Host details modal"
-msgstr "호스트 세부 정보 모달"
-
-#: screens/Host/Host.js:96
-#: screens/Inventory/InventoryHost/InventoryHost.js:100
-msgid "Host not found."
-msgstr "호스트를 찾을 수 없습니다."
-
-#: screens/Job/JobOutput/shared/HostStatusBar.js:76
-msgid "Host status information for this job is unavailable."
-msgstr "이 작업의 호스트 상태 정보를 사용할 수 없습니다."
-
-#: routeConfig.js:85
-#: screens/ActivityStream/ActivityStream.js:176
-#: screens/Dashboard/Dashboard.js:81
-#: screens/Host/HostList/HostList.js:143
-#: screens/Host/HostList/HostList.js:191
-#: screens/Host/Hosts.js:14
-#: screens/Host/Hosts.js:23
-#: screens/Inventory/Inventories.js:65
-#: screens/Inventory/Inventories.js:79
-#: screens/Inventory/Inventory.js:67
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:67
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:189
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:272
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:112
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:172
-#: screens/Inventory/SmartInventory.js:68
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:71
-#: screens/Job/JobOutput/shared/OutputToolbar.js:97
-#: util/getRelatedResourceDeleteDetails.js:123
-msgid "Hosts"
-msgstr "호스트"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:92
-msgid "Hosts automated"
-msgstr "자동화된 호스트"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:118
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:125
-msgid "Hosts available"
-msgstr "사용 가능한 호스트"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:107
-msgid "Hosts imported"
-msgstr "가져온 호스트"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:112
-msgid "Hosts remaining"
-msgstr "남아 있는 호스트"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:183
-#: components/Schedule/shared/FrequencyDetailSubform.js:175
-#: components/Schedule/shared/ScheduleFormFields.js:126
-#: components/Schedule/shared/ScheduleFormFields.js:186
-msgid "Hour"
-msgstr "시간"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:209
-#: screens/Instances/InstanceList/InstanceList.js:153
-msgid "Hybrid"
-msgstr "하이브리드"
-
-#: screens/TopologyView/Legend.js:95
-msgid "Hybrid node"
-msgstr "하이브리드 노드"
-
-#: components/JobList/JobList.js:200
-#: components/Lookup/HostFilterLookup.js:98
-#: screens/Team/TeamRoles/TeamRolesList.js:155
-msgid "ID"
-msgstr "ID"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:193
-msgid "ID of the Dashboard"
-msgstr "대시보드 ID"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:198
-msgid "ID of the Panel"
-msgstr "패널 ID"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:167
-msgid "ID of the dashboard (optional)"
-msgstr "대시보드 ID (선택 사항)"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:173
-msgid "ID of the panel (optional)"
-msgstr "패널 ID (선택 사항)"
-
-#: screens/TopologyView/Tooltip.js:265
-msgid "IP address"
-msgstr "IP 주소"
-
-#: components/NotificationList/NotificationList.js:196
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:137
-msgid "IRC"
-msgstr "IRC"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:228
-msgid "IRC Nick"
-msgstr "IRC 닉네임"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:223
-msgid "IRC Server Address"
-msgstr "IRC 서버 주소"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:218
-msgid "IRC Server Port"
-msgstr "IRC 서버 포트"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:223
-msgid "IRC nick"
-msgstr "IRC 닉네임"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:215
-msgid "IRC server address"
-msgstr "IRC 서버 주소"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:201
-msgid "IRC server password"
-msgstr "IRC 서버 암호"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:206
-msgid "IRC server port"
-msgstr "IRC 서버 포트"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:263
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:308
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:272
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:343
-msgid "Icon URL"
-msgstr "아이콘 URL"
-
-#: screens/Inventory/shared/Inventory.helptext.js:100
-msgid ""
-"If checked, all variables for child groups\n"
-"and hosts will be removed and replaced by those found\n"
-"on the external source."
-msgstr "이 옵션을 선택하면 하위 그룹 및 호스트의 모든 변수가 제거되고 외부 소스에 있는 변수로 대체됩니다."
-
-#: screens/Inventory/shared/Inventory.helptext.js:84
-msgid ""
-"If checked, any hosts and groups that were\n"
-"previously present on the external source but are now removed\n"
-"will be removed from the inventory. Hosts and groups\n"
-"that were not managed by the inventory source will be promoted\n"
-"to the next manually created group or if there is no manually\n"
-"created group to promote them into, they will be left in the \"all\"\n"
-"default group for the inventory."
-msgstr "이 옵션을 선택하면 이전에 외부 소스에 있었지만 지금은 삭제된 모든 호스트 및 그룹이 인벤토리에서 제거됩니다. 인벤토리 소스에서 관리하지 않은 호스트 및 그룹은 수동으로 생성된 다음 그룹으로 승격되거나 승격할 수동으로 생성된 그룹이 없는 경우 인벤토리의 \"모든\" 기본 그룹에 남아 있게 됩니다."
-
-#: screens/Template/shared/JobTemplate.helptext.js:30
-msgid "If enabled, run this playbook as an administrator."
-msgstr "활성화하면 이 플레이북을 관리자로 실행합니다."
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:184
-msgid ""
-"If enabled, show the changes made\n"
-"by Ansible tasks, where supported. This is equivalent to Ansible’s\n"
-"--diff mode."
-msgstr "활성화된 경우 지원되는 Ansible 작업에서 변경한 내용을 표시합니다. 이는 Ansible의 --diff 모드와 동일합니다."
-
-#: screens/Template/shared/JobTemplate.helptext.js:19
-msgid "If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible's --diff mode."
-msgstr "활성화된 경우 지원되는 Ansible 작업에서 변경한 내용을 표시합니다. 이는 Ansible의 --diff 모드와 동일합니다."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:181
-msgid "If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible’s --diff mode."
-msgstr "활성화된 경우 지원되는 Ansible 작업에서 변경한 내용을 표시합니다. 이는 Ansible의 --diff 모드와 동일합니다."
-
-#: screens/Template/shared/JobTemplate.helptext.js:32
-msgid "If enabled, simultaneous runs of this job template will be allowed."
-msgstr "활성화하면 이 작업 템플릿을 동시에 실행할 수 있습니다."
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:16
-msgid "If enabled, simultaneous runs of this workflow job template will be allowed."
-msgstr "활성화하면 이 워크플로 작업 템플릿을 동시에 실행할 수 있습니다."
-
-#: screens/Inventory/shared/Inventory.helptext.js:194
-msgid ""
-"If enabled, the inventory will prevent adding any organization instance groups to the list of preferred instances groups to run associated job templates on.\n"
-"Note: If this setting is enabled and you provided an empty list, the global instance groups will be applied."
-msgstr "활성화된 경우 인벤토리에서 연결된 작업 템플릿을 실행할 기본 인스턴스 그룹 목록에 조직 인스턴스 그룹을 추가할 수 없습니다. \n"
-" 참고: 이 설정이 활성화되어 있고 빈 목록을 제공한 경우 글로벌 인스턴스 그룹이 적용됩니다."
-
-#: screens/Template/shared/JobTemplate.helptext.js:33
-msgid ""
-"If enabled, the job template will prevent adding any inventory or organization instance groups to the list of preferred instances groups to run on.\n"
-"Note: If this setting is enabled and you provided an empty list, the global instance groups will be applied."
-msgstr "활성화된 경우 작업 템플릿에서 실행할 기본 인스턴스 그룹 목록에 인벤토리 또는 조직 인스턴스 그룹을 추가하지 않습니다. \n"
-" 참고: 이 설정이 활성화되어 있고 빈 목록을 제공한 경우 글로벌 인스턴스 그룹이 적용됩니다."
-
-#: screens/Template/shared/JobTemplate.helptext.js:35
-msgid "If enabled, this will store gathered facts so they can be viewed at the host level. Facts are persisted and injected into the fact cache at runtime."
-msgstr "활성화하면 수집된 사실이 저장되어 호스트 수준에서 볼 수 있습니다. 팩트는 지속되며 런타임 시 팩트 캐시에 주입됩니다."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:275
-msgid "If specified, this field will be shown on the node instead of the resource name when viewing the workflow"
-msgstr "지정된 경우 이 필드는 워크플로우를 볼 때 리소스 이름 대신 노드에 표시됩니다."
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:180
-msgid "If you are ready to upgrade or renew, please <0>contact us.0>"
-msgstr "업그레이드 또는 갱신할 준비가 되었으면 <0>에 문의0>하십시오."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:63
-msgid ""
-"If you do not have a subscription, you can visit\n"
-"Red Hat to obtain a trial subscription."
-msgstr "서브스크립션이 없는 경우 Red Hat에 문의하여 평가판 서브스크립션을 받을 수 있습니다."
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:46
-msgid "If you only want to remove access for this particular user, please remove them from the team."
-msgstr "이 특정 사용자에 대한 액세스 권한만 제거하려면 팀에서 제거하십시오."
-
-#: screens/Inventory/shared/Inventory.helptext.js:120
-#: screens/Inventory/shared/Inventory.helptext.js:139
-msgid ""
-"If you want the Inventory Source to update on\n"
-"launch and on project update, click on Update on launch, and also go to"
-msgstr "시작 및 프로젝트 업데이트 시 인벤토리 소스를 업데이트하려면 시작 시 업데이트를 클릭하고 다음으로 이동합니다."
-
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:80
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:91
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:101
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:54
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:141
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:147
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:166
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:75
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:98
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:89
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:108
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.js:21
-msgid "Image"
-msgstr "이미지"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:117
-msgid "Including File"
-msgstr "파일 포함"
-
-#: components/HostToggle/HostToggle.js:16
-msgid ""
-"Indicates if a host is available and should be included in running\n"
-"jobs. For hosts that are part of an external inventory, this may be\n"
-"reset by the inventory sync process."
-msgstr "호스트를 사용할 수 있고 실행 중인 작업에 포함되어야 하는지 여부를 나타냅니다. 외부 인벤토리의 일부인 호스트의 경우 인벤토리 동기화 프로세스에서 재설정할 수 있습니다."
-
-#: components/AppContainer/PageHeaderToolbar.js:103
-msgid "Info"
-msgstr "정보"
-
-#: screens/ActivityStream/ActivityStreamListItem.js:45
-msgid "Initiated By"
-msgstr "초기자"
-
-#: screens/ActivityStream/ActivityStream.js:253
-#: screens/ActivityStream/ActivityStream.js:263
-#: screens/ActivityStream/ActivityStreamDetailButton.js:44
-msgid "Initiated by"
-msgstr "초기자"
-
-#: screens/ActivityStream/ActivityStream.js:243
-msgid "Initiated by (username)"
-msgstr "초기자 (사용자 이름)"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:82
-#: screens/CredentialType/shared/CredentialTypeForm.js:46
-msgid "Injector configuration"
-msgstr "인젝터 구성"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:74
-#: screens/CredentialType/shared/CredentialTypeForm.js:38
-msgid "Input configuration"
-msgstr "입력 구성"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:79
-msgid "Input schema which defines a set of ordered fields for that type."
-msgstr "해당 유형에 대해 정렬된 필드 집합을 정의하는 입력 스키마입니다."
-
-#: screens/Project/shared/ProjectSubForms/InsightsSubForm.js:31
-msgid "Insights Credential"
-msgstr "Insights 인증 정보"
-
-#: components/Lookup/HostFilterLookup.js:123
-msgid "Insights system ID"
-msgstr "Insights 시스템 ID"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:249
-msgid "Install Bundle"
-msgstr "번들 설치"
-
-#: components/StatusLabel/StatusLabel.js:58
-#: screens/TopologyView/Legend.js:136
-msgid "Installed"
-msgstr "설치됨"
-
-#: screens/Metrics/Metrics.js:187
-msgid "Instance"
-msgstr "인스턴스"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:135
-msgid "Instance Filters"
-msgstr "인스턴스 필터"
-
-#: screens/Job/JobDetail/JobDetail.js:358
-msgid "Instance Group"
-msgstr "인스턴스 그룹"
-
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:96
-#: components/LaunchPrompt/steps/useInstanceGroupsStep.js:31
-#: components/Lookup/InstanceGroupsLookup.js:74
-#: components/Lookup/InstanceGroupsLookup.js:121
-#: components/Lookup/InstanceGroupsLookup.js:141
-#: components/Lookup/InstanceGroupsLookup.js:151
-#: components/PromptDetail/PromptDetail.js:227
-#: components/PromptDetail/PromptJobTemplateDetail.js:228
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:499
-#: routeConfig.js:132
-#: screens/ActivityStream/ActivityStream.js:205
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:106
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:179
-#: screens/InstanceGroup/InstanceGroups.js:16
-#: screens/InstanceGroup/InstanceGroups.js:26
-#: screens/Instances/InstanceDetail/InstanceDetail.js:217
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:107
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:127
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:422
-#: util/getRelatedResourceDeleteDetails.js:282
-msgid "Instance Groups"
-msgstr "인스턴스 그룹"
-
-#: components/Lookup/HostFilterLookup.js:115
-msgid "Instance ID"
-msgstr "인스턴스 ID"
-
-#: screens/Instances/Shared/InstanceForm.js:32
-msgid "Instance State"
-msgstr "인스턴스 상태"
-
-#: screens/Instances/Shared/InstanceForm.js:48
-msgid "Instance Type"
-msgstr "인스턴스 유형"
-
-#: screens/InstanceGroup/InstanceGroups.js:33
-msgid "Instance details"
-msgstr "인스턴스 세부 정보"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:58
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:69
-msgid "Instance group"
-msgstr "인스턴스 그룹"
-
-#: screens/InstanceGroup/InstanceGroup.js:92
-msgid "Instance group not found."
-msgstr "인스턴스 그룹을 찾을 수 없습니다."
-
-#: screens/InstanceGroup/Instances/InstanceListItem.js:165
-#: screens/Instances/InstanceList/InstanceListItem.js:176
-msgid "Instance group used capacity"
-msgstr "인스턴스 그룹이 사용하는 용량"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:122
-#: screens/TopologyView/Tooltip.js:273
-msgid "Instance groups"
-msgstr "인스턴스 그룹"
-
-#: screens/TopologyView/Tooltip.js:234
-msgid "Instance status"
-msgstr "인스턴스 상태"
-
-#: screens/TopologyView/Tooltip.js:240
-msgid "Instance type"
-msgstr "인스턴스 유형"
-
-#: routeConfig.js:137
-#: screens/ActivityStream/ActivityStream.js:203
-#: screens/InstanceGroup/InstanceGroup.js:74
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:198
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:73
-#: screens/InstanceGroup/InstanceGroups.js:31
-#: screens/InstanceGroup/Instances/InstanceList.js:192
-#: screens/InstanceGroup/Instances/InstanceList.js:290
-#: screens/Instances/InstanceList/InstanceList.js:136
-#: screens/Instances/Instances.js:13
-#: screens/Instances/Instances.js:22
-msgid "Instances"
-msgstr "인스턴스"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:93
-msgid "Integer"
-msgstr "정수"
-
-#: util/validators.js:94
-msgid "Invalid email address"
-msgstr "잘못된 이메일 주소"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:116
-msgid "Invalid file format. Please upload a valid Red Hat Subscription Manifest."
-msgstr "잘못된 파일 형식입니다. 유효한 Red Hat 서브스크립션 목록을 업로드하십시오."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:178
-msgid "Invalid link target. Unable to link to children or ancestor nodes. Graph cycles are not supported."
-msgstr "잘못된 링크 대상입니다. 자식 또는 상위 노드에 연결할 수 없습니다. 그래프 주기는 지원되지 않습니다."
-
-#: util/validators.js:33
-msgid "Invalid time format"
-msgstr "잘못된 시간 형식"
-
-#: screens/Login/Login.js:153
-msgid "Invalid username or password. Please try again."
-msgstr "사용자 이름 또는 암호가 잘못되었습니다. 다시 시도하십시오."
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:119
-#: routeConfig.js:80
-#: screens/ActivityStream/ActivityStream.js:173
-#: screens/Dashboard/Dashboard.js:92
-#: screens/Inventory/Inventories.js:17
-#: screens/Inventory/InventoryList/InventoryList.js:174
-#: screens/Inventory/InventoryList/InventoryList.js:237
-#: util/getRelatedResourceDeleteDetails.js:202
-#: util/getRelatedResourceDeleteDetails.js:270
-msgid "Inventories"
-msgstr "인벤토리"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:153
-msgid "Inventories with sources cannot be copied"
-msgstr "소스와 함께 인벤토리를 복사할 수 없습니다."
-
-#: components/HostForm/HostForm.js:48
-#: components/JobList/JobListItem.js:223
-#: components/LaunchPrompt/steps/InventoryStep.js:105
-#: components/LaunchPrompt/steps/useInventoryStep.js:48
-#: components/Lookup/HostFilterLookup.js:424
-#: components/Lookup/HostListItem.js:10
-#: components/Lookup/InventoryLookup.js:119
-#: components/Lookup/InventoryLookup.js:128
-#: components/Lookup/InventoryLookup.js:169
-#: components/Lookup/InventoryLookup.js:184
-#: components/Lookup/InventoryLookup.js:224
-#: components/PromptDetail/PromptDetail.js:214
-#: components/PromptDetail/PromptInventorySourceDetail.js:76
-#: components/PromptDetail/PromptJobTemplateDetail.js:119
-#: components/PromptDetail/PromptJobTemplateDetail.js:129
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:79
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:430
-#: components/TemplateList/TemplateListItem.js:285
-#: components/TemplateList/TemplateListItem.js:295
-#: screens/Host/HostDetail/HostDetail.js:77
-#: screens/Host/HostList/HostList.js:171
-#: screens/Host/HostList/HostListItem.js:61
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:94
-#: screens/Inventory/InventoryList/InventoryList.js:186
-#: screens/Inventory/InventoryList/InventoryListItem.js:117
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:38
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:113
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:41
-#: screens/Job/JobDetail/JobDetail.js:107
-#: screens/Job/JobDetail/JobDetail.js:122
-#: screens/Job/JobDetail/JobDetail.js:129
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:214
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:224
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:141
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:33
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:252
-msgid "Inventory"
-msgstr "인벤토리"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:104
-msgid "Inventory (Name)"
-msgstr "인벤토리(이름)"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:99
-msgid "Inventory File"
-msgstr "인벤토리 파일"
-
-#: components/Lookup/HostFilterLookup.js:106
-msgid "Inventory ID"
-msgstr "인벤토리 ID"
-
-#: screens/Job/JobDetail/JobDetail.js:282
-msgid "Inventory Source"
-msgstr "인벤토리 소스"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:73
-msgid "Inventory Source Sync"
-msgstr "인벤토리 소스 동기화"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:299
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:110
-msgid "Inventory Source Sync Error"
-msgstr "인벤토리 소스 동기화 오류"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:176
-#: screens/Inventory/InventorySources/InventorySourceList.js:193
-#: util/getRelatedResourceDeleteDetails.js:67
-#: util/getRelatedResourceDeleteDetails.js:147
-msgid "Inventory Sources"
-msgstr "인벤토리 소스"
-
-#: components/JobList/JobList.js:212
-#: components/JobList/JobListItem.js:43
-#: components/Schedule/ScheduleList/ScheduleListItem.js:36
-#: components/Workflow/WorkflowLegend.js:100
-#: screens/Job/JobDetail/JobDetail.js:66
-msgid "Inventory Sync"
-msgstr "인벤토리 동기화"
-
-#: screens/Inventory/InventoryList/InventoryList.js:183
-msgid "Inventory Type"
-msgstr "인벤토리 유형"
-
-#: components/Workflow/WorkflowNodeHelp.js:71
-msgid "Inventory Update"
-msgstr "인벤토리 업데이트"
-
-#: screens/Inventory/InventoryList/InventoryList.js:121
-msgid "Inventory copied successfully"
-msgstr "인벤토리가 성공적으로 복사됨"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:226
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:109
-msgid "Inventory file"
-msgstr "인벤토리 파일"
-
-#: screens/Inventory/Inventory.js:94
-msgid "Inventory not found."
-msgstr "인벤토리를 찾을 수 없음"
-
-#: screens/Dashboard/DashboardGraph.js:140
-msgid "Inventory sync"
-msgstr "인벤토리 동기화"
-
-#: screens/Dashboard/Dashboard.js:98
-msgid "Inventory sync failures"
-msgstr "인벤토리 동기화 실패"
-
-#: components/DataListToolbar/DataListToolbar.js:110
-msgid "Is expanded"
-msgstr "확장됨"
-
-#: components/DataListToolbar/DataListToolbar.js:112
-msgid "Is not expanded"
-msgstr "확장되지 않음"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:118
-msgid "Item Failed"
-msgstr "항목 실패"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:119
-msgid "Item OK"
-msgstr "항목 확인"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:120
-msgid "Item Skipped"
-msgstr "건너뛴 항목"
-
-#: components/AssociateModal/AssociateModal.js:20
-#: components/PaginatedTable/PaginatedTable.js:42
-msgid "Items"
-msgstr "항목"
-
-#: components/Pagination/Pagination.js:27
-msgid "Items per page"
-msgstr "페이지당 항목"
-
-#: components/Sparkline/Sparkline.js:28
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:157
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:36
-#: screens/Project/ProjectDetail/ProjectDetail.js:132
-#: screens/Project/ProjectList/ProjectListItem.js:70
-msgid "JOB ID:"
-msgstr "작업 ID:"
-
-#: screens/Job/JobOutput/HostEventModal.js:136
-msgid "JSON"
-msgstr "JSON"
-
-#: screens/Job/JobOutput/HostEventModal.js:137
-msgid "JSON tab"
-msgstr "JSON 탭"
-
-#: screens/Inventory/shared/Inventory.helptext.js:49
-msgid "JSON:"
-msgstr "JSON:"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:159
-#: components/Schedule/shared/FrequencyDetailSubform.js:98
-msgid "January"
-msgstr "1월"
-
-#: components/JobList/JobListItem.js:112
-#: screens/Job/JobDetail/JobDetail.js:599
-#: screens/Job/JobOutput/JobOutput.js:845
-#: screens/Job/JobOutput/JobOutput.js:846
-#: screens/Job/JobOutput/shared/OutputToolbar.js:136
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:88
-msgid "Job Cancel Error"
-msgstr "작업 취소 오류"
-
-#: screens/Job/JobDetail/JobDetail.js:621
-#: screens/Job/JobOutput/JobOutput.js:834
-#: screens/Job/JobOutput/JobOutput.js:835
-msgid "Job Delete Error"
-msgstr "작업 삭제 오류"
-
-#: screens/Job/JobDetail/JobDetail.js:206
-msgid "Job ID"
-msgstr "작업 ID"
-
-#: screens/Dashboard/shared/LineChart.js:128
-msgid "Job Runs"
-msgstr "작업 실행"
-
-#: components/JobList/JobListItem.js:314
-#: screens/Job/JobDetail/JobDetail.js:374
-msgid "Job Slice"
-msgstr "작업 분할"
-
-#: components/JobList/JobListItem.js:319
-#: screens/Job/JobDetail/JobDetail.js:382
-msgid "Job Slice Parent"
-msgstr "작업 분할 부모"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:76
-#: components/PromptDetail/PromptDetail.js:349
-#: components/PromptDetail/PromptJobTemplateDetail.js:158
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:494
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:289
-#: screens/Template/shared/JobTemplateForm.js:453
-msgid "Job Slicing"
-msgstr "작업 분할"
-
-#: components/Workflow/WorkflowNodeHelp.js:164
-msgid "Job Status"
-msgstr "작업 상태"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:97
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:98
-#: components/PromptDetail/PromptDetail.js:267
-#: components/PromptDetail/PromptJobTemplateDetail.js:247
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:571
-#: screens/Job/JobDetail/JobDetail.js:474
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:449
-#: screens/Template/shared/JobTemplateForm.js:521
-#: screens/Template/shared/WorkflowJobTemplateForm.js:218
-msgid "Job Tags"
-msgstr "작업 태그"
-
-#: components/JobList/JobListItem.js:191
-#: components/TemplateList/TemplateList.js:217
-#: components/Workflow/WorkflowLegend.js:92
-#: components/Workflow/WorkflowNodeHelp.js:59
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:97
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:19
-#: screens/Job/JobDetail/JobDetail.js:233
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:79
-msgid "Job Template"
-msgstr "작업 템플릿"
-
-#: components/LaunchPrompt/steps/credentialsValidator.js:38
-msgid "Job Template default credentials must be replaced with one of the same type. Please select a credential for the following types in order to proceed: {0}"
-msgstr "작업 템플릿 기본 인증 정보는 동일한 유형 중 하나로 교체해야 합니다. 계속하려면 다음 유형의 인증 정보를 선택하십시오. {0}"
-
-#: screens/Credential/Credential.js:79
-#: screens/Credential/Credentials.js:30
-#: screens/Inventory/Inventories.js:62
-#: screens/Inventory/Inventory.js:74
-#: screens/Inventory/SmartInventory.js:74
-#: screens/Project/Project.js:107
-#: screens/Project/Projects.js:29
-#: util/getRelatedResourceDeleteDetails.js:56
-#: util/getRelatedResourceDeleteDetails.js:101
-#: util/getRelatedResourceDeleteDetails.js:133
-msgid "Job Templates"
-msgstr "작업 템플릿"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.js:25
-msgid "Job Templates with a missing inventory or project cannot be selected when creating or editing nodes. Select another template or fix the missing fields to proceed."
-msgstr "노드를 생성하거나 편집할 때 인벤토리 또는 프로젝트가 누락된 작업 템플릿을 선택할 수 없습니다. 다른 템플릿을 선택하거나 누락된 필드를 수정하여 계속 진행합니다."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useWorkflowNodeSteps.js:357
-msgid "Job Templates with credentials that prompt for passwords cannot be selected when creating or editing nodes"
-msgstr "노드를 생성하거나 편집할 때 암호를 입력하라는 인증 정보가 있는 작업 템플릿을 선택할 수 없습니다."
-
-#: components/JobList/JobList.js:208
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:146
-#: components/PromptDetail/PromptDetail.js:185
-#: components/PromptDetail/PromptJobTemplateDetail.js:102
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:423
-#: screens/Job/JobDetail/JobDetail.js:267
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:192
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:138
-#: screens/Template/shared/JobTemplateForm.js:256
-msgid "Job Type"
-msgstr "작업 유형"
-
-#: screens/Dashboard/Dashboard.js:125
-msgid "Job status"
-msgstr "작업 상태"
-
-#: screens/Dashboard/Dashboard.js:123
-msgid "Job status graph tab"
-msgstr "작업 상태 그래프 탭"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:156
-#: components/RelatedTemplateList/RelatedTemplateList.js:206
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:15
-msgid "Job templates"
-msgstr "작업 템플릿"
-
-#: components/JobList/JobList.js:191
-#: components/JobList/JobList.js:274
-#: routeConfig.js:39
-#: screens/ActivityStream/ActivityStream.js:150
-#: screens/Dashboard/shared/LineChart.js:64
-#: screens/Host/Host.js:73
-#: screens/Host/Hosts.js:30
-#: screens/InstanceGroup/ContainerGroup.js:71
-#: screens/InstanceGroup/InstanceGroup.js:79
-#: screens/InstanceGroup/InstanceGroups.js:34
-#: screens/InstanceGroup/InstanceGroups.js:39
-#: screens/Inventory/Inventories.js:60
-#: screens/Inventory/Inventories.js:70
-#: screens/Inventory/Inventory.js:70
-#: screens/Inventory/InventoryHost/InventoryHost.js:88
-#: screens/Inventory/SmartInventory.js:70
-#: screens/Job/Jobs.js:22
-#: screens/Job/Jobs.js:32
-#: screens/Setting/SettingList.js:91
-#: screens/Setting/Settings.js:75
-#: screens/Template/Template.js:155
-#: screens/Template/Templates.js:47
-#: screens/Template/WorkflowJobTemplate.js:141
-msgid "Jobs"
-msgstr "작업"
-
-#: screens/Setting/SettingList.js:96
-msgid "Jobs settings"
-msgstr "작업 설정"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:165
-#: components/Schedule/shared/FrequencyDetailSubform.js:128
-msgid "July"
-msgstr "7월"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:164
-#: components/Schedule/shared/FrequencyDetailSubform.js:123
-msgid "June"
-msgstr "6월"
-
-#: components/Search/AdvancedSearch.js:262
-msgid "Key"
-msgstr "키"
-
-#: components/Search/AdvancedSearch.js:253
-msgid "Key select"
-msgstr "키 선택"
-
-#: components/Search/AdvancedSearch.js:256
-msgid "Key typeahead"
-msgstr "키 유형 헤드"
-
-#: screens/ActivityStream/ActivityStream.js:238
-msgid "Keyword"
-msgstr "키워드"
-
-#: screens/User/UserDetail/UserDetail.js:56
-#: screens/User/UserList/UserListItem.js:44
-msgid "LDAP"
-msgstr "LDAP"
-
-#: screens/Setting/Settings.js:80
-msgid "LDAP 1"
-msgstr "LDAP 1"
-
-#: screens/Setting/Settings.js:81
-msgid "LDAP 2"
-msgstr "LDAP 2"
-
-#: screens/Setting/Settings.js:82
-msgid "LDAP 3"
-msgstr "LDAP 3"
-
-#: screens/Setting/Settings.js:83
-msgid "LDAP 4"
-msgstr "LDAP 4"
-
-#: screens/Setting/Settings.js:84
-msgid "LDAP 5"
-msgstr "LDAP 5"
-
-#: screens/Setting/Settings.js:79
-msgid "LDAP Default"
-msgstr "LDAP 기본값"
-
-#: screens/Setting/SettingList.js:69
-msgid "LDAP settings"
-msgstr "LDAP 설정"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:107
-msgid "LDAP1"
-msgstr "LDAP1"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:112
-msgid "LDAP2"
-msgstr "LDAP2"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:117
-msgid "LDAP3"
-msgstr "LDAP3"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:122
-msgid "LDAP4"
-msgstr "LDAP4"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:127
-msgid "LDAP5"
-msgstr "LDAP5"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:178
-#: components/TemplateList/TemplateList.js:234
-msgid "Label"
-msgstr "레이블"
-
-#: components/JobList/JobList.js:204
-msgid "Label Name"
-msgstr "레이블 이름"
-
-#: components/JobList/JobListItem.js:284
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:223
-#: components/PromptDetail/PromptDetail.js:323
-#: components/PromptDetail/PromptJobTemplateDetail.js:209
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:116
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:551
-#: components/TemplateList/TemplateListItem.js:347
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:148
-#: screens/Inventory/shared/InventoryForm.js:83
-#: screens/Job/JobDetail/JobDetail.js:453
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:401
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:206
-#: screens/Template/shared/JobTemplateForm.js:387
-#: screens/Template/shared/WorkflowJobTemplateForm.js:195
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:273
-msgid "Labels"
-msgstr "레이블"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:417
-msgid "Last"
-msgstr "마지막"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:220
-#: screens/InstanceGroup/Instances/InstanceListItem.js:133
-#: screens/InstanceGroup/Instances/InstanceListItem.js:208
-#: screens/Instances/InstanceDetail/InstanceDetail.js:243
-#: screens/Instances/InstanceList/InstanceListItem.js:138
-#: screens/Instances/InstanceList/InstanceListItem.js:223
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:47
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:87
-msgid "Last Health Check"
-msgstr "마지막 상태 점검"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:183
-#: screens/Project/ProjectDetail/ProjectDetail.js:161
-msgid "Last Job Status"
-msgstr "마지막 작업 상태"
-
-#: screens/User/UserDetail/UserDetail.js:80
-msgid "Last Login"
-msgstr "마지막 로그인"
-
-#: components/PromptDetail/PromptDetail.js:161
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:411
-#: components/TemplateList/TemplateListItem.js:316
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:106
-#: screens/Application/ApplicationsList/ApplicationListItem.js:45
-#: screens/Application/ApplicationsList/ApplicationsList.js:159
-#: screens/Credential/CredentialDetail/CredentialDetail.js:263
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:95
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:108
-#: screens/Host/HostDetail/HostDetail.js:89
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:72
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:98
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:178
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:45
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:84
-#: screens/Job/JobDetail/JobDetail.js:538
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:398
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:120
-#: screens/Project/ProjectDetail/ProjectDetail.js:297
-#: screens/Team/TeamDetail/TeamDetail.js:48
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:358
-#: screens/User/UserDetail/UserDetail.js:84
-#: screens/User/UserTokenDetail/UserTokenDetail.js:66
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:204
-msgid "Last Modified"
-msgstr "최종 업데이트"
-
-#: components/AddRole/AddResourceRole.js:32
-#: components/AddRole/AddResourceRole.js:46
-#: components/ResourceAccessList/ResourceAccessList.js:182
-#: screens/User/UserDetail/UserDetail.js:65
-#: screens/User/UserList/UserList.js:128
-#: screens/User/UserList/UserList.js:162
-#: screens/User/UserList/UserListItem.js:54
-#: screens/User/shared/UserForm.js:69
-msgid "Last Name"
-msgstr "성"
-
-#: components/TemplateList/TemplateList.js:245
-#: components/TemplateList/TemplateListItem.js:194
-msgid "Last Ran"
-msgstr "마지막 실행"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:341
-msgid "Last Run"
-msgstr "마지막 실행"
-
-#: components/Lookup/HostFilterLookup.js:119
-msgid "Last job"
-msgstr "마지막 작업"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:280
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:151
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:49
-#: screens/Project/ProjectList/ProjectListItem.js:308
-#: screens/TopologyView/Tooltip.js:331
-msgid "Last modified"
-msgstr "마지막으로 변경된 사항"
-
-#: components/ResourceAccessList/ResourceAccessList.js:228
-#: components/ResourceAccessList/ResourceAccessListItem.js:68
-msgid "Last name"
-msgstr "성"
-
-#: screens/TopologyView/Tooltip.js:337
-msgid "Last seen"
-msgstr "마지막 확인"
-
-#: screens/Project/ProjectList/ProjectListItem.js:313
-msgid "Last used"
-msgstr "마지막으로 사용됨"
-
-#: components/AdHocCommands/useAdHocPreviewStep.js:22
-#: components/LaunchPrompt/steps/usePreviewStep.js:35
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:54
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:57
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:520
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:529
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:245
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:254
-msgid "Launch"
-msgstr "시작"
-
-#: components/TemplateList/TemplateListItem.js:214
-msgid "Launch Template"
-msgstr "템플릿 시작"
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:32
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:34
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:46
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:47
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:87
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:90
-msgid "Launch management job"
-msgstr "관리 작업 시작"
-
-#: components/TemplateList/TemplateListItem.js:222
-msgid "Launch template"
-msgstr "템플릿 시작"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:119
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:120
-msgid "Launch workflow"
-msgstr "워크플로우 시작"
-
-#: components/LaunchPrompt/LaunchPrompt.js:130
-msgid "Launch | {0}"
-msgstr "시작 | {0}"
-
-#: components/DetailList/LaunchedByDetail.js:54
-msgid "Launched By"
-msgstr "시작자"
-
-#: components/JobList/JobList.js:220
-msgid "Launched By (Username)"
-msgstr "(사용자 이름)에 의해 시작됨"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:120
-msgid "Learn more about Automation Analytics"
-msgstr "Automation Analytics에 대해 자세히 알아보기"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:69
-msgid "Leave this field blank to make the execution environment globally available."
-msgstr "이 필드를 비워 두고 실행 환경을 전역적으로 사용할 수 있도록 합니다."
-
-#: components/Workflow/WorkflowLegend.js:86
-#: screens/Metrics/LineChart.js:120
-#: screens/TopologyView/Header.js:102
-#: screens/TopologyView/Legend.js:67
-msgid "Legend"
-msgstr "범례"
-
-#: components/Search/LookupTypeInput.js:120
-msgid "Less than comparison."
-msgstr "비교 값보다 적습니다."
-
-#: components/Search/LookupTypeInput.js:127
-msgid "Less than or equal to comparison."
-msgstr "비교 값보다 적거나 같습니다."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:140
-#: components/AdHocCommands/AdHocDetailsStep.js:141
-#: components/JobList/JobList.js:238
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:65
-#: components/PromptDetail/PromptDetail.js:255
-#: components/PromptDetail/PromptJobTemplateDetail.js:153
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:90
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:473
-#: screens/Job/JobDetail/JobDetail.js:325
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:265
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:151
-#: screens/Template/shared/JobTemplateForm.js:429
-#: screens/Template/shared/WorkflowJobTemplateForm.js:159
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:238
-msgid "Limit"
-msgstr "제한"
-
-#: screens/TopologyView/Legend.js:237
-msgid "Link state types"
-msgstr "링크 상태 유형"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:261
-msgid "Link to an available node"
-msgstr "사용 가능한 노드에 대한 링크"
-
-#: screens/Instances/Shared/InstanceForm.js:40
-msgid "Listener Port"
-msgstr "리스너 포트"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:353
-msgid "Loading"
-msgstr "로딩 중"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:49
-msgid "Local"
-msgstr "지역"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:343
-msgid "Local Time Zone"
-msgstr "현지 시간대"
-
-#: components/Schedule/shared/ScheduleFormFields.js:95
-msgid "Local time zone"
-msgstr "현지 시간대"
-
-#: screens/Login/Login.js:217
-msgid "Log In"
-msgstr "로그인"
-
-#: screens/Setting/Settings.js:97
-msgid "Logging"
-msgstr "로깅"
-
-#: screens/Setting/SettingList.js:115
-msgid "Logging settings"
-msgstr "로깅 설정"
-
-#: components/AppContainer/AppContainer.js:81
-#: components/AppContainer/AppContainer.js:150
-#: components/AppContainer/PageHeaderToolbar.js:169
-msgid "Logout"
-msgstr "로그 아웃"
-
-#: components/Lookup/HostFilterLookup.js:367
-#: components/Lookup/Lookup.js:187
-msgid "Lookup modal"
-msgstr "검색 모달"
-
-#: components/Search/LookupTypeInput.js:22
-msgid "Lookup select"
-msgstr "검색 선택"
-
-#: components/Search/LookupTypeInput.js:31
-msgid "Lookup type"
-msgstr "검색 유형"
-
-#: components/Search/LookupTypeInput.js:25
-msgid "Lookup typeahead"
-msgstr "자동 완성 검색"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:155
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:34
-#: screens/Project/ProjectDetail/ProjectDetail.js:130
-#: screens/Project/ProjectList/ProjectListItem.js:68
-msgid "MOST RECENT SYNC"
-msgstr "최신 동기화"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:97
-#: components/AdHocCommands/AdHocCredentialStep.js:98
-#: components/AdHocCommands/AdHocCredentialStep.js:112
-#: screens/Job/JobDetail/JobDetail.js:407
-msgid "Machine Credential"
-msgstr "시스템 인증 정보"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:65
-msgid "Managed"
-msgstr "관리됨"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:147
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:169
-msgid "Managed nodes"
-msgstr "관리형 노드"
-
-#: components/JobList/JobList.js:215
-#: components/JobList/JobListItem.js:46
-#: components/Schedule/ScheduleList/ScheduleListItem.js:39
-#: components/Workflow/WorkflowLegend.js:108
-#: components/Workflow/WorkflowNodeHelp.js:79
-#: screens/Job/JobDetail/JobDetail.js:69
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:102
-msgid "Management Job"
-msgstr "관리 작업"
-
-#: routeConfig.js:127
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:84
-msgid "Management Jobs"
-msgstr "관리 작업"
-
-#: screens/ManagementJob/ManagementJobs.js:20
-msgid "Management job"
-msgstr "관리 작업"
-
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:109
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:110
-msgid "Management job launch error"
-msgstr "관리 작업 시작 오류"
-
-#: screens/ManagementJob/ManagementJob.js:133
-msgid "Management job not found."
-msgstr "관리 작업을 찾을 수 없습니다."
-
-#: screens/ManagementJob/ManagementJobs.js:13
-msgid "Management jobs"
-msgstr "관리 작업"
-
-#: components/Lookup/ProjectLookup.js:135
-#: components/PromptDetail/PromptProjectDetail.js:98
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:88
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:157
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:215
-#: screens/InstanceGroup/Instances/InstanceListItem.js:204
-#: screens/Instances/InstanceDetail/InstanceDetail.js:209
-#: screens/Instances/InstanceList/InstanceListItem.js:219
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:83
-#: screens/Job/JobDetail/JobDetail.js:74
-#: screens/Project/ProjectDetail/ProjectDetail.js:192
-#: screens/Project/ProjectList/ProjectList.js:197
-#: screens/Project/ProjectList/ProjectListItem.js:219
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:96
-msgid "Manual"
-msgstr "수동"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:161
-#: components/Schedule/shared/FrequencyDetailSubform.js:108
-msgid "March"
-msgstr "3월"
-
-#: components/NotificationList/NotificationList.js:197
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:138
-msgid "Mattermost"
-msgstr "가장 중요"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:98
-#: screens/Organization/shared/OrganizationForm.js:71
-msgid "Max Hosts"
-msgstr "최대 호스트"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:220
-msgid "Maximum"
-msgstr "최대"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:204
-msgid "Maximum length"
-msgstr "최대 길이"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:163
-#: components/Schedule/shared/FrequencyDetailSubform.js:118
-msgid "May"
-msgstr "5월"
-
-#: screens/Organization/OrganizationList/OrganizationList.js:144
-#: screens/Organization/OrganizationList/OrganizationListItem.js:63
-msgid "Members"
-msgstr "멤버"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:47
-msgid "Metadata"
-msgstr "메타데이터"
-
-#: screens/Metrics/Metrics.js:207
-msgid "Metric"
-msgstr "메트릭"
-
-#: screens/Metrics/Metrics.js:179
-msgid "Metrics"
-msgstr "메트릭"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:100
-msgid "Microsoft Azure Resource Manager"
-msgstr "Microsoft Azure Resource Manager"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:214
-msgid "Minimum"
-msgstr "최소"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:198
-msgid "Minimum length"
-msgstr "최소 길이"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:65
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:31
-msgid ""
-"Minimum number of instances that will be automatically\n"
-"assigned to this group when new instances come online."
-msgstr "새 인스턴스가 온라인 상태가 되면 이 그룹에 자동으로 할당되는 최소 인스턴스 수입니다."
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:71
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:41
-msgid ""
-"Minimum percentage of all instances that will be automatically\n"
-"assigned to this group when new instances come online."
-msgstr "새 인스턴스가 온라인 상태가 되면 이 그룹에 자동으로 할당되는 모든 인스턴스의 최소 백분율입니다."
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:182
-#: components/Schedule/shared/FrequencyDetailSubform.js:173
-#: components/Schedule/shared/ScheduleFormFields.js:125
-#: components/Schedule/shared/ScheduleFormFields.js:185
-msgid "Minute"
-msgstr "분"
-
-#: screens/Setting/Settings.js:100
-msgid "Miscellaneous Authentication"
-msgstr "기타 인증"
-
-#: screens/Setting/SettingList.js:111
-msgid "Miscellaneous Authentication settings"
-msgstr "기타 인증 설정"
-
-#: screens/Setting/Settings.js:103
-msgid "Miscellaneous System"
-msgstr "기타 시스템"
-
-#: screens/Setting/SettingList.js:107
-msgid "Miscellaneous System settings"
-msgstr "기타 시스템 설정"
-
-#: components/Workflow/WorkflowNodeHelp.js:120
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:87
-msgid "Missing"
-msgstr "누락됨"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:66
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:109
-msgid "Missing resource"
-msgstr "누락된 리소스"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:192
-#: screens/User/UserTokenList/UserTokenList.js:154
-msgid "Modified"
-msgstr "수정됨"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:126
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:116
-#: components/AddRole/AddResourceRole.js:61
-#: components/AssociateModal/AssociateModal.js:148
-#: components/LaunchPrompt/steps/CredentialsStep.js:177
-#: components/LaunchPrompt/steps/InventoryStep.js:93
-#: components/Lookup/CredentialLookup.js:198
-#: components/Lookup/InventoryLookup.js:156
-#: components/Lookup/InventoryLookup.js:211
-#: components/Lookup/MultiCredentialsLookup.js:198
-#: components/Lookup/OrganizationLookup.js:138
-#: components/Lookup/ProjectLookup.js:147
-#: components/NotificationList/NotificationList.js:210
-#: components/RelatedTemplateList/RelatedTemplateList.js:170
-#: components/Schedule/ScheduleList/ScheduleList.js:202
-#: components/TemplateList/TemplateList.js:230
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:31
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:62
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:100
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:131
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:169
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:200
-#: screens/Credential/CredentialList/CredentialList.js:154
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:100
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:136
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:106
-#: screens/Host/HostGroups/HostGroupsList.js:168
-#: screens/Host/HostList/HostList.js:161
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:203
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:133
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:178
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:132
-#: screens/Inventory/InventoryList/InventoryList.js:203
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:189
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:98
-#: screens/Organization/OrganizationList/OrganizationList.js:135
-#: screens/Project/ProjectList/ProjectList.js:209
-#: screens/Team/TeamList/TeamList.js:134
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:165
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:108
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:112
-msgid "Modified By (Username)"
-msgstr "(사용자 이름)에 의해 수정됨"
-
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:85
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:151
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:77
-msgid "Modified by (username)"
-msgstr "(사용자 이름)에 의해 수정됨"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:59
-#: screens/Job/JobOutput/HostEventModal.js:125
-msgid "Module"
-msgstr "모듈"
-
-#: screens/Job/JobDetail/JobDetail.js:530
-msgid "Module Arguments"
-msgstr "모듈 인수"
-
-#: screens/Job/JobDetail/JobDetail.js:524
-msgid "Module Name"
-msgstr "모듈 이름"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:266
-msgid "Mon"
-msgstr "월요일"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:77
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:183
-#: components/Schedule/shared/FrequencyDetailSubform.js:271
-#: components/Schedule/shared/FrequencyDetailSubform.js:433
-msgid "Monday"
-msgstr "월요일"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:186
-#: components/Schedule/shared/FrequencyDetailSubform.js:181
-#: components/Schedule/shared/ScheduleFormFields.js:129
-#: components/Schedule/shared/ScheduleFormFields.js:189
-msgid "Month"
-msgstr "월"
-
-#: components/Popover/Popover.js:32
-msgid "More information"
-msgstr "더 많은 정보"
-
-#: screens/Setting/shared/SharedFields.js:73
-msgid "More information for"
-msgstr "더 많은 정보"
-
-#: screens/Template/Survey/SurveyReorderModal.js:162
-#: screens/Template/Survey/SurveyReorderModal.js:163
-msgid "Multi-Select"
-msgstr "다중 선택"
-
-#: screens/Template/Survey/SurveyReorderModal.js:146
-#: screens/Template/Survey/SurveyReorderModal.js:147
-msgid "Multiple Choice"
-msgstr "다중 선택 옵션"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:91
-msgid "Multiple Choice (multiple select)"
-msgstr "다중 선택(여러 선택)"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:86
-msgid "Multiple Choice (single select)"
-msgstr "다중 선택(단일 선택)"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:258
-msgid "Multiple Choice Options"
-msgstr "다중 선택 옵션"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:117
-#: components/AdHocCommands/AdHocCredentialStep.js:132
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:107
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:122
-#: components/AddRole/AddResourceRole.js:52
-#: components/AddRole/AddResourceRole.js:68
-#: components/AssociateModal/AssociateModal.js:139
-#: components/AssociateModal/AssociateModal.js:154
-#: components/HostForm/HostForm.js:96
-#: components/JobList/JobList.js:195
-#: components/JobList/JobList.js:244
-#: components/JobList/JobListItem.js:86
-#: components/LaunchPrompt/steps/CredentialsStep.js:168
-#: components/LaunchPrompt/steps/CredentialsStep.js:183
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:76
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:86
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:97
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:78
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:89
-#: components/LaunchPrompt/steps/InventoryStep.js:84
-#: components/LaunchPrompt/steps/InventoryStep.js:99
-#: components/Lookup/ApplicationLookup.js:100
-#: components/Lookup/ApplicationLookup.js:111
-#: components/Lookup/CredentialLookup.js:189
-#: components/Lookup/CredentialLookup.js:204
-#: components/Lookup/ExecutionEnvironmentLookup.js:177
-#: components/Lookup/ExecutionEnvironmentLookup.js:184
-#: components/Lookup/HostFilterLookup.js:93
-#: components/Lookup/HostFilterLookup.js:422
-#: components/Lookup/HostListItem.js:8
-#: components/Lookup/InstanceGroupsLookup.js:103
-#: components/Lookup/InstanceGroupsLookup.js:114
-#: components/Lookup/InventoryLookup.js:147
-#: components/Lookup/InventoryLookup.js:162
-#: components/Lookup/InventoryLookup.js:202
-#: components/Lookup/InventoryLookup.js:217
-#: components/Lookup/MultiCredentialsLookup.js:189
-#: components/Lookup/MultiCredentialsLookup.js:204
-#: components/Lookup/OrganizationLookup.js:129
-#: components/Lookup/OrganizationLookup.js:144
-#: components/Lookup/ProjectLookup.js:127
-#: components/Lookup/ProjectLookup.js:157
-#: components/NotificationList/NotificationList.js:181
-#: components/NotificationList/NotificationList.js:218
-#: components/NotificationList/NotificationListItem.js:28
-#: components/OptionsList/OptionsList.js:57
-#: components/PaginatedTable/PaginatedTable.js:72
-#: components/PromptDetail/PromptDetail.js:114
-#: components/RelatedTemplateList/RelatedTemplateList.js:161
-#: components/RelatedTemplateList/RelatedTemplateList.js:186
-#: components/ResourceAccessList/ResourceAccessListItem.js:58
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:325
-#: components/Schedule/ScheduleList/ScheduleList.js:168
-#: components/Schedule/ScheduleList/ScheduleList.js:189
-#: components/Schedule/ScheduleList/ScheduleListItem.js:86
-#: components/Schedule/shared/ScheduleFormFields.js:72
-#: components/TemplateList/TemplateList.js:205
-#: components/TemplateList/TemplateList.js:242
-#: components/TemplateList/TemplateListItem.js:142
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:18
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:37
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:49
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:68
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:80
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:110
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:122
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:137
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:149
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:179
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:191
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:206
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:60
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:109
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:135
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:28
-#: screens/Application/Applications.js:81
-#: screens/Application/ApplicationsList/ApplicationListItem.js:33
-#: screens/Application/ApplicationsList/ApplicationsList.js:118
-#: screens/Application/ApplicationsList/ApplicationsList.js:155
-#: screens/Application/shared/ApplicationForm.js:54
-#: screens/Credential/CredentialDetail/CredentialDetail.js:217
-#: screens/Credential/CredentialList/CredentialList.js:141
-#: screens/Credential/CredentialList/CredentialList.js:164
-#: screens/Credential/CredentialList/CredentialListItem.js:58
-#: screens/Credential/shared/CredentialForm.js:161
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:71
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:91
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:68
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:123
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:176
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:33
-#: screens/CredentialType/shared/CredentialTypeForm.js:21
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:49
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:136
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:165
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:69
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:89
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:115
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:12
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:89
-#: screens/Host/HostDetail/HostDetail.js:69
-#: screens/Host/HostGroups/HostGroupItem.js:28
-#: screens/Host/HostGroups/HostGroupsList.js:159
-#: screens/Host/HostGroups/HostGroupsList.js:176
-#: screens/Host/HostList/HostList.js:148
-#: screens/Host/HostList/HostList.js:169
-#: screens/Host/HostList/HostListItem.js:50
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:41
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:49
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:161
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:194
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:61
-#: screens/InstanceGroup/Instances/InstanceList.js:199
-#: screens/InstanceGroup/Instances/InstanceList.js:215
-#: screens/InstanceGroup/Instances/InstanceList.js:266
-#: screens/InstanceGroup/Instances/InstanceList.js:299
-#: screens/InstanceGroup/Instances/InstanceListItem.js:124
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:44
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:19
-#: screens/Instances/InstanceList/InstanceList.js:143
-#: screens/Instances/InstanceList/InstanceList.js:160
-#: screens/Instances/InstanceList/InstanceList.js:201
-#: screens/Instances/InstanceList/InstanceListItem.js:128
-#: screens/Instances/InstancePeers/InstancePeerList.js:80
-#: screens/Instances/InstancePeers/InstancePeerList.js:87
-#: screens/Instances/InstancePeers/InstancePeerList.js:96
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:37
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:89
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:31
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:194
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:209
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:215
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:34
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:119
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:141
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:74
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:36
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:169
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:186
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:33
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:119
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:138
-#: screens/Inventory/InventoryList/InventoryList.js:178
-#: screens/Inventory/InventoryList/InventoryList.js:209
-#: screens/Inventory/InventoryList/InventoryList.js:218
-#: screens/Inventory/InventoryList/InventoryListItem.js:92
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:180
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:195
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:232
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:181
-#: screens/Inventory/InventorySources/InventorySourceList.js:211
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:71
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:98
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:30
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:76
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:111
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:33
-#: screens/Inventory/shared/InventoryForm.js:50
-#: screens/Inventory/shared/InventoryGroupForm.js:32
-#: screens/Inventory/shared/InventorySourceForm.js:100
-#: screens/Inventory/shared/SmartInventoryForm.js:47
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:90
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:100
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:67
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:107
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:122
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:178
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:112
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:41
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:91
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:84
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:107
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.js:16
-#: screens/Organization/OrganizationList/OrganizationList.js:122
-#: screens/Organization/OrganizationList/OrganizationList.js:143
-#: screens/Organization/OrganizationList/OrganizationListItem.js:45
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:68
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:85
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:14
-#: screens/Organization/shared/OrganizationForm.js:56
-#: screens/Project/ProjectDetail/ProjectDetail.js:176
-#: screens/Project/ProjectList/ProjectList.js:185
-#: screens/Project/ProjectList/ProjectList.js:221
-#: screens/Project/ProjectList/ProjectListItem.js:179
-#: screens/Project/shared/ProjectForm.js:214
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:146
-#: screens/Team/TeamDetail/TeamDetail.js:37
-#: screens/Team/TeamList/TeamList.js:117
-#: screens/Team/TeamList/TeamList.js:142
-#: screens/Team/TeamList/TeamListItem.js:33
-#: screens/Team/shared/TeamForm.js:29
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:185
-#: screens/Template/Survey/SurveyList.js:102
-#: screens/Template/Survey/SurveyList.js:102
-#: screens/Template/Survey/SurveyListItem.js:39
-#: screens/Template/Survey/SurveyReorderModal.js:218
-#: screens/Template/Survey/SurveyReorderModal.js:218
-#: screens/Template/Survey/SurveyReorderModal.js:238
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:111
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:69
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:88
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:120
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:152
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:178
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:88
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.js:74
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.js:94
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:75
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:95
-#: screens/Template/shared/JobTemplateForm.js:243
-#: screens/Template/shared/WorkflowJobTemplateForm.js:109
-#: screens/User/UserOrganizations/UserOrganizationList.js:75
-#: screens/User/UserOrganizations/UserOrganizationList.js:79
-#: screens/User/UserOrganizations/UserOrganizationListItem.js:13
-#: screens/User/UserRoles/UserRolesList.js:155
-#: screens/User/UserRoles/UserRolesListItem.js:12
-#: screens/User/UserTeams/UserTeamList.js:180
-#: screens/User/UserTeams/UserTeamList.js:232
-#: screens/User/UserTeams/UserTeamListItem.js:18
-#: screens/User/UserTokenList/UserTokenListItem.js:22
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:140
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:123
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:163
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:43
-msgid "Name"
-msgstr "이름"
-
-#: components/AppContainer/AppContainer.js:95
-msgid "Navigation"
-msgstr "탐색"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:221
-#: components/Schedule/shared/FrequencyDetailSubform.js:512
-#: screens/Dashboard/shared/ChartTooltip.js:106
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:57
-msgid "Never"
-msgstr "없음"
-
-#: components/Workflow/WorkflowNodeHelp.js:114
-msgid "Never Updated"
-msgstr "업데이트되지 않음"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:47
-msgid "Never expires"
-msgstr "만료되지 않음"
-
-#: components/JobList/JobList.js:227
-#: components/Workflow/WorkflowNodeHelp.js:90
-msgid "New"
-msgstr "새로운"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:51
-#: components/AdHocCommands/useAdHocCredentialStep.js:29
-#: components/AdHocCommands/useAdHocDetailsStep.js:40
-#: components/AdHocCommands/useAdHocExecutionEnvironmentStep.js:22
-#: components/AddRole/AddResourceRole.js:196
-#: components/AddRole/AddResourceRole.js:231
-#: components/LaunchPrompt/LaunchPrompt.js:160
-#: components/Schedule/shared/SchedulePromptableFields.js:127
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:66
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:59
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:132
-msgid "Next"
-msgstr "다음"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:337
-#: components/Schedule/ScheduleList/ScheduleList.js:171
-#: components/Schedule/ScheduleList/ScheduleListItem.js:118
-#: components/Schedule/ScheduleList/ScheduleListItem.js:122
-msgid "Next Run"
-msgstr "다음 실행"
-
-#: components/Search/Search.js:232
-msgid "No"
-msgstr "제공되지 않음"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:121
-msgid "No Hosts Matched"
-msgstr "일치하는 호스트가 없음"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:122
-#: screens/Job/JobOutput/JobOutputSearch.js:123
-msgid "No Hosts Remaining"
-msgstr "남아 있는 호스트가 없음"
-
-#: screens/Job/JobOutput/HostEventModal.js:150
-msgid "No JSON Available"
-msgstr "사용할 수 있는 JSON 없음"
-
-#: screens/Dashboard/shared/ChartTooltip.js:82
-msgid "No Jobs"
-msgstr "작업 없음"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:72
-msgid "No inventory sync failures."
-msgstr "인벤토리 동기화 실패 없음"
-
-#: components/ContentEmpty/ContentEmpty.js:20
-msgid "No items found."
-msgstr "항목을 찾을 수 없습니다."
-
-#: screens/Host/HostList/HostListItem.js:100
-msgid "No job data available"
-msgstr "사용 가능한 작업 데이터가 없습니다."
-
-#: screens/Job/JobOutput/EmptyOutput.js:52
-msgid "No output found for this job."
-msgstr "이 작업에 대한 출력을 찾을 수 없습니다."
-
-#: screens/Job/JobOutput/HostEventModal.js:126
-msgid "No result found"
-msgstr "결과를 찾을 수 없음"
-
-#: components/LabelSelect/LabelSelect.js:130
-#: components/LaunchPrompt/steps/SurveyStep.js:136
-#: components/LaunchPrompt/steps/SurveyStep.js:195
-#: components/MultiSelect/TagMultiSelect.js:60
-#: components/Search/AdvancedSearch.js:151
-#: components/Search/AdvancedSearch.js:266
-#: components/Search/LookupTypeInput.js:33
-#: components/Search/RelatedLookupTypeInput.js:26
-#: components/Search/Search.js:153
-#: components/Search/Search.js:202
-#: components/Search/Search.js:226
-#: screens/ActivityStream/ActivityStream.js:142
-#: screens/Credential/shared/CredentialForm.js:143
-#: screens/Credential/shared/CredentialFormFields/BecomeMethodField.js:65
-#: screens/Dashboard/DashboardGraph.js:106
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:137
-#: screens/Template/Survey/SurveyReorderModal.js:166
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:260
-#: screens/Template/shared/PlaybookSelect.js:72
-msgid "No results found"
-msgstr "결과를 찾을 수 없음"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:116
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:137
-msgid "No subscriptions found"
-msgstr "서브스크립션을 찾을 수 없음"
-
-#: screens/Template/Survey/SurveyList.js:147
-msgid "No survey questions found."
-msgstr "설문 조사 질문을 찾을 수 없습니다."
-
-#: screens/Job/JobDetail/JobDetail.js:399
-msgid "No timeout specified"
-msgstr "시간 초과가 지정되지 않음"
-
-#: components/PaginatedTable/PaginatedTable.js:80
-msgid "No {pluralizedItemName} Found"
-msgstr "{pluralizedItemName} 을/를 찾을 수 없음"
-
-#: components/Workflow/WorkflowNodeHelp.js:148
-#: components/Workflow/WorkflowNodeHelp.js:184
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:273
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:274
-msgid "Node Alias"
-msgstr "노드 별칭"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:223
-#: screens/InstanceGroup/Instances/InstanceList.js:204
-#: screens/InstanceGroup/Instances/InstanceList.js:268
-#: screens/InstanceGroup/Instances/InstanceList.js:300
-#: screens/InstanceGroup/Instances/InstanceListItem.js:142
-#: screens/Instances/InstanceDetail/InstanceDetail.js:204
-#: screens/Instances/InstanceList/InstanceList.js:148
-#: screens/Instances/InstanceList/InstanceList.js:203
-#: screens/Instances/InstanceList/InstanceListItem.js:150
-#: screens/Instances/InstancePeers/InstancePeerList.js:98
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:59
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:118
-msgid "Node Type"
-msgstr "노드 유형"
-
-#: screens/TopologyView/Legend.js:107
-msgid "Node state types"
-msgstr "노드 상태 유형"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.js:75
-msgid "Node type"
-msgstr "노드 유형"
-
-#: screens/TopologyView/Legend.js:70
-msgid "Node types"
-msgstr "노드 유형"
-
-#: components/Schedule/shared/ScheduleFormFields.js:180
-#: components/Schedule/shared/ScheduleFormFields.js:184
-#: components/Workflow/WorkflowNodeHelp.js:123
-msgid "None"
-msgstr "없음"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:193
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:196
-msgid "None (Run Once)"
-msgstr "없음 (한 번 실행)"
-
-#: components/Schedule/shared/ScheduleFormFields.js:120
-#: components/Schedule/shared/ScheduleFormFields.js:124
-msgid "None (run once)"
-msgstr "없음 (한 번 실행)"
-
-#: screens/User/UserDetail/UserDetail.js:51
-#: screens/User/UserList/UserListItem.js:23
-#: screens/User/shared/UserForm.js:29
-msgid "Normal User"
-msgstr "일반 사용자"
-
-#: components/ContentError/ContentError.js:37
-msgid "Not Found"
-msgstr "찾을 수 없음"
-
-#: screens/Setting/shared/SettingDetail.js:71
-#: screens/Setting/shared/SettingDetail.js:112
-msgid "Not configured"
-msgstr "구성되지 않음"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:75
-msgid "Not configured for inventory sync."
-msgstr "인벤토리 동기화에 대해 구성되지 않았습니다."
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:248
-msgid ""
-"Note that only hosts directly in this group can\n"
-"be disassociated. Hosts in sub-groups must be disassociated\n"
-"directly from the sub-group level that they belong."
-msgstr "이 그룹에서 직접 호스트만 연결할 수 있습니다. 하위 그룹의 호스트는 자신이 속한 하위 그룹 수준에서 직접 연결을 끊을 수 있어야 합니다."
-
-#: screens/Host/HostGroups/HostGroupsList.js:212
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:230
-msgid ""
-"Note that you may still see the group in the list after\n"
-"disassociating if the host is also a member of that group’s\n"
-"children. This list shows all groups the host is associated\n"
-"with directly and indirectly."
-msgstr "호스트가 해당 그룹의 하위 그룹의 멤버이기도 한 경우 연결 해제 후에도 목록에 그룹이 계속 표시됩니다. 이 목록에는 호스트가 직접 또는 간접적으로 연결된 모든 그룹이 표시됩니다."
-
-#: components/Lookup/InstanceGroupsLookup.js:90
-msgid "Note: The order in which these are selected sets the execution precedence. Select more than one to enable drag."
-msgstr "참고: 선택한 순서에 따라 실행 우선 순위가 설정됩니다. 드래그를 활성화하려면 둘 이상의 항목을 선택합니다."
-
-#: screens/Organization/shared/OrganizationForm.js:116
-msgid "Note: The order of these credentials sets precedence for the sync and lookup of the content. Select more than one to enable drag."
-msgstr "참고: 이러한 인증 정보의 순서는 콘텐츠의 동기화 및 조회에 대한 우선 순위를 설정합니다. 끌어오기를 활성화하려면 하나 이상 선택합니다."
-
-#: screens/Project/shared/Project.helptext.js:81
-msgid "Note: This field assumes the remote name is \"origin\"."
-msgstr "참고: 이 필드는 원격 이름이 \"origin\"이라고 가정합니다."
-
-#: screens/Project/shared/Project.helptext.js:35
-msgid ""
-"Note: When using SSH protocol for GitHub or\n"
-"Bitbucket, enter an SSH key only, do not enter a username\n"
-"(other than git). Additionally, GitHub and Bitbucket do\n"
-"not support password authentication when using SSH. GIT\n"
-"read only protocol (git://) does not use username or\n"
-"password information."
-msgstr "참고: GitHub 또는 Bitbucket에 SSH 프로토콜을 사용하는 경우 SSH 키만 입력하고 사용자 이름( git 제외)을 입력하지 마십시오. SSH를 사용할 때 GitHub 및 Bitbucket은 암호 인증을 지원하지 않습니다. GIT 읽기 전용 프로토콜 (git://)은 사용자 이름 또는 암호 정보를 사용하지 않습니다."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:331
-msgid "Notification Color"
-msgstr "알림 색상"
-
-#: screens/NotificationTemplate/NotificationTemplate.js:58
-#: screens/NotificationTemplate/NotificationTemplateAdd.js:50
-msgid "Notification Template not found."
-msgstr "알림 템플릿을 찾을 수 없습니다."
-
-#: screens/ActivityStream/ActivityStream.js:198
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:117
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:171
-#: screens/NotificationTemplate/NotificationTemplates.js:14
-#: screens/NotificationTemplate/NotificationTemplates.js:21
-#: util/getRelatedResourceDeleteDetails.js:181
-msgid "Notification Templates"
-msgstr "알림 템플릿"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:134
-msgid "Notification Type"
-msgstr "알림 유형"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:380
-msgid "Notification color"
-msgstr "알림 색상"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:193
-msgid "Notification sent successfully"
-msgstr "알림이 전송되었습니다."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:449
-msgid "Notification test failed."
-msgstr "알림 테스트에 실패했습니다."
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:197
-msgid "Notification timed out"
-msgstr "알림 시간 초과"
-
-#: components/NotificationList/NotificationList.js:190
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:131
-msgid "Notification type"
-msgstr "알림 유형"
-
-#: components/NotificationList/NotificationList.js:177
-#: routeConfig.js:122
-#: screens/Inventory/Inventories.js:93
-#: screens/Inventory/InventorySource/InventorySource.js:99
-#: screens/ManagementJob/ManagementJob.js:116
-#: screens/ManagementJob/ManagementJobs.js:22
-#: screens/Organization/Organization.js:135
-#: screens/Organization/Organizations.js:33
-#: screens/Project/Project.js:114
-#: screens/Project/Projects.js:28
-#: screens/Template/Template.js:141
-#: screens/Template/Templates.js:46
-#: screens/Template/WorkflowJobTemplate.js:123
-msgid "Notifications"
-msgstr "알림"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:169
-#: components/Schedule/shared/FrequencyDetailSubform.js:148
-msgid "November"
-msgstr "11월"
-
-#: components/StatusLabel/StatusLabel.js:44
-#: components/Workflow/WorkflowNodeHelp.js:117
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:66
-#: screens/Job/JobOutput/shared/HostStatusBar.js:35
-msgid "OK"
-msgstr "OK"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:42
-#: components/Schedule/shared/FrequencyDetailSubform.js:549
-msgid "Occurrences"
-msgstr "발생"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:168
-#: components/Schedule/shared/FrequencyDetailSubform.js:143
-msgid "October"
-msgstr "10월"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:189
-#: components/HostToggle/HostToggle.js:61
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:192
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:195
-#: components/PromptDetail/PromptDetail.js:362
-#: components/PromptDetail/PromptJobTemplateDetail.js:156
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:489
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:58
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:45
-#: screens/Setting/shared/SettingDetail.js:98
-#: screens/Setting/shared/SharedFields.js:150
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:284
-#: screens/Template/shared/JobTemplateForm.js:504
-msgid "Off"
-msgstr "Off"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:188
-#: components/HostToggle/HostToggle.js:60
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:192
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:194
-#: components/PromptDetail/PromptDetail.js:362
-#: components/PromptDetail/PromptJobTemplateDetail.js:156
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:489
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:57
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:45
-#: screens/Setting/shared/SettingDetail.js:98
-#: screens/Setting/shared/SharedFields.js:149
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:284
-#: screens/Template/shared/JobTemplateForm.js:504
-msgid "On"
-msgstr "On"
-
-#: components/Workflow/WorkflowLegend.js:126
-#: components/Workflow/WorkflowLinkHelp.js:30
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:39
-msgid "On Failure"
-msgstr "실패 시"
-
-#: components/Workflow/WorkflowLegend.js:122
-#: components/Workflow/WorkflowLinkHelp.js:27
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:63
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:32
-msgid "On Success"
-msgstr "성공 시"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:536
-msgid "On date"
-msgstr "날짜에"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:99
-#: components/Schedule/shared/FrequencyDetailSubform.js:251
-msgid "On days"
-msgstr "요일에"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:18
-msgid ""
-"One Slack channel per line. The pound symbol (#)\n"
-"is required for channels. To respond to or start a thread to a specific message add the parent message Id to the channel where the parent message Id is 16 digits. A dot (.) must be manually inserted after the 10th digit. ie:#destination-channel, 1231257890.006423. See Slack"
-msgstr "한 줄에 하나의 Slack 채널입니다. 채널에 대해 파운드 기호(#)가 필요합니다. 특정 메시지에 응답하거나 스레드를 시작하려면 상위 메시지 ID가16자리인 채널에 상위 메시지 ID를 추가합니다. 10 번째 자리 숫자 뒤에 점(.)을 수동으로 삽입해야 합니다. 예:#destination-channel, 1231257890.006423. Slack 참조"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:154
-msgid "Only Group By"
-msgstr "그룹 별로만"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:103
-msgid "OpenStack"
-msgstr "OpenStack"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:111
-msgid "Option Details"
-msgstr "옵션 세부 정보"
-
-#: screens/Inventory/shared/Inventory.helptext.js:25
-msgid ""
-"Optional labels that describe this inventory,\n"
-"such as 'dev' or 'test'. Labels can be used to group and filter\n"
-"inventories and completed jobs."
-msgstr "'dev' 또는 'test'와 같이 이 인벤토리를 설명하는 선택적 레이블입니다. 레이블을 사용하여 인벤토리 및 완료된 작업을 그룹화하고 필터링할 수 있습니다."
-
-#: screens/Job/Job.helptext.js:12
-#: screens/Template/shared/JobTemplate.helptext.js:13
-msgid "Optional labels that describe this job template, such as 'dev' or 'test'. Labels can be used to group and filter job templates and completed jobs."
-msgstr "'dev' 또는 'test'와 같이 이 작업 템플릿을 설명하는 선택적 레이블입니다. 레이블을 사용하여 작업 템플릿과 완료된 작업을 그룹화하고 필터링할 수 있습니다."
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:11
-msgid ""
-"Optional labels that describe this workflow job template,\n"
-"such as 'dev' or 'test'. Labels can be used to group and filter\n"
-"workflow job templates and completed jobs."
-msgstr "'dev' 또는 'test'와 같이 이 워크플로 작업 템플릿을 설명하는 선택적 레이블입니다. 레이블을 사용하여 워크플로 작업 템플릿과 완료된 작업을 그룹화하고 필터링할 수 있습니다."
-
-#: screens/Template/shared/JobTemplate.helptext.js:26
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:19
-msgid "Optionally select the credential to use to send status updates back to the webhook service."
-msgstr "선택적으로 상태 업데이트를 웹 후크 서비스로 다시 보내는 데 사용할 인증 정보를 선택합니다."
-
-#: components/NotificationList/NotificationList.js:220
-#: components/NotificationList/NotificationListItem.js:34
-#: screens/Credential/shared/TypeInputsSubForm.js:47
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:61
-#: screens/Instances/Shared/InstanceForm.js:54
-#: screens/Inventory/shared/InventoryForm.js:94
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:69
-#: screens/Template/shared/JobTemplateForm.js:545
-#: screens/Template/shared/WorkflowJobTemplateForm.js:241
-msgid "Options"
-msgstr "옵션"
-
-#: screens/Template/Survey/SurveyReorderModal.js:217
-#: screens/Template/Survey/SurveyReorderModal.js:217
-#: screens/Template/Survey/SurveyReorderModal.js:233
-msgid "Order"
-msgstr "순서"
-
-#: components/Lookup/ApplicationLookup.js:119
-#: components/Lookup/OrganizationLookup.js:101
-#: components/Lookup/OrganizationLookup.js:107
-#: components/Lookup/OrganizationLookup.js:124
-#: components/PromptDetail/PromptInventorySourceDetail.js:62
-#: components/PromptDetail/PromptInventorySourceDetail.js:72
-#: components/PromptDetail/PromptJobTemplateDetail.js:105
-#: components/PromptDetail/PromptJobTemplateDetail.js:115
-#: components/PromptDetail/PromptProjectDetail.js:77
-#: components/PromptDetail/PromptProjectDetail.js:88
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:67
-#: components/TemplateList/TemplateList.js:244
-#: components/TemplateList/TemplateListItem.js:185
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:70
-#: screens/Application/ApplicationsList/ApplicationListItem.js:38
-#: screens/Application/ApplicationsList/ApplicationsList.js:157
-#: screens/Credential/CredentialDetail/CredentialDetail.js:230
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:70
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:155
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:167
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:76
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:96
-#: screens/Inventory/InventoryList/InventoryList.js:191
-#: screens/Inventory/InventoryList/InventoryList.js:221
-#: screens/Inventory/InventoryList/InventoryListItem.js:119
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:202
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:107
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:121
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:131
-#: screens/Project/ProjectDetail/ProjectDetail.js:180
-#: screens/Project/ProjectList/ProjectListItem.js:287
-#: screens/Project/ProjectList/ProjectListItem.js:298
-#: screens/Team/TeamDetail/TeamDetail.js:40
-#: screens/Team/TeamList/TeamList.js:143
-#: screens/Team/TeamList/TeamListItem.js:38
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:199
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:210
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:120
-#: screens/User/UserTeams/UserTeamList.js:181
-#: screens/User/UserTeams/UserTeamList.js:237
-#: screens/User/UserTeams/UserTeamListItem.js:23
-msgid "Organization"
-msgstr "조직"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:100
-msgid "Organization (Name)"
-msgstr "조직(이름)"
-
-#: screens/Team/TeamList/TeamList.js:126
-msgid "Organization Name"
-msgstr "조직 이름"
-
-#: screens/Organization/Organization.js:154
-msgid "Organization not found."
-msgstr "조직을 찾을 수 없습니다."
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:188
-#: routeConfig.js:96
-#: screens/ActivityStream/ActivityStream.js:181
-#: screens/Organization/OrganizationList/OrganizationList.js:117
-#: screens/Organization/OrganizationList/OrganizationList.js:163
-#: screens/Organization/Organizations.js:16
-#: screens/Organization/Organizations.js:26
-#: screens/User/User.js:66
-#: screens/User/UserOrganizations/UserOrganizationList.js:72
-#: screens/User/Users.js:33
-#: util/getRelatedResourceDeleteDetails.js:232
-#: util/getRelatedResourceDeleteDetails.js:266
-msgid "Organizations"
-msgstr "조직"
-
-#: components/LaunchPrompt/steps/useOtherPromptsStep.js:90
-msgid "Other prompts"
-msgstr "기타 프롬프트"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:79
-msgid "Out of compliance"
-msgstr "규정 준수 외"
-
-#: screens/Job/Job.js:131
-#: screens/Job/JobOutput/HostEventModal.js:156
-#: screens/Job/Jobs.js:34
-msgid "Output"
-msgstr "출력"
-
-#: screens/Job/JobOutput/HostEventModal.js:157
-msgid "Output tab"
-msgstr "출력 탭"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:80
-msgid "Overwrite"
-msgstr "덮어쓰기"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:41
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:121
-msgid "Overwrite local groups and hosts from remote inventory source"
-msgstr "원격 인벤토리 소스에서 로컬 그룹 및 호스트 덮어쓰기"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:46
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:127
-msgid "Overwrite local variables from remote inventory source"
-msgstr "원격 인벤토리 소스에서 로컬 변수 덮어쓰기"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:86
-msgid "Overwrite variables"
-msgstr "변수 덮어쓰기"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:493
-msgid "POST"
-msgstr "POST"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:494
-msgid "PUT"
-msgstr "PUT"
-
-#: components/NotificationList/NotificationList.js:198
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:139
-msgid "Pagerduty"
-msgstr "PagerDuty"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:279
-msgid "Pagerduty Subdomain"
-msgstr "PagerDuty 하위 도메인"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:298
-msgid "Pagerduty subdomain"
-msgstr "PagerDuty 하위 도메인"
-
-#: components/Pagination/Pagination.js:35
-msgid "Pagination"
-msgstr "페이지 번호"
-
-#: components/Workflow/WorkflowTools.js:165
-msgid "Pan Down"
-msgstr "Pan Down"
-
-#: components/Workflow/WorkflowTools.js:132
-msgid "Pan Left"
-msgstr "Panhiera"
-
-#: components/Workflow/WorkflowTools.js:176
-msgid "Pan Right"
-msgstr "Pan right"
-
-#: components/Workflow/WorkflowTools.js:143
-msgid "Pan Up"
-msgstr "Pan Up"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:243
-msgid "Pass extra command line changes. There are two ansible command line parameters:"
-msgstr "추가 명령줄 변경 사항을 전달합니다. 두 가지 ansible 명령행 매개변수가 있습니다."
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:14
-msgid "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. Refer to the Ansible Controller documentation for example syntax."
-msgstr "플레이북에 추가 명령줄 변수를 전달합니다. ansible-playbook에 대해 -e 또는 --extra-vars 명령줄 매개 변수입니다. YAML 또는 JSON을 사용하여 키/값 쌍을 제공합니다. 예제 구문에 대한 Ansible Controller 설명서를 참조하십시오."
-
-#: screens/Job/Job.helptext.js:13
-#: screens/Template/shared/JobTemplate.helptext.js:14
-msgid "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. Refer to the documentation for example syntax."
-msgstr "플레이북에 추가 명령줄 변수를 전달합니다. ansible-playbook에 대해 -e 또는 --extra-vars 명령줄 매개 변수입니다. YAML 또는 JSON을 사용하여 키/값 쌍을 제공합니다. 예제 구문에 대한 설명서를 참조하십시오."
-
-#: screens/Login/Login.js:227
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:73
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:101
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:212
-#: screens/Template/Survey/SurveyQuestionForm.js:82
-#: screens/User/shared/UserForm.js:88
-msgid "Password"
-msgstr "암호"
-
-#: screens/Dashboard/DashboardGraph.js:119
-msgid "Past 24 hours"
-msgstr "지난 24 시간"
-
-#: screens/Dashboard/DashboardGraph.js:110
-msgid "Past month"
-msgstr "지난 한 달"
-
-#: screens/Dashboard/DashboardGraph.js:113
-msgid "Past two weeks"
-msgstr "지난 2주"
-
-#: screens/Dashboard/DashboardGraph.js:116
-msgid "Past week"
-msgstr "지난 주"
-
-#: screens/Instances/Instance.js:51
-#: screens/Instances/InstancePeers/InstancePeerList.js:74
-msgid "Peers"
-msgstr "피어"
-
-#: components/JobList/JobList.js:228
-#: components/StatusLabel/StatusLabel.js:49
-#: components/Workflow/WorkflowNodeHelp.js:93
-msgid "Pending"
-msgstr "보류 중"
-
-#: components/AppContainer/PageHeaderToolbar.js:76
-msgid "Pending Workflow Approvals"
-msgstr "워크플로우 승인 보류 중"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:128
-msgid "Pending delete"
-msgstr "삭제 보류 중"
-
-#: components/Lookup/HostFilterLookup.js:370
-msgid "Perform a search to define a host filter"
-msgstr "호스트 필터를 정의하여 검색을 수행"
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:73
-#: screens/User/UserTokenList/UserTokenList.js:105
-msgid "Personal Access Token"
-msgstr "개인 액세스 토큰"
-
-#: screens/User/UserTokenList/UserTokenListItem.js:26
-msgid "Personal access token"
-msgstr "개인 액세스 토큰"
-
-#: screens/Job/JobOutput/HostEventModal.js:122
-msgid "Play"
-msgstr "플레이"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:84
-msgid "Play Count"
-msgstr "플레이 수"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:124
-msgid "Play Started"
-msgstr "플레이 시작됨"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:148
-#: screens/Job/JobDetail/JobDetail.js:319
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:253
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:43
-#: screens/Template/shared/JobTemplateForm.js:357
-msgid "Playbook"
-msgstr "Playbook"
-
-#: components/JobList/JobListItem.js:44
-#: screens/Job/JobDetail/JobDetail.js:67
-msgid "Playbook Check"
-msgstr "플레이북 확인"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:125
-msgid "Playbook Complete"
-msgstr "플레이북 완료"
-
-#: components/PromptDetail/PromptProjectDetail.js:165
-#: screens/Project/ProjectDetail/ProjectDetail.js:288
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:72
-msgid "Playbook Directory"
-msgstr "플레이북 디렉토리"
-
-#: components/JobList/JobList.js:213
-#: components/JobList/JobListItem.js:44
-#: components/Schedule/ScheduleList/ScheduleListItem.js:37
-#: screens/Job/JobDetail/JobDetail.js:67
-msgid "Playbook Run"
-msgstr "플레이북 실행"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:126
-msgid "Playbook Started"
-msgstr "플레이북 시작됨"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:174
-#: components/TemplateList/TemplateList.js:222
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:23
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:54
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:157
-msgid "Playbook name"
-msgstr "플레이북 이름"
-
-#: screens/Dashboard/DashboardGraph.js:146
-msgid "Playbook run"
-msgstr "플레이북 실행"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:85
-msgid "Plays"
-msgstr "플레이"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:149
-msgid "Please add a Schedule to populate this list."
-msgstr "이 목록을 채울 일정을 추가하십시오."
-
-#: components/Schedule/ScheduleList/ScheduleList.js:152
-msgid "Please add a Schedule to populate this list. Schedules can be added to a Template, Project, or Inventory Source."
-msgstr "이 목록을 채울 일정을 추가하십시오. 템플릿, 프로젝트 또는 인벤토리 소스에 일정을 추가할 수 있습니다."
-
-#: screens/Template/Survey/SurveyList.js:146
-msgid "Please add survey questions."
-msgstr "설문 조사를 추가하십시오."
-
-#: components/PaginatedTable/PaginatedTable.js:93
-msgid "Please add {pluralizedItemName} to populate this list"
-msgstr "이 목록을 채우려면 {pluralizedItemName} 을 추가하십시오."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:43
-msgid "Please click the Start button to begin."
-msgstr "시작하려면 시작 버튼을 클릭하십시오."
-
-#: components/Schedule/shared/ScheduleForm.js:421
-msgid "Please enter a number of occurrences."
-msgstr "이벤트 발생 횟수를 입력해 주십시오."
-
-#: util/validators.js:160
-msgid "Please enter a valid URL"
-msgstr "유효한 URL을 입력하십시오."
-
-#: screens/User/shared/UserTokenForm.js:20
-msgid "Please enter a value."
-msgstr "값을 입력하십시오."
-
-#: screens/Login/Login.js:191
-msgid "Please log in"
-msgstr "로그인하십시오"
-
-#: components/JobList/JobList.js:190
-msgid "Please run a job to populate this list."
-msgstr "이 목록을 채우려면 작업을 실행하십시오."
-
-#: components/Schedule/shared/ScheduleForm.js:417
-msgid "Please select a day number between 1 and 31."
-msgstr "1에서 31 사이의 날짜 번호를 선택하십시오."
-
-#: screens/Template/shared/JobTemplateForm.js:174
-msgid "Please select an Inventory or check the Prompt on Launch option"
-msgstr "인벤토리를 선택하거나 시작 시 프롬프트 옵션을 선택하십시오."
-
-#: components/Schedule/shared/ScheduleForm.js:429
-msgid "Please select an end date/time that comes after the start date/time."
-msgstr "시작 날짜/시간 이후의 종료 날짜/시간을 선택하십시오."
-
-#: components/Lookup/HostFilterLookup.js:359
-msgid "Please select an organization before editing the host filter"
-msgstr "호스트 필터를 편집하기 전에 조직을 선택하십시오."
-
-#: screens/Job/JobOutput/EmptyOutput.js:32
-msgid "Please try another search using the filter above"
-msgstr "위의 필터를 사용하여 다른 검색을 시도하십시오."
-
-#: screens/TopologyView/ContentLoading.js:40
-msgid "Please wait until the topology view is populated..."
-msgstr "토폴로지 보기가 채워질 때까지 기다리십시오..."
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:78
-msgid "Pod spec override"
-msgstr "Pod 사양 덮어쓰기"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:214
-#: screens/InstanceGroup/Instances/InstanceListItem.js:203
-#: screens/Instances/InstanceDetail/InstanceDetail.js:208
-#: screens/Instances/InstanceList/InstanceListItem.js:218
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:82
-msgid "Policy Type"
-msgstr "정책 유형"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:63
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:26
-msgid "Policy instance minimum"
-msgstr "정책 인스턴스 최소"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:70
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:36
-msgid "Policy instance percentage"
-msgstr "정책 인스턴스 백분율"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginField.js:64
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginField.js:70
-msgid "Populate field from an external secret management system"
-msgstr "외부 보안 관리 시스템에서 필드 채우기"
-
-#: components/Lookup/HostFilterLookup.js:349
-msgid ""
-"Populate the hosts for this inventory by using a search\n"
-"filter. Example: ansible_facts__ansible_distribution:\"RedHat\".\n"
-"Refer to the documentation for further syntax and\n"
-"examples. Refer to the Ansible Controller documentation for further syntax and\n"
-"examples."
-msgstr "검색 필터를 사용하여 이 인벤토리의 호스트를 채웁니다. 예: ansible_facts__ansible_distribution:\"RedHat\". 추가 구문 및 예제를 보려면 설명서를 참조하십시오. 추가 구문 및 예를 보려면 Ansible Controller 설명서를 참조하십시오."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:165
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:104
-msgid "Port"
-msgstr "포트"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:231
-msgid "Preconditions for running this node when there are multiple parents. Refer to the"
-msgstr "여러 명의 부모가 있을 때 이 노드를 실행하기 위한 전제 조건"
-
-#: screens/Template/Survey/MultipleChoiceField.js:59
-msgid ""
-"Press 'Enter' to add more answer choices. One answer\n"
-"choice per line."
-msgstr "'Enter'를 눌러 더 많은 답변 선택 사항을 추가합니다. 행당 하나의 응답 선택."
-
-#: components/CodeEditor/CodeEditor.js:181
-msgid "Press Enter to edit. Press ESC to stop editing."
-msgstr "Enter를 눌러 편집합니다. ESC를 눌러 편집을 중지합니다."
-
-#: components/SelectedList/DraggableSelectedList.js:85
-msgid ""
-"Press space or enter to begin dragging,\n"
-"and use the arrow keys to navigate up or down.\n"
-"Press enter to confirm the drag, or any other key to\n"
-"cancel the drag operation."
-msgstr "스페이스바 또는 Enter 키를 눌러 드래그를 시작하고 화살표 키를 사용하여 위로 또는 아래로 이동합니다. Enter 키를 눌러 끌어오기하거나 다른 키를 눌러 끌어오기 작업을 취소합니다."
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:71
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:130
-#: screens/Inventory/shared/InventoryForm.js:99
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:147
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:347
-#: screens/Template/shared/JobTemplateForm.js:603
-msgid "Prevent Instance Group Fallback"
-msgstr "인스턴스 그룹 폴백 방지"
-
-#: screens/Inventory/shared/Inventory.helptext.js:197
-msgid "Prevent Instance Group Fallback: If enabled, the inventory will prevent adding any organization instance groups to the list of preferred instances groups to run associated job templates on."
-msgstr "인스턴스 그룹 폴백 방지: 활성화된 경우, 인벤토리에서 연결된 작업 템플릿을 실행하도록 기본 인스턴스 그룹 목록에 조직 인스턴스 그룹을 추가할 수 없습니다."
-
-#: screens/Template/shared/JobTemplate.helptext.js:43
-msgid "Prevent Instance Group Fallback: If enabled, the job template will prevent adding any inventory or organization instance groups to the list of preferred instances groups to run on."
-msgstr "인스턴스 그룹 폴백 방지: 활성화된 경우 작업 템플릿에서 실행할 기본 인스턴스 그룹 목록에 인벤토리 또는 조직 인스턴스 그룹을 추가하지 않습니다."
-
-#: components/AdHocCommands/useAdHocPreviewStep.js:17
-#: components/LaunchPrompt/steps/usePreviewStep.js:23
-msgid "Preview"
-msgstr "미리보기"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:103
-msgid "Private key passphrase"
-msgstr "개인 키 암호"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:58
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:122
-#: screens/Template/shared/JobTemplateForm.js:551
-msgid "Privilege Escalation"
-msgstr "권한 에스컬레이션"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:111
-msgid "Privilege escalation password"
-msgstr "권한 에스컬레이션 암호"
-
-#: screens/Template/shared/JobTemplate.helptext.js:40
-msgid "Privilege escalation: If enabled, run this playbook as an administrator."
-msgstr "권한 에스컬레이션: 활성화하면 이 플레이북을 관리자로 실행합니다."
-
-#: components/JobList/JobListItem.js:239
-#: components/Lookup/ProjectLookup.js:104
-#: components/Lookup/ProjectLookup.js:109
-#: components/Lookup/ProjectLookup.js:166
-#: components/PromptDetail/PromptInventorySourceDetail.js:87
-#: components/PromptDetail/PromptJobTemplateDetail.js:133
-#: components/PromptDetail/PromptJobTemplateDetail.js:141
-#: components/TemplateList/TemplateListItem.js:300
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:216
-#: screens/Job/JobDetail/JobDetail.js:172
-#: screens/Job/JobDetail/JobDetail.js:198
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:229
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:239
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:38
-msgid "Project"
-msgstr "프로젝트"
-
-#: components/PromptDetail/PromptProjectDetail.js:158
-#: screens/Project/ProjectDetail/ProjectDetail.js:281
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:61
-msgid "Project Base Path"
-msgstr "프로젝트 기본 경로"
-
-#: components/Workflow/WorkflowLegend.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:85
-msgid "Project Sync"
-msgstr "프로젝트 동기화"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:320
-#: screens/Project/ProjectList/ProjectListItem.js:229
-msgid "Project Sync Error"
-msgstr "프로젝트 동기화 오류"
-
-#: components/Workflow/WorkflowNodeHelp.js:67
-msgid "Project Update"
-msgstr "프로젝트 업데이트"
-
-#: screens/Job/JobDetail/JobDetail.js:180
-msgid "Project Update Status"
-msgstr "프로젝트 업데이트 상태"
-
-#: screens/Job/Job.helptext.js:22
-msgid "Project checkout results"
-msgstr "프로젝트 체크아웃 결과"
-
-#: screens/Project/ProjectList/ProjectList.js:132
-msgid "Project copied successfully"
-msgstr "프로젝트가 성공적으로 복사됨"
-
-#: screens/Project/Project.js:136
-msgid "Project not found."
-msgstr "프로젝트를 찾을 수 없음"
-
-#: screens/Dashboard/Dashboard.js:109
-msgid "Project sync failures"
-msgstr "프로젝트 동기화 실패"
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:146
-#: routeConfig.js:75
-#: screens/ActivityStream/ActivityStream.js:170
-#: screens/Dashboard/Dashboard.js:103
-#: screens/Project/ProjectList/ProjectList.js:180
-#: screens/Project/ProjectList/ProjectList.js:249
-#: screens/Project/Projects.js:12
-#: screens/Project/Projects.js:22
-#: util/getRelatedResourceDeleteDetails.js:60
-#: util/getRelatedResourceDeleteDetails.js:195
-#: util/getRelatedResourceDeleteDetails.js:225
-msgid "Projects"
-msgstr "프로젝트"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:139
-msgid "Promote Child Groups and Hosts"
-msgstr "하위 그룹 및 호스트 승격"
-
-#: components/Schedule/shared/ScheduleForm.js:540
-#: components/Schedule/shared/ScheduleForm.js:543
-msgid "Prompt"
-msgstr "프롬프트"
-
-#: components/PromptDetail/PromptDetail.js:182
-msgid "Prompt Overrides"
-msgstr "프롬프트 덮어쓰기"
-
-#: components/CodeEditor/VariablesField.js:241
-#: components/FieldWithPrompt/FieldWithPrompt.js:46
-#: screens/Credential/CredentialDetail/CredentialDetail.js:175
-msgid "Prompt on launch"
-msgstr "시작 시 프롬프트"
-
-#: components/Schedule/shared/SchedulePromptableFields.js:97
-msgid "Prompt | {0}"
-msgstr "프롬프트 | {0}"
-
-#: components/PromptDetail/PromptDetail.js:180
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:418
-msgid "Prompted Values"
-msgstr "프롬프트 값"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:6
-msgid ""
-"Provide a host pattern to further constrain\n"
-"the list of hosts that will be managed or affected by the\n"
-"playbook. Multiple patterns are allowed. Refer to Ansible\n"
-"documentation for more information and examples on patterns."
-msgstr "플레이북에 의해 관리 또는 영향을 받는 호스트 목록을 추가로 제한하기 위해 호스트 패턴을 제공합니다. 여러 패턴이 허용됩니다. 패턴에 대한 자세한 정보와 예제는 Ansible 문서를 참조하십시오."
-
-#: screens/Job/Job.helptext.js:14
-#: screens/Template/shared/JobTemplate.helptext.js:15
-msgid "Provide a host pattern to further constrain the list of hosts that will be managed or affected by the playbook. Multiple patterns are allowed. Refer to Ansible documentation for more information and examples on patterns."
-msgstr "플레이북에 의해 관리 또는 영향을 받는 호스트 목록을 추가로 제한하기 위해 호스트 패턴을 제공합니다. 여러 패턴이 허용됩니다. 패턴에 대한 자세한 정보와 예제는 Ansible 문서를 참조하십시오."
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:179
-msgid "Provide a value for this field or select the Prompt on launch option."
-msgstr "이 필드에 값을 제공하거나 시작 시 프롬프트 실행 옵션을 선택합니다."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:247
-msgid ""
-"Provide key/value pairs using either\n"
-"YAML or JSON."
-msgstr "YAML 또는 JSON을 사용하여 키/값 쌍을 제공합니다."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:191
-msgid ""
-"Provide your Red Hat or Red Hat Satellite credentials\n"
-"below and you can choose from a list of your available subscriptions.\n"
-"The credentials you use will be stored for future use in\n"
-"retrieving renewal or expanded subscriptions."
-msgstr "아래에서 Red Hat 또는 Red Hat Satellite 인증 정보를 제공하고 사용 가능한 서브스크립션 목록에서 선택할 수 있습니다. 사용하는 인증 정보는 향후 갱신 또는 확장 서브스크립션을 검색하는데 사용할 수 있도록 저장됩니다."
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:83
-msgid "Provide your Red Hat or Red Hat Satellite credentials to enable Automation Analytics."
-msgstr "Automation Analytics를 활성화하려면 Red Hat 또는 Red Hat Satellite 인증 정보를 제공합니다."
-
-#: components/StatusLabel/StatusLabel.js:59
-#: screens/TopologyView/Legend.js:150
-msgid "Provisioning"
-msgstr "프로비저닝"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:162
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:302
-#: screens/Template/shared/JobTemplateForm.js:620
-msgid "Provisioning Callback URL"
-msgstr "콜백 URL 프로비저닝"
-
-#: screens/Template/shared/JobTemplateForm.js:615
-msgid "Provisioning Callback details"
-msgstr "프로비저닝 호출 세부 정보"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:63
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:127
-#: screens/Template/shared/JobTemplateForm.js:555
-#: screens/Template/shared/JobTemplateForm.js:558
-msgid "Provisioning Callbacks"
-msgstr "프로비저닝 콜백"
-
-#: screens/Template/shared/JobTemplate.helptext.js:41
-msgid "Provisioning callbacks: Enables creation of a provisioning callback URL. Using the URL a host can contact Ansible AWX and request a configuration update using this job template."
-msgstr "프로비저닝 콜백: 프로비저닝 콜백 URL 생성을 활성화합니다. 호스트에서 URL을 사용하면 Ansible AWX에 연락하고 이 작업 템플릿을 사용하여 구성 업데이트를 요청할 수 있습니다."
-
-#: components/StatusLabel/StatusLabel.js:62
-msgid "Provisioning fail"
-msgstr "프로비저닝 실패"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:86
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:114
-msgid "Pull"
-msgstr "Pull"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:163
-msgid "Question"
-msgstr "질문"
-
-#: screens/Setting/Settings.js:106
-msgid "RADIUS"
-msgstr "RADIUS"
-
-#: screens/Setting/SettingList.js:73
-msgid "RADIUS settings"
-msgstr "RADIUS 설정"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:244
-#: screens/InstanceGroup/Instances/InstanceListItem.js:161
-#: screens/Instances/InstanceDetail/InstanceDetail.js:287
-#: screens/Instances/InstanceList/InstanceListItem.js:171
-#: screens/TopologyView/Tooltip.js:307
-msgid "RAM {0}"
-msgstr "RAM {0}"
-
-#: screens/User/shared/UserTokenForm.js:76
-msgid "Read"
-msgstr "읽기"
-
-#: components/StatusLabel/StatusLabel.js:57
-#: screens/TopologyView/Legend.js:122
-msgid "Ready"
-msgstr "준비됨"
-
-#: screens/Dashboard/Dashboard.js:133
-msgid "Recent Jobs"
-msgstr "최근 작업"
-
-#: screens/Dashboard/Dashboard.js:131
-msgid "Recent Jobs list tab"
-msgstr "최근 작업 목록 탭"
-
-#: screens/Dashboard/Dashboard.js:145
-msgid "Recent Templates"
-msgstr "최근 템플릿"
-
-#: screens/Dashboard/Dashboard.js:143
-msgid "Recent Templates list tab"
-msgstr "최근 템플릿 목록 탭"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:188
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:112
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:38
-msgid "Recent jobs"
-msgstr "최근 작업"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:154
-msgid "Recipient List"
-msgstr "수신자 목록"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:86
-msgid "Recipient list"
-msgstr "수신자 목록"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:105
-msgid "Red Hat Ansible Automation Platform"
-msgstr "Red Hat Ansible Automation Platform"
-
-#: components/Lookup/ProjectLookup.js:139
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:92
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:161
-#: screens/Job/JobDetail/JobDetail.js:77
-#: screens/Project/ProjectList/ProjectList.js:201
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:100
-msgid "Red Hat Insights"
-msgstr "Red Hat Insights"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:102
-msgid "Red Hat Satellite 6"
-msgstr "Red Hat Satellite 6"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:104
-msgid "Red Hat Virtualization"
-msgstr "Red Hat Virtualization"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:117
-msgid "Red Hat subscription manifest"
-msgstr "Red Hat 서브스크립션 매니페스트"
-
-#: components/About/About.js:36
-msgid "Red Hat, Inc."
-msgstr "Red Hat, Inc."
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:94
-#: screens/Application/shared/ApplicationForm.js:107
-msgid "Redirect URIs"
-msgstr "리디렉션 URI"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:259
-msgid "Redirecting to dashboard"
-msgstr "대시보드로 리디렉션"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:263
-msgid "Redirecting to subscription detail"
-msgstr "서브스크립션 세부 정보로 리디렉션"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:261
-#: screens/Template/shared/JobTemplate.helptext.js:55
-msgid "Refer to the"
-msgstr "참조"
-
-#: screens/Job/Job.helptext.js:27
-#: screens/Template/shared/JobTemplate.helptext.js:50
-msgid "Refer to the Ansible documentation for details about the configuration file."
-msgstr "구성 파일에 대한 자세한 내용은 Ansible 설명서를 참조하십시오."
-
-#: screens/User/UserTokens/UserTokens.js:77
-msgid "Refresh Token"
-msgstr "토큰 새로 고침"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:81
-msgid "Refresh Token Expiration"
-msgstr "토큰 만료 새로 고침"
-
-#: screens/Project/ProjectList/ProjectListItem.js:132
-msgid "Refresh for revision"
-msgstr "버전 새로 고침"
-
-#: screens/Project/ProjectList/ProjectListItem.js:134
-msgid "Refresh project revision"
-msgstr "프로젝트 버전 새로 고침"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:116
-msgid "Regions"
-msgstr "리전"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:92
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:143
-msgid "Registry credential"
-msgstr "레지스트리 인증 정보"
-
-#: screens/Inventory/shared/Inventory.helptext.js:156
-msgid "Regular expression where only matching host names will be imported. The filter is applied as a post-processing step after any inventory plugin filters are applied."
-msgstr "호스트 이름과 일치하는 정규 표현식을 가져옵니다. 필터는 인벤토리 플러그인 필터를 적용한 후 사후 처리 단계로 적용됩니다."
-
-#: screens/Inventory/Inventories.js:81
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:62
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:175
-msgid "Related Groups"
-msgstr "관련 그룹"
-
-#: components/Search/AdvancedSearch.js:287
-msgid "Related Keys"
-msgstr "관련 키"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:169
-#: components/Schedule/ScheduleList/ScheduleListItem.js:105
-msgid "Related resource"
-msgstr "관련 리소스"
-
-#: components/Search/RelatedLookupTypeInput.js:16
-#: components/Search/RelatedLookupTypeInput.js:24
-msgid "Related search type"
-msgstr "관련 검색 유형"
-
-#: components/Search/RelatedLookupTypeInput.js:19
-msgid "Related search type typeahead"
-msgstr "관련 검색 자동 완성"
-
-#: components/JobList/JobListItem.js:146
-#: components/LaunchButton/ReLaunchDropDown.js:82
-#: screens/Job/JobDetail/JobDetail.js:580
-#: screens/Job/JobDetail/JobDetail.js:588
-#: screens/Job/JobOutput/shared/OutputToolbar.js:167
-msgid "Relaunch"
-msgstr "다시 시작"
-
-#: components/JobList/JobListItem.js:126
-#: screens/Job/JobOutput/shared/OutputToolbar.js:147
-msgid "Relaunch Job"
-msgstr "작업 다시 시작"
-
-#: components/LaunchButton/ReLaunchDropDown.js:41
-msgid "Relaunch all hosts"
-msgstr "모든 호스트 다시 시작"
-
-#: components/LaunchButton/ReLaunchDropDown.js:54
-msgid "Relaunch failed hosts"
-msgstr "실패한 호스트 다시 시작"
-
-#: components/LaunchButton/ReLaunchDropDown.js:30
-#: components/LaunchButton/ReLaunchDropDown.js:35
-msgid "Relaunch on"
-msgstr "다시 시작"
-
-#: components/JobList/JobListItem.js:125
-#: screens/Job/JobOutput/shared/OutputToolbar.js:146
-msgid "Relaunch using host parameters"
-msgstr "호스트 매개변수를 사용하여 다시 시작"
-
-#: components/HealthCheckAlert/HealthCheckAlert.js:27
-msgid "Reload"
-msgstr "다시 로드"
-
-#: screens/Job/JobOutput/JobOutput.js:723
-msgid "Reload output"
-msgstr "출력 다시 로드"
-
-#: components/Lookup/ProjectLookup.js:138
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:91
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:160
-#: screens/Job/JobDetail/JobDetail.js:78
-#: screens/Project/ProjectList/ProjectList.js:200
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:99
-msgid "Remote Archive"
-msgstr "원격 아카이브"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:374
-#: screens/Instances/InstanceList/InstanceList.js:242
-msgid "Removal Error"
-msgstr "제거 오류"
-
-#: components/SelectedList/DraggableSelectedList.js:105
-#: screens/Instances/Shared/RemoveInstanceButton.js:75
-#: screens/Instances/Shared/RemoveInstanceButton.js:129
-#: screens/Instances/Shared/RemoveInstanceButton.js:143
-#: screens/Instances/Shared/RemoveInstanceButton.js:163
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:21
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:29
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:40
-msgid "Remove"
-msgstr "제거"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:36
-msgid "Remove All Nodes"
-msgstr "모든 노드 제거"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:152
-msgid "Remove Instances"
-msgstr "인스턴스 제거"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:17
-msgid "Remove Link"
-msgstr "링크 제거"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:28
-msgid "Remove Node {nodeName}"
-msgstr "{nodeName} 노드 제거"
-
-#: screens/Project/shared/Project.helptext.js:113
-msgid "Remove any local modifications prior to performing an update."
-msgstr "업데이트를 수행하기 전에 로컬 수정 사항을 제거합니다."
-
-#: components/Search/AdvancedSearch.js:206
-msgid "Remove the current search related to ansible facts to enable another search using this key."
-msgstr "이 키를 사용하여 다른 검색을 활성화하려면 ansible 팩트와 관련된 현재 검색을 제거합니다."
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:14
-msgid "Remove {0} Access"
-msgstr "{0} 액세스 제거"
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:45
-msgid "Remove {0} chip"
-msgstr "{0} 칩 제거"
-
-#: screens/TopologyView/Legend.js:285
-msgid "Removing"
-msgstr "제거 중"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:48
-msgid "Removing this link will orphan the rest of the branch and cause it to be executed immediately on launch."
-msgstr "이 링크를 제거하면 나머지 분기가 분리되고 시작 시 즉시 실행됩니다."
-
-#: components/SelectedList/DraggableSelectedList.js:83
-msgid "Reorder"
-msgstr "재정렬"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:349
-msgid "Repeat Frequency"
-msgstr "반복 빈도"
-
-#: components/Schedule/shared/ScheduleFormFields.js:113
-msgid "Repeat frequency"
-msgstr "반복 빈도"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:52
-msgid "Replace"
-msgstr "교체"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:60
-msgid "Replace field with new value"
-msgstr "필드를 새 값으로 교체"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:67
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:74
-msgid "Request subscription"
-msgstr "서브스크립션 요청"
-
-#: screens/Template/Survey/SurveyListItem.js:51
-#: screens/Template/Survey/SurveyQuestionForm.js:188
-msgid "Required"
-msgstr "필수 항목"
-
-#: screens/TopologyView/Header.js:87
-#: screens/TopologyView/Header.js:90
-msgid "Reset zoom"
-msgstr "확대/축소 재설정"
-
-#: components/Workflow/WorkflowNodeHelp.js:154
-#: components/Workflow/WorkflowNodeHelp.js:190
-#: screens/Team/TeamRoles/TeamRoleListItem.js:12
-#: screens/Team/TeamRoles/TeamRolesList.js:180
-msgid "Resource Name"
-msgstr "리소스 이름"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:246
-msgid "Resource deleted"
-msgstr "삭제된 리소스"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:170
-#: components/Schedule/ScheduleList/ScheduleListItem.js:111
-msgid "Resource type"
-msgstr "리소스 유형"
-
-#: routeConfig.js:61
-#: screens/ActivityStream/ActivityStream.js:159
-msgid "Resources"
-msgstr "리소스"
-
-#: components/TemplateList/TemplateListItem.js:149
-msgid "Resources are missing from this template."
-msgstr "이 템플릿에서 리소스가 누락되어 있습니다."
-
-#: screens/Setting/shared/RevertButton.js:43
-msgid "Restore initial value."
-msgstr "초기 값을 복원합니다."
-
-#: screens/Inventory/shared/Inventory.helptext.js:153
-msgid ""
-"Retrieve the enabled state from the given dict of host variables.\n"
-"The enabled variable may be specified using dot notation, e.g: 'foo.bar'"
-msgstr "호스트 변수의 지정된 dict에서 활성화된 상태를 검색합니다. 활성화된 변수는 점 표기법을 사용하여 지정할 수 있습니다(예: 'foo.bar')."
-
-#: components/JobCancelButton/JobCancelButton.js:96
-#: components/JobCancelButton/JobCancelButton.js:100
-#: components/JobList/JobListCancelButton.js:160
-#: components/JobList/JobListCancelButton.js:163
-#: screens/Job/JobOutput/JobOutput.js:819
-#: screens/Job/JobOutput/JobOutput.js:822
-msgid "Return"
-msgstr "돌아가기"
-
-#: screens/Job/JobOutput/EmptyOutput.js:40
-msgid "Return to"
-msgstr "다음으로 돌아가기"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:129
-msgid "Return to subscription management."
-msgstr "서브스크립션 관리로 돌아가기"
-
-#: components/Search/AdvancedSearch.js:171
-msgid "Returns results that have values other than this one as well as other filters."
-msgstr "이 필터 및 다른 필터를 제외한 값으로 결과를 반환합니다."
-
-#: components/Search/AdvancedSearch.js:158
-msgid "Returns results that satisfy this one as well as other filters. This is the default set type if nothing is selected."
-msgstr "이 필터와 다른 필터를 충족하는 결과를 반환합니다. 아무것도 선택하지 않은 경우 기본 설정 유형입니다."
-
-#: components/Search/AdvancedSearch.js:164
-msgid "Returns results that satisfy this one or any other filters."
-msgstr "이 필터를 하나 또는 다른 필터를 충족하는 결과를 반환합니다."
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:52
-#: screens/Setting/shared/RevertButton.js:53
-#: screens/Setting/shared/RevertButton.js:62
-msgid "Revert"
-msgstr "되돌리기"
-
-#: screens/Setting/shared/RevertAllAlert.js:23
-msgid "Revert all"
-msgstr "모두 되돌리기"
-
-#: screens/Setting/shared/RevertFormActionGroup.js:21
-#: screens/Setting/shared/RevertFormActionGroup.js:27
-msgid "Revert all to default"
-msgstr "모두 기본값으로 되돌립니다."
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:59
-msgid "Revert field to previously saved value"
-msgstr "이전에 저장된 값으로 필드를 되돌리기"
-
-#: screens/Setting/shared/RevertAllAlert.js:11
-msgid "Revert settings"
-msgstr "설정 복원"
-
-#: screens/Setting/shared/RevertButton.js:42
-msgid "Revert to factory default."
-msgstr "팩토리 기본 설정으로 되돌립니다."
-
-#: screens/Job/JobDetail/JobDetail.js:314
-#: screens/Project/ProjectList/ProjectList.js:224
-#: screens/Project/ProjectList/ProjectListItem.js:221
-msgid "Revision"
-msgstr "버전"
-
-#: screens/Project/shared/ProjectSubForms/SvnSubForm.js:22
-msgid "Revision #"
-msgstr "버전 #"
-
-#: components/NotificationList/NotificationList.js:199
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:140
-msgid "Rocket.Chat"
-msgstr "Rocket.Chat"
-
-#: screens/Team/TeamRoles/TeamRoleListItem.js:20
-#: screens/Team/TeamRoles/TeamRolesList.js:148
-#: screens/Team/TeamRoles/TeamRolesList.js:182
-#: screens/User/UserList/UserList.js:163
-#: screens/User/UserList/UserListItem.js:55
-#: screens/User/UserRoles/UserRolesList.js:146
-#: screens/User/UserRoles/UserRolesList.js:157
-#: screens/User/UserRoles/UserRolesListItem.js:26
-msgid "Role"
-msgstr "역할"
-
-#: components/ResourceAccessList/ResourceAccessList.js:189
-#: components/ResourceAccessList/ResourceAccessList.js:202
-#: components/ResourceAccessList/ResourceAccessList.js:229
-#: components/ResourceAccessList/ResourceAccessListItem.js:69
-#: screens/Team/Team.js:59
-#: screens/Team/Teams.js:32
-#: screens/User/User.js:71
-#: screens/User/Users.js:31
-msgid "Roles"
-msgstr "역할"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:134
-#: components/Workflow/WorkflowLinkHelp.js:39
-#: screens/Credential/shared/ExternalTestModal.js:89
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:49
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:23
-#: screens/Template/shared/JobTemplateForm.js:214
-msgid "Run"
-msgstr "실행"
-
-#: components/AdHocCommands/AdHocCommands.js:131
-#: components/AdHocCommands/AdHocCommands.js:135
-#: components/AdHocCommands/AdHocCommands.js:141
-#: components/AdHocCommands/AdHocCommands.js:145
-#: screens/Job/JobDetail/JobDetail.js:68
-msgid "Run Command"
-msgstr "명령 실행"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:277
-#: screens/Instances/InstanceDetail/InstanceDetail.js:335
-msgid "Run a health check on the instance"
-msgstr "인스턴스에서 상태 점검을 실행합니다."
-
-#: components/AdHocCommands/AdHocCommands.js:125
-msgid "Run ad hoc command"
-msgstr "애드혹 명령 실행"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:48
-msgid "Run command"
-msgstr "명령 실행"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:93
-#: components/Schedule/shared/FrequencyDetailSubform.js:223
-msgid "Run every"
-msgstr "모두 실행"
-
-#: components/HealthCheckButton/HealthCheckButton.js:32
-#: components/HealthCheckButton/HealthCheckButton.js:45
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:286
-#: screens/Instances/InstanceDetail/InstanceDetail.js:344
-msgid "Run health check"
-msgstr "실행 상태 점검"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:129
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:138
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:175
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:197
-#: components/Schedule/shared/FrequencyDetailSubform.js:344
-msgid "Run on"
-msgstr "실행"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useRunTypeStep.js:32
-msgid "Run type"
-msgstr "실행 유형"
-
-#: components/JobList/JobList.js:230
-#: components/StatusLabel/StatusLabel.js:48
-#: components/TemplateList/TemplateListItem.js:118
-#: components/Workflow/WorkflowNodeHelp.js:99
-msgid "Running"
-msgstr "실행 중"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:127
-msgid "Running Handlers"
-msgstr "실행 중인 Handlers"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:217
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:196
-#: screens/InstanceGroup/Instances/InstanceListItem.js:194
-#: screens/Instances/InstanceDetail/InstanceDetail.js:212
-#: screens/Instances/InstanceList/InstanceListItem.js:209
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:73
-msgid "Running Jobs"
-msgstr "실행 중인 작업"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:284
-#: screens/Instances/InstanceDetail/InstanceDetail.js:342
-msgid "Running health check"
-msgstr "실행 중인 상태 점검"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:71
-msgid "Running jobs"
-msgstr "실행 중인 작업"
-
-#: screens/Setting/Settings.js:109
-msgid "SAML"
-msgstr "SAML"
-
-#: screens/Setting/SettingList.js:77
-msgid "SAML settings"
-msgstr "SAML 설정"
-
-#: screens/Dashboard/DashboardGraph.js:143
-msgid "SCM update"
-msgstr "SCM 업데이트"
-
-#: screens/User/UserDetail/UserDetail.js:58
-#: screens/User/UserList/UserListItem.js:49
-msgid "SOCIAL"
-msgstr "SOCIAL"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:95
-msgid "SSH password"
-msgstr "SSH 암호"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:239
-msgid "SSL Connection"
-msgstr "SSL 연결"
-
-#: components/Workflow/WorkflowStartNode.js:60
-#: components/Workflow/workflowReducer.js:419
-msgid "START"
-msgstr "시작"
-
-#: components/Sparkline/Sparkline.js:31
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:160
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:39
-#: screens/Project/ProjectDetail/ProjectDetail.js:135
-#: screens/Project/ProjectList/ProjectListItem.js:73
-msgid "STATUS:"
-msgstr "상태:"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:321
-msgid "Sat"
-msgstr "토요일"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:82
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:188
-#: components/Schedule/shared/FrequencyDetailSubform.js:326
-#: components/Schedule/shared/FrequencyDetailSubform.js:458
-msgid "Saturday"
-msgstr "토요일"
-
-#: components/AddRole/AddResourceRole.js:247
-#: components/AssociateModal/AssociateModal.js:104
-#: components/AssociateModal/AssociateModal.js:110
-#: components/FormActionGroup/FormActionGroup.js:13
-#: components/FormActionGroup/FormActionGroup.js:19
-#: components/Schedule/shared/ScheduleForm.js:526
-#: components/Schedule/shared/ScheduleForm.js:532
-#: components/Schedule/shared/useSchedulePromptSteps.js:49
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:130
-#: screens/Credential/shared/CredentialForm.js:318
-#: screens/Credential/shared/CredentialForm.js:323
-#: screens/Setting/shared/RevertFormActionGroup.js:12
-#: screens/Setting/shared/RevertFormActionGroup.js:18
-#: screens/Template/Survey/SurveyReorderModal.js:205
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:35
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:131
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:158
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:162
-msgid "Save"
-msgstr "저장"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:33
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:36
-msgid "Save & Exit"
-msgstr "저장 및 종료"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:32
-msgid "Save link changes"
-msgstr "링크 변경 저장"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:254
-msgid "Save successful!"
-msgstr "성공적으로 저장했습니다!"
-
-#: components/JobList/JobListItem.js:181
-#: components/JobList/JobListItem.js:187
-msgid "Schedule"
-msgstr "스케줄"
-
-#: screens/Project/Projects.js:34
-#: screens/Template/Templates.js:54
-msgid "Schedule Details"
-msgstr "일정 세부 정보"
-
-#: components/Schedule/shared/UnsupportedScheduleForm.js:15
-msgid "Schedule Rules"
-msgstr "일정 규칙"
-
-#: screens/Inventory/Inventories.js:92
-msgid "Schedule details"
-msgstr "일정 세부 정보"
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:49
-msgid "Schedule is active"
-msgstr "일정이 활성화됨"
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:49
-msgid "Schedule is inactive"
-msgstr "일정이 비활성 상태입니다"
-
-#: components/Schedule/shared/ScheduleForm.js:394
-msgid "Schedule is missing rrule"
-msgstr "일정에 규칙이 누락되어 있습니다"
-
-#: components/Schedule/Schedule.js:82
-msgid "Schedule not found."
-msgstr "스케줄을 찾을 수 없습니다."
-
-#: components/Schedule/ScheduleList/ScheduleList.js:163
-#: components/Schedule/ScheduleList/ScheduleList.js:229
-#: routeConfig.js:44
-#: screens/ActivityStream/ActivityStream.js:153
-#: screens/Inventory/Inventories.js:89
-#: screens/Inventory/InventorySource/InventorySource.js:88
-#: screens/ManagementJob/ManagementJob.js:108
-#: screens/ManagementJob/ManagementJobs.js:23
-#: screens/Project/Project.js:120
-#: screens/Project/Projects.js:31
-#: screens/Schedule/AllSchedules.js:21
-#: screens/Template/Template.js:148
-#: screens/Template/Templates.js:51
-#: screens/Template/WorkflowJobTemplate.js:130
-msgid "Schedules"
-msgstr "일정"
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:136
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:33
-#: screens/User/UserTokenDetail/UserTokenDetail.js:50
-#: screens/User/UserTokenList/UserTokenList.js:142
-#: screens/User/UserTokenList/UserTokenList.js:189
-#: screens/User/UserTokenList/UserTokenListItem.js:32
-#: screens/User/shared/UserTokenForm.js:68
-msgid "Scope"
-msgstr "범위"
-
-#: screens/User/shared/User.helptext.js:5
-msgid "Scope for the token's access"
-msgstr "토큰 액세스 범위"
-
-#: screens/Job/JobOutput/PageControls.js:79
-msgid "Scroll first"
-msgstr "먼저 스크롤"
-
-#: screens/Job/JobOutput/PageControls.js:87
-msgid "Scroll last"
-msgstr "마지막 스크롤"
-
-#: screens/Job/JobOutput/PageControls.js:71
-msgid "Scroll next"
-msgstr "다음 스크롤"
-
-#: screens/Job/JobOutput/PageControls.js:63
-msgid "Scroll previous"
-msgstr "이전 스크롤"
-
-#: components/Lookup/HostFilterLookup.js:290
-#: components/Lookup/Lookup.js:143
-msgid "Search"
-msgstr "검색"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:151
-msgid "Search is disabled while the job is running"
-msgstr "작업이 실행되는 동안 검색이 비활성화됩니다."
-
-#: components/Search/AdvancedSearch.js:311
-#: components/Search/Search.js:259
-msgid "Search submit button"
-msgstr "검색 제출 버튼"
-
-#: components/Search/Search.js:248
-msgid "Search text input"
-msgstr "검색 텍스트 입력"
-
-#: components/Lookup/HostFilterLookup.js:398
-msgid "Searching by ansible_facts requires special syntax. Refer to the"
-msgstr "ansible_facts로 검색하는 경우 특수 구문이 필요합니다."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:408
-msgid "Second"
-msgstr "초"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:103
-#: components/PromptDetail/PromptProjectDetail.js:153
-#: screens/Project/ProjectDetail/ProjectDetail.js:269
-msgid "Seconds"
-msgstr "초"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:34
-msgid "See Django"
-msgstr "Django 참조"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:35
-#: components/LaunchPrompt/steps/PreviewStep.js:61
-msgid "See errors on the left"
-msgstr "왼쪽의 오류 보기"
-
-#: components/JobList/JobListItem.js:84
-#: components/Lookup/HostFilterLookup.js:380
-#: components/Lookup/Lookup.js:200
-#: components/Pagination/Pagination.js:33
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:98
-msgid "Select"
-msgstr "선택"
-
-#: screens/Credential/shared/CredentialForm.js:129
-msgid "Select Credential Type"
-msgstr "인증 정보 유형 선택"
-
-#: screens/Host/HostGroups/HostGroupsList.js:237
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:254
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:257
-msgid "Select Groups"
-msgstr "그룹 선택"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:278
-msgid "Select Hosts"
-msgstr "호스트 선택"
-
-#: components/AnsibleSelect/AnsibleSelect.js:38
-msgid "Select Input"
-msgstr "입력 선택"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:295
-msgid "Select Instances"
-msgstr "인스턴스 선택"
-
-#: components/AssociateModal/AssociateModal.js:22
-msgid "Select Items"
-msgstr "항목 선택"
-
-#: components/AddRole/AddResourceRole.js:201
-msgid "Select Items from List"
-msgstr "목록에서 항목 선택"
-
-#: components/LabelSelect/LabelSelect.js:127
-msgid "Select Labels"
-msgstr "레이블 선택"
-
-#: components/AddRole/AddResourceRole.js:236
-msgid "Select Roles to Apply"
-msgstr "적용할 역할 선택"
-
-#: screens/User/UserTeams/UserTeamList.js:251
-msgid "Select Teams"
-msgstr "팀 선택"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:25
-msgid "Select a JSON formatted service account key to autopopulate the following fields."
-msgstr "JSON 형식의 서비스 계정 키를 선택하여 다음 필드를 자동으로 채웁니다."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:122
-msgid "Select a Node Type"
-msgstr "노드 유형 선택"
-
-#: components/AddRole/AddResourceRole.js:170
-msgid "Select a Resource Type"
-msgstr "리소스 유형 선택"
-
-#: screens/Job/Job.helptext.js:21
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:10
-msgid "Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch."
-msgstr "워크플로의 분기를 선택합니다. 이 분기는 분기를 요청하는 모든 작업 템플릿 노드에 적용됩니다."
-
-#: screens/Credential/shared/CredentialForm.js:139
-msgid "Select a credential Type"
-msgstr "인증 정보 유형 선택"
-
-#: components/JobList/JobListCancelButton.js:98
-msgid "Select a job to cancel"
-msgstr "취소할 작업 선택"
-
-#: screens/Metrics/Metrics.js:211
-msgid "Select a metric"
-msgstr "메트릭 선택"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:75
-msgid "Select a module"
-msgstr "모듈 선택"
-
-#: screens/Template/shared/PlaybookSelect.js:60
-#: screens/Template/shared/PlaybookSelect.js:61
-msgid "Select a playbook"
-msgstr "Playbook 선택"
-
-#: screens/Template/shared/JobTemplateForm.js:323
-msgid "Select a project before editing the execution environment."
-msgstr "실행 환경을 편집하기 전에 프로젝트를 선택합니다."
-
-#: screens/Template/Survey/SurveyToolbar.js:82
-msgid "Select a question to delete"
-msgstr "삭제할 질문을 선택"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:160
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:103
-msgid "Select a row to delete"
-msgstr "삭제할 행 선택"
-
-#: components/DisassociateButton/DisassociateButton.js:75
-msgid "Select a row to disassociate"
-msgstr "연결할 행을 선택"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:77
-msgid "Select a row to remove"
-msgstr "삭제할 행 선택"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:87
-msgid "Select a subscription"
-msgstr "서브스크립션 선택"
-
-#: components/HostForm/HostForm.js:39
-#: components/Schedule/shared/FrequencyDetailSubform.js:71
-#: components/Schedule/shared/FrequencyDetailSubform.js:78
-#: components/Schedule/shared/FrequencyDetailSubform.js:88
-#: components/Schedule/shared/ScheduleFormFields.js:33
-#: components/Schedule/shared/ScheduleFormFields.js:37
-#: components/Schedule/shared/ScheduleFormFields.js:61
-#: screens/Credential/shared/CredentialForm.js:44
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:79
-#: screens/Inventory/shared/InventoryForm.js:72
-#: screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.js:46
-#: screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/GCESubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.js:46
-#: screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:97
-#: screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.js:44
-#: screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.js:46
-#: screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.js:46
-#: screens/Inventory/shared/SmartInventoryForm.js:67
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:24
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:61
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:436
-#: screens/Project/shared/ProjectForm.js:234
-#: screens/Project/shared/ProjectSubForms/InsightsSubForm.js:39
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:37
-#: screens/Team/shared/TeamForm.js:49
-#: screens/Template/Survey/SurveyQuestionForm.js:30
-#: screens/Template/shared/WorkflowJobTemplateForm.js:130
-#: screens/User/shared/UserForm.js:139
-#: util/validators.js:201
-msgid "Select a value for this field"
-msgstr "이 필드의 값을 선택"
-
-#: screens/Template/shared/JobTemplate.helptext.js:23
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:20
-msgid "Select a webhook service."
-msgstr "Webhook 서비스 선택"
-
-#: components/DataListToolbar/DataListToolbar.js:121
-#: components/DataListToolbar/DataListToolbar.js:125
-#: screens/Template/Survey/SurveyToolbar.js:49
-msgid "Select all"
-msgstr "모두 선택"
-
-#: screens/ActivityStream/ActivityStream.js:129
-msgid "Select an activity type"
-msgstr "활동 유형 선택"
-
-#: screens/Metrics/Metrics.js:200
-msgid "Select an instance"
-msgstr "인스턴스 선택"
-
-#: screens/Metrics/Metrics.js:242
-msgid "Select an instance and a metric to show chart"
-msgstr "차트를 표시할 인스턴스 및 메트릭을 선택합니다."
-
-#: components/HealthCheckButton/HealthCheckButton.js:19
-msgid "Select an instance to run a health check."
-msgstr "상태 점검을 실행할 인스턴스를 선택합니다."
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:5
-msgid "Select an inventory for the workflow. This inventory is applied to all workflow nodes that prompt for an inventory."
-msgstr "워크플로에 대한 인벤토리를 선택합니다. 이 인벤토리는 인벤토리를 요청하는 모든 워크플로 노드에 적용됩니다."
-
-#: components/LaunchPrompt/steps/SurveyStep.js:131
-msgid "Select an option"
-msgstr "옵션 선택"
-
-#: screens/Project/shared/ProjectForm.js:245
-msgid "Select an organization before editing the default execution environment."
-msgstr "기본 실행 환경을 편집하기 전에 조직을 선택합니다."
-
-#: screens/Job/Job.helptext.js:11
-#: screens/Template/shared/JobTemplate.helptext.js:12
-msgid "Select credentials for accessing the nodes this job will be ran against. You can only select one credential of each type. For machine credentials (SSH), checking \"Prompt on launch\" without selecting credentials will require you to select a machine credential at run time. If you select credentials and check \"Prompt on launch\", the selected credential(s) become the defaults that can be updated at run time."
-msgstr "이 작업이 실행될 노드에 액세스하기 위한 인증 정보를 선택합니다. 각 유형에 대해 하나의 인증 정보만 선택할 수 있습니다. 시스템 인증 정보 (SSH)의 경우 인증 정보를 선택하지 않으면 런타임에 시스템 인증 정보를 선택해야합니다. 인증 정보를 선택하고 \"시작 시 프롬프트\"를 선택하면 선택한 인증 정보를 런타임에 업데이트할 수 있는 기본값이 됩니다."
-
-#: components/Schedule/shared/ScheduleFormFields.js:120
-#: components/Schedule/shared/ScheduleFormFields.js:179
-msgid "Select frequency"
-msgstr "빈도 선택"
-
-#: screens/Project/shared/Project.helptext.js:18
-msgid ""
-"Select from the list of directories found in\n"
-"the Project Base Path. Together the base path and the playbook\n"
-"directory provide the full path used to locate playbooks."
-msgstr "프로젝트 기본 경로에 있는 디렉터리 목록에서 선택합니다. 기본 경로와 플레이북 디렉토리는 플레이북을 찾는 데 사용되는 전체 경로를 제공합니다."
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:98
-msgid "Select items from list"
-msgstr "목록에서 항목 선택"
-
-#: screens/Dashboard/DashboardGraph.js:124
-#: screens/Dashboard/DashboardGraph.js:125
-msgid "Select job type"
-msgstr "작업 유형 선택"
-
-#: components/LaunchPrompt/steps/SurveyStep.js:179
-msgid "Select option(s)"
-msgstr "옵션 선택"
-
-#: screens/Dashboard/DashboardGraph.js:95
-#: screens/Dashboard/DashboardGraph.js:96
-#: screens/Dashboard/DashboardGraph.js:97
-msgid "Select period"
-msgstr "기간 선택"
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:117
-msgid "Select roles to apply"
-msgstr "적용할 역할 선택"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:128
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:129
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:130
-msgid "Select source path"
-msgstr "소스 경로 선택"
-
-#: screens/Dashboard/DashboardGraph.js:151
-#: screens/Dashboard/DashboardGraph.js:152
-msgid "Select status"
-msgstr "상태 선택"
-
-#: components/MultiSelect/TagMultiSelect.js:59
-msgid "Select tags"
-msgstr "태그 선택"
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:94
-msgid "Select the Execution Environment you want this command to run inside."
-msgstr "이 명령을 실행할 실행 환경을 선택합니다."
-
-#: screens/Inventory/shared/SmartInventoryForm.js:87
-msgid "Select the Instance Groups for this Inventory to run on."
-msgstr "이 인벤토리의 인스턴스 그룹을 선택하여 실행할 인스턴스를 선택합니다."
-
-#: screens/Job/Job.helptext.js:18
-#: screens/Template/shared/JobTemplate.helptext.js:20
-msgid "Select the Instance Groups for this Job Template to run on."
-msgstr "이 작업 템플릿의 인스턴스 그룹을 선택합니다."
-
-#: screens/Organization/shared/OrganizationForm.js:83
-msgid "Select the Instance Groups for this Organization to run on."
-msgstr "이 조직에서 실행할 인스턴스 그룹을 선택합니다."
-
-#: components/AdHocCommands/AdHocCredentialStep.js:104
-msgid "Select the credential you want to use when accessing the remote hosts to run the command. Choose the credential containing the username and SSH key or password that Ansible will need to log into the remote hosts."
-msgstr "원격 호스트에 액세스하여 명령을 실행할 때 사용할 인증 정보를 선택합니다. Ansible에서 원격 호스트에 로그인해야 하는 사용자 이름 및 SSH 키 또는 암호가 포함된 인증 정보를 선택합니다."
-
-#: components/Lookup/InventoryLookup.js:123
-msgid ""
-"Select the inventory containing the hosts\n"
-"you want this job to manage."
-msgstr "이 작업을 관리할 호스트가 포함된 인벤토리를 선택합니다."
-
-#: screens/Job/Job.helptext.js:6
-#: screens/Template/shared/JobTemplate.helptext.js:7
-msgid "Select the inventory containing the hosts you want this job to manage."
-msgstr "이 작업을 관리할 호스트가 포함된 인벤토리를 선택합니다."
-
-#: components/HostForm/HostForm.js:32
-#: components/HostForm/HostForm.js:51
-msgid "Select the inventory that this host will belong to."
-msgstr "이 호스트가 속할 인벤토리를 선택합니다."
-
-#: screens/Job/Job.helptext.js:10
-#: screens/Template/shared/JobTemplate.helptext.js:11
-msgid "Select the playbook to be executed by this job."
-msgstr "이 작업에서 실행할 플레이북을 선택합니다."
-
-#: screens/Instances/Shared/InstanceForm.js:43
-msgid "Select the port that Receptor will listen on for incoming connections. Default is 27199."
-msgstr "수신자(Receptor)가 들어오는 연결을 수신할 포트를 선택합니다.. 기본값은 27199입니다."
-
-#: screens/Template/shared/JobTemplate.helptext.js:8
-msgid "Select the project containing the playbook you want this job to execute."
-msgstr "이 작업을 실행할 플레이북을 포함하는 프로젝트를 선택합니다."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:79
-msgid "Select your Ansible Automation Platform subscription to use."
-msgstr "사용할 Ansible Automation Platform 서브스크립션을 선택합니다."
-
-#: components/Lookup/Lookup.js:186
-msgid "Select {0}"
-msgstr "{0} 선택"
-
-#: components/AddRole/AddResourceRole.js:212
-#: components/AddRole/AddResourceRole.js:224
-#: components/AddRole/AddResourceRole.js:242
-#: components/AddRole/SelectRoleStep.js:27
-#: components/CheckboxListItem/CheckboxListItem.js:44
-#: components/Lookup/InstanceGroupsLookup.js:87
-#: components/OptionsList/OptionsList.js:74
-#: components/Schedule/ScheduleList/ScheduleListItem.js:84
-#: components/TemplateList/TemplateListItem.js:140
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:107
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:125
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:26
-#: screens/Application/ApplicationsList/ApplicationListItem.js:31
-#: screens/Credential/CredentialList/CredentialListItem.js:56
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:31
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:65
-#: screens/Host/HostGroups/HostGroupItem.js:26
-#: screens/Host/HostList/HostListItem.js:48
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:59
-#: screens/InstanceGroup/Instances/InstanceListItem.js:122
-#: screens/Instances/InstanceList/InstanceListItem.js:126
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:42
-#: screens/Inventory/InventoryList/InventoryListItem.js:90
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:37
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:110
-#: screens/Organization/OrganizationList/OrganizationListItem.js:43
-#: screens/Organization/shared/OrganizationForm.js:113
-#: screens/Project/ProjectList/ProjectListItem.js:177
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:242
-#: screens/Team/TeamList/TeamListItem.js:31
-#: screens/Template/Survey/SurveyListItem.js:34
-#: screens/User/UserTokenList/UserTokenListItem.js:19
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:41
-msgid "Selected"
-msgstr "선택됨"
-
-#: components/LaunchPrompt/steps/CredentialsStep.js:142
-#: components/LaunchPrompt/steps/CredentialsStep.js:147
-#: components/Lookup/MultiCredentialsLookup.js:162
-#: components/Lookup/MultiCredentialsLookup.js:167
-msgid "Selected Category"
-msgstr "선택한 카테고리"
-
-#: components/Schedule/shared/ScheduleForm.js:446
-#: components/Schedule/shared/ScheduleForm.js:447
-msgid "Selected date range must have at least 1 schedule occurrence."
-msgstr "선택한 날짜 범위는 하나 이상의 일정이 포함되어 있어야 합니다."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:160
-msgid "Sender Email"
-msgstr "보낸 사람 이메일"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:96
-msgid "Sender e-mail"
-msgstr "보낸 사람 이메일"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:167
-#: components/Schedule/shared/FrequencyDetailSubform.js:138
-msgid "September"
-msgstr "9월"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:24
-msgid "Service account JSON file"
-msgstr "서비스 계정 JSON 파일"
-
-#: screens/Inventory/shared/InventorySourceForm.js:46
-#: screens/Project/shared/ProjectForm.js:112
-msgid "Set a value for this field"
-msgstr "이 필드의 값을 설정합니다."
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:70
-msgid "Set how many days of data should be retained."
-msgstr "유지해야 하는 데이터 일 수를 설정합니다."
-
-#: screens/Setting/SettingList.js:122
-msgid "Set preferences for data collection, logos, and logins"
-msgstr "데이터 수집, 로고 및 로그인에 대한 기본 설정"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:131
-msgid "Set source path to"
-msgstr "소스 경로 설정"
-
-#: components/InstanceToggle/InstanceToggle.js:48
-#: screens/Instances/Shared/InstanceForm.js:59
-msgid "Set the instance enabled or disabled. If disabled, jobs will not be assigned to this instance."
-msgstr "인스턴스 활성화 또는 비활성화를 설정합니다. 비활성화된 경우 작업이 이 인스턴스에 할당되지 않습니다."
-
-#: screens/Application/shared/Application.helptext.js:5
-msgid "Set to Public or Confidential depending on how secure the client device is."
-msgstr "클라이언트 장치의 보안에 따라 공개 또는 기밀로 설정합니다."
-
-#: components/Search/AdvancedSearch.js:149
-msgid "Set type"
-msgstr "설정 유형"
-
-#: components/Search/AdvancedSearch.js:239
-msgid "Set type disabled for related search field fuzzy searches"
-msgstr "관련 검색 필드 퍼지 검색에 대해 설정 유형 비활성화"
-
-#: components/Search/AdvancedSearch.js:140
-msgid "Set type select"
-msgstr "설정 유형 선택"
-
-#: components/Search/AdvancedSearch.js:143
-msgid "Set type typeahead"
-msgstr "설정 유형 자동 완성"
-
-#: components/Workflow/WorkflowTools.js:154
-msgid "Set zoom to 100% and center graph"
-msgstr "zoom을 100% 및 센터 그래프로 설정"
-
-#: screens/Instances/Shared/InstanceForm.js:35
-msgid "Sets the current life cycle stage of this instance. Default is \"installed.\""
-msgstr "이 인스턴스의 현재 라이프사이클 단계를 설정합니다. 기본값은 \"설치됨\"입니다."
-
-#: screens/Instances/Shared/InstanceForm.js:51
-msgid "Sets the role that this instance will play within mesh topology. Default is \"execution.\""
-msgstr "이 인스턴스가 메시 토폴로지 내에서 수행할 역할을 설정합니다. 기본값은 \"실행\"입니다."
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:46
-msgid "Setting category"
-msgstr "카테고리 설정"
-
-#: screens/Setting/shared/RevertButton.js:46
-msgid "Setting matches factory default."
-msgstr "설정이 기본 설정과 일치합니다."
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:49
-msgid "Setting name"
-msgstr "설정 이름"
-
-#: routeConfig.js:159
-#: routeConfig.js:163
-#: screens/ActivityStream/ActivityStream.js:220
-#: screens/ActivityStream/ActivityStream.js:222
-#: screens/Setting/Settings.js:43
-msgid "Settings"
-msgstr "설정"
-
-#: components/FormField/PasswordInput.js:35
-msgid "Show"
-msgstr "표시"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:182
-#: components/PromptDetail/PromptDetail.js:361
-#: components/PromptDetail/PromptJobTemplateDetail.js:156
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:488
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:283
-#: screens/Template/shared/JobTemplateForm.js:497
-msgid "Show Changes"
-msgstr "변경 사항 표시"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:177
-#: components/AdHocCommands/AdHocDetailsStep.js:178
-msgid "Show changes"
-msgstr "변경 사항 표시"
-
-#: components/LaunchPrompt/LaunchPrompt.js:135
-#: components/Schedule/shared/SchedulePromptableFields.js:102
-msgid "Show description"
-msgstr "설명 표시"
-
-#: components/ChipGroup/ChipGroup.js:12
-msgid "Show less"
-msgstr "더 적은 수를 표시"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:126
-msgid "Show only root groups"
-msgstr "root 그룹만 표시"
-
-#: screens/Login/Login.js:262
-msgid "Sign in with Azure AD"
-msgstr "Azure AD로 로그인"
-
-#: screens/Login/Login.js:276
-msgid "Sign in with GitHub"
-msgstr "GitHub로 로그인"
-
-#: screens/Login/Login.js:318
-msgid "Sign in with GitHub Enterprise"
-msgstr "GitHub Enterprise로 로그인"
-
-#: screens/Login/Login.js:333
-msgid "Sign in with GitHub Enterprise Organizations"
-msgstr "GitHub Enterprise 조직으로 로그인"
-
-#: screens/Login/Login.js:349
-msgid "Sign in with GitHub Enterprise Teams"
-msgstr "GitHub Enterprise 팀으로 로그인"
-
-#: screens/Login/Login.js:290
-msgid "Sign in with GitHub Organizations"
-msgstr "GitHub 조직으로 로그인"
-
-#: screens/Login/Login.js:304
-msgid "Sign in with GitHub Teams"
-msgstr "GitHub 팀으로 로그인"
-
-#: screens/Login/Login.js:364
-msgid "Sign in with Google"
-msgstr "Google로 로그인"
-
-#: screens/Login/Login.js:378
-msgid "Sign in with OIDC"
-msgstr "OIDC로 로그인"
-
-#: screens/Login/Login.js:397
-msgid "Sign in with SAML"
-msgstr "SAML으로 로그인"
-
-#: screens/Login/Login.js:396
-msgid "Sign in with SAML {samlIDP}"
-msgstr "SAML {samlIDP}으로 로그인"
-
-#: components/Search/Search.js:145
-#: components/Search/Search.js:146
-msgid "Simple key select"
-msgstr "간단한 키 선택"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:106
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:107
-#: components/PromptDetail/PromptDetail.js:295
-#: components/PromptDetail/PromptJobTemplateDetail.js:267
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:595
-#: screens/Job/JobDetail/JobDetail.js:500
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:475
-#: screens/Template/shared/JobTemplateForm.js:533
-#: screens/Template/shared/WorkflowJobTemplateForm.js:230
-msgid "Skip Tags"
-msgstr "태그 건너뛰기"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:93
-#: components/Schedule/shared/FrequencyDetailSubform.js:223
-msgid "Skip every"
-msgstr "모두 건너뛰기"
-
-#: screens/Job/Job.helptext.js:20
-#: screens/Template/shared/JobTemplate.helptext.js:22
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:22
-msgid "Skip tags are useful when you have a large playbook, and you want to skip specific parts of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags."
-msgstr "건너뛰기 태그는 대용량 플레이북이 있고 플레이 또는 작업의 특정 부분을 건너뛰려는 경우 유용합니다. 쉼표를 사용하여 여러 태그를 구분합니다. 태그 사용에 대한 자세한 내용은 문서를 참조하십시오."
-
-#: screens/Job/JobOutput/shared/HostStatusBar.js:39
-msgid "Skipped"
-msgstr "건너뜀"
-
-#: components/StatusLabel/StatusLabel.js:50
-msgid "Skipped'"
-msgstr "건너뜀'"
-
-#: components/NotificationList/NotificationList.js:200
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:141
-msgid "Slack"
-msgstr "Slack"
-
-#: screens/Host/HostList/SmartInventoryButton.js:39
-#: screens/Host/HostList/SmartInventoryButton.js:48
-#: screens/Host/HostList/SmartInventoryButton.js:52
-#: screens/Inventory/InventoryList/InventoryList.js:187
-#: screens/Inventory/InventoryList/InventoryListItem.js:117
-msgid "Smart Inventory"
-msgstr "스마트 인벤토리"
-
-#: screens/Inventory/SmartInventory.js:94
-msgid "Smart Inventory not found."
-msgstr "스마트 인벤토리를 찾을 수 없습니다."
-
-#: components/Lookup/HostFilterLookup.js:345
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:116
-msgid "Smart host filter"
-msgstr "스마트 호스트 필터"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:105
-msgid "Smart inventory"
-msgstr "스마트 인벤토리"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:32
-#: components/LaunchPrompt/steps/PreviewStep.js:58
-msgid "Some of the previous step(s) have errors"
-msgstr "이전 단계 중 일부에는 오류가 있습니다."
-
-#: screens/Host/HostList/SmartInventoryButton.js:17
-msgid "Some search modifiers like not__ and __search are not supported in Smart Inventory host filters. Remove these to create a new Smart Inventory with this filter."
-msgstr "not__ 및 __search와 같은 일부 검색 수정자는 스마트 인벤토리 호스트 필터에서 지원되지 않습니다. 이 필터를 사용하여 새 스마트 인벤토리를 생성하려면 제거합니다."
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.js:39
-msgid "Something went wrong with the request to test this credential and metadata."
-msgstr "이 인증 정보 및 메타데이터 테스트 요청에 문제가 발생했습니다."
-
-#: components/ContentError/ContentError.js:37
-msgid "Something went wrong..."
-msgstr "문제가 발생했습니다.."
-
-#: components/Sort/Sort.js:139
-msgid "Sort"
-msgstr "분류"
-
-#: components/JobList/JobListItem.js:169
-#: components/PromptDetail/PromptInventorySourceDetail.js:84
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:199
-#: screens/Inventory/shared/InventorySourceForm.js:130
-#: screens/Job/JobDetail/JobDetail.js:172
-#: screens/Job/JobDetail/JobDetail.js:294
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:93
-msgid "Source"
-msgstr "소스"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:44
-#: components/PromptDetail/PromptDetail.js:250
-#: components/PromptDetail/PromptJobTemplateDetail.js:147
-#: components/PromptDetail/PromptProjectDetail.js:106
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:89
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:467
-#: screens/Job/JobDetail/JobDetail.js:307
-#: screens/Project/ProjectDetail/ProjectDetail.js:230
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:248
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:133
-#: screens/Template/shared/JobTemplateForm.js:335
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:245
-msgid "Source Control Branch"
-msgstr "소스 제어 분기"
-
-#: screens/Project/shared/ProjectSubForms/GitSubForm.js:29
-msgid "Source Control Branch/Tag/Commit"
-msgstr "소스 제어 분기/태그/커밋"
-
-#: components/PromptDetail/PromptProjectDetail.js:117
-#: screens/Project/ProjectDetail/ProjectDetail.js:256
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:56
-msgid "Source Control Credential"
-msgstr "소스 제어 인증 정보"
-
-#: components/PromptDetail/PromptProjectDetail.js:111
-#: screens/Project/ProjectDetail/ProjectDetail.js:235
-#: screens/Project/shared/ProjectSubForms/GitSubForm.js:32
-msgid "Source Control Refspec"
-msgstr "소스 제어 참조"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:195
-msgid "Source Control Revision"
-msgstr "소스 제어 버전"
-
-#: components/PromptDetail/PromptProjectDetail.js:96
-#: screens/Job/JobDetail/JobDetail.js:273
-#: screens/Project/ProjectDetail/ProjectDetail.js:191
-#: screens/Project/shared/ProjectForm.js:259
-msgid "Source Control Type"
-msgstr "소스 제어 유형"
-
-#: components/Lookup/ProjectLookup.js:143
-#: components/PromptDetail/PromptProjectDetail.js:101
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:96
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:165
-#: screens/Project/ProjectDetail/ProjectDetail.js:225
-#: screens/Project/ProjectList/ProjectList.js:205
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:16
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:104
-msgid "Source Control URL"
-msgstr "소스 제어 URL"
-
-#: components/JobList/JobList.js:211
-#: components/JobList/JobListItem.js:42
-#: components/Schedule/ScheduleList/ScheduleListItem.js:38
-#: screens/Job/JobDetail/JobDetail.js:65
-msgid "Source Control Update"
-msgstr "소스 제어 업데이트"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:340
-msgid "Source Phone Number"
-msgstr "소스 전화 번호"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:176
-msgid "Source Variables"
-msgstr "소스 변수"
-
-#: components/JobList/JobListItem.js:213
-#: screens/Job/JobDetail/JobDetail.js:257
-msgid "Source Workflow Job"
-msgstr "소스 워크플로 작업"
-
-#: screens/Template/shared/WorkflowJobTemplateForm.js:177
-msgid "Source control branch"
-msgstr "소스 제어 분기"
-
-#: screens/Inventory/shared/InventorySourceForm.js:152
-msgid "Source details"
-msgstr "소스 세부 정보"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:403
-msgid "Source phone number"
-msgstr "소스 전화 번호"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:269
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:21
-msgid "Source variables"
-msgstr "소스 변수"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:97
-msgid "Sourced from a project"
-msgstr "프로젝트에서 소싱"
-
-#: screens/Inventory/Inventories.js:84
-#: screens/Inventory/Inventory.js:68
-msgid "Sources"
-msgstr "소스"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:30
-msgid ""
-"Specify HTTP Headers in JSON format. Refer to\n"
-"the Ansible Controller documentation for example syntax."
-msgstr "HTTP 헤더를 JSON 형식으로 지정합니다. 예를 들어 Ansible Controller 설명서를 참조하십시오."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:24
-msgid ""
-"Specify a notification color. Acceptable colors are hex\n"
-"color code (example: #3af or #789abc)."
-msgstr "알림 색상을 지정합니다. 허용되는 색상은 16진수 색상 코드 (예: #3af 또는 #789abc)입니다."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:26
-msgid "Specify the conditions under which this node should be executed"
-msgstr "이 노드를 실행해야 하는 조건을 지정합니다."
-
-#: screens/Job/JobOutput/HostEventModal.js:173
-msgid "Standard Error"
-msgstr "표준 오류"
-
-#: screens/Job/JobOutput/HostEventModal.js:174
-msgid "Standard error tab"
-msgstr "표준 오류 탭"
-
-#: components/NotificationList/NotificationListItem.js:57
-#: components/NotificationList/NotificationListItem.js:58
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:47
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:53
-msgid "Start"
-msgstr "시작"
-
-#: components/JobList/JobList.js:247
-#: components/JobList/JobListItem.js:99
-msgid "Start Time"
-msgstr "시작 시간"
-
-#: components/Schedule/shared/DateTimePicker.js:51
-msgid "Start date"
-msgstr "시작일"
-
-#: components/Schedule/shared/ScheduleFormFields.js:87
-msgid "Start date/time"
-msgstr "시작일/시간"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:465
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:105
-msgid "Start message"
-msgstr "시작 메시지"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:474
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:114
-msgid "Start message body"
-msgstr "메시지 본문 시작"
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:33
-msgid "Start sync process"
-msgstr "동기화 프로세스 시작"
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:37
-msgid "Start sync source"
-msgstr "동기화 소스 시작"
-
-#: components/Schedule/shared/DateTimePicker.js:61
-msgid "Start time"
-msgstr "시작 시간"
-
-#: screens/Job/JobDetail/JobDetail.js:220
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:165
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:63
-msgid "Started"
-msgstr "시작됨"
-
-#: components/JobList/JobList.js:224
-#: components/JobList/JobList.js:245
-#: components/JobList/JobListItem.js:95
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:206
-#: screens/InstanceGroup/Instances/InstanceList.js:267
-#: screens/InstanceGroup/Instances/InstanceListItem.js:129
-#: screens/Instances/InstanceDetail/InstanceDetail.js:196
-#: screens/Instances/InstanceList/InstanceList.js:202
-#: screens/Instances/InstanceList/InstanceListItem.js:134
-#: screens/Instances/InstancePeers/InstancePeerList.js:97
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:43
-#: screens/Inventory/InventoryList/InventoryListItem.js:101
-#: screens/Inventory/InventorySources/InventorySourceList.js:212
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:87
-#: screens/Job/JobDetail/JobDetail.js:210
-#: screens/Job/JobOutput/HostEventModal.js:118
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:115
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:179
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:117
-#: screens/Project/ProjectList/ProjectList.js:222
-#: screens/Project/ProjectList/ProjectListItem.js:197
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:61
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:162
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:166
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:66
-msgid "Status"
-msgstr "상태"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:91
-msgid "Stdout"
-msgstr "Stdout"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:37
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:49
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:212
-msgid "Submit"
-msgstr "제출"
-
-#: screens/Project/shared/Project.helptext.js:118
-msgid ""
-"Submodules will track the latest commit on\n"
-"their master branch (or other branch specified in\n"
-".gitmodules). If no, submodules will be kept at\n"
-"the revision specified by the main project.\n"
-"This is equivalent to specifying the --remote\n"
-"flag to git submodule update."
-msgstr "하위 모듈은 마스터 분기 (또는 .gitmodules에 지정된 다른 분기)의 최신 커밋을 추적합니다. 그러지 않으면 하위 모듈이 기본 프로젝트에서 지정된 개정 버전으로 유지됩니다. 이는 git submodule update에 --remote 플래그를 지정하는 것과 동일합니다."
-
-#: screens/Setting/SettingList.js:132
-#: screens/Setting/Settings.js:112
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:136
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:195
-msgid "Subscription"
-msgstr "서브스크립션"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:41
-msgid "Subscription Details"
-msgstr "서브스크립션 세부 정보"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:194
-msgid "Subscription Management"
-msgstr "서브스크립션 관리"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:82
-msgid "Subscription manifest"
-msgstr "서브스크립션 매니페스트"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:84
-msgid "Subscription selection modal"
-msgstr "서브스크립션 선택 모달"
-
-#: screens/Setting/SettingList.js:137
-msgid "Subscription settings"
-msgstr "서브스크립션 설정"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:131
-msgid "Subscription type"
-msgstr "서브스크립션 유형"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:142
-msgid "Subscriptions table"
-msgstr "서브스크립션 테이블"
-
-#: components/Lookup/ProjectLookup.js:137
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:90
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:159
-#: screens/Job/JobDetail/JobDetail.js:76
-#: screens/Project/ProjectList/ProjectList.js:199
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:98
-msgid "Subversion"
-msgstr "Subversion"
-
-#: components/NotificationList/NotificationListItem.js:71
-#: components/NotificationList/NotificationListItem.js:72
-#: components/StatusLabel/StatusLabel.js:41
-msgid "Success"
-msgstr "성공"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:483
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:123
-msgid "Success message"
-msgstr "성공 메시지"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:492
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:132
-msgid "Success message body"
-msgstr "성공 메시지 본문"
-
-#: components/JobList/JobList.js:231
-#: components/StatusLabel/StatusLabel.js:43
-#: components/Workflow/WorkflowNodeHelp.js:102
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:96
-#: screens/Dashboard/shared/ChartTooltip.js:59
-msgid "Successful"
-msgstr "성공"
-
-#: screens/Dashboard/DashboardGraph.js:166
-msgid "Successful jobs"
-msgstr "성공적인 작업"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:28
-msgid "Successfully Approved"
-msgstr "성공적으로 승인됨"
-
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:25
-msgid "Successfully Denied"
-msgstr "성공적으로 거부됨"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:201
-#: screens/Project/ProjectList/ProjectListItem.js:97
-msgid "Successfully copied to clipboard!"
-msgstr "클립보드에 성공적으로 복사되었습니다."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:255
-msgid "Sun"
-msgstr "일요일"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:83
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:182
-#: components/Schedule/shared/FrequencyDetailSubform.js:260
-#: components/Schedule/shared/FrequencyDetailSubform.js:428
-msgid "Sunday"
-msgstr "이벤트"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:26
-#: screens/Template/Template.js:159
-#: screens/Template/Templates.js:48
-#: screens/Template/WorkflowJobTemplate.js:145
-msgid "Survey"
-msgstr "설문 조사"
-
-#: screens/Template/Survey/SurveyToolbar.js:105
-msgid "Survey Disabled"
-msgstr "설문 조사 비활성화"
-
-#: screens/Template/Survey/SurveyToolbar.js:104
-msgid "Survey Enabled"
-msgstr "설문 조사 활성화"
-
-#: screens/Template/Survey/SurveyReorderModal.js:191
-msgid "Survey Question Order"
-msgstr "설문 조사 질문 순서"
-
-#: screens/Template/Survey/SurveyToolbar.js:102
-msgid "Survey Toggle"
-msgstr "설문조사 토글"
-
-#: screens/Template/Survey/SurveyReorderModal.js:192
-msgid "Survey preview modal"
-msgstr "설문 조사 프리뷰 모달"
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:120
-#: screens/Inventory/shared/InventorySourceSyncButton.js:41
-#: screens/Project/shared/ProjectSyncButton.js:41
-#: screens/Project/shared/ProjectSyncButton.js:53
-msgid "Sync"
-msgstr "동기화"
-
-#: screens/Project/ProjectList/ProjectListItem.js:238
-#: screens/Project/shared/ProjectSyncButton.js:37
-#: screens/Project/shared/ProjectSyncButton.js:48
-msgid "Sync Project"
-msgstr "동기화 프로젝트"
-
-#: screens/Inventory/InventoryList/InventoryList.js:219
-msgid "Sync Status"
-msgstr "동기화 상태"
-
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:19
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:29
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:32
-msgid "Sync all"
-msgstr "모두 동기화"
-
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:25
-msgid "Sync all sources"
-msgstr "모든 소스 동기화"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:236
-msgid "Sync error"
-msgstr "동기화 오류"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:213
-#: screens/Project/ProjectList/ProjectListItem.js:109
-msgid "Sync for revision"
-msgstr "버전의 동기화"
-
-#: screens/Project/ProjectList/ProjectListItem.js:122
-msgid "Syncing"
-msgstr "동기화"
-
-#: screens/Setting/SettingList.js:102
-#: screens/User/UserRoles/UserRolesListItem.js:18
-msgid "System"
-msgstr "시스템"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:128
-#: screens/User/UserDetail/UserDetail.js:47
-#: screens/User/UserList/UserListItem.js:19
-#: screens/User/UserRoles/UserRolesList.js:127
-#: screens/User/shared/UserForm.js:41
-msgid "System Administrator"
-msgstr "시스템 관리자"
-
-#: screens/User/UserDetail/UserDetail.js:49
-#: screens/User/UserList/UserListItem.js:21
-#: screens/User/shared/UserForm.js:35
-msgid "System Auditor"
-msgstr "시스템 감사"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:128
-msgid "System Warning"
-msgstr "시스템 경고"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:131
-#: screens/User/UserRoles/UserRolesList.js:130
-msgid "System administrators have unrestricted access to all resources."
-msgstr "시스템 관리자는 모든 리소스에 무제한 액세스할 수 있습니다."
-
-#: screens/Setting/Settings.js:115
-msgid "TACACS+"
-msgstr "TACACS+"
-
-#: screens/Setting/SettingList.js:81
-msgid "TACACS+ settings"
-msgstr "TACACS + 설정"
-
-#: screens/Dashboard/Dashboard.js:117
-#: screens/Job/JobOutput/HostEventModal.js:94
-msgid "Tabs"
-msgstr "탭"
-
-#: screens/Job/Job.helptext.js:19
-#: screens/Template/shared/JobTemplate.helptext.js:21
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:21
-msgid "Tags are useful when you have a large playbook, and you want to run a specific part of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags."
-msgstr "태그는 대용량 플레이북이 있고 플레이 또는 작업의 특정 부분을 실행하려는 경우 유용합니다. 쉼표를 사용하여 여러 태그를 구분합니다. 태그 사용에 대한 자세한 내용은 문서를 참조하십시오."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:203
-msgid "Tags for the Annotation"
-msgstr "주석 태그"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:179
-msgid "Tags for the annotation (optional)"
-msgstr "주석 태그(선택 사항)"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:248
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:298
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:366
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:252
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:329
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:453
-msgid "Target URL"
-msgstr "대상 URL"
-
-#: screens/Job/JobOutput/HostEventModal.js:123
-msgid "Task"
-msgstr "작업"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:90
-msgid "Task Count"
-msgstr "작업 수"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:129
-msgid "Task Started"
-msgstr "호스트 시작됨"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:91
-msgid "Tasks"
-msgstr "작업"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:13
-msgid "Team"
-msgstr "팀"
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:85
-#: screens/Team/TeamRoles/TeamRolesList.js:144
-msgid "Team Roles"
-msgstr "팀 역할"
-
-#: screens/Team/Team.js:75
-msgid "Team not found."
-msgstr "팀을 찾을 수 없음"
-
-#: components/AddRole/AddResourceRole.js:188
-#: components/AddRole/AddResourceRole.js:189
-#: routeConfig.js:106
-#: screens/ActivityStream/ActivityStream.js:187
-#: screens/Organization/Organization.js:125
-#: screens/Organization/OrganizationList/OrganizationList.js:145
-#: screens/Organization/OrganizationList/OrganizationListItem.js:66
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:64
-#: screens/Organization/Organizations.js:32
-#: screens/Team/TeamList/TeamList.js:112
-#: screens/Team/TeamList/TeamList.js:166
-#: screens/Team/Teams.js:15
-#: screens/Team/Teams.js:25
-#: screens/User/User.js:70
-#: screens/User/UserTeams/UserTeamList.js:175
-#: screens/User/UserTeams/UserTeamList.js:246
-#: screens/User/Users.js:32
-#: util/getRelatedResourceDeleteDetails.js:174
-msgid "Teams"
-msgstr "팀"
-
-#: screens/Setting/Jobs/JobsEdit/JobsEdit.js:130
-msgid "Template"
-msgstr "템플릿"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:115
-#: components/TemplateList/TemplateList.js:133
-msgid "Template copied successfully"
-msgstr "템플릿이 성공적으로 복사됨"
-
-#: screens/Template/Template.js:175
-#: screens/Template/WorkflowJobTemplate.js:175
-msgid "Template not found."
-msgstr "템플릿을 찾을 수 없습니다."
-
-#: components/TemplateList/TemplateList.js:200
-#: components/TemplateList/TemplateList.js:263
-#: routeConfig.js:65
-#: screens/ActivityStream/ActivityStream.js:164
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:70
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:83
-#: screens/Template/Templates.js:17
-#: util/getRelatedResourceDeleteDetails.js:218
-#: util/getRelatedResourceDeleteDetails.js:275
-msgid "Templates"
-msgstr "템플릿"
-
-#: screens/Credential/shared/CredentialForm.js:331
-#: screens/Credential/shared/CredentialForm.js:337
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:80
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:426
-msgid "Test"
-msgstr "테스트"
-
-#: screens/Credential/shared/ExternalTestModal.js:77
-msgid "Test External Credential"
-msgstr "외부 인증 정보 테스트"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:128
-msgid "Test Notification"
-msgstr "테스트 알림"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:125
-msgid "Test notification"
-msgstr "테스트 알림"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.js:44
-msgid "Test passed"
-msgstr "통과된 테스트"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:80
-#: screens/Template/Survey/SurveyReorderModal.js:181
-msgid "Text"
-msgstr "텍스트"
-
-#: screens/Template/Survey/SurveyReorderModal.js:135
-msgid "Text Area"
-msgstr "텍스트 영역"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:81
-msgid "Textarea"
-msgstr "텍스트 영역"
-
-#: components/Lookup/Lookup.js:63
-msgid "That value was not found. Please enter or select a valid value."
-msgstr "이 값을 찾을 수 없습니다. 유효한 값을 입력하거나 선택하십시오."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:398
-msgid "The"
-msgstr "The"
-
-#: screens/Application/shared/Application.helptext.js:4
-msgid "The Grant type the user must use to acquire tokens for this application"
-msgstr "사용자가 이 애플리케이션의 토큰을 얻는 데 사용해야 하는 Grant 유형"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:128
-msgid "The Instance Groups for this Organization to run on."
-msgstr "이 조직에서 실행할 인스턴스 그룹입니다."
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:219
-msgid "The Instance Groups to which this instance belongs."
-msgstr "이 인스턴스가 속하는 인스턴스 그룹입니다."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:6
-msgid ""
-"The amount of time (in seconds) before the email\n"
-"notification stops trying to reach the host and times out. Ranges\n"
-"from 1 to 120 seconds."
-msgstr "이메일 알림에서 호스트에 도달하려는 시도를 중지하고 시간 초과되기 전 까지의 시간(초)입니다. 범위는 1초에서 120초 사이입니다."
-
-#: screens/Job/Job.helptext.js:17
-#: screens/Template/shared/JobTemplate.helptext.js:18
-msgid "The amount of time (in seconds) to run before the job is canceled. Defaults to 0 for no job timeout."
-msgstr "작업이 취소되기 전에 실행할 시간(초)입니다. 작업 제한 시간이 없는 경우 기본값은 0입니다."
-
-#: screens/User/shared/User.helptext.js:4
-msgid "The application that this token belongs to, or leave this field empty to create a Personal Access Token."
-msgstr "이 토큰이 속한 애플리케이션이나 이 필드를 비워 개인 액세스 토큰을 만듭니다."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:9
-msgid ""
-"The base URL of the Grafana server - the\n"
-"/api/annotations endpoint will be added automatically to the base\n"
-"Grafana URL."
-msgstr "Grafana 서버의 기본 URL - /api/annotations 엔드포인트가 기본 Grafana URL에 자동으로 추가됩니다."
-
-#: screens/Template/shared/JobTemplate.helptext.js:9
-msgid "The container image to be used for execution."
-msgstr "실행에 사용할 컨테이너 이미지입니다."
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:109
-msgid ""
-"The execution environment that will be used for jobs\n"
-"inside of this organization. This will be used a fallback when\n"
-"an execution environment has not been explicitly assigned at the\n"
-"project, job template or workflow level."
-msgstr "이 조직 내의 작업에 사용할 실행 환경입니다. 실행 환경이 프로젝트, 작업 템플릿 또는 워크플로 수준에서 명시적으로 할당되지 않은 경우 폴백으로 사용됩니다."
-
-#: screens/Organization/shared/OrganizationForm.js:93
-msgid "The execution environment that will be used for jobs inside of this organization. This will be used a fallback when an execution environment has not been explicitly assigned at the project, job template or workflow level."
-msgstr "이 조직 내의 작업에 사용할 실행 환경입니다. 실행 환경이 프로젝트, 작업 템플릿 또는 워크플로 수준에서 명시적으로 할당되지 않은 경우 폴백으로 사용됩니다."
-
-#: screens/Project/shared/Project.helptext.js:5
-msgid "The execution environment that will be used for jobs that use this project. This will be used as fallback when an execution environment has not been explicitly assigned at the job template or workflow level."
-msgstr "이 프로젝트를 사용하는 작업에 사용할 실행 환경입니다. 작업 템플릿 또는 워크플로 수준에서 실행 환경이 명시적으로 할당되지 않은 경우 폴백으로 사용됩니다."
-
-#: screens/Job/Job.helptext.js:9
-#: screens/Template/shared/JobTemplate.helptext.js:10
-msgid "The execution environment that will be used when launching this job template. The resolved execution environment can be overridden by explicitly assigning a different one to this job template."
-msgstr "이 작업 템플릿을 시작할 때 사용할 실행 환경입니다. 해결된 실행 환경은 이 작업 템플릿에 다른 실행 환경을 명시적으로 할당하여 재정의할 수 있습니다."
-
-#: screens/Project/shared/Project.helptext.js:93
-msgid ""
-"The first fetches all references. The second\n"
-"fetches the Github pull request number 62, in this example\n"
-"the branch needs to be \"pull/62/head\"."
-msgstr "모든 참조에 대한 첫 번째 참조를 가져옵니다. Github pull 요청 번호 62에 대한 두 번째 참조를 가져옵니다. 이 예제에서 분기는 \"pull/62/head\"여야 합니다."
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:7
-msgid "The full image location, including the container registry, image name, and version tag."
-msgstr "컨테이너 레지스트리, 이미지 이름, 버전 태그를 포함한 전체 이미지 위치입니다."
-
-#: screens/Inventory/shared/Inventory.helptext.js:191
-msgid ""
-"The inventory file\n"
-"to be synced by this source. You can select from\n"
-"the dropdown or enter a file within the input."
-msgstr "이 소스에서 동기화할 인벤토리 파일입니다. 드롭다운에서 선택하거나 입력 란에 파일을 입력할 수 있습니다."
-
-#: screens/Host/HostDetail/HostDetail.js:79
-msgid "The inventory that this host belongs to."
-msgstr "이 호스트가 속할 인벤토리입니다."
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:141
-msgid "The last {dayOfWeek}"
-msgstr "마지막 {dayOfWeek}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:200
-msgid "The last {weekday} of {month}"
-msgstr "마지막 {weekday} / {month}"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:100
-msgid ""
-"The maximum number of hosts allowed to be managed by\n"
-"this organization. Value defaults to 0 which means no limit.\n"
-"Refer to the Ansible documentation for more details."
-msgstr "이 조직에서 관리할 수 있는 최대 호스트 수입니다. 기본값은 0이며 이는 제한이 없음을 의미합니다. 자세한 내용은 Ansible 설명서를 참조하십시오."
-
-#: screens/Organization/shared/OrganizationForm.js:72
-msgid ""
-"The maximum number of hosts allowed to be managed by this organization.\n"
-"Value defaults to 0 which means no limit. Refer to the Ansible\n"
-"documentation for more details."
-msgstr "이 조직에서 관리할 수 있는 최대 호스트 수입니다. 기본값은 0이며 이는 제한이 없음을 의미합니다. 자세한 내용은 Ansible 설명서를 참조하십시오."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:26
-msgid ""
-"The number associated with the \"Messaging\n"
-"Service\" in Twilio with the format +18005550199."
-msgstr "+18005550199 형식으로 Twilio의 \"메시징 서비스(Messaging Service)\"와 연결된 번호입니다."
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:69
-msgid "The number of hosts you have automated against is below your subscription count."
-msgstr "자동화된 호스트 수는 서브스크립션 수 보다 적습니다."
-
-#: screens/Job/Job.helptext.js:25
-#: screens/Template/shared/JobTemplate.helptext.js:48
-msgid "The number of parallel or simultaneous processes to use while executing the playbook. An empty value, or a value less than 1 will use the Ansible default which is usually 5. The default number of forks can be overwritten with a change to"
-msgstr "플레이북을 실행하는 동안 사용할 병렬 또는 동시 프로세스 수입니다. 비어 있는 값 또는 1보다 작은 값은 Ansible 기본값(일반적으로 5)을 사용합니다. 기본 포크 수는 다음과 같이 변경합니다."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:164
-msgid "The number of parallel or simultaneous processes to use while executing the playbook. Inputting no value will use the default value from the ansible configuration file. You can find more information"
-msgstr "플레이북을 실행하는 동안 사용할 병렬 또는 동시 프로세스 수입니다. 값을 입력하지 않으면 ansible 구성 파일에서 기본값을 사용합니다. 자세한 정보를 참조하십시오."
-
-#: components/ContentError/ContentError.js:41
-#: screens/Job/Job.js:161
-msgid "The page you requested could not be found."
-msgstr "요청하신 페이지를 찾을 수 없습니다."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:144
-msgid "The pattern used to target hosts in the inventory. Leaving the field blank, all, and * will all target all hosts in the inventory. You can find more information about Ansible's host patterns"
-msgstr "인벤토리의 호스트를 대상으로 지정하는 데 사용되는 패턴입니다. 필드를 비워두면 all 및 *는 인벤토리의 모든 호스트를 대상으로 합니다. Ansible의 호스트 패턴에 대한 자세한 정보를 찾을 수 있습니다."
-
-#: screens/Job/Job.helptext.js:7
-msgid "The project containing the playbook this job will execute."
-msgstr "이 작업이 실행될 플레이북을 포함하는 프로젝트입니다."
-
-#: screens/Job/Job.helptext.js:8
-msgid "The project from which this inventory update is sourced."
-msgstr "이 인벤토리 업데이트가 제공되는 프로젝트입니다."
-
-#: screens/Project/ProjectList/ProjectListItem.js:120
-msgid "The project is currently syncing and the revision will be available after the sync is complete."
-msgstr "현재 프로젝트가 동기화되고 있으며 동기화가 완료된 후 리버전을 사용할 수 있습니다."
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:211
-#: screens/Project/ProjectList/ProjectListItem.js:107
-msgid "The project must be synced before a revision is available."
-msgstr "버전을 사용할 수 있으려면 프로젝트를 동기화해야 합니다."
-
-#: screens/Project/ProjectList/ProjectListItem.js:130
-msgid "The project revision is currently out of date. Please refresh to fetch the most recent revision."
-msgstr "현재 프로젝트 버전이 최신 버전이 아닙니다. 최신 버전을 가져오려면 새로 고침하십시오."
-
-#: components/Workflow/WorkflowNodeHelp.js:138
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:131
-msgid "The resource associated with this node has been deleted."
-msgstr "이 노드와 연결된 리소스가 삭제되었습니다."
-
-#: screens/Job/JobOutput/EmptyOutput.js:31
-msgid "The search filter did not produce any results…"
-msgstr "검색 필터에서 결과를 생성하지 않았습니다."
-
-#: screens/Template/Survey/SurveyQuestionForm.js:180
-msgid ""
-"The suggested format for variable names is lowercase and\n"
-"underscore-separated (for example, foo_bar, user_id, host_name,\n"
-"etc.). Variable names with spaces are not allowed."
-msgstr "변수 이름에 대한 권장되는 형식은 소문자 및 밑줄로 구분되어 있습니다(예: foo_bar, user_id, host_name 등). 공백이 있는 변수 이름은 허용되지 않습니다."
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:50
-msgid ""
-"There are no available playbook directories in {project_base_dir}.\n"
-"Either that directory is empty, or all of the contents are already\n"
-"assigned to other projects. Create a new directory there and make\n"
-"sure the playbook files can be read by the \"awx\" system user,\n"
-"or have {brandName} directly retrieve your playbooks from\n"
-"source control using the Source Control Type option above."
-msgstr "{project_base_dir}에 사용 가능한 플레이북 디렉터리가 없습니다. 해당 디렉터리가 비어 있거나 모든 콘텐츠가 이미 다른 프로젝트에 할당되어 있습니다. 새 디렉토리를 만들고 \"awx\" 시스템 사용자가 플레이북 파일을 읽을 수 있는지 확인하거나 {brandName} 위의 소스 제어 유형 옵션을 사용하여 소스제어에서 직접 플레이북을 검색하도록 합니다."
-
-#: screens/Template/Survey/MultipleChoiceField.js:34
-msgid "There must be a value in at least one input"
-msgstr "하나의 입력에 최소한 하나의 값이 있어야 합니다."
-
-#: screens/Login/Login.js:155
-msgid "There was a problem logging in. Please try again."
-msgstr "로그인하는 데 문제가 있었습니다. 다시 시도하십시오."
-
-#: components/ContentError/ContentError.js:42
-msgid "There was an error loading this content. Please reload the page."
-msgstr "이 콘텐츠를 로드하는 동안 오류가 발생했습니다. 페이지를 다시 로드하십시오."
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:56
-msgid "There was an error parsing the file. Please check the file formatting and try again."
-msgstr "파일을 구문 분석하는 동안 오류가 발생했습니다. 파일 형식을 확인하고 다시 시도하십시오."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:713
-msgid "There was an error saving the workflow."
-msgstr "워크플로를 저장하는 동안 오류가 발생했습니다."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:69
-msgid "These are the modules that {brandName} supports running commands against."
-msgstr "다음은 {brandName}에서 명령 실행을 지원하는 모듈입니다."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:129
-msgid "These are the verbosity levels for standard out of the command run that are supported."
-msgstr "이는 표준 실행 명령을 실행하기 위해 지원되는 상세 수준입니다."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:122
-#: screens/Job/Job.helptext.js:43
-msgid "These arguments are used with the specified module."
-msgstr "이러한 인수는 지정된 모듈과 함께 사용됩니다."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:111
-msgid "These arguments are used with the specified module. You can find information about {0} by clicking"
-msgstr "이러한 인수는 지정된 모듈과 함께 사용됩니다. {0} 에 대한 정보를 클릭하면 확인할 수 있습니다."
-
-#: screens/Job/Job.helptext.js:33
-msgid "These arguments are used with the specified module. You can find information about {moduleName} by clicking"
-msgstr "이러한 인수는 지정된 모듈과 함께 사용됩니다. {moduleName} 에 대한 정보를 클릭하면 확인할 수 있습니다."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:410
-msgid "Third"
-msgstr "세 번째"
-
-#: screens/Template/shared/JobTemplateForm.js:157
-msgid "This Project needs to be updated"
-msgstr "이 프로젝트를 업데이트해야 합니다."
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:286
-#: screens/Template/Survey/SurveyList.js:82
-msgid "This action will delete the following:"
-msgstr "이 작업은 다음을 삭제합니다."
-
-#: screens/User/UserTeams/UserTeamList.js:217
-msgid "This action will disassociate all roles for this user from the selected teams."
-msgstr "이 작업은 선택한 팀에서 이 사용자의 모든 역할을 제거합니다."
-
-#: screens/Team/TeamRoles/TeamRolesList.js:236
-#: screens/User/UserRoles/UserRolesList.js:232
-msgid "This action will disassociate the following role from {0}:"
-msgstr "이 작업은 {0} 에서 다음 역할의 연결을 해제합니다."
-
-#: components/DisassociateButton/DisassociateButton.js:148
-msgid "This action will disassociate the following:"
-msgstr "이 작업은 다음과 같이 연결을 해제합니다."
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:178
-msgid "This action will remove the following instances:"
-msgstr "이 작업은 다음 인스턴스를 제거합니다."
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:113
-msgid "This container group is currently being by other resources. Are you sure you want to delete it?"
-msgstr "현재 이 컨테이너 그룹에 다른 리소스가 있습니다. 삭제하시겠습니까?"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:304
-msgid "This credential is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "현재 다른 리소스에서 이 인증 정보를 사용하고 있습니다. 삭제하시겠습니까?"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:121
-msgid "This credential type is currently being used by some credentials and cannot be deleted"
-msgstr "현재 일부 인증 정보에서 이 인증 정보 유형을 사용하고 있으며 삭제할 수 없습니다."
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:74
-msgid ""
-"This data is used to enhance\n"
-"future releases of the Software and to provide\n"
-"Automation Analytics."
-msgstr "이 데이터는\n"
-"향후 소프트웨어 릴리스를 개선하고\n"
-"Automation Analytics를 제공하는 데 사용됩니다."
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:62
-msgid ""
-"This data is used to enhance\n"
-"future releases of the Tower Software and help\n"
-"streamline customer experience and success."
-msgstr "이 데이터는 향후 Tower 소프트웨어의 릴리스를 개선하고 고객 경험 및 성공을 단순화하는 데 사용됩니다."
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:132
-msgid "This execution environment is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "현재 다른 리소스에서 이 실행 환경이 사용되고 있습니다. 삭제하시겠습니까?"
-
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:74
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:79
-msgid "This feature is deprecated and will be removed in a future release."
-msgstr "이 기능은 더 이상 사용되지 않으며 향후 릴리스에서 제거될 예정입니다."
-
-#: screens/Inventory/shared/Inventory.helptext.js:155
-msgid "This field is ignored unless an Enabled Variable is set. If the enabled variable matches this value, the host will be enabled on import."
-msgstr "활성화된 변수가 설정되지 않은 경우 이 필드는 무시됩니다. 사용 가능한 변수가 이 값과 일치하면 호스트는 가져오기에서 활성화됩니다."
-
-#: components/AdHocCommands/useAdHocCredentialPasswordStep.js:44
-#: components/LaunchPrompt/steps/useCredentialPasswordsStep.js:50
-msgid "This field may not be blank"
-msgstr "이 필드는 비워 둘 수 없습니다."
-
-#: util/validators.js:127
-msgid "This field must be a number"
-msgstr "이 필드는 숫자여야 합니다."
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:107
-msgid "This field must be a number and have a value between {0} and {1}"
-msgstr "이 필드는 {0}과 {1} 사이의 값을 가진 숫자여야 합니다."
-
-#: util/validators.js:67
-msgid "This field must be a number and have a value between {min} and {max}"
-msgstr "이 필드는 {min}과 {max} 사이의 값을 가진 숫자여야 합니다."
-
-#: util/validators.js:64
-msgid "This field must be a number and have a value greater than {min}"
-msgstr "이 필드는 {min}보다 큰 값을 가진 숫자여야 합니다."
-
-#: util/validators.js:61
-msgid "This field must be a number and have a value less than {max}"
-msgstr "이 필드는 {max}보다 작은값을 가진 숫자여야 합니다."
-
-#: util/validators.js:184
-msgid "This field must be a regular expression"
-msgstr "이 필드는 정규식이어야 합니다."
-
-#: util/validators.js:111
-#: util/validators.js:194
-msgid "This field must be an integer"
-msgstr "이 필드는 정수여야 합니다."
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:99
-msgid "This field must be at least {0} characters"
-msgstr "이 필드는 {0} 자 이상이어야 합니다."
-
-#: util/validators.js:52
-msgid "This field must be at least {min} characters"
-msgstr "이 필드는 {min} 자 이상이어야 합니다."
-
-#: util/validators.js:197
-msgid "This field must be greater than 0"
-msgstr "이 필드는 0보다 커야 합니다."
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:52
-#: components/LaunchPrompt/steps/useSurveyStep.js:111
-#: screens/Template/shared/JobTemplateForm.js:154
-#: screens/User/shared/UserForm.js:92
-#: screens/User/shared/UserForm.js:103
-#: util/validators.js:5
-#: util/validators.js:76
-msgid "This field must not be blank"
-msgstr "이 필드는 비워 둘 수 없습니다."
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:46
-msgid "This field must not be blank."
-msgstr "이 필드는 비워 둘 수 없습니다."
-
-#: util/validators.js:101
-msgid "This field must not contain spaces"
-msgstr "이 필드에는 공백을 포함할 수 없습니다."
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:102
-msgid "This field must not exceed {0} characters"
-msgstr "이 필드는 {0} 자를 초과할 수 없습니다."
-
-#: util/validators.js:43
-msgid "This field must not exceed {max} characters"
-msgstr "이 필드는 {max} 자를 초과할 수 없습니다."
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:51
-msgid "This field will be retrieved from an external secret management system using the specified credential."
-msgstr "이 필드는 지정된 인증 정보를 사용하여 외부 시크릿 관리 시스템에서 검색됩니다."
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:82
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:89
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:104
-msgid "This has already been acted on"
-msgstr "이 작업은 이미 수행되었습니다."
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:125
-msgid "This instance group is currently being by other resources. Are you sure you want to delete it?"
-msgstr "이 인스턴스 그룹은 현재 다른 리소스에 의해 있습니다. 삭제하시겠습니까?"
-
-#: components/LaunchPrompt/steps/useInventoryStep.js:59
-msgid "This inventory is applied to all workflow nodes within this workflow ({0}) that prompt for an inventory."
-msgstr "이 인벤토리는 이 워크플로우({0}) 내의 모든 워크플로 노드에 적용되며, 인벤토리를 요청하는 메시지를 표시합니다."
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:199
-msgid "This inventory is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "이 인벤토리는 현재 다른 리소스에서 사용하고 있습니다. 삭제하시겠습니까?"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:313
-msgid "This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?"
-msgstr "이 인벤토리 소스는 현재 이를 사용하는 다른 리소스에서 사용되고 있습니다. 삭제하시겠습니까?"
-
-#: screens/Application/Applications.js:77
-msgid "This is the only time the client secret will be shown."
-msgstr "이는 클라이언트 시크릿이 표시되는 유일한 시간입니다."
-
-#: screens/User/UserTokens/UserTokens.js:59
-msgid "This is the only time the token value and associated refresh token value will be shown."
-msgstr "토큰 값과 연결된 새로 고침 토큰 값이 표시되는 유일한 시간입니다."
-
-#: screens/Job/JobOutput/EmptyOutput.js:37
-msgid "This job failed and has no output."
-msgstr "이 작업은 실패하여 출력이 없습니다."
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:543
-msgid "This job template is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "이 작업 템플릿은 현재 다른 리소스에서 사용하고 있습니다. 삭제하시겠습니까?"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:197
-msgid "This organization is currently being by other resources. Are you sure you want to delete it?"
-msgstr "이 조직은 현재 다른 리소스에서 사용 중입니다. 삭제하시겠습니까?"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:338
-msgid "This project is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "이 프로젝트는 현재 다른 리소스에서 사용하고 있습니다. 삭제하시겠습니까?"
-
-#: screens/Project/shared/Project.helptext.js:59
-msgid "This project is currently on sync and cannot be clicked until sync process completed"
-msgstr "이 프로젝트는 현재 동기화 상태에 있으며 동기화 프로세스가 완료될 때까지 클릭할 수 없습니다."
-
-#: components/Schedule/shared/ScheduleForm.js:460
-msgid "This schedule has no occurrences due to the selected exceptions."
-msgstr "이 일정에는 선택한 예외로 인해 발생하지 않습니다."
-
-#: components/Schedule/ScheduleList/ScheduleList.js:122
-msgid "This schedule is missing an Inventory"
-msgstr "이 일정에는 인벤토리가 없습니다."
-
-#: components/Schedule/ScheduleList/ScheduleList.js:147
-msgid "This schedule is missing required survey values"
-msgstr "이 일정에는 필수 설문 조사 값이 없습니다."
-
-#: components/Schedule/shared/UnsupportedScheduleForm.js:12
-msgid ""
-"This schedule uses complex rules that are not supported in the\n"
-"UI. Please use the API to manage this schedule."
-msgstr "이 일정은 UI에서 지원되지 않는 복잡한 규칙을 사용합니다. \n"
-" API를 사용하여 이 일정을 관리하십시오."
-
-#: components/LaunchPrompt/steps/StepName.js:26
-msgid "This step contains errors"
-msgstr "이 단계에는 오류가 포함되어 있습니다."
-
-#: screens/User/shared/UserForm.js:150
-msgid "This value does not match the password you entered previously. Please confirm that password."
-msgstr "이 값은 이전에 입력한 암호와 일치하지 않습니다. 암호를 확인하십시오."
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:106
-msgid "This will cancel all subsequent nodes in this workflow"
-msgstr "이 워크플로우의 모든 후속 노드가 취소됩니다."
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:328
-msgid "This will cancel all subsequent nodes in this workflow."
-msgstr "이 워크플로우의 모든 후속 노드가 취소됩니다."
-
-#: screens/Setting/shared/RevertAllAlert.js:36
-msgid ""
-"This will revert all configuration values on this page to\n"
-"their factory defaults. Are you sure you want to proceed?"
-msgstr "이렇게 하면 이 페이지의 모든 구성 값을 해당 팩토리 기본값으로 되돌립니다. 계속 진행하시겠습니까?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:40
-msgid "This workflow does not have any nodes configured."
-msgstr "이 워크플로에는 노드가 구성되어 있지 않습니다."
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:43
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:35
-msgid "This workflow has already been acted on"
-msgstr "이 워크플로우는 이미 수행되었습니다."
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:267
-msgid "This workflow job template is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "이 워크플로우 작업 템플릿은 현재 다른 리소스에서 사용되고 있습니다. 삭제하시겠습니까?"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:299
-msgid "Thu"
-msgstr "목요일"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:80
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:186
-#: components/Schedule/shared/FrequencyDetailSubform.js:304
-#: components/Schedule/shared/FrequencyDetailSubform.js:448
-msgid "Thursday"
-msgstr "목요일"
-
-#: screens/ActivityStream/ActivityStream.js:249
-#: screens/ActivityStream/ActivityStream.js:261
-#: screens/ActivityStream/ActivityStreamDetailButton.js:41
-#: screens/ActivityStream/ActivityStreamListItem.js:42
-msgid "Time"
-msgstr "시간"
-
-#: screens/Project/shared/Project.helptext.js:128
-msgid ""
-"Time in seconds to consider a project\n"
-"to be current. During job runs and callbacks the task\n"
-"system will evaluate the timestamp of the latest project\n"
-"update. If it is older than Cache Timeout, it is not\n"
-"considered current, and a new project update will be\n"
-"performed."
-msgstr "프로젝트가 최신 상태인 것으로 간주하는데 걸리는 시간(초)입니다. 작업 실행 및 콜백 중에 작업 시스템은 최신 프로젝트 업데이트의 타임스탬프를 평가합니다. 캐시 시간 초과보다 오래된 경우 최신 상태로 간주되지 않으며 새 프로젝트 업데이트가 수행됩니다."
-
-#: screens/Inventory/shared/Inventory.helptext.js:147
-msgid ""
-"Time in seconds to consider an inventory sync\n"
-"to be current. During job runs and callbacks the task system will\n"
-"evaluate the timestamp of the latest sync. If it is older than\n"
-"Cache Timeout, it is not considered current, and a new\n"
-"inventory sync will be performed."
-msgstr "인벤토리 동기화가 최신 상태인 것으로 간주하는데 걸리는 시간(초)입니다. 작업 실행 및 콜백 중에 작업 시스템은 최신 동기화의 타임스탬프를 평가합니다. 캐시 시간 초과보다 오래된 경우 최신 상태로 간주되지 않으며 새 인벤토리 동기화가 수행됩니다."
-
-#: components/StatusLabel/StatusLabel.js:51
-msgid "Timed out"
-msgstr "시간 초과"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:86
-#: components/PromptDetail/PromptDetail.js:136
-#: components/PromptDetail/PromptDetail.js:355
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:484
-#: screens/Job/JobDetail/JobDetail.js:397
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:170
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:114
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:277
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:186
-#: screens/Template/shared/JobTemplateForm.js:475
-msgid "Timeout"
-msgstr "시간 초과"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:193
-msgid "Timeout minutes"
-msgstr "시간 제한 (분)"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:207
-msgid "Timeout seconds"
-msgstr "시간 제한 (초)"
-
-#: screens/Host/HostList/SmartInventoryButton.js:20
-msgid "To create a smart inventory using ansible facts, go to the smart inventory screen."
-msgstr "ansible 팩트를 사용하여 스마트 인벤토리를 생성하려면 스마트 인벤토리 화면으로 이동합니다."
-
-#: screens/Template/Survey/SurveyReorderModal.js:194
-msgid "To reorder the survey questions drag and drop them in the desired location."
-msgstr "설문조사 질문을 재정렬하려면 원하는 위치에 끌어다 놓습니다."
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:106
-msgid "Toggle Legend"
-msgstr "범례 전환"
-
-#: components/FormField/PasswordInput.js:39
-msgid "Toggle Password"
-msgstr "암호 전환"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:116
-msgid "Toggle Tools"
-msgstr "툴 전환"
-
-#: components/HostToggle/HostToggle.js:70
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:56
-msgid "Toggle host"
-msgstr "호스트 전환"
-
-#: components/InstanceToggle/InstanceToggle.js:61
-msgid "Toggle instance"
-msgstr "인스턴스 전환"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:80
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:82
-#: screens/TopologyView/Header.js:99
-msgid "Toggle legend"
-msgstr "범례 전환"
-
-#: components/NotificationList/NotificationListItem.js:50
-msgid "Toggle notification approvals"
-msgstr "알림 승인 전환"
-
-#: components/NotificationList/NotificationListItem.js:92
-msgid "Toggle notification failure"
-msgstr "알림 전환 실패"
-
-#: components/NotificationList/NotificationListItem.js:64
-msgid "Toggle notification start"
-msgstr "알림 시작 전환"
-
-#: components/NotificationList/NotificationListItem.js:78
-msgid "Toggle notification success"
-msgstr "알림 전환 성공"
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:66
-msgid "Toggle schedule"
-msgstr "일정 전환"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:92
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:94
-msgid "Toggle tools"
-msgstr "툴 전환"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:373
-#: screens/User/UserTokens/UserTokens.js:64
-msgid "Token"
-msgstr "토큰"
-
-#: screens/User/UserTokens/UserTokens.js:50
-#: screens/User/UserTokens/UserTokens.js:53
-msgid "Token information"
-msgstr "토큰 정보"
-
-#: screens/User/UserToken/UserToken.js:73
-msgid "Token not found."
-msgstr "토큰을 찾을 수 없습니다."
-
-#: screens/Application/Application/Application.js:80
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:105
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:128
-#: screens/Application/Applications.js:40
-#: screens/User/User.js:76
-#: screens/User/UserTokenList/UserTokenList.js:118
-#: screens/User/Users.js:34
-msgid "Tokens"
-msgstr "토큰"
-
-#: components/Workflow/WorkflowTools.js:83
-msgid "Tools"
-msgstr "툴"
-
-#: routeConfig.js:152
-#: screens/TopologyView/TopologyView.js:40
-msgid "Topology View"
-msgstr "토폴로지 보기"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:218
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:197
-#: screens/InstanceGroup/Instances/InstanceListItem.js:199
-#: screens/Instances/InstanceDetail/InstanceDetail.js:213
-#: screens/Instances/InstanceList/InstanceListItem.js:214
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:78
-msgid "Total Jobs"
-msgstr "총 작업"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:76
-msgid "Total Nodes"
-msgstr "총 노드"
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:103
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:119
-msgid "Total hosts"
-msgstr "총 호스트"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:72
-msgid "Total jobs"
-msgstr "총 작업"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:87
-msgid "Track submodules"
-msgstr "하위 모듈 추적"
-
-#: components/PromptDetail/PromptProjectDetail.js:56
-#: screens/Project/ProjectDetail/ProjectDetail.js:109
-msgid "Track submodules latest commit on branch"
-msgstr "분기에서 하위 모듈의 최신 커밋 추적"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:141
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:168
-msgid "Trial"
-msgstr "평가판"
-
-#: components/JobList/JobListItem.js:319
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:66
-#: screens/Job/JobDetail/JobDetail.js:383
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:210
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:240
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:270
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:315
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:373
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:142
-msgid "True"
-msgstr "True"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:277
-msgid "Tue"
-msgstr "화요일"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:78
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:184
-#: components/Schedule/shared/FrequencyDetailSubform.js:282
-#: components/Schedule/shared/FrequencyDetailSubform.js:438
-msgid "Tuesday"
-msgstr "화요일"
-
-#: components/NotificationList/NotificationList.js:201
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:142
-msgid "Twilio"
-msgstr "Twilio"
-
-#: components/JobList/JobList.js:246
-#: components/JobList/JobListItem.js:98
-#: components/Lookup/ProjectLookup.js:132
-#: components/NotificationList/NotificationList.js:219
-#: components/NotificationList/NotificationListItem.js:33
-#: components/PromptDetail/PromptDetail.js:124
-#: components/RelatedTemplateList/RelatedTemplateList.js:187
-#: components/TemplateList/TemplateList.js:214
-#: components/TemplateList/TemplateList.js:243
-#: components/TemplateList/TemplateListItem.js:184
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:85
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:154
-#: components/Workflow/WorkflowNodeHelp.js:160
-#: components/Workflow/WorkflowNodeHelp.js:196
-#: screens/Credential/CredentialList/CredentialList.js:165
-#: screens/Credential/CredentialList/CredentialListItem.js:63
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:94
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:116
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:17
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:46
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:54
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:195
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:66
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:94
-#: screens/Inventory/InventoryList/InventoryList.js:220
-#: screens/Inventory/InventoryList/InventoryListItem.js:116
-#: screens/Inventory/InventorySources/InventorySourceList.js:213
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:100
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:105
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:180
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:120
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:68
-#: screens/Project/ProjectList/ProjectList.js:194
-#: screens/Project/ProjectList/ProjectList.js:223
-#: screens/Project/ProjectList/ProjectListItem.js:218
-#: screens/Team/TeamRoles/TeamRoleListItem.js:17
-#: screens/Team/TeamRoles/TeamRolesList.js:181
-#: screens/Template/Survey/SurveyList.js:103
-#: screens/Template/Survey/SurveyList.js:103
-#: screens/Template/Survey/SurveyListItem.js:60
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:93
-#: screens/User/UserDetail/UserDetail.js:75
-#: screens/User/UserRoles/UserRolesList.js:156
-#: screens/User/UserRoles/UserRolesListItem.js:21
-msgid "Type"
-msgstr "유형"
-
-#: screens/Credential/shared/TypeInputsSubForm.js:25
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:47
-#: screens/Project/shared/ProjectForm.js:298
-msgid "Type Details"
-msgstr "유형 세부 정보"
-
-#: screens/Template/Survey/MultipleChoiceField.js:56
-msgid ""
-"Type answer then click checkbox on right to select answer as\n"
-"default."
-msgstr "답을 입력한 다음 확인란 오른쪽을 클릭하여 답변을 기본값으로 선택합니다."
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:50
-msgid "UTC"
-msgstr "UTC"
-
-#: components/HostForm/HostForm.js:62
-msgid "Unable to change inventory on a host"
-msgstr "호스트에서 인벤토리를 변경할 수 없음"
-
-#: screens/Project/ProjectList/ProjectListItem.js:211
-msgid "Unable to load last job update"
-msgstr "마지막 작업 업데이트를 로드할 수 없음"
-
-#: components/StatusLabel/StatusLabel.js:61
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:260
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:87
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:46
-#: screens/InstanceGroup/Instances/InstanceListItem.js:78
-#: screens/Instances/InstanceDetail/InstanceDetail.js:306
-#: screens/Instances/InstanceList/InstanceListItem.js:77
-#: screens/TopologyView/Tooltip.js:121
-msgid "Unavailable"
-msgstr "사용할 수 없음"
-
-#: screens/Setting/shared/RevertButton.js:53
-#: screens/Setting/shared/RevertButton.js:62
-msgid "Undo"
-msgstr "실행 취소"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:183
-msgid "Unfollow"
-msgstr "팔로우 취소"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:126
-msgid "Unlimited"
-msgstr "무제한"
-
-#: components/StatusLabel/StatusLabel.js:47
-#: screens/Job/JobOutput/shared/HostStatusBar.js:51
-#: screens/Job/JobOutput/shared/OutputToolbar.js:103
-msgid "Unreachable"
-msgstr "연결할 수 없음"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:102
-msgid "Unreachable Host Count"
-msgstr "연결할 수 없는 호스트 수"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:104
-msgid "Unreachable Hosts"
-msgstr "연결할 수 없는 호스트"
-
-#: util/dates.js:74
-msgid "Unrecognized day string"
-msgstr "인식되지 않는 요일 문자열"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:15
-msgid "Unsaved changes modal"
-msgstr "저장되지 않은 변경 사항 모달"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:94
-msgid "Update Revision on Launch"
-msgstr "시작 시 버전 업데이트"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:51
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:133
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:92
-msgid "Update on launch"
-msgstr "시작 시 업데이트"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:75
-msgid "Update options"
-msgstr "업데이트 옵션"
-
-#: components/PromptDetail/PromptProjectDetail.js:61
-#: screens/Project/ProjectDetail/ProjectDetail.js:115
-msgid "Update revision on job launch"
-msgstr "작업 시작 시 버전 업데이트"
-
-#: screens/Setting/SettingList.js:92
-msgid "Update settings pertaining to Jobs within {brandName}"
-msgstr "{brandName}의 작업 관련 설정 업데이트"
-
-#: screens/Template/shared/WebhookSubForm.js:188
-msgid "Update webhook key"
-msgstr "Webhook 키 업데이트"
-
-#: components/Workflow/WorkflowNodeHelp.js:126
-msgid "Updating"
-msgstr "업데이트 중"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:118
-msgid "Upload a .zip file"
-msgstr ".zip 파일 업로드"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:97
-msgid "Upload a Red Hat Subscription Manifest containing your subscription. To generate your subscription manifest, go to <0>subscription allocations0> on the Red Hat Customer Portal."
-msgstr "서브스크립션이 포함된 Red Hat 서브스크립션 매니페스트를 업로드합니다. 서브스크립션 매니페스트를 생성하려면 Red Hat 고객 포털에서 <0>서브스크립션 할당0>으로 이동하십시오."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:53
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:132
-msgid "Use SSL"
-msgstr "SSL 사용"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:58
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:137
-msgid "Use TLS"
-msgstr "TLS 사용"
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:72
-msgid ""
-"Use custom messages to change the content of\n"
-"notifications sent when a job starts, succeeds, or fails. Use\n"
-"curly braces to access information about the job:"
-msgstr "사용자 지정 메시지를 사용하여 작업이 시작, 성공 또는 실패할 때 전송된 알림 내용을 변경합니다. 작업에 대한 정보에 액세스하려면 중괄호를 사용합니다."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:12
-msgid "Use one Annotation Tag per line, without commas."
-msgstr "쉼표 없이 한 줄에 하나의 주석 태그를 사용합니다."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:13
-msgid ""
-"Use one IRC channel or username per line. The pound\n"
-"symbol (#) for channels, and the at (@) symbol for users, are not\n"
-"required."
-msgstr "한 줄에 하나의 IRC 채널 또는 사용자 이름을 사용합니다. 채널에는 # 기호가 필요하지 않으며 사용자의 경우 @ 기호는 필요하지 않습니다."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:5
-msgid "Use one email address per line to create a recipient list for this type of notification."
-msgstr "이 유형의 알림에 대한 수신자 목록을 만들려면 한 줄에 하나의 이메일 주소를 사용합니다."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:28
-msgid ""
-"Use one phone number per line to specify where to\n"
-"route SMS messages. Phone numbers should be formatted +11231231234. For more information see Twilio documentation"
-msgstr "SMS 메시지를 라우팅할 위치를 지정하려면 한 줄에 하나의 전화 번호를 사용합니다. 전화 번호는 +11231231234 형식이어야 합니다. 자세한 내용은 Twilio 문서를 참조하십시오."
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:249
-#: screens/InstanceGroup/Instances/InstanceList.js:270
-#: screens/Instances/InstanceDetail/InstanceDetail.js:292
-#: screens/Instances/InstanceList/InstanceList.js:205
-msgid "Used Capacity"
-msgstr "사용된 용량"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:253
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:257
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:78
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:86
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:42
-#: screens/InstanceGroup/Instances/InstanceListItem.js:74
-#: screens/Instances/InstanceDetail/InstanceDetail.js:297
-#: screens/Instances/InstanceDetail/InstanceDetail.js:303
-#: screens/Instances/InstanceList/InstanceListItem.js:73
-#: screens/TopologyView/Tooltip.js:117
-msgid "Used capacity"
-msgstr "사용된 용량"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:13
-msgid "User"
-msgstr "사용자"
-
-#: components/AppContainer/PageHeaderToolbar.js:160
-msgid "User Details"
-msgstr "사용자 세부 정보"
-
-#: screens/Setting/SettingList.js:121
-#: screens/Setting/Settings.js:118
-msgid "User Interface"
-msgstr "사용자 인터페이스"
-
-#: screens/Setting/SettingList.js:126
-msgid "User Interface settings"
-msgstr "사용자 인터페이스 설정"
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:72
-#: screens/User/UserRoles/UserRolesList.js:142
-msgid "User Roles"
-msgstr "사용자 역할"
-
-#: screens/User/UserDetail/UserDetail.js:72
-#: screens/User/shared/UserForm.js:119
-msgid "User Type"
-msgstr "사용자 유형"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:59
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:60
-msgid "User analytics"
-msgstr "사용자 분석"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:34
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:202
-msgid "User and Automation Analytics"
-msgstr "사용자 및 자동화 분석"
-
-#: components/AppContainer/PageHeaderToolbar.js:154
-msgid "User details"
-msgstr "사용자 세부 정보"
-
-#: screens/User/User.js:96
-msgid "User not found."
-msgstr "사용자를 찾을 수 없음"
-
-#: screens/User/UserTokenList/UserTokenList.js:180
-msgid "User tokens"
-msgstr "사용자 토큰"
-
-#: components/AddRole/AddResourceRole.js:23
-#: components/AddRole/AddResourceRole.js:38
-#: components/ResourceAccessList/ResourceAccessList.js:173
-#: components/ResourceAccessList/ResourceAccessList.js:226
-#: screens/Login/Login.js:230
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:144
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:253
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:303
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:361
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:67
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:260
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:337
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:442
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:92
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:204
-#: screens/User/UserDetail/UserDetail.js:68
-#: screens/User/UserList/UserList.js:120
-#: screens/User/UserList/UserList.js:160
-#: screens/User/UserList/UserListItem.js:38
-#: screens/User/shared/UserForm.js:76
-msgid "Username"
-msgstr "사용자 이름"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:88
-msgid "Username / password"
-msgstr "사용자 이름 / 암호"
-
-#: components/AddRole/AddResourceRole.js:178
-#: components/AddRole/AddResourceRole.js:179
-#: routeConfig.js:101
-#: screens/ActivityStream/ActivityStream.js:184
-#: screens/Team/Teams.js:30
-#: screens/User/UserList/UserList.js:110
-#: screens/User/UserList/UserList.js:153
-#: screens/User/Users.js:15
-#: screens/User/Users.js:26
-msgid "Users"
-msgstr "사용자"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:101
-msgid "VMware vCenter"
-msgstr "VMware vCenter"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:69
-#: components/HostForm/HostForm.js:113
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:115
-#: components/PromptDetail/PromptDetail.js:168
-#: components/PromptDetail/PromptDetail.js:369
-#: components/PromptDetail/PromptJobTemplateDetail.js:286
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:135
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:620
-#: screens/Host/HostDetail/HostDetail.js:93
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:165
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:37
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:88
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:143
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:53
-#: screens/Inventory/shared/InventoryForm.js:108
-#: screens/Inventory/shared/InventoryGroupForm.js:46
-#: screens/Inventory/shared/SmartInventoryForm.js:93
-#: screens/Job/JobDetail/JobDetail.js:546
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:501
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:226
-#: screens/Template/shared/JobTemplateForm.js:402
-#: screens/Template/shared/WorkflowJobTemplateForm.js:212
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:293
-msgid "Variables"
-msgstr "변수"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:130
-msgid "Variables Prompted"
-msgstr "프롬프트 변수"
-
-#: screens/Inventory/shared/Inventory.helptext.js:43
-msgid "Variables must be in JSON or YAML syntax. Use the radio button to toggle between the two."
-msgstr "변수는 JSON 또는 YAML 구문이어야 합니다. 라디오 버튼을 사용하여 둘 사이를 전환합니다."
-
-#: screens/Inventory/shared/Inventory.helptext.js:166
-msgid "Variables used to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>{sourceType}1> plugin configuration guide."
-msgstr "인벤토리 소스를 구성하는데 사용되는 변수입니다. 이 플러그인을 구성하는 방법에 대한 자세한 내용은 설명서의 <0>인벤토리 플러그인0> 섹션과 <1>{sourceType}1> 플러그인 구성 가이드를 참조하십시오."
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:121
-msgid "Vault password"
-msgstr "Vault 암호"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:121
-msgid "Vault password | {credId}"
-msgstr "Vault 암호 | {credId}"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:131
-msgid "Verbose"
-msgstr "상세 정보"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:63
-#: components/PromptDetail/PromptDetail.js:260
-#: components/PromptDetail/PromptInventorySourceDetail.js:100
-#: components/PromptDetail/PromptJobTemplateDetail.js:154
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:478
-#: components/VerbositySelectField/VerbositySelectField.js:34
-#: components/VerbositySelectField/VerbositySelectField.js:45
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:232
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:47
-#: screens/Job/JobDetail/JobDetail.js:331
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:271
-msgid "Verbosity"
-msgstr "상세 정보"
-
-#: screens/Setting/AzureAD/AzureAD.js:25
-msgid "View Azure AD settings"
-msgstr "Azure AD 설정 보기"
-
-#: screens/Credential/Credential.js:143
-#: screens/Credential/Credential.js:155
-msgid "View Credential Details"
-msgstr "인증 정보 세부 정보보기"
-
-#: components/Schedule/Schedule.js:151
-msgid "View Details"
-msgstr "세부 정보 보기"
-
-#: screens/Setting/GitHub/GitHub.js:58
-msgid "View GitHub Settings"
-msgstr "GitHub 설정 보기"
-
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2.js:26
-msgid "View Google OAuth 2.0 settings"
-msgstr "Google OAuth 2 설정 보기"
-
-#: screens/Host/Host.js:137
-msgid "View Host Details"
-msgstr "호스트 세부 정보 보기"
-
-#: screens/Instances/Instance.js:78
-msgid "View Instance Details"
-msgstr "인스턴스 세부 정보 보기"
-
-#: screens/Inventory/Inventory.js:192
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:142
-#: screens/Inventory/SmartInventory.js:175
-msgid "View Inventory Details"
-msgstr "인벤토리 세부 정보보기"
-
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:92
-msgid "View Inventory Groups"
-msgstr "인벤토리 그룹 보기"
-
-#: screens/Inventory/InventoryHost/InventoryHost.js:160
-msgid "View Inventory Host Details"
-msgstr "인벤토리 호스트 세부 정보 보기"
-
-#: screens/Inventory/shared/Inventory.helptext.js:55
-msgid "View JSON examples at <0>www.json.org0>"
-msgstr "<0>www.json.org0>에서 JSON 예제 보기"
-
-#: screens/Job/Job.js:212
-msgid "View Job Details"
-msgstr "작업 세부 정보보기"
-
-#: screens/Setting/Jobs/Jobs.js:25
-msgid "View Jobs settings"
-msgstr "작업 설정 보기"
-
-#: screens/Setting/LDAP/LDAP.js:38
-msgid "View LDAP Settings"
-msgstr "LDAP 설정 보기"
-
-#: screens/Setting/Logging/Logging.js:32
-msgid "View Logging settings"
-msgstr "로깅 설정 보기"
-
-#: screens/Setting/MiscAuthentication/MiscAuthentication.js:32
-msgid "View Miscellaneous Authentication settings"
-msgstr "기타 인증 설정 보기"
-
-#: screens/Setting/MiscSystem/MiscSystem.js:32
-msgid "View Miscellaneous System settings"
-msgstr "기타 시스템 설정 보기"
-
-#: screens/Setting/OIDC/OIDC.js:25
-msgid "View OIDC settings"
-msgstr "OIDC 설정 보기"
-
-#: screens/Organization/Organization.js:225
-msgid "View Organization Details"
-msgstr "조직 세부 정보 보기"
-
-#: screens/Project/Project.js:200
-msgid "View Project Details"
-msgstr "프로젝트 세부 정보보기"
-
-#: screens/Setting/RADIUS/RADIUS.js:25
-msgid "View RADIUS settings"
-msgstr "RADIUS 설정 보기"
-
-#: screens/Setting/SAML/SAML.js:25
-msgid "View SAML settings"
-msgstr "SAML 설정 보기"
-
-#: components/Schedule/Schedule.js:83
-#: components/Schedule/Schedule.js:101
-msgid "View Schedules"
-msgstr "일정 보기"
-
-#: screens/Setting/Subscription/Subscription.js:30
-msgid "View Settings"
-msgstr "설정 보기"
-
-#: screens/Template/Template.js:159
-#: screens/Template/WorkflowJobTemplate.js:145
-msgid "View Survey"
-msgstr "설문 조사보기"
-
-#: screens/Setting/TACACS/TACACS.js:25
-msgid "View TACACS+ settings"
-msgstr "TACACS + 설정 보기"
-
-#: screens/Team/Team.js:118
-msgid "View Team Details"
-msgstr "팀 세부 정보 보기"
-
-#: screens/Template/Template.js:260
-#: screens/Template/WorkflowJobTemplate.js:275
-msgid "View Template Details"
-msgstr "템플릿 세부 정보 보기"
-
-#: screens/User/UserToken/UserToken.js:100
-msgid "View Tokens"
-msgstr "토큰 보기"
-
-#: screens/User/User.js:141
-msgid "View User Details"
-msgstr "사용자 세부 정보보기"
-
-#: screens/Setting/UI/UI.js:26
-msgid "View User Interface settings"
-msgstr "사용자 인터페이스 설정 보기"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:105
-msgid "View Workflow Approval Details"
-msgstr "워크플로우 승인 세부 정보 보기"
-
-#: screens/Inventory/shared/Inventory.helptext.js:66
-msgid "View YAML examples at <0>docs.ansible.com0>"
-msgstr "<0>docs.ansible.com0>에서 YAML 예제 보기"
-
-#: components/ScreenHeader/ScreenHeader.js:65
-#: components/ScreenHeader/ScreenHeader.js:68
-msgid "View activity stream"
-msgstr "활동 스트림 보기"
-
-#: screens/Credential/Credential.js:99
-msgid "View all Credentials."
-msgstr "모든 인증 정보 보기"
-
-#: screens/Host/Host.js:97
-msgid "View all Hosts."
-msgstr "모든 호스트 보기"
-
-#: screens/Inventory/Inventory.js:95
-#: screens/Inventory/SmartInventory.js:95
-msgid "View all Inventories."
-msgstr "모든 인벤토리 보기"
-
-#: screens/Inventory/InventoryHost/InventoryHost.js:101
-msgid "View all Inventory Hosts."
-msgstr "모든 인벤토리 호스트 보기"
-
-#: screens/Job/JobTypeRedirect.js:40
-msgid "View all Jobs"
-msgstr "모든 작업 보기"
-
-#: screens/Job/Job.js:162
-msgid "View all Jobs."
-msgstr "모든 작업 보기."
-
-#: screens/NotificationTemplate/NotificationTemplate.js:60
-#: screens/NotificationTemplate/NotificationTemplateAdd.js:52
-msgid "View all Notification Templates."
-msgstr "모든 알림 템플릿 보기."
-
-#: screens/Organization/Organization.js:155
-msgid "View all Organizations."
-msgstr "모든 조직 보기."
-
-#: screens/Project/Project.js:137
-msgid "View all Projects."
-msgstr "모든 프로젝트 보기."
-
-#: screens/Team/Team.js:76
-msgid "View all Teams."
-msgstr "모든 팀 보기."
-
-#: screens/Template/Template.js:176
-#: screens/Template/WorkflowJobTemplate.js:176
-msgid "View all Templates."
-msgstr "모든 템플릿 보기."
-
-#: screens/User/User.js:97
-msgid "View all Users."
-msgstr "모든 사용자 보기."
-
-#: screens/WorkflowApproval/WorkflowApproval.js:54
-msgid "View all Workflow Approvals."
-msgstr "모든 워크플로우 승인 보기."
-
-#: screens/Application/Application/Application.js:96
-msgid "View all applications."
-msgstr "모든 애플리케이션 보기."
-
-#: screens/CredentialType/CredentialType.js:78
-msgid "View all credential types"
-msgstr "모든 인증 정보 유형 보기"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:85
-msgid "View all execution environments"
-msgstr "모든 실행 환경 보기"
-
-#: screens/InstanceGroup/ContainerGroup.js:86
-#: screens/InstanceGroup/InstanceGroup.js:94
-msgid "View all instance groups"
-msgstr "모든 인스턴스 그룹 보기"
-
-#: screens/ManagementJob/ManagementJob.js:135
-msgid "View all management jobs"
-msgstr "모든 관리 작업 보기"
-
-#: screens/Setting/Settings.js:204
-msgid "View all settings"
-msgstr "모든 설정 보기"
-
-#: screens/User/UserToken/UserToken.js:74
-msgid "View all tokens."
-msgstr "모든 토큰 보기"
-
-#: screens/Setting/SettingList.js:133
-msgid "View and edit your subscription information"
-msgstr "서브스크립션 정보 보기 및 편집"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:25
-#: screens/ActivityStream/ActivityStreamListItem.js:50
-msgid "View event details"
-msgstr "이벤트 세부 정보 보기"
-
-#: screens/Inventory/InventorySource/InventorySource.js:167
-msgid "View inventory source details"
-msgstr "인벤토리 소스 세부 정보 보기"
-
-#: components/Sparkline/Sparkline.js:44
-msgid "View job {0}"
-msgstr "작업 {0} 보기 "
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:220
-msgid "View node details"
-msgstr "노드 세부 정보 보기"
-
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:85
-msgid "View smart inventory host details"
-msgstr "스마트 인벤토리 호스트 세부 정보 보기"
-
-#: routeConfig.js:30
-#: screens/ActivityStream/ActivityStream.js:145
-msgid "Views"
-msgstr "보기"
-
-#: components/TemplateList/TemplateListItem.js:198
-#: components/TemplateList/TemplateListItem.js:204
-#: screens/Template/WorkflowJobTemplate.js:137
-msgid "Visualizer"
-msgstr "시각화 도구"
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:44
-msgid "WARNING:"
-msgstr "경고:"
-
-#: components/JobList/JobList.js:229
-#: components/StatusLabel/StatusLabel.js:52
-#: components/Workflow/WorkflowNodeHelp.js:96
-msgid "Waiting"
-msgstr "대기 중"
-
-#: screens/Job/JobOutput/EmptyOutput.js:35
-msgid "Waiting for job output…"
-msgstr "작업 출력을 기다리는 중.."
-
-#: components/Workflow/WorkflowLegend.js:118
-#: screens/Job/JobOutput/JobOutputSearch.js:132
-msgid "Warning"
-msgstr "경고"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:14
-msgid "Warning: Unsaved Changes"
-msgstr "경고: 저장하지 않은 변경 사항"
-
-#: components/Schedule/shared/ScheduleFormFields.js:43
-msgid "Warning: {selectedValue} is a link to {0} and will be saved as that."
-msgstr "경고: {selectedValue}은/는 {0} 에 대한 링크이며 다음과 같이 저장됩니다."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:119
-msgid "We were unable to locate licenses associated with this account."
-msgstr "이 계정과 연결된 라이선스를 찾을 수 없습니다."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:138
-msgid "We were unable to locate subscriptions associated with this account."
-msgstr "이 계정과 연결된 서브스크립션을 찾을 수 없습니다."
-
-#: components/DetailList/LaunchedByDetail.js:24
-#: components/NotificationList/NotificationList.js:202
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:143
-msgid "Webhook"
-msgstr "Webhook"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:177
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:103
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:333
-#: screens/Template/shared/WebhookSubForm.js:199
-msgid "Webhook Credential"
-msgstr "Webhook 인증 정보"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:175
-msgid "Webhook Credentials"
-msgstr "Webhook 인증 정보"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:173
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:92
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:326
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:168
-#: screens/Template/shared/WebhookSubForm.js:173
-msgid "Webhook Key"
-msgstr "Webhook 키"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:166
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:91
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:311
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:156
-#: screens/Template/shared/WebhookSubForm.js:129
-msgid "Webhook Service"
-msgstr "Webhook 서비스"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:169
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:95
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:319
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:162
-#: screens/Template/shared/WebhookSubForm.js:161
-#: screens/Template/shared/WebhookSubForm.js:167
-msgid "Webhook URL"
-msgstr "Webhook URL"
-
-#: screens/Template/shared/JobTemplateForm.js:646
-#: screens/Template/shared/WorkflowJobTemplateForm.js:271
-msgid "Webhook details"
-msgstr "Webhook 세부 정보"
-
-#: screens/Template/shared/JobTemplate.helptext.js:24
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:17
-msgid "Webhook services can launch jobs with this workflow job template by making a POST request to this URL."
-msgstr "Webhook 서비스는 이 URL에 대한 POST 요청을 작성하고 이 워크플로 작업 템플릿을 사용하여 작업을 시작할 수 있습니다."
-
-#: screens/Template/shared/JobTemplate.helptext.js:25
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:18
-msgid "Webhook services can use this as a shared secret."
-msgstr "Webhook 서비스는 이를 공유 시크릿으로 사용할 수 있습니다."
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:78
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:41
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:142
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:63
-msgid "Webhooks"
-msgstr "Webhook"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:26
-msgid "Webhooks: Enable Webhook for this workflow job template."
-msgstr "Webhook: 이 워크플로 작업 템플릿에 대한 Webhook을 활성화합니다."
-
-#: screens/Template/shared/JobTemplate.helptext.js:42
-msgid "Webhooks: Enable webhook for this template."
-msgstr "Webhook: 이 템플릿에 대한 Webhook을 활성화합니다."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:288
-msgid "Wed"
-msgstr "수요일"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:79
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:185
-#: components/Schedule/shared/FrequencyDetailSubform.js:293
-#: components/Schedule/shared/FrequencyDetailSubform.js:443
-msgid "Wednesday"
-msgstr "수요일"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:185
-#: components/Schedule/shared/FrequencyDetailSubform.js:179
-#: components/Schedule/shared/ScheduleFormFields.js:128
-#: components/Schedule/shared/ScheduleFormFields.js:188
-msgid "Week"
-msgstr "주"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:464
-msgid "Weekday"
-msgstr "평일"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:469
-msgid "Weekend day"
-msgstr "주말"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:59
-msgid ""
-"Welcome to Red Hat Ansible Automation Platform!\n"
-"Please complete the steps below to activate your subscription."
-msgstr "Red Hat Ansible Automation Platform에 오신 것을 환영합니다! 서브스크립션을 활성화하려면 아래 단계를 완료하십시오."
-
-#: screens/Login/Login.js:190
-msgid "Welcome to {brandName}!"
-msgstr "{brandName}에 오신 것을 환영합니다!"
-
-#: screens/Inventory/shared/Inventory.helptext.js:105
-msgid ""
-"When not checked, a merge will be performed,\n"
-"combining local variables with those found on the\n"
-"external source."
-msgstr "선택하지 않으면 병합이 수행되어 로컬 변수와 외부 소스에 있는 변수를 병합합니다."
-
-#: screens/Inventory/shared/Inventory.helptext.js:93
-msgid ""
-"When not checked, local child\n"
-"hosts and groups not found on the external source will remain\n"
-"untouched by the inventory update process."
-msgstr "선택 해제하면 외부 소스에서 찾을 수 없는 로컬 하위 호스트 및 그룹은 인벤토리 업데이트 프로세스에 의해 변경되지 않은 상태로 유지됩니다."
-
-#: components/Workflow/WorkflowLegend.js:96
-msgid "Workflow"
-msgstr "워크플로우"
-
-#: components/Workflow/WorkflowNodeHelp.js:75
-msgid "Workflow Approval"
-msgstr "워크플로우 승인"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:52
-msgid "Workflow Approval not found."
-msgstr "워크플로우 승인을 찾을 수 없습니다."
-
-#: routeConfig.js:54
-#: screens/ActivityStream/ActivityStream.js:156
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:118
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:145
-#: screens/WorkflowApproval/WorkflowApprovals.js:13
-#: screens/WorkflowApproval/WorkflowApprovals.js:22
-msgid "Workflow Approvals"
-msgstr "워크플로우 승인"
-
-#: components/JobList/JobList.js:216
-#: components/JobList/JobListItem.js:47
-#: components/Schedule/ScheduleList/ScheduleListItem.js:40
-#: screens/Job/JobDetail/JobDetail.js:70
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:224
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:164
-msgid "Workflow Job"
-msgstr "워크플로우 작업"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:76
-msgid "Workflow Job 1/{0}"
-msgstr "워크플로 작업 1/{0}"
-
-#: components/JobList/JobListItem.js:201
-#: components/Workflow/WorkflowNodeHelp.js:63
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:20
-#: screens/Job/JobDetail/JobDetail.js:244
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:91
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:186
-#: util/getRelatedResourceDeleteDetails.js:105
-msgid "Workflow Job Template"
-msgstr "워크플로우 작업 템플릿"
-
-#: util/getRelatedResourceDeleteDetails.js:115
-#: util/getRelatedResourceDeleteDetails.js:157
-#: util/getRelatedResourceDeleteDetails.js:260
-msgid "Workflow Job Template Nodes"
-msgstr "워크플로 작업 템플릿 노드"
-
-#: util/getRelatedResourceDeleteDetails.js:140
-msgid "Workflow Job Templates"
-msgstr "워크플로우 작업 템플릿"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:23
-msgid "Workflow Link"
-msgstr "워크플로우 링크"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:100
-msgid "Workflow Nodes"
-msgstr "워크플로 노드"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:86
-msgid "Workflow Statuses"
-msgstr "워크플로우 상태"
-
-#: components/TemplateList/TemplateList.js:218
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:98
-msgid "Workflow Template"
-msgstr "워크플로우 템플릿"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:519
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:159
-msgid "Workflow approved message"
-msgstr "워크플로우 승인 메시지"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:531
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:168
-msgid "Workflow approved message body"
-msgstr "워크플로우 승인 메시지 본문"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:543
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:177
-msgid "Workflow denied message"
-msgstr "워크플로우 거부 메시지"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:555
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:186
-msgid "Workflow denied message body"
-msgstr "워크플로우 거부 메시지 본문"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:106
-msgid "Workflow documentation"
-msgstr "워크플로우 문서"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:220
-msgid "Workflow job details"
-msgstr "워크플로우 작업 세부 정보"
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:46
-msgid "Workflow job templates"
-msgstr "워크플로우 작업 템플릿"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:24
-msgid "Workflow link modal"
-msgstr "워크플로우 링크 모달"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:247
-msgid "Workflow node view modal"
-msgstr "워크플로우 노드 보기 모달"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:567
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:195
-msgid "Workflow pending message"
-msgstr "워크플로우 보류 메시지"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:579
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:204
-msgid "Workflow pending message body"
-msgstr "워크플로우 보류 메시지 본문"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:591
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:213
-msgid "Workflow timed out message"
-msgstr "워크플로우 시간 초과 메시지"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:603
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:222
-msgid "Workflow timed out message body"
-msgstr "워크플로우 시간 초과 메시지 본문"
-
-#: screens/User/shared/UserTokenForm.js:77
-msgid "Write"
-msgstr "쓰기"
-
-#: screens/Inventory/shared/Inventory.helptext.js:52
-msgid "YAML:"
-msgstr "YAML:"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:187
-#: components/Schedule/shared/FrequencyDetailSubform.js:183
-#: components/Schedule/shared/ScheduleFormFields.js:130
-#: components/Schedule/shared/ScheduleFormFields.js:190
-msgid "Year"
-msgstr "년"
-
-#: components/Search/Search.js:229
-msgid "Yes"
-msgstr "제공됨"
-
-#: components/Lookup/MultiCredentialsLookup.js:155
-msgid "You cannot select multiple vault credentials with the same vault ID. Doing so will automatically deselect the other with the same vault ID."
-msgstr "동일한 vault ID로 여러 인증 정보를 선택할 수 없습니다. 이렇게 하면 동일한 vault ID를 가진 다른 인증 정보가 자동으로 선택 취소됩니다."
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:96
-msgid "You do not have permission to delete the following Groups: {itemsUnableToDelete}"
-msgstr "다음 그룹을 삭제할 권한이 없습니다. {itemsUnableToDelete}"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:152
-msgid "You do not have permission to delete {pluralizedItemName}: {itemsUnableToDelete}"
-msgstr "{pluralizedItemName}: {itemsUnableToDelete}을 삭제할 수 있는 권한이 없습니다."
-
-#: components/DisassociateButton/DisassociateButton.js:66
-msgid "You do not have permission to disassociate the following: {itemsUnableToDisassociate}"
-msgstr "{itemsUnableToDisassociate}과 같이 연결을 해제할 수 있는 권한이 없습니다."
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:72
-msgid "You do not have permission to remove instances: {itemsUnableToremove}"
-msgstr "인스턴스를 제거할 수 있는 권한이 없습니다. {itemsUnableToremove}"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:82
-msgid "You have automated against more hosts than your subscription allows."
-msgstr "서브스크립션에서 허용하는 것보다 더 많은 호스트에 대해 자동화되었습니다."
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:87
-msgid ""
-"You may apply a number of possible variables in the\n"
-"message. For more information, refer to the"
-msgstr "메시지에 사용 가능한 여러 변수를 적용할 수 있습니다. 자세한 내용은 다음을 참조하십시오."
-
-#: screens/Login/Login.js:198
-msgid "Your session has expired. Please log in to continue where you left off."
-msgstr "세션이 만료되었습니다. 세션이 만료되기 전의 위치에서 계속하려면 로그인하십시오."
-
-#: components/AppContainer/AppContainer.js:130
-msgid "Your session is about to expire"
-msgstr "세션이 만료될 예정입니다."
-
-#: components/Workflow/WorkflowTools.js:121
-msgid "Zoom In"
-msgstr "확대"
-
-#: components/Workflow/WorkflowTools.js:100
-msgid "Zoom Out"
-msgstr "축소"
-
-#: screens/TopologyView/Header.js:51
-#: screens/TopologyView/Header.js:54
-msgid "Zoom in"
-msgstr "확대"
-
-#: screens/TopologyView/Header.js:63
-#: screens/TopologyView/Header.js:66
-msgid "Zoom out"
-msgstr "축소"
-
-#: screens/Template/shared/JobTemplateForm.js:754
-#: screens/Template/shared/WebhookSubForm.js:150
-msgid "a new webhook key will be generated on save."
-msgstr "저장 시 새 Webhook 키가 생성됩니다."
-
-#: screens/Template/shared/JobTemplateForm.js:751
-#: screens/Template/shared/WebhookSubForm.js:140
-msgid "a new webhook url will be generated on save."
-msgstr "저장 시 새 Webhook URL이 생성됩니다."
-
-#: screens/Inventory/shared/Inventory.helptext.js:123
-#: screens/Inventory/shared/Inventory.helptext.js:142
-msgid "and click on Update Revision on Launch"
-msgstr "실행 시 버전 업데이트를 클릭합니다"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:505
-msgid "approved"
-msgstr "승인됨"
-
-#: components/AppContainer/AppContainer.js:55
-msgid "brand logo"
-msgstr "브랜드 로고"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:279
-#: screens/Template/Survey/SurveyList.js:72
-msgid "cancel delete"
-msgstr "삭제 취소"
-
-#: screens/Setting/shared/SharedFields.js:341
-msgid "cancel edit login redirect"
-msgstr "로그인 리디렉션 편집 취소"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:169
-msgid "cancel remove"
-msgstr "취소 삭제"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:34
-msgid "canceled"
-msgstr "취소됨"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:217
-msgid "command"
-msgstr "command"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:267
-#: screens/Template/Survey/SurveyList.js:63
-msgid "confirm delete"
-msgstr "삭제 확인"
-
-#: components/DisassociateButton/DisassociateButton.js:130
-#: screens/Team/TeamRoles/TeamRolesList.js:219
-msgid "confirm disassociate"
-msgstr "연결 해제 확인"
-
-#: screens/Setting/shared/SharedFields.js:330
-msgid "confirm edit login redirect"
-msgstr "로그인 리디렉션 편집 확인"
-
-#: screens/TopologyView/ContentLoading.js:32
-msgid "content-loading-in-progress"
-msgstr "content-loading-in-progress"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:189
-msgid "day"
-msgstr "일"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:151
-msgid "deletion error"
-msgstr "삭제 오류"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:513
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:37
-msgid "denied"
-msgstr "거부됨"
-
-#: screens/Job/JobOutput/EmptyOutput.js:41
-msgid "details."
-msgstr "세부 정보"
-
-#: components/DisassociateButton/DisassociateButton.js:91
-msgid "disassociate"
-msgstr "연결 해제"
-
-#: components/Lookup/HostFilterLookup.js:406
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:20
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:39
-#: screens/Template/Survey/SurveyQuestionForm.js:269
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:239
-#: screens/Template/shared/JobTemplate.helptext.js:61
-msgid "documentation"
-msgstr "문서"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:105
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:117
-#: screens/Host/HostDetail/HostDetail.js:104
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:97
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:109
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:99
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:289
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:161
-#: screens/Project/ProjectDetail/ProjectDetail.js:309
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:195
-#: screens/User/UserDetail/UserDetail.js:92
-msgid "edit"
-msgstr "편집"
-
-#: screens/Template/Survey/SurveyListItem.js:65
-#: screens/Template/Survey/SurveyReorderModal.js:125
-msgid "encrypted"
-msgstr "암호화"
-
-#: components/Lookup/HostFilterLookup.js:408
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:241
-msgid "for more info."
-msgstr "자세한 내용"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:21
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:40
-#: screens/Template/Survey/SurveyQuestionForm.js:271
-#: screens/Template/shared/JobTemplate.helptext.js:63
-msgid "for more information."
-msgstr "자세한 내용"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:150
-msgid "here"
-msgstr "여기"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:118
-#: components/AdHocCommands/AdHocDetailsStep.js:170
-#: screens/Job/Job.helptext.js:39
-msgid "here."
-msgstr "여기."
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:49
-msgid "host-description-{0}"
-msgstr "host-description-{0}"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:44
-msgid "host-name-{0}"
-msgstr "host-name-{0}"
-
-#: components/Lookup/HostFilterLookup.js:418
-msgid "hosts"
-msgstr "호스트"
-
-#: components/Pagination/Pagination.js:24
-msgid "items"
-msgstr "항목"
-
-#: screens/User/UserList/UserListItem.js:44
-msgid "ldap user"
-msgstr "LDAP 사용자"
-
-#: screens/User/UserDetail/UserDetail.js:76
-msgid "login type"
-msgstr "로그인 유형"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:203
-msgid "min"
-msgstr "분"
-
-#: screens/Template/Survey/MultipleChoiceField.js:76
-msgid "new choice"
-msgstr "새로운 선택"
-
-#: components/Pagination/Pagination.js:36
-#: components/Schedule/shared/FrequencyDetailSubform.js:480
-msgid "of"
-msgstr "/"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:215
-msgid "option to the"
-msgstr "옵션"
-
-#: components/Pagination/Pagination.js:25
-msgid "page"
-msgstr "페이지"
-
-#: components/Pagination/Pagination.js:26
-msgid "pages"
-msgstr "페이지"
-
-#: components/Pagination/Pagination.js:28
-msgid "per page"
-msgstr "페이지당"
-
-#: components/LaunchButton/ReLaunchDropDown.js:77
-#: components/LaunchButton/ReLaunchDropDown.js:100
-msgid "relaunch jobs"
-msgstr "작업 다시 시작"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:217
-msgid "sec"
-msgstr "초"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:238
-msgid "seconds"
-msgstr "초"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:58
-msgid "select module"
-msgstr "모듈 선택"
-
-#: screens/User/UserList/UserListItem.js:49
-msgid "social login"
-msgstr "소셜 로그인"
-
-#: screens/Template/shared/JobTemplateForm.js:346
-#: screens/Template/shared/WorkflowJobTemplateForm.js:188
-msgid "source control branch"
-msgstr "소스 제어 분기"
-
-#: screens/ActivityStream/ActivityStreamListItem.js:30
-msgid "system"
-msgstr "시스템"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:511
-msgid "timed out"
-msgstr "시간 초과"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:195
-msgid "toggle changes"
-msgstr "변경 사항 토글"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:516
-msgid "updated"
-msgstr "업데이트됨"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:190
-msgid "weekday"
-msgstr "평일"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:191
-msgid "weekend day"
-msgstr "주말"
-
-#: screens/Template/shared/WebhookSubForm.js:181
-msgid "workflow job template webhook key"
-msgstr "워크플로 작업 템플릿 webhook 키"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:65
-msgid "{0, plural, one {# source with sync failures.} other {# sources with sync failures.}}"
-msgstr "{0, plural, one {# source with sync failures.} other {# sources with sync failures.}}"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:115
-msgid "{0, plural, one {Are you sure you want delete the group below?} other {Are you sure you want delete the groups below?}}"
-msgstr "{0, plural, one {Are you sure you want delete the group below?} other {Are you sure you want delete the groups below?}}"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:86
-msgid "{0, plural, one {Delete Group?} other {Delete Groups?}}"
-msgstr "{0, plural, one {Delete Group?} other {Delete Groups?}}"
-
-#: util/validators.js:138
-msgid "{0, plural, one {Please enter a valid phone number.} other {Please enter valid phone numbers.}}"
-msgstr "{0, plural, one {Please enter a valid phone number.} other {Please enter valid phone numbers.}}"
-
-#: screens/Inventory/InventoryList/InventoryList.js:247
-msgid "{0, plural, one {The inventory will be in a pending status until the final delete is processed.} other {The inventories will be in a pending status until the final delete is processed.}}"
-msgstr "{0, plural, one {The inventory will be in a pending status until the final delete is processed.} other {The inventories will be in a pending status until the final delete is processed.}}"
-
-#: components/JobList/JobList.js:280
-msgid "{0, plural, one {The selected job cannot be deleted due to insufficient permission or a running job status} other {The selected jobs cannot be deleted due to insufficient permissions or a running job status}}"
-msgstr "{0, plural, one {The selected job cannot be deleted due to insufficient permission or a running job status} other {The selected jobs cannot be deleted due to insufficient permissions or a running job status}}"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:151
-msgid "{0, plural, one {This approval cannot be deleted due to insufficient permissions or a pending job status} other {These approvals cannot be deleted due to insufficient permissions or a pending job status}}"
-msgstr "{0, plural, one {This approval cannot be deleted due to insufficient permissions or a pending job status} other {These approvals cannot be deleted due to insufficient permissions or a pending job status}}"
-
-#: screens/Credential/CredentialList/CredentialList.js:198
-msgid "{0, plural, one {This credential is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these credentials could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This credential is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these credentials could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:164
-msgid "{0, plural, one {This credential type is currently being used by some credentials and cannot be deleted.} other {Credential types that are being used by credentials cannot be deleted. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This credential type is currently being used by some credentials and cannot be deleted.} other {Credential types that are being used by credentials cannot be deleted. Are you sure you want to delete anyway?}}"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:194
-msgid "{0, plural, one {This execution environment is currently being used by other resources. Are you sure you want to delete it?} other {These execution environments could be in use by other resources that rely on them. Are you sure you want to delete them anyway?}}"
-msgstr "{0, plural, one {This execution environment is currently being used by other resources. Are you sure you want to delete it?} other {These execution environments could be in use by other resources that rely on them. Are you sure you want to delete them anyway?}}"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:182
-msgid "{0, plural, one {This instance group is currently being by other resources. Are you sure you want to delete it?} other {Deleting these instance groups could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This instance group is currently being by other resources. Are you sure you want to delete it?} other {Deleting these instance groups could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:85
-msgid "{0, plural, one {This intance is currently being used by other resources. Are you sure you want to delete it?} other {Deprovisioning these instances could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This intance is currently being used by other resources. Are you sure you want to delete it?} other {Deprovisioning these instances could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Inventory/InventoryList/InventoryList.js:240
-msgid "{0, plural, one {This inventory is currently being used by some templates. Are you sure you want to delete it?} other {Deleting these inventories could impact some templates that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This inventory is currently being used by some templates. Are you sure you want to delete it?} other {Deleting these inventories could impact some templates that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:196
-msgid "{0, plural, one {This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?} other {Deleting these inventory sources could impact other resources that rely on them. Are you sure you want to delete anyway}}"
-msgstr "{0, plural, one {This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?} other {Deleting these inventory sources could impact other resources that rely on them. Are you sure you want to delete anyway}}"
-
-#: screens/Organization/OrganizationList/OrganizationList.js:166
-msgid "{0, plural, one {This organization is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these organizations could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This organization is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these organizations could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Project/ProjectList/ProjectList.js:252
-msgid "{0, plural, one {This project is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these projects could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This project is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these projects could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:209
-#: components/TemplateList/TemplateList.js:266
-msgid "{0, plural, one {This template is currently being used by some workflow nodes. Are you sure you want to delete it?} other {Deleting these templates could impact some workflow nodes that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This template is currently being used by some workflow nodes. Are you sure you want to delete it?} other {Deleting these templates could impact some workflow nodes that rely on them. Are you sure you want to delete anyway?}}"
-
-#: components/JobList/JobListCancelButton.js:72
-msgid "{0, plural, one {You cannot cancel the following job because it is not running:} other {You cannot cancel the following jobs because they are not running:}}"
-msgstr "{0, plural, one {You cannot cancel the following job because it is not running:} other {You cannot cancel the following jobs because they are not running:}}"
-
-#: components/JobList/JobListCancelButton.js:56
-msgid "{0, plural, one {You do not have permission to cancel the following job:} other {You do not have permission to cancel the following jobs:}}"
-msgstr "{0, plural, one {You do not have permission to cancel the following job:} other {You do not have permission to cancel the following jobs:}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:143
-msgid "{0, selectordinal, one {The first {dayOfWeek}} two {The second {dayOfWeek}} =3 {The third {dayOfWeek}} =4 {The fourth {dayOfWeek}} =5 {The fifth {dayOfWeek}}}"
-msgstr "{0, selectordinal, one {The first {dayOfWeek}} two {The second {dayOfWeek}} =3 {The third {dayOfWeek}} =4 {The fourth {dayOfWeek}} =5 {The fifth {dayOfWeek}}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:202
-msgid "{0, selectordinal, one {The first {weekday} of {month}} two {The second {weekday} of {month}} =3 {The third {weekday} of {month}} =4 {The fourth {weekday} of {month}} =5 {The fifth {weekday} of {month}}}"
-msgstr "{0, selectordinal, one {The first {weekday} of {month}} two {The second {weekday} of {month}} =3 {The third {weekday} of {month}} =4 {The fourth {weekday} of {month}} =5 {The fifth {weekday} of {month}}}"
-
-#: screens/ActivityStream/ActivityStreamListItem.js:28
-msgid "{0} (deleted)"
-msgstr "{0} (삭제됨)"
-
-#: components/ChipGroup/ChipGroup.js:13
-msgid "{0} more"
-msgstr "{0} 기타 정보"
-
-#: screens/Job/JobDetail/JobDetail.js:399
-msgid "{0} seconds"
-msgstr "{0} 초"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:95
-msgid "{automatedInstancesCount} since {automatedInstancesSinceDateTime}"
-msgstr "{automatedInstancesSinceDateTime} 이후 {automatedInstancesCount}"
-
-#: components/AppContainer/AppContainer.js:55
-msgid "{brandName} logo"
-msgstr "{brandName} 로고"
-
-#: components/DetailList/UserDateDetail.js:23
-msgid "{dateStr} by <0>{username}0>"
-msgstr "{dateStr} (<0>{username}0> 기준)"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:231
-#: screens/InstanceGroup/Instances/InstanceListItem.js:148
-#: screens/Instances/InstanceDetail/InstanceDetail.js:274
-#: screens/Instances/InstanceList/InstanceListItem.js:158
-#: screens/TopologyView/Tooltip.js:288
-msgid "{forks, plural, one {# fork} other {# forks}}"
-msgstr "{forks, plural, one {# fork} other {# forks}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:41
-msgid "{interval, plural, one {{interval} day} other {{interval} days}}"
-msgstr "{interval, plural, one {{interval} 일} other {{interval} 일}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:33
-msgid "{interval, plural, one {{interval} hour} other {{interval} hours}}"
-msgstr "{interval, plural, one {{interval} 시} other {{interval} 시}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:25
-msgid "{interval, plural, one {{interval} minute} other {{interval} minutes}}"
-msgstr "{interval, plural, one {{interval} 분} other {{interval} 분}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:57
-msgid "{interval, plural, one {{interval} month} other {{interval} months}}"
-msgstr "{interval, plural, one {{interval} 달} other {{interval} 달}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:49
-msgid "{interval, plural, one {{interval} week} other {{interval} weeks}}"
-msgstr "{interval, plural, one {{interval} 주} other {{interval} 주}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:65
-msgid "{interval, plural, one {{interval} year} other {{interval} years}}"
-msgstr "{interval, plural, one {{interval} 년} other {{interval} 년}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:198
-msgid "{intervalValue, plural, one {day} other {days}}"
-msgstr "{intervalValue, plural, one {day} other {days}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:196
-msgid "{intervalValue, plural, one {hour} other {hours}}"
-msgstr "{intervalValue, plural, one {hour} other {hours}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:194
-msgid "{intervalValue, plural, one {minute} other {minutes}}"
-msgstr "{intervalValue, plural, one {minute} other {minutes}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:202
-msgid "{intervalValue, plural, one {month} other {months}}"
-msgstr "{intervalValue, plural, one {month} other {months}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:200
-msgid "{intervalValue, plural, one {week} other {weeks}}"
-msgstr "{intervalValue, plural, one {week} other {weeks}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:204
-msgid "{intervalValue, plural, one {year} other {years}}"
-msgstr "{intervalValue, plural, one {year} other {years}}"
-
-#: components/PromptDetail/PromptDetail.js:44
-msgid "{minutes} min {seconds} sec"
-msgstr "{minutes} 분 {seconds} 초"
-
-#: components/JobList/JobListCancelButton.js:106
-msgid "{numJobsToCancel, plural, one {Cancel job} other {Cancel jobs}}"
-msgstr "{numJobsToCancel, plural, one {Cancel job} other {Cancel jobs}}"
-
-#: components/JobList/JobListCancelButton.js:168
-msgid "{numJobsToCancel, plural, one {This action will cancel the following job:} other {This action will cancel the following jobs:}}"
-msgstr "{numJobsToCancel, plural, one {This action will cancel the following job:} other {This action will cancel the following jobs:}}"
-
-#: components/JobList/JobListCancelButton.js:91
-msgid "{numJobsToCancel, plural, one {{0}} other {{1}}}"
-msgstr "{numJobsToCancel, plural, one {{0}} other {{1}}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:226
-msgid "{numOccurrences, plural, one {After {numOccurrences} occurrence} other {After {numOccurrences} occurrences}}"
-msgstr "{numOccurrences, plural, one {After {numOccurrences} occurrence} other {After {numOccurrences} occurrences}}"
-
-#: components/PaginatedTable/PaginatedTable.js:79
-msgid "{pluralizedItemName} List"
-msgstr "{pluralizedItemName} 목록"
-
-#: components/HealthCheckButton/HealthCheckButton.js:13
-msgid "{selectedItemsCount, plural, one {Click to run a health check on the selected instance.} other {Click to run a health check on the selected instances.}}"
-msgstr "{selectedItemsCount, plural, one {Click to run a health check on the selected instance.} other {Click to run a health check on the selected instances.}}"
-
-#: components/AppContainer/AppContainer.js:154
-msgid "{sessionCountdown, plural, one {You will be logged out in # second due to inactivity} other {You will be logged out in # seconds due to inactivity}}"
-msgstr "{sessionCountdown, plural, one {You will be logged out in # second due to inactivity} other {You will be logged out in # seconds due to inactivity}}"
diff --git a/awx/ui/src/locales/nl/messages.po b/awx/ui/src/locales/nl/messages.po
deleted file mode 100644
index 807ac819daf0..000000000000
--- a/awx/ui/src/locales/nl/messages.po
+++ /dev/null
@@ -1,10724 +0,0 @@
-msgid ""
-msgstr ""
-"POT-Creation-Date: 2018-12-10 10:08-0500\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: @lingui/cli\n"
-"Language: en\n"
-"Project-Id-Version: \n"
-"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: \n"
-"Last-Translator: \n"
-"Language-Team: \n"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:43
-msgid "(Limited to first 10)"
-msgstr "(Beperkt tot de eerste 10)"
-
-#: components/TemplateList/TemplateListItem.js:103
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:161
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:90
-msgid "(Prompt on launch)"
-msgstr "(Melding bij opstarten)"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:283
-msgid "* This field will be retrieved from an external secret management system using the specified credential."
-msgstr "* Dit veld wordt met behulp van de opgegeven referentie opgehaald uit een extern geheimbeheersysteem."
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:228
-msgid "/ (project root)"
-msgstr "/ (projectroot)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:10
-msgid "0 (Normal)"
-msgstr "0 (Normaal)"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:38
-msgid "0 (Warning)"
-msgstr "0 (Waarschuwing)"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:39
-msgid "1 (Info)"
-msgstr "1 (Info)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:11
-msgid "1 (Verbose)"
-msgstr "1 (Uitgebreid)"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:40
-msgid "2 (Debug)"
-msgstr "2 (Foutopsporing)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:12
-msgid "2 (More Verbose)"
-msgstr "2 (Meer verbaal)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:13
-msgid "3 (Debug)"
-msgstr "3 (Foutopsporing)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:14
-msgid "4 (Connection Debug)"
-msgstr "4 (Foutopsporing verbinding)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:15
-msgid "5 (WinRM Debug)"
-msgstr "5 (WinRM-foutopsporing)"
-
-#: screens/Project/shared/Project.helptext.js:76
-msgid ""
-"A refspec to fetch (passed to the Ansible git\n"
-"module). This parameter allows access to references via\n"
-"the branch field not otherwise available."
-msgstr "Een refspec om op te halen (doorgegeven aan de Ansible git-module). Deze parameter maakt toegang tot referenties mogelijk via het vertakkingsveld dat anders niet beschikbaar is."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:122
-msgid "A subscription manifest is an export of a Red Hat Subscription. To generate a subscription manifest, go to <0>access.redhat.com0>. For more information, see the <1>User Guide1>."
-msgstr "Een abonnementsmanifest is een export van een Red Hat-abonnement. Om een abonnementsmanifest te genereren, gaat u naar <0>access.redhat.com0>. Raadpleeg voor meer informatie de <1>Gebruikershandleiding1>."
-
-#: screens/Job/WorkflowOutput/WorkflowOutputNode.js:143
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:326
-msgid "ALL"
-msgstr "ALLE"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:284
-msgid "API Service/Integration Key"
-msgstr "Service-/integratiesleutel API"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:291
-msgid "API Token"
-msgstr "API-token"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:306
-msgid "API service/integration key"
-msgstr "Service-/integratiesleutel API"
-
-#: components/AppContainer/PageHeaderToolbar.js:125
-msgid "About"
-msgstr "Over"
-
-#: routeConfig.js:92
-#: screens/ActivityStream/ActivityStream.js:179
-#: screens/Credential/Credential.js:74
-#: screens/Credential/Credentials.js:29
-#: screens/Inventory/Inventories.js:59
-#: screens/Inventory/Inventory.js:65
-#: screens/Inventory/SmartInventory.js:67
-#: screens/Organization/Organization.js:124
-#: screens/Organization/Organizations.js:31
-#: screens/Project/Project.js:105
-#: screens/Project/Projects.js:27
-#: screens/Team/Team.js:58
-#: screens/Team/Teams.js:31
-#: screens/Template/Template.js:136
-#: screens/Template/Templates.js:45
-#: screens/Template/WorkflowJobTemplate.js:118
-msgid "Access"
-msgstr "Toegang"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:75
-msgid "Access Token Expiration"
-msgstr "Toegangstoken vervallen"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:352
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:421
-msgid "Account SID"
-msgstr "SID account"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:396
-msgid "Account token"
-msgstr "Accounttoken"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:50
-msgid "Action"
-msgstr "Actie"
-
-#: components/JobList/JobList.js:249
-#: components/JobList/JobListItem.js:103
-#: components/RelatedTemplateList/RelatedTemplateList.js:189
-#: components/Schedule/ScheduleList/ScheduleList.js:172
-#: components/Schedule/ScheduleList/ScheduleListItem.js:128
-#: components/SelectedList/DraggableSelectedList.js:101
-#: components/TemplateList/TemplateList.js:246
-#: components/TemplateList/TemplateListItem.js:195
-#: screens/ActivityStream/ActivityStream.js:266
-#: screens/ActivityStream/ActivityStreamListItem.js:49
-#: screens/Application/ApplicationsList/ApplicationListItem.js:48
-#: screens/Application/ApplicationsList/ApplicationsList.js:160
-#: screens/Credential/CredentialList/CredentialList.js:166
-#: screens/Credential/CredentialList/CredentialListItem.js:66
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:177
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:38
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:168
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:87
-#: screens/Host/HostGroups/HostGroupItem.js:34
-#: screens/Host/HostGroups/HostGroupsList.js:177
-#: screens/Host/HostList/HostList.js:172
-#: screens/Host/HostList/HostListItem.js:70
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:200
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:75
-#: screens/InstanceGroup/Instances/InstanceList.js:271
-#: screens/InstanceGroup/Instances/InstanceListItem.js:171
-#: screens/Instances/InstanceList/InstanceList.js:206
-#: screens/Instances/InstanceList/InstanceListItem.js:183
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:218
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:53
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:39
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:142
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:41
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:187
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:44
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:140
-#: screens/Inventory/InventoryList/InventoryList.js:222
-#: screens/Inventory/InventoryList/InventoryListItem.js:131
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:233
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:44
-#: screens/Inventory/InventorySources/InventorySourceList.js:214
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:101
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:102
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:73
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:181
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:124
-#: screens/Organization/OrganizationList/OrganizationList.js:146
-#: screens/Organization/OrganizationList/OrganizationListItem.js:69
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:86
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:19
-#: screens/Project/ProjectList/ProjectList.js:225
-#: screens/Project/ProjectList/ProjectListItem.js:222
-#: screens/Team/TeamList/TeamList.js:144
-#: screens/Team/TeamList/TeamListItem.js:47
-#: screens/Template/Survey/SurveyList.js:105
-#: screens/Template/Survey/SurveyList.js:105
-#: screens/Template/Survey/SurveyListItem.js:90
-#: screens/User/UserList/UserList.js:164
-#: screens/User/UserList/UserListItem.js:56
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:167
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:78
-msgid "Actions"
-msgstr "Acties"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:98
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:61
-#: components/TemplateList/TemplateListItem.js:277
-#: screens/Host/HostDetail/HostDetail.js:71
-#: screens/Host/HostList/HostListItem.js:95
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:217
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:50
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:76
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:100
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:32
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:115
-msgid "Activity"
-msgstr "Activiteit"
-
-#: routeConfig.js:49
-#: screens/ActivityStream/ActivityStream.js:43
-#: screens/ActivityStream/ActivityStream.js:121
-#: screens/Setting/Settings.js:44
-msgid "Activity Stream"
-msgstr "Activiteitenlogboek"
-
-#: screens/ActivityStream/ActivityStream.js:124
-msgid "Activity Stream type selector"
-msgstr "Keuzeschakelaar type activiteitenlogboek"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:169
-msgid "Actor"
-msgstr "Actor"
-
-#: components/AddDropDownButton/AddDropDownButton.js:40
-#: components/PaginatedTable/ToolbarAddButton.js:14
-msgid "Add"
-msgstr "Toevoegen"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkAddModal.js:14
-msgid "Add Link"
-msgstr "Link toevoegen"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.js:78
-msgid "Add Node"
-msgstr "Knooppunt toevoegen"
-
-#: screens/Template/Templates.js:49
-msgid "Add Question"
-msgstr "Vraag toevoegen"
-
-#: components/AddRole/AddResourceRole.js:164
-msgid "Add Roles"
-msgstr "Rollen toevoegen"
-
-#: components/AddRole/AddResourceRole.js:161
-msgid "Add Team Roles"
-msgstr "Teamrollen toevoegen"
-
-#: components/AddRole/AddResourceRole.js:158
-msgid "Add User Roles"
-msgstr "Gebruikersrollen toevoegen"
-
-#: components/Workflow/WorkflowStartNode.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:238
-msgid "Add a new node"
-msgstr "Een nieuw knooppunt toevoegen"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:49
-msgid "Add a new node between these two nodes"
-msgstr "Nieuw knooppunt toevoegen tussen deze twee knooppunten"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:108
-msgid "Add container group"
-msgstr "Containergroep toevoegen"
-
-#: components/Schedule/shared/ScheduleFormFields.js:171
-msgid "Add exceptions"
-msgstr "Uitzonderingen toevoegen"
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:140
-msgid "Add existing group"
-msgstr "Bestaande groep toevoegen"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:152
-msgid "Add existing host"
-msgstr "Bestaande host toevoegen"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:109
-msgid "Add instance group"
-msgstr "Instantiegroep toevoegen"
-
-#: screens/Inventory/InventoryList/InventoryList.js:136
-msgid "Add inventory"
-msgstr "Inventaris toevoegen"
-
-#: components/TemplateList/TemplateList.js:151
-msgid "Add job template"
-msgstr "Taaksjabloon toevoegen"
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:141
-msgid "Add new group"
-msgstr "Nieuwe groep toevoegen"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:153
-msgid "Add new host"
-msgstr "Nieuwe host toevoegen"
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:77
-msgid "Add resource type"
-msgstr "Brontype toevoegen"
-
-#: screens/Inventory/InventoryList/InventoryList.js:137
-msgid "Add smart inventory"
-msgstr "Smart-inventaris toevoegen"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:202
-msgid "Add team permissions"
-msgstr "Teammachtigingen toevoegen"
-
-#: screens/User/UserRoles/UserRolesList.js:198
-msgid "Add user permissions"
-msgstr "Gebruikersmachtigingen toevoegen"
-
-#: components/TemplateList/TemplateList.js:152
-msgid "Add workflow template"
-msgstr "Workflowsjabloon toevoegen"
-
-#: screens/TopologyView/Legend.js:269
-msgid "Adding"
-msgstr "Het toevoegen van"
-
-#: routeConfig.js:113
-#: screens/ActivityStream/ActivityStream.js:190
-msgid "Administration"
-msgstr "Beheer"
-
-#: components/DataListToolbar/DataListToolbar.js:139
-#: screens/Job/JobOutput/JobOutputSearch.js:136
-msgid "Advanced"
-msgstr "Geavanceerd"
-
-#: components/Search/AdvancedSearch.js:318
-msgid "Advanced search documentation"
-msgstr "Documentatie over geavanceerd zoeken"
-
-#: components/Search/AdvancedSearch.js:211
-#: components/Search/AdvancedSearch.js:225
-msgid "Advanced search value input"
-msgstr "Geavanceerde invoer zoekwaarden"
-
-#: screens/Inventory/shared/Inventory.helptext.js:131
-msgid ""
-"After every project update where the SCM revision\n"
-"changes, refresh the inventory from the selected source\n"
-"before executing job tasks. This is intended for static content,\n"
-"like the Ansible inventory .ini file format."
-msgstr "Na iedere projectupdate waarbij de SCM-revisie verandert, dient het inventaris vernieuwd te worden vanuit de geselecteerde bron voordat de opdrachten die bij de taak horen uitgevoerd worden. Dit is bedoeld voor statische content, zoals .ini, het inventarisbestandsformaat van Ansible."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:524
-msgid "After number of occurrences"
-msgstr "Na aantal voorvallen"
-
-#: components/AlertModal/AlertModal.js:75
-msgid "Alert modal"
-msgstr "Waarschuwingsmodus"
-
-#: components/LaunchButton/ReLaunchDropDown.js:48
-#: components/PromptDetail/PromptDetail.js:132
-#: screens/Metrics/Metrics.js:82
-#: screens/Metrics/Metrics.js:82
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:266
-msgid "All"
-msgstr "Alle"
-
-#: screens/Dashboard/DashboardGraph.js:137
-msgid "All job types"
-msgstr "Alle taaktypen"
-
-#: screens/Dashboard/DashboardGraph.js:162
-msgid "All jobs"
-msgstr "Alle taken"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:101
-msgid "Allow Branch Override"
-msgstr "Overschrijven van vertakking toelaten"
-
-#: components/PromptDetail/PromptProjectDetail.js:66
-#: screens/Project/ProjectDetail/ProjectDetail.js:121
-msgid "Allow branch override"
-msgstr "Overschrijven van vertakking toelaten"
-
-#: screens/Project/shared/Project.helptext.js:126
-msgid ""
-"Allow changing the Source Control branch or revision in a job\n"
-"template that uses this project."
-msgstr "Wijzigen van de broncontrolevertakking of de revisie toelaten in een taaksjabloon die gebruik maakt van dit project."
-
-#: screens/Application/shared/Application.helptext.js:6
-msgid "Allowed URIs list, space separated"
-msgstr "Lijst met toegestane URI's, door spaties gescheiden"
-
-#: components/Workflow/WorkflowLegend.js:130
-#: components/Workflow/WorkflowLinkHelp.js:24
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:58
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:46
-msgid "Always"
-msgstr "Altijd"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:98
-msgid "Amazon EC2"
-msgstr "Amazon EC2"
-
-#: components/Lookup/shared/LookupErrorMessage.js:12
-msgid "An error occurred"
-msgstr "Er is een fout opgetreden"
-
-#: components/LaunchPrompt/steps/useInventoryStep.js:35
-msgid "An inventory must be selected"
-msgstr "Er moet een inventaris worden gekozen"
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:96
-msgid "Ansible Controller Documentation."
-msgstr "Ansible Controller Documentatie."
-
-#: screens/Template/Survey/SurveyQuestionForm.js:43
-msgid "Answer type"
-msgstr "Antwoordtype"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:177
-msgid "Answer variable name"
-msgstr "Antwoord naam variabele"
-
-#: components/PromptDetail/PromptDetail.js:132
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:263
-msgid "Any"
-msgstr "Iedere"
-
-#: components/Lookup/ApplicationLookup.js:83
-#: screens/User/UserTokenDetail/UserTokenDetail.js:39
-#: screens/User/shared/UserTokenForm.js:48
-msgid "Application"
-msgstr "Toepassing"
-
-#: screens/User/UserTokenList/UserTokenList.js:187
-msgid "Application Name"
-msgstr "Toepassingsnaam"
-
-#: screens/Application/Applications.js:67
-#: screens/Application/Applications.js:70
-msgid "Application information"
-msgstr "Toepassingsinformatie"
-
-#: screens/User/UserTokenList/UserTokenList.js:123
-#: screens/User/UserTokenList/UserTokenList.js:134
-msgid "Application name"
-msgstr "Toepassingsnaam"
-
-#: screens/Application/Application/Application.js:95
-msgid "Application not found."
-msgstr "Toepassing niet gevonden."
-
-#: components/Lookup/ApplicationLookup.js:96
-#: routeConfig.js:142
-#: screens/Application/Applications.js:26
-#: screens/Application/Applications.js:35
-#: screens/Application/ApplicationsList/ApplicationsList.js:113
-#: screens/Application/ApplicationsList/ApplicationsList.js:148
-#: util/getRelatedResourceDeleteDetails.js:209
-msgid "Applications"
-msgstr "Toepassingen"
-
-#: screens/ActivityStream/ActivityStream.js:211
-msgid "Applications & Tokens"
-msgstr "Toepassingen en tokens"
-
-#: components/NotificationList/NotificationListItem.js:39
-#: components/NotificationList/NotificationListItem.js:40
-#: components/Workflow/WorkflowLegend.js:114
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:67
-msgid "Approval"
-msgstr "Goedkeuring"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:82
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:44
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:47
-msgid "Approve"
-msgstr "Goedkeuring"
-
-#: components/StatusLabel/StatusLabel.js:39
-msgid "Approved"
-msgstr "Goedgekeurd"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:11
-msgid "Approved - {0}. See the Activity Stream for more information."
-msgstr "Goedgekeurd - {0}. Zie de activiteitenstroom voor meer informatie."
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:7
-msgid "Approved by {0} - {1}"
-msgstr "Goedgekeurd door {0} - {1}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:162
-#: components/Schedule/shared/FrequencyDetailSubform.js:113
-msgid "April"
-msgstr "April"
-
-#: components/JobCancelButton/JobCancelButton.js:104
-msgid "Are you sure you want to cancel this job?"
-msgstr "Weet u zeker dat u deze taak wilt annuleren?"
-
-#: components/DeleteButton/DeleteButton.js:127
-msgid "Are you sure you want to delete:"
-msgstr "Weet u zeker dat u dit wilt verwijderen:"
-
-#: screens/Setting/shared/SharedFields.js:142
-msgid "Are you sure you want to disable local authentication? Doing so could impact users' ability to log in and the system administrator's ability to reverse this change."
-msgstr "Weet u zeker dat u lokale authenticatie wilt uitschakelen? Als u dat doet, kan dat gevolgen hebben voor de mogelijkheid van gebruikers om in te loggen en voor de mogelijkheid van de systeembeheerder om deze wijziging terug te draaien."
-
-#: screens/Setting/shared/SharedFields.js:350
-msgid "Are you sure you want to edit login redirect override URL? Doing so could impact users' ability to log in to the system once local authentication is also disabled."
-msgstr "Weet u zeker dat u de login redirect override URL wilt bewerken? Als u dat doet, kan dat invloed hebben op de mogelijkheid van gebruikers om in te loggen op het systeem als de lokale authenticatie ook is uitgeschakeld."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:41
-msgid "Are you sure you want to exit the Workflow Creator without saving your changes?"
-msgstr "Weet u zeker dat u de workflowcreator wil verlaten zonder uw wijzigingen op te slaan?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:40
-msgid "Are you sure you want to remove all the nodes in this workflow?"
-msgstr "Weet u zeker dat u alle knooppunten in deze workflow wilt verwijderen?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:56
-msgid "Are you sure you want to remove the node below:"
-msgstr "Weet u zeker dat u het onderstaande knooppunt wilt verwijderen:"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:43
-msgid "Are you sure you want to remove this link?"
-msgstr "Weet u zeker dat u deze link wilt verwijderen?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:63
-msgid "Are you sure you want to remove this node?"
-msgstr "Weet u zeker dat u dit knooppunt wilt verwijderen?"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:43
-msgid "Are you sure you want to remove {0} access from {1}? Doing so affects all members of the team."
-msgstr "Weet u zeker dat u de {0} toegang vanuit {1} wilt verwijderen? Als u dat doet, heeft dat gevolgen voor alle leden van het team."
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:50
-msgid "Are you sure you want to remove {0} access from {username}?"
-msgstr "Weet u zeker dat u de {0} toegang vanuit {username} wilt verwijderen?"
-
-#: screens/Job/JobOutput/JobOutput.js:826
-msgid "Are you sure you want to submit the request to cancel this job?"
-msgstr "Weet u zeker dat u het verzoek om deze taak te annuleren in wilt dienen?"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:102
-#: components/AdHocCommands/AdHocDetailsStep.js:104
-msgid "Arguments"
-msgstr "Argumenten"
-
-#: screens/Job/JobDetail/JobDetail.js:559
-msgid "Artifacts"
-msgstr "Artefacten"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:233
-#: screens/User/UserTeams/UserTeamList.js:208
-msgid "Associate"
-msgstr "Associate"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:244
-#: screens/User/UserRoles/UserRolesList.js:240
-msgid "Associate role error"
-msgstr "Fout in geassocieerde rol"
-
-#: components/AssociateModal/AssociateModal.js:98
-msgid "Association modal"
-msgstr "Associatiemodus"
-
-#: components/LaunchPrompt/steps/SurveyStep.js:168
-msgid "At least one value must be selected for this field."
-msgstr "Voor dit veld moet ten minste één waarde worden geselecteerd."
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:166
-#: components/Schedule/shared/FrequencyDetailSubform.js:133
-msgid "August"
-msgstr "Augustus"
-
-#: screens/Setting/SettingList.js:52
-msgid "Authentication"
-msgstr "Authenticatie"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:88
-msgid "Authorization Code Expiration"
-msgstr "Machtigingscode vervallen"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:81
-#: screens/Application/shared/ApplicationForm.js:85
-msgid "Authorization grant type"
-msgstr "Type authenticatieverlening"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:215
-#: screens/InstanceGroup/Instances/InstanceListItem.js:204
-#: screens/Instances/InstanceDetail/InstanceDetail.js:209
-#: screens/Instances/InstanceList/InstanceListItem.js:219
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:83
-msgid "Auto"
-msgstr "Auto"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:71
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:72
-msgid "Automation Analytics"
-msgstr "Automatiseringsanalyse"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:111
-msgid "Automation Analytics dashboard"
-msgstr "Dashboard automatiseringsanalyse"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:175
-msgid "Automation controller version"
-msgstr "Versie automatiseringscontroller"
-
-#: screens/Setting/Settings.js:47
-msgid "Azure AD"
-msgstr "Azure AD"
-
-#: screens/Setting/SettingList.js:57
-msgid "Azure AD settings"
-msgstr "Azure AD-instellingen"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:49
-#: components/AddRole/AddResourceRole.js:267
-#: components/LaunchPrompt/LaunchPrompt.js:158
-#: components/Schedule/shared/SchedulePromptableFields.js:125
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:90
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:70
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:154
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:157
-msgid "Back"
-msgstr "Terug"
-
-#: screens/Credential/Credential.js:65
-msgid "Back to Credentials"
-msgstr "Terug naar toegangsgegevens"
-
-#: components/ContentError/ContentError.js:43
-msgid "Back to Dashboard."
-msgstr "Terug naar dashboard."
-
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:49
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:50
-msgid "Back to Groups"
-msgstr "Terug naar groepen"
-
-#: screens/Host/Host.js:50
-#: screens/Inventory/InventoryHost/InventoryHost.js:66
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:53
-msgid "Back to Hosts"
-msgstr "Terug naar hosts"
-
-#: screens/InstanceGroup/InstanceGroup.js:61
-msgid "Back to Instance Groups"
-msgstr "Terug naar instantiegroepen"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:173
-#: screens/Instances/Instance.js:22
-msgid "Back to Instances"
-msgstr "Terug naar instanties"
-
-#: screens/Inventory/Inventory.js:57
-#: screens/Inventory/SmartInventory.js:60
-msgid "Back to Inventories"
-msgstr "Terug naar inventarissen"
-
-#: screens/Job/Job.js:123
-msgid "Back to Jobs"
-msgstr "Terug naar taken"
-
-#: screens/NotificationTemplate/NotificationTemplate.js:76
-msgid "Back to Notifications"
-msgstr "Terug naar berichten"
-
-#: screens/Organization/Organization.js:116
-msgid "Back to Organizations"
-msgstr "Terug naar organisaties"
-
-#: screens/Project/Project.js:97
-msgid "Back to Projects"
-msgstr "Terug naar projecten"
-
-#: components/Schedule/Schedule.js:64
-msgid "Back to Schedules"
-msgstr "Terug naar schema's"
-
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:44
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:78
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:44
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:58
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:95
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:69
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:43
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:90
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:44
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:49
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:45
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:34
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:49
-#: screens/Setting/UI/UIDetail/UIDetail.js:59
-msgid "Back to Settings"
-msgstr "Terug naar instellingen"
-
-#: screens/Inventory/InventorySource/InventorySource.js:76
-msgid "Back to Sources"
-msgstr "Terug naar bronnen"
-
-#: screens/Team/Team.js:50
-msgid "Back to Teams"
-msgstr "Terug naar teams"
-
-#: screens/Template/Template.js:128
-#: screens/Template/WorkflowJobTemplate.js:110
-msgid "Back to Templates"
-msgstr "Terug naar sjablonen"
-
-#: screens/User/UserToken/UserToken.js:47
-msgid "Back to Tokens"
-msgstr "Terug naar tokens"
-
-#: screens/User/User.js:57
-msgid "Back to Users"
-msgstr "Terug naar gebruikers"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:69
-msgid "Back to Workflow Approvals"
-msgstr "Terug naar workflowgoedkeuringen"
-
-#: screens/Application/Application/Application.js:72
-msgid "Back to applications"
-msgstr "Terug naar toepassingen"
-
-#: screens/CredentialType/CredentialType.js:55
-msgid "Back to credential types"
-msgstr "Terug naar typen toegangsgegevens"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:57
-msgid "Back to execution environments"
-msgstr "Terug naar uitvoeringsomgevingen"
-
-#: screens/InstanceGroup/ContainerGroup.js:59
-msgid "Back to instance groups"
-msgstr "Terug naar instantiegroepen"
-
-#: screens/ManagementJob/ManagementJob.js:98
-msgid "Back to management jobs"
-msgstr "Terug naar beheerderstaken"
-
-#: screens/Project/shared/Project.helptext.js:8
-msgid ""
-"Base path used for locating playbooks. Directories\n"
-"found inside this path will be listed in the playbook directory drop-down.\n"
-"Together the base path and selected playbook directory provide the full\n"
-"path used to locate playbooks."
-msgstr "Basispad dat gebruikt wordt voor het vinden van draaiboeken. Mappen die binnen dit pad gevonden worden, zullen in het uitklapbare menu van de draaiboekmap genoemd worden. Het basispad en de gekozen draaiboekmap bieden samen het volledige pad dat gebruikt wordt om draaiboeken te vinden."
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:448
-msgid "Basic auth password"
-msgstr "Wachtwoord basisauthenticatie"
-
-#: screens/Project/shared/Project.helptext.js:104
-msgid ""
-"Branch to checkout. In addition to branches,\n"
-"you can input tags, commit hashes, and arbitrary refs. Some\n"
-"commit hashes and refs may not be available unless you also\n"
-"provide a custom refspec."
-msgstr "Vertakking naar de kassa. Naast vertakkingen kunt u ook tags, commit hashes en willekeurige refs invoeren. Sommige commit hashes en refs zijn mogelijk niet beschikbaar, tenzij u ook een aangepaste refspec aanlevert."
-
-#: screens/Template/shared/JobTemplate.helptext.js:27
-msgid "Branch to use in job run. Project default used if blank. Only allowed if project allow_override field is set to true."
-msgstr "Vertakking om bij het uitvoeren van een klus te gebruiken. Projectstandaard wordt gebruikt indien leeg. Alleen toegestaan als project allow_override field is ingesteld op true."
-
-#: components/About/About.js:45
-msgid "Brand Image"
-msgstr "Merkimago"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:158
-msgid "Browse"
-msgstr "Bladeren"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:94
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:115
-msgid "Browse…"
-msgstr "Bladeren..."
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:36
-msgid "By default, we collect and transmit analytics data on the service usage to Red Hat. There are two categories of data collected by the service. For more information, see <0>this Tower documentation page0>. Uncheck the following boxes to disable this feature."
-msgstr "Standaard verzamelen wij analytische gegevens over het gebruik van de service en sturen deze door naar Red Hat. Er zijn twee categorieën gegevens die door de service worden verzameld. Zie voor meer informatie <0>deze Tower-documentatiepagina0>. Schakel de volgende vakjes uit om deze functie uit te schakelen."
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:228
-#: screens/InstanceGroup/Instances/InstanceListItem.js:145
-#: screens/Instances/InstanceDetail/InstanceDetail.js:271
-#: screens/Instances/InstanceList/InstanceListItem.js:155
-#: screens/TopologyView/Tooltip.js:285
-msgid "CPU {0}"
-msgstr "CPU {0}"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:102
-#: components/PromptDetail/PromptProjectDetail.js:151
-#: screens/Project/ProjectDetail/ProjectDetail.js:268
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:118
-msgid "Cache Timeout"
-msgstr "Cache time-out"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:237
-msgid "Cache timeout"
-msgstr "Cache time-out"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:106
-msgid "Cache timeout (seconds)"
-msgstr "Cache time-out (seconden)"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:50
-#: components/AddRole/AddResourceRole.js:268
-#: components/AssociateModal/AssociateModal.js:114
-#: components/AssociateModal/AssociateModal.js:119
-#: components/DeleteButton/DeleteButton.js:120
-#: components/DeleteButton/DeleteButton.js:123
-#: components/DisassociateButton/DisassociateButton.js:139
-#: components/DisassociateButton/DisassociateButton.js:142
-#: components/FormActionGroup/FormActionGroup.js:23
-#: components/FormActionGroup/FormActionGroup.js:29
-#: components/LaunchPrompt/LaunchPrompt.js:159
-#: components/Lookup/HostFilterLookup.js:388
-#: components/Lookup/Lookup.js:209
-#: components/PaginatedTable/ToolbarDeleteButton.js:282
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:37
-#: components/Schedule/shared/ScheduleForm.js:548
-#: components/Schedule/shared/ScheduleForm.js:553
-#: components/Schedule/shared/SchedulePromptableFields.js:126
-#: components/Schedule/shared/UnsupportedScheduleForm.js:22
-#: components/Schedule/shared/UnsupportedScheduleForm.js:27
-#: screens/Credential/shared/CredentialForm.js:343
-#: screens/Credential/shared/CredentialForm.js:348
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:100
-#: screens/Credential/shared/ExternalTestModal.js:98
-#: screens/Instances/Shared/RemoveInstanceButton.js:174
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:111
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:63
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:66
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:80
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:101
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:107
-#: screens/Setting/shared/RevertAllAlert.js:32
-#: screens/Setting/shared/RevertFormActionGroup.js:31
-#: screens/Setting/shared/RevertFormActionGroup.js:37
-#: screens/Setting/shared/SharedFields.js:133
-#: screens/Setting/shared/SharedFields.js:139
-#: screens/Setting/shared/SharedFields.js:346
-#: screens/Team/TeamRoles/TeamRolesList.js:228
-#: screens/Team/TeamRoles/TeamRolesList.js:231
-#: screens/Template/Survey/SurveyList.js:78
-#: screens/Template/Survey/SurveyReorderModal.js:211
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:31
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:39
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:45
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:50
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:164
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:167
-#: screens/User/UserRoles/UserRolesList.js:224
-#: screens/User/UserRoles/UserRolesList.js:227
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:104
-msgid "Cancel"
-msgstr "Annuleren"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:300
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:112
-msgid "Cancel Inventory Source Sync"
-msgstr "Synchronisatie van inventarisbron annuleren"
-
-#: components/JobCancelButton/JobCancelButton.js:69
-#: screens/Job/JobOutput/JobOutput.js:802
-#: screens/Job/JobOutput/JobOutput.js:803
-msgid "Cancel Job"
-msgstr "Taak annuleren"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:321
-#: screens/Project/ProjectList/ProjectListItem.js:230
-msgid "Cancel Project Sync"
-msgstr "Projectsynchronisatie annuleren"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:302
-#: screens/Project/ProjectDetail/ProjectDetail.js:323
-msgid "Cancel Sync"
-msgstr "Synchronisatie annuleren"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:322
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:327
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:95
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:101
-msgid "Cancel Workflow"
-msgstr "Workflow annuleren"
-
-#: screens/Job/JobOutput/JobOutput.js:810
-#: screens/Job/JobOutput/JobOutput.js:813
-msgid "Cancel job"
-msgstr "Taak annuleren"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:42
-msgid "Cancel link changes"
-msgstr "Linkwijzigingen annuleren"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:34
-msgid "Cancel link removal"
-msgstr "Verwijdering van link annuleren"
-
-#: components/Lookup/Lookup.js:207
-msgid "Cancel lookup"
-msgstr "Opzoeken annuleren"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:28
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:47
-msgid "Cancel node removal"
-msgstr "Verwijdering van knooppunt annuleren"
-
-#: screens/Setting/shared/RevertAllAlert.js:29
-msgid "Cancel revert"
-msgstr "Terugzetten annuleren"
-
-#: components/JobList/JobListCancelButton.js:93
-msgid "Cancel selected job"
-msgstr "Geselecteerde taak annuleren"
-
-#: components/JobList/JobListCancelButton.js:94
-msgid "Cancel selected jobs"
-msgstr "Geselecteerde taken annuleren"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:77
-msgid "Cancel subscription edit"
-msgstr "Abonnement bewerken annuleren"
-
-#: components/JobList/JobListItem.js:113
-#: screens/Job/JobDetail/JobDetail.js:600
-#: screens/Job/JobOutput/shared/OutputToolbar.js:137
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:89
-msgid "Cancel {0}"
-msgstr "Annuleren {0}"
-
-#: components/JobList/JobList.js:234
-#: components/StatusLabel/StatusLabel.js:54
-#: components/Workflow/WorkflowNodeHelp.js:111
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:212
-msgid "Canceled"
-msgstr "Geannuleerd"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:129
-msgid ""
-"Cannot enable log aggregator without providing\n"
-"logging aggregator host and logging aggregator type."
-msgstr "Kan aggregator logboekregistraties niet inschakelen zonder de host en het type ervan te verstrekken."
-
-#: screens/Instances/InstanceList/InstanceList.js:199
-#: screens/Instances/InstancePeers/InstancePeerList.js:94
-msgid "Cannot run health check on hop nodes."
-msgstr "Kan geen gezondheidscontrole uitvoeren voor hop-knooppunten."
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:199
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:74
-#: screens/TopologyView/Tooltip.js:312
-msgid "Capacity"
-msgstr "Capaciteit"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:225
-#: screens/InstanceGroup/Instances/InstanceList.js:269
-#: screens/InstanceGroup/Instances/InstanceListItem.js:143
-#: screens/Instances/InstanceDetail/InstanceDetail.js:267
-#: screens/Instances/InstanceList/InstanceList.js:204
-#: screens/Instances/InstanceList/InstanceListItem.js:153
-msgid "Capacity Adjustment"
-msgstr "Capaciteitsaanpassing"
-
-#: components/Search/LookupTypeInput.js:59
-msgid "Case-insensitive version of contains"
-msgstr "Hoofdletterongevoelige versie van bevat"
-
-#: components/Search/LookupTypeInput.js:87
-msgid "Case-insensitive version of endswith."
-msgstr "Hoofdletterongevoelige versie van endswith."
-
-#: components/Search/LookupTypeInput.js:45
-msgid "Case-insensitive version of exact."
-msgstr "Hoofdletterongevoelige versie van exact."
-
-#: components/Search/LookupTypeInput.js:100
-msgid "Case-insensitive version of regex."
-msgstr "Hoofdletterongevoelige versie van regex."
-
-#: components/Search/LookupTypeInput.js:73
-msgid "Case-insensitive version of startswith."
-msgstr "Hoofdletterongevoelige versie van startswith."
-
-#: screens/Project/shared/Project.helptext.js:14
-msgid ""
-"Change PROJECTS_ROOT when deploying\n"
-"{brandName} to change this location."
-msgstr "Wijzig PROJECTS_ROOT bij het uitrollen van\n"
-"{brandName} om deze locatie te wijzigen."
-
-#: components/StatusLabel/StatusLabel.js:55
-#: screens/Job/JobOutput/shared/HostStatusBar.js:43
-msgid "Changed"
-msgstr "Gewijzigd"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:53
-msgid "Changes"
-msgstr "Wijzigingen"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:258
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:266
-msgid "Channel"
-msgstr "Kanaal"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:138
-#: screens/Template/shared/JobTemplateForm.js:218
-msgid "Check"
-msgstr "Controleren"
-
-#: components/Search/LookupTypeInput.js:134
-msgid "Check whether the given field or related object is null; expects a boolean value."
-msgstr "Controleert of het gegeven veld of verwante object null is; verwacht een booleaanse waarde."
-
-#: components/Search/LookupTypeInput.js:140
-msgid "Check whether the given field's value is present in the list provided; expects a comma-separated list of items."
-msgstr "Controleert of de waarde van het opgegeven veld voorkomt in de opgegeven lijst; verwacht een door komma's gescheiden lijst met items."
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:32
-msgid "Choose a .json file"
-msgstr "Kies een .json-bestand"
-
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:78
-msgid "Choose a Notification Type"
-msgstr "Kies een type bericht"
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:25
-msgid "Choose a Playbook Directory"
-msgstr "Kies een draaiboekmap"
-
-#: screens/Project/shared/ProjectForm.js:268
-msgid "Choose a Source Control Type"
-msgstr "Kies een broncontroletype"
-
-#: screens/Template/shared/WebhookSubForm.js:100
-msgid "Choose a Webhook Service"
-msgstr "Kies een Webhookservice"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:131
-#: screens/Template/shared/JobTemplateForm.js:211
-msgid "Choose a job type"
-msgstr "Kies een soort taak"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:82
-msgid "Choose a module"
-msgstr "Kies een module"
-
-#: screens/Inventory/shared/InventorySourceForm.js:139
-msgid "Choose a source"
-msgstr "Kies een bron"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:490
-msgid "Choose an HTTP method"
-msgstr "Kies een HTTP-methode"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:46
-msgid ""
-"Choose an answer type or format you want as the prompt for the user.\n"
-"Refer to the Ansible Controller Documentation for more additional\n"
-"information about each option."
-msgstr "Kies een antwoordtype of -formaat dat u als melding voor de gebruiker wilt. Raadpleeg de documentatie van Ansible Tower voor meer informatie over iedere optie."
-
-#: components/AddRole/SelectRoleStep.js:20
-msgid "Choose roles to apply to the selected resources. Note that all selected roles will be applied to all selected resources."
-msgstr "Kies de rollen die op de geselecteerde bronnen moeten worden toegepast. Alle geselecteerde rollen worden toegepast op alle geselecteerde bronnen."
-
-#: components/AddRole/SelectResourceStep.js:81
-msgid "Choose the resources that will be receiving new roles. You'll be able to select the roles to apply in the next step. Note that the resources chosen here will receive all roles chosen in the next step."
-msgstr "Kies de bronnen die nieuwe rollen gaan ontvangen. U kunt de rollen selecteren die u in de volgende stap wilt toepassen. Merk op dat de hier gekozen bronnen alle rollen ontvangen die in de volgende stap worden gekozen."
-
-#: components/AddRole/AddResourceRole.js:174
-msgid "Choose the type of resource that will be receiving new roles. For example, if you'd like to add new roles to a set of users please choose Users and click Next. You'll be able to select the specific resources in the next step."
-msgstr "Kies het type bron dat de nieuwe rollen gaat ontvangen. Als u bijvoorbeeld nieuwe rollen wilt toevoegen aan een groep gebruikers, kies dan Gebruikers en klik op Volgende. In de volgende stap kunt u de specifieke bronnen selecteren."
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:74
-msgid "Clean"
-msgstr "Opschonen"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:95
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:116
-msgid "Clear"
-msgstr "Wissen"
-
-#: components/DataListToolbar/DataListToolbar.js:95
-#: screens/Job/JobOutput/JobOutputSearch.js:144
-msgid "Clear all filters"
-msgstr "Alle filters wissen"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:247
-msgid "Clear subscription"
-msgstr "Abonnement wissen"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:252
-msgid "Clear subscription selection"
-msgstr "Abonnementskeuze wissen"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerGraph.js:245
-msgid "Click an available node to create a new link. Click outside the graph to cancel."
-msgstr "Klik op een beschikbaar knooppunt om een nieuwe link te maken. Klik buiten de grafiek om te annuleren."
-
-#: screens/TopologyView/Tooltip.js:191
-msgid "Click on a node icon to display the details."
-msgstr "Klik op een knooppuntpictogram om de details weer te geven."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:134
-msgid "Click the Edit button below to reconfigure the node."
-msgstr "Klik op de knop Bewerken hieronder om het knooppunt opnieuw te configureren."
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:71
-msgid "Click this button to verify connection to the secret management system using the selected credential and specified inputs."
-msgstr "Klik op deze knop om de verbinding met het geheimbeheersysteem te verifiëren met behulp van de geselecteerde referenties en de opgegeven inputs."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:179
-msgid "Click to create a new link to this node."
-msgstr "Klik om een nieuwe link naar dit knooppunt te maken."
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:251
-msgid "Click to download bundle"
-msgstr "Klik om de bundel te downloaden"
-
-#: screens/Template/Survey/SurveyToolbar.js:64
-msgid "Click to rearrange the order of the survey questions"
-msgstr "Klik op om de volgorde van de enquêtevragen te wijzigen"
-
-#: screens/Template/Survey/MultipleChoiceField.js:117
-msgid "Click to toggle default value"
-msgstr "Klik om de standaardwaarde te wijzigen"
-
-#: components/Workflow/WorkflowNodeHelp.js:202
-msgid "Click to view job details"
-msgstr "Klik om de taakdetails weer te geven"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:89
-#: screens/Application/Applications.js:84
-msgid "Client ID"
-msgstr "Client-id"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:289
-msgid "Client Identifier"
-msgstr "Clientidentificatie"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:314
-msgid "Client identifier"
-msgstr "Clientidentificatie"
-
-#: screens/Application/Applications.js:97
-msgid "Client secret"
-msgstr "Clientgeheim"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:100
-#: screens/Application/shared/ApplicationForm.js:127
-msgid "Client type"
-msgstr "Type client"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:105
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:169
-msgid "Close"
-msgstr "Sluiten"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:123
-msgid "Close subscription modal"
-msgstr "Inschrijvingsmodus sluiten"
-
-#: components/CredentialChip/CredentialChip.js:11
-msgid "Cloud"
-msgstr "Cloud"
-
-#: components/ExpandCollapse/ExpandCollapse.js:41
-msgid "Collapse"
-msgstr "Samenvouwen"
-
-#: screens/Job/JobOutput/PageControls.js:50
-msgid "Collapse all job events"
-msgstr "Alle taakgebeurtenissen samenvouwen"
-
-#: screens/Job/JobOutput/shared/JobEventLineToggle.js:39
-msgid "Collapse section"
-msgstr "Sectie samenvouwen"
-
-#: components/JobList/JobList.js:214
-#: components/JobList/JobListItem.js:45
-#: screens/Job/JobOutput/HostEventModal.js:129
-msgid "Command"
-msgstr "Opdracht"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:66
-msgid "Compliant"
-msgstr "Compliant"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:68
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:36
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:132
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:58
-#: screens/Template/shared/JobTemplateForm.js:591
-msgid "Concurrent Jobs"
-msgstr "Gelijktijdige taken"
-
-#: screens/Template/shared/JobTemplate.helptext.js:38
-msgid "Concurrent jobs: If enabled, simultaneous runs of this job template will be allowed."
-msgstr "Indien deze mogelijkheid ingeschakeld is, zijn gelijktijdige uitvoeringen van dit taaksjabloon toegestaan."
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:25
-msgid "Concurrent jobs: If enabled, simultaneous runs of this workflow job template will be allowed."
-msgstr "Indien deze mogelijkheid ingeschakeld is, zijn gelijktijdige uitvoeringen van deze workflow-taaksjabloon toegestaan."
-
-#: screens/Setting/shared/SharedFields.js:121
-#: screens/Setting/shared/SharedFields.js:127
-#: screens/Setting/shared/SharedFields.js:336
-msgid "Confirm"
-msgstr "Bevestigen"
-
-#: components/DeleteButton/DeleteButton.js:107
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:95
-msgid "Confirm Delete"
-msgstr "Verwijderen bevestigen"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:191
-msgid "Confirm Disable Local Authorization"
-msgstr "Lokale autorisatie uitschakelen bevestigen"
-
-#: screens/User/shared/UserForm.js:99
-msgid "Confirm Password"
-msgstr "Wachtwoord bevestigen"
-
-#: components/JobCancelButton/JobCancelButton.js:86
-msgid "Confirm cancel job"
-msgstr "Taak annuleren bevestigen"
-
-#: components/JobCancelButton/JobCancelButton.js:90
-msgid "Confirm cancellation"
-msgstr "Annuleren bevestigen"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:26
-msgid "Confirm delete"
-msgstr "Verwijderen bevestigen"
-
-#: screens/User/UserRoles/UserRolesList.js:215
-msgid "Confirm disassociate"
-msgstr "Loskoppelen bevestigen"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:24
-msgid "Confirm link removal"
-msgstr "Link verwijderen bevestigen"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:37
-msgid "Confirm node removal"
-msgstr "Knooppunt verwijderen bevestigen"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:18
-msgid "Confirm removal of all nodes"
-msgstr "Verwijderen van alle knooppunten bevestigen"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:160
-msgid "Confirm remove"
-msgstr "Reset bevestigen"
-
-#: screens/Setting/shared/RevertAllAlert.js:20
-msgid "Confirm revert all"
-msgstr "Alles terugzetten bevestigen"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:91
-msgid "Confirm selection"
-msgstr "Selectie bevestigen"
-
-#: screens/Job/JobDetail/JobDetail.js:366
-msgid "Container Group"
-msgstr "Containergroep"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:47
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:57
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:68
-msgid "Container group"
-msgstr "Containergroep"
-
-#: screens/InstanceGroup/ContainerGroup.js:84
-msgid "Container group not found."
-msgstr "Containergroep niet gevonden."
-
-#: components/LaunchPrompt/LaunchPrompt.js:153
-#: components/Schedule/shared/SchedulePromptableFields.js:120
-msgid "Content Loading"
-msgstr "Inhoud laden"
-
-#: components/PromptDetail/PromptProjectDetail.js:130
-#: screens/Project/ProjectDetail/ProjectDetail.js:240
-#: screens/Project/shared/ProjectForm.js:290
-msgid "Content Signature Validation Credential"
-msgstr "Content Signature Validation Credential"
-
-#: components/AppContainer/AppContainer.js:142
-msgid "Continue"
-msgstr "Doorgaan"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:207
-#: screens/Instances/InstanceList/InstanceList.js:151
-msgid "Control"
-msgstr "Controle"
-
-#: screens/TopologyView/Legend.js:77
-msgid "Control node"
-msgstr "Controleknooppunt"
-
-#: screens/Inventory/shared/Inventory.helptext.js:79
-msgid ""
-"Control the level of output Ansible\n"
-"will produce for inventory source update jobs."
-msgstr "Stel in hoeveel output Ansible produceert bij taken die de inventarisbron updaten."
-
-#: screens/Job/Job.helptext.js:15
-#: screens/Template/shared/JobTemplate.helptext.js:16
-msgid "Control the level of output ansible will produce as the playbook executes."
-msgstr "Stel in hoeveel output Ansible produceert bij het uitvoeren van het draaiboek."
-
-#: screens/Job/JobDetail/JobDetail.js:351
-msgid "Controller Node"
-msgstr "Naam controller"
-
-#: components/PromptDetail/PromptDetail.js:130
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:225
-msgid "Convergence"
-msgstr "Convergentie"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:256
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:257
-msgid "Convergence select"
-msgstr "Convergentie selecteren"
-
-#: components/CopyButton/CopyButton.js:40
-msgid "Copy"
-msgstr "Kopiëren"
-
-#: screens/Credential/CredentialList/CredentialListItem.js:80
-msgid "Copy Credential"
-msgstr "Toegangsgegevens kopiëren"
-
-#: components/CopyButton/CopyButton.js:48
-msgid "Copy Error"
-msgstr "Kopieerfout"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:104
-msgid "Copy Execution Environment"
-msgstr "Uitvoeringsomgeving kopiëren"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:154
-msgid "Copy Inventory"
-msgstr "Inventaris kopiëren"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:152
-msgid "Copy Notification Template"
-msgstr "Berichtsjabloon kopiëren"
-
-#: screens/Project/ProjectList/ProjectListItem.js:262
-msgid "Copy Project"
-msgstr "Project kopiëren"
-
-#: components/TemplateList/TemplateListItem.js:248
-msgid "Copy Template"
-msgstr "Sjabloon kopiëren"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:202
-#: screens/Project/ProjectList/ProjectListItem.js:98
-msgid "Copy full revision to clipboard."
-msgstr "Volledige herziening kopiëren naar klembord."
-
-#: components/About/About.js:35
-msgid "Copyright"
-msgstr "Copyright"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:231
-#: components/MultiSelect/TagMultiSelect.js:62
-#: screens/Credential/shared/CredentialFormFields/BecomeMethodField.js:66
-#: screens/Inventory/shared/InventoryForm.js:91
-#: screens/Template/shared/JobTemplateForm.js:396
-#: screens/Template/shared/WorkflowJobTemplateForm.js:204
-msgid "Create"
-msgstr "Maken"
-
-#: screens/Application/Applications.js:27
-#: screens/Application/Applications.js:36
-msgid "Create New Application"
-msgstr "Nieuwe toepassing maken"
-
-#: screens/Credential/Credentials.js:15
-#: screens/Credential/Credentials.js:25
-msgid "Create New Credential"
-msgstr "Nieuwe toegangsgegevens maken"
-
-#: screens/Host/Hosts.js:15
-#: screens/Host/Hosts.js:24
-msgid "Create New Host"
-msgstr "Nieuwe host maken"
-
-#: screens/Template/Templates.js:18
-msgid "Create New Job Template"
-msgstr "Nieuwe taaksjabloon maken"
-
-#: screens/NotificationTemplate/NotificationTemplates.js:15
-#: screens/NotificationTemplate/NotificationTemplates.js:22
-msgid "Create New Notification Template"
-msgstr "Nieuwe berichtsjabloon maken"
-
-#: screens/Organization/Organizations.js:17
-#: screens/Organization/Organizations.js:27
-msgid "Create New Organization"
-msgstr "Nieuwe organisatie maken"
-
-#: screens/Project/Projects.js:13
-#: screens/Project/Projects.js:23
-msgid "Create New Project"
-msgstr "Nieuw project maken"
-
-#: screens/Inventory/Inventories.js:91
-#: screens/ManagementJob/ManagementJobs.js:24
-#: screens/Project/Projects.js:32
-#: screens/Template/Templates.js:52
-msgid "Create New Schedule"
-msgstr "Nieuw schema toevoegen"
-
-#: screens/Team/Teams.js:16
-#: screens/Team/Teams.js:26
-msgid "Create New Team"
-msgstr "Nieuw team maken"
-
-#: screens/User/Users.js:16
-#: screens/User/Users.js:27
-msgid "Create New User"
-msgstr "Nieuwe gebruiker maken"
-
-#: screens/Template/Templates.js:19
-msgid "Create New Workflow Template"
-msgstr "Nieuwe workflowsjabloon maken"
-
-#: screens/Host/HostList/SmartInventoryButton.js:26
-msgid "Create a new Smart Inventory with the applied filter"
-msgstr "Nieuwe Smart-inventaris met het toegepaste filter maken"
-
-#: screens/Instances/Instances.js:14
-msgid "Create new Instance"
-msgstr "Nieuwe instantiegroep maken"
-
-#: screens/InstanceGroup/InstanceGroups.js:18
-#: screens/InstanceGroup/InstanceGroups.js:28
-msgid "Create new container group"
-msgstr "Nieuwe containergroep maken"
-
-#: screens/CredentialType/CredentialTypes.js:23
-msgid "Create new credential Type"
-msgstr "Nieuw type toegangsgegevens maken"
-
-#: screens/CredentialType/CredentialTypes.js:14
-msgid "Create new credential type"
-msgstr "Nieuw type toegangsgegevens maken"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:14
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:23
-msgid "Create new execution environment"
-msgstr "Nieuwe uitvoeringsomgeving maken"
-
-#: screens/Inventory/Inventories.js:75
-#: screens/Inventory/Inventories.js:82
-msgid "Create new group"
-msgstr "Nieuwe groep maken"
-
-#: screens/Inventory/Inventories.js:66
-#: screens/Inventory/Inventories.js:80
-msgid "Create new host"
-msgstr "Nieuwe host maken"
-
-#: screens/InstanceGroup/InstanceGroups.js:17
-#: screens/InstanceGroup/InstanceGroups.js:27
-msgid "Create new instance group"
-msgstr "Nieuwe instantiegroep maken"
-
-#: screens/Inventory/Inventories.js:18
-msgid "Create new inventory"
-msgstr "Nieuwe inventaris maken"
-
-#: screens/Inventory/Inventories.js:19
-msgid "Create new smart inventory"
-msgstr "Nieuwe Smart-inventaris maken"
-
-#: screens/Inventory/Inventories.js:85
-msgid "Create new source"
-msgstr "Nieuwe bron maken"
-
-#: screens/User/Users.js:35
-msgid "Create user token"
-msgstr "Gebruikerstoken maken"
-
-#: components/Lookup/ApplicationLookup.js:115
-#: components/PromptDetail/PromptDetail.js:154
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:406
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:105
-#: screens/Credential/CredentialDetail/CredentialDetail.js:257
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:90
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:103
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:151
-#: screens/Host/HostDetail/HostDetail.js:86
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:67
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:93
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:173
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:43
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:81
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:277
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:149
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:46
-#: screens/Job/JobDetail/JobDetail.js:534
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:393
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:115
-#: screens/Project/ProjectDetail/ProjectDetail.js:292
-#: screens/Team/TeamDetail/TeamDetail.js:47
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:353
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:187
-#: screens/User/UserDetail/UserDetail.js:82
-#: screens/User/UserTokenDetail/UserTokenDetail.js:61
-#: screens/User/UserTokenList/UserTokenList.js:150
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:199
-msgid "Created"
-msgstr "Gemaakt"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:122
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:112
-#: components/AddRole/AddResourceRole.js:57
-#: components/AssociateModal/AssociateModal.js:144
-#: components/LaunchPrompt/steps/CredentialsStep.js:173
-#: components/LaunchPrompt/steps/InventoryStep.js:89
-#: components/Lookup/CredentialLookup.js:194
-#: components/Lookup/InventoryLookup.js:152
-#: components/Lookup/InventoryLookup.js:207
-#: components/Lookup/MultiCredentialsLookup.js:194
-#: components/Lookup/OrganizationLookup.js:134
-#: components/Lookup/ProjectLookup.js:151
-#: components/NotificationList/NotificationList.js:206
-#: components/RelatedTemplateList/RelatedTemplateList.js:166
-#: components/Schedule/ScheduleList/ScheduleList.js:198
-#: components/TemplateList/TemplateList.js:226
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:27
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:58
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:104
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:127
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:173
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:196
-#: screens/Credential/CredentialList/CredentialList.js:150
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:96
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:132
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:102
-#: screens/Host/HostGroups/HostGroupsList.js:164
-#: screens/Host/HostList/HostList.js:157
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:199
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:129
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:174
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:128
-#: screens/Inventory/InventoryList/InventoryList.js:199
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:185
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:94
-#: screens/Organization/OrganizationList/OrganizationList.js:131
-#: screens/Project/ProjectList/ProjectList.js:213
-#: screens/Team/TeamList/TeamList.js:130
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:161
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:112
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:108
-msgid "Created By (Username)"
-msgstr "Gemaakt door (Gebruikersnaam)"
-
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:81
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:147
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:73
-msgid "Created by (username)"
-msgstr "Gemaakt door (gebruikersnaam)"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:54
-#: components/AdHocCommands/useAdHocCredentialStep.js:24
-#: components/PromptDetail/PromptInventorySourceDetail.js:107
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:40
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:52
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:50
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:258
-#: screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.js:38
-#: screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/GCESubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.js:38
-#: screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:84
-#: screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.js:36
-#: screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.js:38
-#: screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.js:38
-#: util/getRelatedResourceDeleteDetails.js:167
-msgid "Credential"
-msgstr "Toegangsgegeven"
-
-#: util/getRelatedResourceDeleteDetails.js:74
-msgid "Credential Input Sources"
-msgstr "Inputbronnen toegangsgegevens"
-
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:83
-#: components/Lookup/InstanceGroupsLookup.js:108
-msgid "Credential Name"
-msgstr "Naam toegangsgegevens"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:241
-#: screens/Credential/CredentialList/CredentialList.js:158
-#: screens/Credential/shared/CredentialForm.js:128
-#: screens/Credential/shared/CredentialForm.js:196
-msgid "Credential Type"
-msgstr "Type toegangsgegevens"
-
-#: routeConfig.js:117
-#: screens/ActivityStream/ActivityStream.js:192
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:118
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:161
-#: screens/CredentialType/CredentialTypes.js:13
-#: screens/CredentialType/CredentialTypes.js:22
-msgid "Credential Types"
-msgstr "Types toegangsgegevens"
-
-#: screens/Credential/CredentialList/CredentialList.js:113
-msgid "Credential copied successfully"
-msgstr "Toegangsgegeven gekopieerd"
-
-#: screens/Credential/Credential.js:98
-msgid "Credential not found."
-msgstr "Toegangsgegevens niet gevonden."
-
-#: components/AdHocCommands/useAdHocCredentialPasswordStep.js:23
-#: components/LaunchPrompt/steps/useCredentialPasswordsStep.js:28
-msgid "Credential passwords"
-msgstr "Wachtwoorden toegangsgegevens"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:53
-msgid "Credential to authenticate with Kubernetes or OpenShift"
-msgstr "Credential om te authenticeren met Kubernetes of OpenShift"
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:57
-msgid "Credential to authenticate with Kubernetes or OpenShift. Must be of type \"Kubernetes/OpenShift API Bearer Token\". If left blank, the underlying Pod's service account will be used."
-msgstr "Toegangsgegevens voor authenticatie met Kubernetes of OpenShift. Moet van het type 'Kubernetes/OpenShift API Bearer Token' zijn. Indien leeg gelaten, wordt de serviceaccount van de onderliggende Pod gebruikt."
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:21
-msgid "Credential to authenticate with a protected container registry."
-msgstr "Toegangsgegevens voor authenticatie met een beschermd containerregister."
-
-#: screens/CredentialType/CredentialType.js:76
-msgid "Credential type not found."
-msgstr "Type toegangsgegevens niet gevonden."
-
-#: components/JobList/JobListItem.js:260
-#: components/LaunchPrompt/steps/CredentialsStep.js:190
-#: components/LaunchPrompt/steps/useCredentialsStep.js:62
-#: components/Lookup/MultiCredentialsLookup.js:138
-#: components/Lookup/MultiCredentialsLookup.js:211
-#: components/PromptDetail/PromptDetail.js:192
-#: components/PromptDetail/PromptJobTemplateDetail.js:191
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:528
-#: components/TemplateList/TemplateListItem.js:323
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:77
-#: routeConfig.js:70
-#: screens/ActivityStream/ActivityStream.js:167
-#: screens/Credential/CredentialList/CredentialList.js:195
-#: screens/Credential/Credentials.js:14
-#: screens/Credential/Credentials.js:24
-#: screens/Job/JobDetail/JobDetail.js:429
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:374
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:49
-#: screens/Template/shared/JobTemplateForm.js:372
-#: util/getRelatedResourceDeleteDetails.js:91
-msgid "Credentials"
-msgstr "Toegangsgegevens"
-
-#: components/LaunchPrompt/steps/credentialsValidator.js:52
-msgid "Credentials that require passwords on launch are not permitted. Please remove or replace the following credentials with a credential of the same type in order to proceed: {0}"
-msgstr "Toegangsgegevens die een wachtwoord vereisen bij het opstarten zijn niet toegestaan. Verwijder of vervang de volgende toegangsgegevens door toegangsgegevens van hetzelfde type om verder te gaan: {0}"
-
-#: components/Pagination/Pagination.js:34
-msgid "Current page"
-msgstr "Huidige pagina"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:85
-msgid "Custom Kubernetes or OpenShift Pod specification."
-msgstr "Veld voor het opgeven van een aangepaste Kubernetes of OpenShift Pod-specificatie."
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:79
-msgid "Custom pod spec"
-msgstr "Aangepaste podspecificatie"
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:79
-#: screens/Organization/OrganizationList/OrganizationListItem.js:55
-#: screens/Project/ProjectList/ProjectListItem.js:188
-msgid "Custom virtual environment {0} must be replaced by an execution environment."
-msgstr "Aangepaste virtuele omgeving {0} moet worden vervangen door een uitvoeringsomgeving."
-
-#: components/TemplateList/TemplateListItem.js:163
-msgid "Custom virtual environment {0} must be replaced by an execution environment. For more information about migrating to execution environments see <0>the documentation.0>"
-msgstr "Aangepaste virtuele omgeving {0} moet worden vervangen door een uitvoeringsomgeving. Raadpleeg voor meer informatie over het migreren van uitvoeringsomgevingen <0>de documentatie.0>"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:73
-msgid "Custom virtual environment {virtualEnvironment} must be replaced by an execution environment. For more information about migrating to execution environments see <0>the documentation.0>"
-msgstr "Aangepaste virtuele omgeving {virtualEnvironment} moet worden vervangen door een uitvoeringsomgeving. Raadpleeg voor meer informatie over het migreren van uitvoeringsomgevingen <0>de documentatie.0>"
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:61
-msgid "Customize messages…"
-msgstr "Berichten aanpassen..."
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:65
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:66
-msgid "Customize pod specification"
-msgstr "Podspecificatie aanpassen"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputNode.js:109
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:212
-msgid "DELETED"
-msgstr "VERWIJDERD"
-
-#: routeConfig.js:34
-#: screens/Dashboard/Dashboard.js:74
-msgid "Dashboard"
-msgstr "Dashboard"
-
-#: screens/ActivityStream/ActivityStream.js:147
-msgid "Dashboard (all activity)"
-msgstr "Dashboard (alle activiteit)"
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:75
-msgid "Data retention period"
-msgstr "Bewaartermijn van gegevens"
-
-#: screens/Dashboard/shared/LineChart.js:168
-msgid "Date"
-msgstr "Datum"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:184
-#: components/Schedule/shared/FrequencyDetailSubform.js:177
-#: components/Schedule/shared/FrequencyDetailSubform.js:356
-#: components/Schedule/shared/FrequencyDetailSubform.js:460
-#: components/Schedule/shared/ScheduleFormFields.js:127
-#: components/Schedule/shared/ScheduleFormFields.js:187
-msgid "Day"
-msgstr "Dag"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:130
-msgid "Day {0}"
-msgstr "Dag {0}"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:399
-#: components/Schedule/shared/ScheduleFormFields.js:136
-msgid "Days of Data to Keep"
-msgstr "Dagen om gegevens te bewaren"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/DaysToKeepStep.js:28
-msgid "Days of data to be retained"
-msgstr "Aantal dagen dat gegevens moeten worden bewaard"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:167
-msgid "Days remaining"
-msgstr "Resterende dagen"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useDaysToKeepStep.js:35
-msgid "Days to keep"
-msgstr "Te behouden dagen"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:102
-msgid "Debug"
-msgstr "Foutopsporing"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:170
-#: components/Schedule/shared/FrequencyDetailSubform.js:153
-msgid "December"
-msgstr "December"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:102
-#: screens/Template/Survey/SurveyList.js:104
-#: screens/Template/Survey/SurveyList.js:104
-#: screens/Template/Survey/SurveyListItem.js:63
-msgid "Default"
-msgstr "Standaard"
-
-#: screens/Template/Survey/SurveyReorderModal.js:219
-#: screens/Template/Survey/SurveyReorderModal.js:219
-#: screens/Template/Survey/SurveyReorderModal.js:241
-msgid "Default Answer(s)"
-msgstr "Standaardantwoord(en)"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:40
-msgid "Default Execution Environment"
-msgstr "Standaarduitvoeringsomgeving"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:238
-#: screens/Template/Survey/SurveyQuestionForm.js:246
-#: screens/Template/Survey/SurveyQuestionForm.js:253
-msgid "Default answer"
-msgstr "Standaardantwoord"
-
-#: screens/Setting/SettingList.js:103
-msgid "Define system-level features and functions"
-msgstr "Kenmerken en functies op systeemniveau definiëren"
-
-#: components/DeleteButton/DeleteButton.js:75
-#: components/DeleteButton/DeleteButton.js:80
-#: components/DeleteButton/DeleteButton.js:90
-#: components/DeleteButton/DeleteButton.js:94
-#: components/DeleteButton/DeleteButton.js:114
-#: components/PaginatedTable/ToolbarDeleteButton.js:158
-#: components/PaginatedTable/ToolbarDeleteButton.js:235
-#: components/PaginatedTable/ToolbarDeleteButton.js:246
-#: components/PaginatedTable/ToolbarDeleteButton.js:250
-#: components/PaginatedTable/ToolbarDeleteButton.js:273
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:29
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:646
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:128
-#: screens/Credential/CredentialDetail/CredentialDetail.js:306
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:124
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:134
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:115
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:127
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:201
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:101
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:316
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:174
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:64
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:68
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:73
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:78
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:102
-#: screens/Job/JobDetail/JobDetail.js:612
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:436
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:199
-#: screens/Project/ProjectDetail/ProjectDetail.js:340
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:80
-#: screens/Team/TeamDetail/TeamDetail.js:70
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:545
-#: screens/Template/Survey/SurveyList.js:66
-#: screens/Template/Survey/SurveyToolbar.js:93
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:269
-#: screens/User/UserDetail/UserDetail.js:107
-#: screens/User/UserTokenDetail/UserTokenDetail.js:78
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:340
-msgid "Delete"
-msgstr "Verwijderen"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:130
-msgid "Delete All Groups and Hosts"
-msgstr "Alle groepen en hosts verwijderen"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:300
-msgid "Delete Credential"
-msgstr "Toegangsgegevens verwijderen"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:127
-msgid "Delete Execution Environment"
-msgstr "Uitvoeringsomgeving verwijderen"
-
-#: screens/Host/HostDetail/HostDetail.js:114
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:109
-msgid "Delete Host"
-msgstr "Host verwijderen"
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:196
-msgid "Delete Inventory"
-msgstr "Inventaris verwijderen"
-
-#: screens/Job/JobDetail/JobDetail.js:608
-#: screens/Job/JobOutput/shared/OutputToolbar.js:195
-#: screens/Job/JobOutput/shared/OutputToolbar.js:199
-msgid "Delete Job"
-msgstr "Taak verwijderen"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:539
-msgid "Delete Job Template"
-msgstr "Taaksjabloon verwijderen"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:432
-msgid "Delete Notification"
-msgstr "Bericht verwijderen"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:193
-msgid "Delete Organization"
-msgstr "Organisatie verwijderen"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:334
-msgid "Delete Project"
-msgstr "Project verwijderen"
-
-#: screens/Template/Survey/SurveyList.js:52
-msgid "Delete Questions"
-msgstr "Vragen verwijderen"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:642
-msgid "Delete Schedule"
-msgstr "Schema verwijderen"
-
-#: screens/Template/Survey/SurveyList.js:52
-msgid "Delete Survey"
-msgstr "Vragenlijst verwijderen"
-
-#: screens/Team/TeamDetail/TeamDetail.js:66
-msgid "Delete Team"
-msgstr "Team verwijderen"
-
-#: screens/User/UserDetail/UserDetail.js:103
-msgid "Delete User"
-msgstr "Gebruiker verwijderen"
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:74
-msgid "Delete User Token"
-msgstr "Gebruikerstoken verwijderen"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:336
-msgid "Delete Workflow Approval"
-msgstr "Workflowgoedkeuring verwijderen"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:263
-msgid "Delete Workflow Job Template"
-msgstr "Workflow-taaksjabloon verwijderen"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:138
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:141
-msgid "Delete all nodes"
-msgstr "Alle knooppunten verwijderen"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:124
-msgid "Delete application"
-msgstr "Toepassing maken"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:116
-msgid "Delete credential type"
-msgstr "Soort toegangsgegevens verwijderen"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:249
-msgid "Delete error"
-msgstr "Fout verwijderen"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:109
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:121
-msgid "Delete instance group"
-msgstr "Instantiegroep verwijderen"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:310
-msgid "Delete inventory source"
-msgstr "Inventarisbron maken"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:170
-msgid "Delete smart inventory"
-msgstr "Smart-inventaris maken"
-
-#: screens/Template/Survey/SurveyToolbar.js:83
-msgid "Delete survey question"
-msgstr "Vragenlijstvraag verwijderen"
-
-#: screens/Project/shared/Project.helptext.js:114
-msgid ""
-"Delete the local repository in its entirety prior to\n"
-"performing an update. Depending on the size of the\n"
-"repository this may significantly increase the amount\n"
-"of time required to complete an update."
-msgstr "De lokale opslagplaats dient volledig verwijderd te worden voordat een update uitgevoerd wordt. Afhankelijk van het formaat van de opslagplaats kan de tijd die nodig is om een update uit te voeren hierdoor sterk verlengd worden."
-
-#: components/PromptDetail/PromptProjectDetail.js:51
-#: screens/Project/ProjectDetail/ProjectDetail.js:100
-msgid "Delete the project before syncing"
-msgstr "Verwijder het project alvorens te synchroniseren."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:83
-msgid "Delete this link"
-msgstr "Deze link verwijderen"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:274
-msgid "Delete this node"
-msgstr "Dit knooppunt verwijderen"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:163
-msgid "Delete {pluralizedItemName}?"
-msgstr "{pluralizedItemName} verwijderen?"
-
-#: components/DetailList/DeletedDetail.js:19
-#: components/Workflow/WorkflowNodeHelp.js:157
-#: components/Workflow/WorkflowNodeHelp.js:193
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:231
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:49
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:60
-msgid "Deleted"
-msgstr "Verwijderd"
-
-#: components/TemplateList/TemplateList.js:296
-#: screens/Credential/CredentialList/CredentialList.js:211
-#: screens/Inventory/InventoryList/InventoryList.js:284
-#: screens/Project/ProjectList/ProjectList.js:290
-msgid "Deletion Error"
-msgstr "Fout bij verwijderen"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:202
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:227
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:219
-msgid "Deletion error"
-msgstr "Fout bij verwijderen"
-
-#: components/StatusLabel/StatusLabel.js:40
-msgid "Denied"
-msgstr "Geweigerd"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:21
-msgid "Denied - {0}. See the Activity Stream for more information."
-msgstr "Geweigerd - {0}. Zie de activiteitstroom voor meer informatie."
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:17
-msgid "Denied by {0} - {1}"
-msgstr "Geweigerd door {0} - {1}"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:89
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:35
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:42
-msgid "Deny"
-msgstr "Weigeren"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:103
-msgid "Deprecated"
-msgstr "Afgeschaft"
-
-#: components/StatusLabel/StatusLabel.js:60
-#: screens/TopologyView/Legend.js:164
-msgid "Deprovisioning"
-msgstr "Deprovisionering"
-
-#: components/StatusLabel/StatusLabel.js:63
-msgid "Deprovisioning fail"
-msgstr "Deprovisionering mislukt"
-
-#: components/HostForm/HostForm.js:104
-#: components/Lookup/ApplicationLookup.js:105
-#: components/Lookup/ApplicationLookup.js:123
-#: components/Lookup/HostFilterLookup.js:423
-#: components/Lookup/HostListItem.js:9
-#: components/NotificationList/NotificationList.js:186
-#: components/PromptDetail/PromptDetail.js:119
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:327
-#: components/Schedule/ScheduleList/ScheduleList.js:194
-#: components/Schedule/shared/ScheduleFormFields.js:80
-#: components/TemplateList/TemplateList.js:210
-#: components/TemplateList/TemplateListItem.js:271
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:65
-#: screens/Application/ApplicationsList/ApplicationsList.js:123
-#: screens/Application/shared/ApplicationForm.js:62
-#: screens/Credential/CredentialDetail/CredentialDetail.js:223
-#: screens/Credential/CredentialList/CredentialList.js:146
-#: screens/Credential/shared/CredentialForm.js:169
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:72
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:128
-#: screens/CredentialType/shared/CredentialTypeForm.js:29
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:60
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:159
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:127
-#: screens/Host/HostDetail/HostDetail.js:75
-#: screens/Host/HostList/HostList.js:153
-#: screens/Host/HostList/HostList.js:170
-#: screens/Host/HostList/HostListItem.js:57
-#: screens/Instances/Shared/InstanceForm.js:26
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:93
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:35
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:216
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:80
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:40
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:124
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:139
-#: screens/Inventory/InventoryList/InventoryList.js:195
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:198
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:104
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:36
-#: screens/Inventory/shared/InventoryForm.js:58
-#: screens/Inventory/shared/InventoryGroupForm.js:40
-#: screens/Inventory/shared/InventorySourceForm.js:108
-#: screens/Inventory/shared/SmartInventoryForm.js:55
-#: screens/Job/JobOutput/HostEventModal.js:112
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:101
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:72
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:109
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:127
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:49
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:95
-#: screens/Organization/OrganizationList/OrganizationList.js:127
-#: screens/Organization/shared/OrganizationForm.js:64
-#: screens/Project/ProjectDetail/ProjectDetail.js:177
-#: screens/Project/ProjectList/ProjectList.js:190
-#: screens/Project/ProjectList/ProjectListItem.js:281
-#: screens/Project/shared/ProjectForm.js:222
-#: screens/Team/TeamDetail/TeamDetail.js:38
-#: screens/Team/TeamList/TeamList.js:122
-#: screens/Team/shared/TeamForm.js:37
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:187
-#: screens/Template/Survey/SurveyQuestionForm.js:171
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:112
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:183
-#: screens/Template/shared/JobTemplateForm.js:251
-#: screens/Template/shared/WorkflowJobTemplateForm.js:117
-#: screens/User/UserOrganizations/UserOrganizationList.js:80
-#: screens/User/UserOrganizations/UserOrganizationListItem.js:18
-#: screens/User/UserTeams/UserTeamList.js:182
-#: screens/User/UserTeams/UserTeamListItem.js:32
-#: screens/User/UserTokenDetail/UserTokenDetail.js:45
-#: screens/User/UserTokenList/UserTokenList.js:128
-#: screens/User/UserTokenList/UserTokenList.js:138
-#: screens/User/UserTokenList/UserTokenList.js:188
-#: screens/User/UserTokenList/UserTokenListItem.js:29
-#: screens/User/shared/UserTokenForm.js:59
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:145
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:128
-msgid "Description"
-msgstr "Omschrijving"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:325
-msgid "Destination Channels"
-msgstr "Bestemmingskanalen"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:233
-msgid "Destination Channels or Users"
-msgstr "Bestemmingskanalen of -gebruikers"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:346
-msgid "Destination SMS Number(s)"
-msgstr "Sms-nummer(s) bestemming"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:412
-msgid "Destination SMS number(s)"
-msgstr "Sms-nummer(s) bestemming"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:364
-msgid "Destination channels"
-msgstr "Bestemmingskanalen"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:231
-msgid "Destination channels or users"
-msgstr "Bestemmingskanalen of -gebruikers"
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:35
-#: components/ErrorDetail/ErrorDetail.js:88
-#: components/Schedule/Schedule.js:71
-#: screens/Application/Application/Application.js:79
-#: screens/Application/Applications.js:39
-#: screens/Credential/Credential.js:72
-#: screens/Credential/Credentials.js:28
-#: screens/CredentialType/CredentialType.js:63
-#: screens/CredentialType/CredentialTypes.js:26
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:65
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:26
-#: screens/Host/Host.js:58
-#: screens/Host/Hosts.js:27
-#: screens/InstanceGroup/ContainerGroup.js:66
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:180
-#: screens/InstanceGroup/InstanceGroup.js:69
-#: screens/InstanceGroup/InstanceGroups.js:30
-#: screens/InstanceGroup/InstanceGroups.js:38
-#: screens/Instances/Instance.js:29
-#: screens/Instances/Instances.js:24
-#: screens/Inventory/Inventories.js:61
-#: screens/Inventory/Inventories.js:87
-#: screens/Inventory/Inventory.js:64
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:57
-#: screens/Inventory/InventoryHost/InventoryHost.js:73
-#: screens/Inventory/InventorySource/InventorySource.js:83
-#: screens/Inventory/SmartInventory.js:66
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:60
-#: screens/Job/Job.js:130
-#: screens/Job/JobOutput/HostEventModal.js:103
-#: screens/Job/Jobs.js:35
-#: screens/ManagementJob/ManagementJobs.js:26
-#: screens/NotificationTemplate/NotificationTemplate.js:84
-#: screens/NotificationTemplate/NotificationTemplates.js:25
-#: screens/Organization/Organization.js:123
-#: screens/Organization/Organizations.js:30
-#: screens/Project/Project.js:104
-#: screens/Project/Projects.js:26
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:51
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:51
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:65
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:76
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:50
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:97
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:51
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:56
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:52
-#: screens/Setting/Settings.js:45
-#: screens/Setting/Settings.js:48
-#: screens/Setting/Settings.js:52
-#: screens/Setting/Settings.js:55
-#: screens/Setting/Settings.js:58
-#: screens/Setting/Settings.js:61
-#: screens/Setting/Settings.js:64
-#: screens/Setting/Settings.js:67
-#: screens/Setting/Settings.js:70
-#: screens/Setting/Settings.js:73
-#: screens/Setting/Settings.js:76
-#: screens/Setting/Settings.js:85
-#: screens/Setting/Settings.js:86
-#: screens/Setting/Settings.js:87
-#: screens/Setting/Settings.js:88
-#: screens/Setting/Settings.js:89
-#: screens/Setting/Settings.js:90
-#: screens/Setting/Settings.js:98
-#: screens/Setting/Settings.js:101
-#: screens/Setting/Settings.js:104
-#: screens/Setting/Settings.js:107
-#: screens/Setting/Settings.js:110
-#: screens/Setting/Settings.js:113
-#: screens/Setting/Settings.js:116
-#: screens/Setting/Settings.js:119
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:56
-#: screens/Setting/UI/UIDetail/UIDetail.js:66
-#: screens/Team/Team.js:57
-#: screens/Team/Teams.js:29
-#: screens/Template/Template.js:135
-#: screens/Template/Templates.js:43
-#: screens/Template/WorkflowJobTemplate.js:117
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:138
-#: screens/TopologyView/Tooltip.js:187
-#: screens/TopologyView/Tooltip.js:213
-#: screens/User/User.js:64
-#: screens/User/UserToken/UserToken.js:54
-#: screens/User/Users.js:30
-#: screens/User/Users.js:36
-#: screens/WorkflowApproval/WorkflowApproval.js:77
-#: screens/WorkflowApproval/WorkflowApprovals.js:24
-msgid "Details"
-msgstr "Meer informatie"
-
-#: screens/Job/JobOutput/HostEventModal.js:100
-msgid "Details tab"
-msgstr "Tabblad Details"
-
-#: components/Search/AdvancedSearch.js:271
-msgid "Direct Keys"
-msgstr "Directe sleutels"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:209
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:268
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:313
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:371
-msgid "Disable SSL Verification"
-msgstr "SSL-verificatie uitschakelen"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:187
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:240
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:279
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:350
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:461
-msgid "Disable SSL verification"
-msgstr "SSL-verificatie uitschakelen"
-
-#: components/InstanceToggle/InstanceToggle.js:56
-#: components/StatusLabel/StatusLabel.js:53
-#: screens/TopologyView/Legend.js:233
-msgid "Disabled"
-msgstr "Uitgeschakeld"
-
-#: components/DisassociateButton/DisassociateButton.js:73
-#: components/DisassociateButton/DisassociateButton.js:97
-#: components/DisassociateButton/DisassociateButton.js:109
-#: components/DisassociateButton/DisassociateButton.js:113
-#: components/DisassociateButton/DisassociateButton.js:133
-#: screens/Team/TeamRoles/TeamRolesList.js:222
-#: screens/User/UserRoles/UserRolesList.js:218
-msgid "Disassociate"
-msgstr "Loskoppelen"
-
-#: screens/Host/HostGroups/HostGroupsList.js:211
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:229
-msgid "Disassociate group from host?"
-msgstr "Groep van host loskoppelen?"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:247
-msgid "Disassociate host from group?"
-msgstr "Host van groep loskoppelen?"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:296
-#: screens/InstanceGroup/Instances/InstanceList.js:245
-msgid "Disassociate instance from instance group?"
-msgstr "Instantie van instantiegroep loskoppelen?"
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:225
-msgid "Disassociate related group(s)?"
-msgstr "Verwante groep(en) loskoppelen?"
-
-#: screens/User/UserTeams/UserTeamList.js:216
-msgid "Disassociate related team(s)?"
-msgstr "Verwant(e) team(s) loskoppelen?"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:209
-#: screens/User/UserRoles/UserRolesList.js:205
-msgid "Disassociate role"
-msgstr "Rol loskoppelen"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:212
-#: screens/User/UserRoles/UserRolesList.js:208
-msgid "Disassociate role!"
-msgstr "Koppel host los!"
-
-#: components/DisassociateButton/DisassociateButton.js:18
-msgid "Disassociate?"
-msgstr "Loskoppelen?"
-
-#: components/PromptDetail/PromptProjectDetail.js:46
-#: screens/Project/ProjectDetail/ProjectDetail.js:94
-msgid "Discard local changes before syncing"
-msgstr "Alle lokale wijzigingen vernietigen alvorens te synchroniseren"
-
-#: screens/Job/Job.helptext.js:16
-#: screens/Template/shared/JobTemplate.helptext.js:17
-msgid "Divide the work done by this job template into the specified number of job slices, each running the same tasks against a portion of the inventory."
-msgstr "Verdeel het uitgevoerde werk met behulp van dit taaksjabloon in het opgegeven aantal taakdelen, elk deel voert dezelfde taken uit voor een deel van de inventaris."
-
-#: screens/Project/shared/Project.helptext.js:100
-msgid "Documentation."
-msgstr "Documentatie."
-
-#: components/CodeEditor/VariablesDetail.js:117
-#: components/CodeEditor/VariablesDetail.js:123
-#: components/CodeEditor/VariablesField.js:139
-#: components/CodeEditor/VariablesField.js:145
-msgid "Done"
-msgstr "Gereed"
-
-#: screens/TopologyView/Tooltip.js:251
-msgid "Download Bundle"
-msgstr "Download Bundel"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:179
-#: screens/Job/JobOutput/shared/OutputToolbar.js:184
-msgid "Download Output"
-msgstr "Download output"
-
-#: screens/TopologyView/Tooltip.js:247
-msgid "Download bundle"
-msgstr "Bundel downloaden"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:93
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:114
-msgid "Drag a file here or browse to upload"
-msgstr "Sleep een bestand hierheen of blader om te uploaden"
-
-#: components/SelectedList/DraggableSelectedList.js:68
-msgid "Draggable list to reorder and remove selected items."
-msgstr "Versleepbare lijst om geselecteerde items te herschikken en te verwijderen."
-
-#: components/SelectedList/DraggableSelectedList.js:43
-msgid "Dragging cancelled. List is unchanged."
-msgstr "Slepen geannuleerd. Lijst is ongewijzigd."
-
-#: components/SelectedList/DraggableSelectedList.js:38
-msgid "Dragging item {id}. Item with index {oldIndex} in now {newIndex}."
-msgstr "Item slepen {id}. Item met index {oldIndex} in nu {newIndex}."
-
-#: components/SelectedList/DraggableSelectedList.js:32
-msgid "Dragging started for item id: {newId}."
-msgstr "Het slepen is begonnen voor item-id: {newId}."
-
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:81
-msgid "E-mail"
-msgstr "E-mail"
-
-#: screens/Inventory/shared/Inventory.helptext.js:113
-msgid ""
-"Each time a job runs using this inventory,\n"
-"refresh the inventory from the selected source before\n"
-"executing job tasks."
-msgstr "Elke keer dat een taak uitgevoerd wordt met dit inventaris, dient het inventaris vernieuwd te worden vanuit de geselecteerde bron voordat de opdrachten van de taak uitgevoerd worden."
-
-#: screens/Project/shared/Project.helptext.js:124
-msgid ""
-"Each time a job runs using this project, update the\n"
-"revision of the project prior to starting the job."
-msgstr "Voer iedere keer dat een taak uitgevoerd wordt met dit project een update uit voor de herziening van het project voordat u de taak start."
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:632
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:636
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:115
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:117
-#: screens/Credential/CredentialDetail/CredentialDetail.js:293
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:109
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:121
-#: screens/Host/HostDetail/HostDetail.js:108
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:101
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:113
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:190
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:55
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:62
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:103
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:292
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:127
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:164
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:418
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:420
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:138
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:182
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:186
-#: screens/Project/ProjectDetail/ProjectDetail.js:313
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:85
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:89
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:148
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:152
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:85
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:89
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:96
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:100
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:164
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:168
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:107
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:111
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:84
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:88
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:152
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:156
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:85
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:89
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:99
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:103
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:86
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:90
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:199
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:103
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:108
-#: screens/Setting/UI/UIDetail/UIDetail.js:105
-#: screens/Setting/UI/UIDetail/UIDetail.js:110
-#: screens/Team/TeamDetail/TeamDetail.js:55
-#: screens/Team/TeamDetail/TeamDetail.js:59
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:514
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:516
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:239
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:241
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:260
-#: screens/User/UserDetail/UserDetail.js:96
-msgid "Edit"
-msgstr "Bewerken"
-
-#: screens/Credential/CredentialList/CredentialListItem.js:67
-#: screens/Credential/CredentialList/CredentialListItem.js:71
-msgid "Edit Credential"
-msgstr "Toegangsgegevens bewerken"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:37
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:42
-msgid "Edit Credential Plugin Configuration"
-msgstr "Toegangsgegevens plug-inconfiguratie bewerken"
-
-#: screens/Application/Applications.js:38
-#: screens/Credential/Credentials.js:27
-#: screens/Host/Hosts.js:26
-#: screens/ManagementJob/ManagementJobs.js:27
-#: screens/NotificationTemplate/NotificationTemplates.js:24
-#: screens/Organization/Organizations.js:29
-#: screens/Project/Projects.js:25
-#: screens/Project/Projects.js:35
-#: screens/Setting/Settings.js:46
-#: screens/Setting/Settings.js:49
-#: screens/Setting/Settings.js:53
-#: screens/Setting/Settings.js:56
-#: screens/Setting/Settings.js:59
-#: screens/Setting/Settings.js:62
-#: screens/Setting/Settings.js:65
-#: screens/Setting/Settings.js:68
-#: screens/Setting/Settings.js:71
-#: screens/Setting/Settings.js:74
-#: screens/Setting/Settings.js:77
-#: screens/Setting/Settings.js:91
-#: screens/Setting/Settings.js:92
-#: screens/Setting/Settings.js:93
-#: screens/Setting/Settings.js:94
-#: screens/Setting/Settings.js:95
-#: screens/Setting/Settings.js:96
-#: screens/Setting/Settings.js:99
-#: screens/Setting/Settings.js:102
-#: screens/Setting/Settings.js:105
-#: screens/Setting/Settings.js:108
-#: screens/Setting/Settings.js:111
-#: screens/Setting/Settings.js:114
-#: screens/Setting/Settings.js:117
-#: screens/Setting/Settings.js:120
-#: screens/Team/Teams.js:28
-#: screens/Template/Templates.js:44
-#: screens/User/Users.js:29
-msgid "Edit Details"
-msgstr "Details bewerken"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:90
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:94
-msgid "Edit Execution Environment"
-msgstr "Uitvoeringsomgeving bewerken"
-
-#: screens/Host/HostGroups/HostGroupItem.js:37
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:46
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:46
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:51
-msgid "Edit Group"
-msgstr "Groep bewerken"
-
-#: screens/Host/HostList/HostListItem.js:74
-#: screens/Host/HostList/HostListItem.js:78
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:61
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:64
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:67
-msgid "Edit Host"
-msgstr "Host bewerken"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:134
-#: screens/Inventory/InventoryList/InventoryListItem.js:139
-msgid "Edit Inventory"
-msgstr "Inventaris bewerken"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkEditModal.js:14
-msgid "Edit Link"
-msgstr "Link bewerken"
-
-#: screens/Setting/shared/SharedFields.js:290
-msgid "Edit Login redirect override URL"
-msgstr "Login doorverwijzen URL overschrijven bewerken"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeEditModal.js:64
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:257
-msgid "Edit Node"
-msgstr "Knooppunt bewerken"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:142
-msgid "Edit Notification Template"
-msgstr "Berichtsjabloon bewerken"
-
-#: screens/Template/Survey/SurveyToolbar.js:73
-msgid "Edit Order"
-msgstr "Volgorde bewerken"
-
-#: screens/Organization/OrganizationList/OrganizationListItem.js:72
-#: screens/Organization/OrganizationList/OrganizationListItem.js:76
-msgid "Edit Organization"
-msgstr "Organisatie bewerken"
-
-#: screens/Project/ProjectList/ProjectListItem.js:248
-#: screens/Project/ProjectList/ProjectListItem.js:253
-msgid "Edit Project"
-msgstr "Project bewerken"
-
-#: screens/Template/Templates.js:50
-msgid "Edit Question"
-msgstr "Vraag bewerken"
-
-#: components/Schedule/ScheduleList/ScheduleListItem.js:132
-#: components/Schedule/ScheduleList/ScheduleListItem.js:136
-#: screens/Template/Templates.js:55
-msgid "Edit Schedule"
-msgstr "Schema bewerken"
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:131
-msgid "Edit Source"
-msgstr "Bron bewerken"
-
-#: screens/Template/Survey/SurveyListItem.js:92
-msgid "Edit Survey"
-msgstr "Vragenlijst wijzigen"
-
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:22
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:26
-#: screens/Team/TeamList/TeamListItem.js:50
-#: screens/Team/TeamList/TeamListItem.js:54
-msgid "Edit Team"
-msgstr "Team bewerken"
-
-#: components/TemplateList/TemplateListItem.js:233
-#: components/TemplateList/TemplateListItem.js:239
-msgid "Edit Template"
-msgstr "Sjabloon bewerken"
-
-#: screens/User/UserList/UserListItem.js:59
-#: screens/User/UserList/UserListItem.js:63
-msgid "Edit User"
-msgstr "Gebruiker bewerken"
-
-#: screens/Application/ApplicationsList/ApplicationListItem.js:51
-#: screens/Application/ApplicationsList/ApplicationListItem.js:55
-msgid "Edit application"
-msgstr "Toepassing bewerken"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:41
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:45
-msgid "Edit credential type"
-msgstr "Type toegangsgegevens bewerken"
-
-#: screens/CredentialType/CredentialTypes.js:25
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:25
-#: screens/InstanceGroup/InstanceGroups.js:35
-#: screens/InstanceGroup/InstanceGroups.js:40
-#: screens/Inventory/Inventories.js:63
-#: screens/Inventory/Inventories.js:68
-#: screens/Inventory/Inventories.js:77
-#: screens/Inventory/Inventories.js:88
-msgid "Edit details"
-msgstr "Details bewerken"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:42
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:44
-msgid "Edit group"
-msgstr "Groep bewerken"
-
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:48
-msgid "Edit host"
-msgstr "Host bewerken"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:78
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:82
-msgid "Edit instance group"
-msgstr "Instantiegroep bewerken"
-
-#: screens/Setting/shared/SharedFields.js:320
-#: screens/Setting/shared/SharedFields.js:322
-msgid "Edit login redirect override URL"
-msgstr "Login doorverwijzen URL overschrijven bewerken"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:70
-msgid "Edit this link"
-msgstr "Deze link bewerken"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:248
-msgid "Edit this node"
-msgstr "Dit knooppunt bewerken"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:97
-msgid "Edit workflow"
-msgstr "Workflow bewerken"
-
-#: components/Workflow/WorkflowNodeHelp.js:170
-#: screens/Job/JobOutput/shared/OutputToolbar.js:125
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:216
-msgid "Elapsed"
-msgstr "Verlopen"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:124
-msgid "Elapsed Time"
-msgstr "Verstreken tijd"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:126
-msgid "Elapsed time that the job ran"
-msgstr "Verstreken tijd in seconden dat de taak is uitgevoerd"
-
-#: components/NotificationList/NotificationList.js:193
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:134
-#: screens/User/UserDetail/UserDetail.js:66
-#: screens/User/UserList/UserList.js:115
-#: screens/User/shared/UserForm.js:73
-msgid "Email"
-msgstr "E-mail"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:177
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:125
-msgid "Email Options"
-msgstr "E-mailopties"
-
-#: screens/Template/shared/WorkflowJobTemplateForm.js:263
-msgid "Enable Concurrent Jobs"
-msgstr "Gelijktijdige taken inschakelen"
-
-#: screens/Template/shared/JobTemplateForm.js:597
-msgid "Enable Fact Storage"
-msgstr "Feitenopslag inschakelen"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:192
-msgid "Enable HTTPS certificate verification"
-msgstr "HTTPS-certificaatcontrole inschakelen"
-
-#: screens/Instances/Shared/InstanceForm.js:58
-msgid "Enable Instance"
-msgstr "Instantie wisselen"
-
-#: screens/Template/shared/JobTemplateForm.js:573
-#: screens/Template/shared/JobTemplateForm.js:576
-#: screens/Template/shared/WorkflowJobTemplateForm.js:244
-#: screens/Template/shared/WorkflowJobTemplateForm.js:247
-msgid "Enable Webhook"
-msgstr "Webhook inschakelen"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:15
-msgid "Enable Webhook for this workflow job template."
-msgstr "Webhook inschakelen voor deze workflowtaaksjabloon."
-
-#: screens/Project/shared/Project.helptext.js:108
-msgid ""
-"Enable content signing to verify that the content \n"
-"has remained secure when a project is synced. \n"
-"If the content has been tampered with, the \n"
-"job will not run."
-msgstr "Inhoud ondertekenen inschakelen om te verifiëren dat de inhoud \n"
-"veilig is gebleven wanneer een project wordt gesynchroniseerd. \n"
-"Als er met de inhoud is geknoeid, zal de \n"
-"opdracht niet uitgevoerd."
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:136
-msgid "Enable external logging"
-msgstr "Externe logboekregistratie inschakelen"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:168
-msgid "Enable log system tracking facts individually"
-msgstr "Logboeksysteem dat feiten individueel bijhoudt inschakelen"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:201
-#: components/AdHocCommands/AdHocDetailsStep.js:204
-msgid "Enable privilege escalation"
-msgstr "Verhoging van rechten inschakelen"
-
-#: screens/Setting/SettingList.js:53
-msgid "Enable simplified login for your {brandName} applications"
-msgstr "Eenvoudig inloggen inschakelen voor uw {brandName} toepassingen"
-
-#: screens/Template/shared/JobTemplate.helptext.js:31
-msgid "Enable webhook for this template."
-msgstr "Webhook inschakelen voor deze sjabloon."
-
-#: components/InstanceToggle/InstanceToggle.js:55
-#: components/Lookup/HostFilterLookup.js:110
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:45
-#: screens/TopologyView/Legend.js:205
-msgid "Enabled"
-msgstr "Ingeschakeld"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:171
-#: components/PromptDetail/PromptJobTemplateDetail.js:187
-#: components/PromptDetail/PromptProjectDetail.js:145
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:99
-#: screens/Credential/CredentialDetail/CredentialDetail.js:268
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:138
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:265
-#: screens/Project/ProjectDetail/ProjectDetail.js:302
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:365
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:199
-msgid "Enabled Options"
-msgstr "Ingeschakelde opties"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:252
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:132
-msgid "Enabled Value"
-msgstr "Ingeschakelde waarde"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:247
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:119
-msgid "Enabled Variable"
-msgstr "Ingeschakelde variabele"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:209
-msgid ""
-"Enables creation of a provisioning\n"
-"callback URL. Using the URL a host can contact {brandName}\n"
-"and request a configuration update using this job\n"
-"template"
-msgstr "Maakt het mogelijk een provisioning terugkoppelings-URL aan te maken. Met deze URL kan een host contact opnemen met {brandName}\n"
-"en een verzoek voor een configuratie-update indienen met behulp van deze taaksjabloon"
-
-#: screens/Template/shared/JobTemplate.helptext.js:29
-msgid "Enables creation of a provisioning callback URL. Using the URL a host can contact {brandName} and request a configuration update using this job template."
-msgstr "Maakt het mogelijk een provisioning terugkoppelings-URL aan te maken. Met deze URL kan een host contact opnemen met {brandName}\n"
-"en een verzoek voor een configuratie-update indienen met behulp van deze taaksjabloon."
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:160
-#: screens/Setting/shared/SettingDetail.js:87
-msgid "Encrypted"
-msgstr "Versleuteld"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:109
-#: components/Schedule/shared/FrequencyDetailSubform.js:507
-msgid "End"
-msgstr "Einde"
-
-#: screens/Setting/Subscription/SubscriptionEdit/EulaStep.js:14
-msgid "End User License Agreement"
-msgstr "Licentie-overeenkomst voor eindgebruikers"
-
-#: components/Schedule/shared/DateTimePicker.js:51
-msgid "End date"
-msgstr "Einddatum"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:561
-msgid "End date/time"
-msgstr "Einddatum/-tijd"
-
-#: components/Schedule/shared/buildRuleObj.js:110
-msgid "End did not match an expected value ({0})"
-msgstr "Einde kwam niet overeen met een verwachte waarde ({0})"
-
-#: components/Schedule/shared/DateTimePicker.js:61
-msgid "End time"
-msgstr "Eindtijd"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:209
-msgid "End user license agreement"
-msgstr "Licentie-overeenkomst voor eindgebruikers"
-
-#: screens/Host/HostList/SmartInventoryButton.js:23
-msgid "Enter at least one search filter to create a new Smart Inventory"
-msgstr "Voer ten minste één zoekfilter in om een nieuwe Smart-inventaris te maken"
-
-#: screens/CredentialType/shared/CredentialTypeForm.js:43
-msgid "Enter injectors using either JSON or YAML syntax. Refer to the Ansible Controller documentation for example syntax."
-msgstr "Geef injectoren op met JSON- of YAML-syntaxis. Raadpleeg de documentatie voor Ansible Tower voor voorbeeldsyntaxis."
-
-#: screens/CredentialType/shared/CredentialTypeForm.js:35
-msgid "Enter inputs using either JSON or YAML syntax. Refer to the Ansible Controller documentation for example syntax."
-msgstr "Geef inputs op met JSON- of YAML-syntaxis. Raadpleeg de documentatie voor Ansible Tower voor voorbeeldsyntaxis."
-
-#: screens/Inventory/shared/SmartInventoryForm.js:94
-msgid ""
-"Enter inventory variables using either JSON or YAML syntax.\n"
-"Use the radio button to toggle between the two. Refer to the\n"
-"Ansible Controller documentation for example syntax."
-msgstr "Voer de variabelen van het inventaris in met JSON- of YAML-syntaxis. Gebruik de radio-knop om tussen de twee te wisselen. Raadpleeg de documentatie van Ansible Tower voor voorbeeldsyntaxis"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:87
-msgid "Environment variables or extra variables that specify the values a credential type can inject."
-msgstr "Omgevingsvariabelen of extra variabelen die aangeven welke waarden een credentialtype kan injecteren."
-
-#: components/JobList/JobList.js:233
-#: components/StatusLabel/StatusLabel.js:46
-#: components/Workflow/WorkflowNodeHelp.js:108
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:133
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:205
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:143
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:230
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:123
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:135
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:222
-#: screens/Job/JobOutput/JobOutputSearch.js:104
-#: screens/TopologyView/Legend.js:178
-msgid "Error"
-msgstr "Fout"
-
-#: screens/Project/ProjectList/ProjectList.js:302
-msgid "Error fetching updated project"
-msgstr "Fout bij ophalen bijgewerkt project"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:501
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:141
-msgid "Error message"
-msgstr "Foutbericht"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:510
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:150
-msgid "Error message body"
-msgstr "Foutbericht body"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:709
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:711
-msgid "Error saving the workflow!"
-msgstr "Fout bij het opslaan van de workflow!"
-
-#: components/AdHocCommands/AdHocCommands.js:104
-#: components/CopyButton/CopyButton.js:51
-#: components/DeleteButton/DeleteButton.js:56
-#: components/HostToggle/HostToggle.js:76
-#: components/InstanceToggle/InstanceToggle.js:67
-#: components/JobList/JobList.js:315
-#: components/JobList/JobList.js:326
-#: components/LaunchButton/LaunchButton.js:185
-#: components/LaunchPrompt/LaunchPrompt.js:96
-#: components/NotificationList/NotificationList.js:246
-#: components/PaginatedTable/ToolbarDeleteButton.js:205
-#: components/RelatedTemplateList/RelatedTemplateList.js:241
-#: components/ResourceAccessList/ResourceAccessList.js:277
-#: components/ResourceAccessList/ResourceAccessList.js:289
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:654
-#: components/Schedule/ScheduleList/ScheduleList.js:239
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:73
-#: components/Schedule/shared/SchedulePromptableFields.js:63
-#: components/TemplateList/TemplateList.js:299
-#: contexts/Config.js:94
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:136
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:155
-#: screens/Application/ApplicationsList/ApplicationsList.js:185
-#: screens/Credential/CredentialDetail/CredentialDetail.js:314
-#: screens/Credential/CredentialList/CredentialList.js:214
-#: screens/Host/HostDetail/HostDetail.js:56
-#: screens/Host/HostDetail/HostDetail.js:123
-#: screens/Host/HostGroups/HostGroupsList.js:244
-#: screens/Host/HostList/HostList.js:233
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:310
-#: screens/InstanceGroup/Instances/InstanceList.js:308
-#: screens/InstanceGroup/Instances/InstanceListItem.js:218
-#: screens/Instances/InstanceDetail/InstanceDetail.js:360
-#: screens/Instances/InstanceDetail/InstanceDetail.js:375
-#: screens/Instances/InstanceList/InstanceList.js:231
-#: screens/Instances/InstanceList/InstanceList.js:243
-#: screens/Instances/InstanceList/InstanceListItem.js:234
-#: screens/Instances/Shared/RemoveInstanceButton.js:104
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:210
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:78
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:285
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:296
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:56
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:118
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:261
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:201
-#: screens/Inventory/InventoryList/InventoryList.js:285
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:264
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:323
-#: screens/Inventory/InventorySources/InventorySourceList.js:239
-#: screens/Inventory/InventorySources/InventorySourceList.js:252
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:183
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:152
-#: screens/Inventory/shared/InventorySourceSyncButton.js:49
-#: screens/Login/Login.js:239
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:125
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:444
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:233
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:169
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:208
-#: screens/Organization/OrganizationList/OrganizationList.js:195
-#: screens/Project/ProjectDetail/ProjectDetail.js:348
-#: screens/Project/ProjectList/ProjectList.js:291
-#: screens/Project/ProjectList/ProjectList.js:303
-#: screens/Project/shared/ProjectSyncButton.js:60
-#: screens/Team/TeamDetail/TeamDetail.js:78
-#: screens/Team/TeamList/TeamList.js:192
-#: screens/Team/TeamRoles/TeamRolesList.js:247
-#: screens/Team/TeamRoles/TeamRolesList.js:258
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:554
-#: screens/Template/TemplateSurvey.js:130
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:277
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:180
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:195
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:337
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:373
-#: screens/TopologyView/MeshGraph.js:405
-#: screens/TopologyView/Tooltip.js:199
-#: screens/User/UserDetail/UserDetail.js:115
-#: screens/User/UserList/UserList.js:189
-#: screens/User/UserRoles/UserRolesList.js:243
-#: screens/User/UserRoles/UserRolesList.js:254
-#: screens/User/UserTeams/UserTeamList.js:259
-#: screens/User/UserTokenDetail/UserTokenDetail.js:85
-#: screens/User/UserTokenList/UserTokenList.js:214
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:348
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:189
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:53
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:48
-msgid "Error!"
-msgstr "Fout!"
-
-#: components/CodeEditor/VariablesDetail.js:105
-msgid "Error:"
-msgstr "Fout:"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:267
-#: screens/Instances/InstanceDetail/InstanceDetail.js:315
-msgid "Errors"
-msgstr "Fouten"
-
-#: screens/TopologyView/Legend.js:253
-msgid "Established"
-msgstr "Gevestigd"
-
-#: screens/ActivityStream/ActivityStream.js:265
-#: screens/ActivityStream/ActivityStreamListItem.js:46
-#: screens/Job/JobOutput/JobOutputSearch.js:99
-msgid "Event"
-msgstr "Gebeurtenis"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:35
-msgid "Event detail"
-msgstr "Gebeurtenisinformatie weergeven"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:36
-msgid "Event detail modal"
-msgstr "Modus gebeurtenisdetails"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:555
-msgid "Event summary not available"
-msgstr "Samenvatting van de gebeurtenis niet beschikbaar"
-
-#: screens/ActivityStream/ActivityStream.js:234
-msgid "Events"
-msgstr "Gebeurtenissen"
-
-#: screens/Job/JobOutput/JobOutput.js:713
-msgid "Events processing complete."
-msgstr "Verwerking van gebeurtenissen voltooid."
-
-#: components/Search/LookupTypeInput.js:39
-msgid "Exact match (default lookup if not specified)."
-msgstr "Exacte overeenkomst (standaard-opzoeken indien niet opgegeven)."
-
-#: components/Search/RelatedLookupTypeInput.js:38
-msgid "Exact search on id field."
-msgstr "Exact zoeken op id-veld."
-
-#: screens/Project/shared/Project.helptext.js:23
-msgid "Example URLs for GIT Source Control include:"
-msgstr "Voorbeeld-URL's voor GIT-broncontrole zijn:"
-
-#: screens/Project/shared/Project.helptext.js:62
-msgid "Example URLs for Remote Archive Source Control include:"
-msgstr "Voorbeeld-URL's voor Remote Archive-broncontrole zijn:"
-
-#: screens/Project/shared/Project.helptext.js:45
-msgid "Example URLs for Subversion Source Control include:"
-msgstr "Voorbeeld-URL's voor Subversion-broncontrole zijn:"
-
-#: screens/Project/shared/Project.helptext.js:84
-msgid "Examples include:"
-msgstr "Voorbeelden hiervan zijn:"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:10
-msgid "Examples:"
-msgstr "Voorbeelden:"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:354
-msgid "Exception Frequency"
-msgstr "Uitzonderingsfrequentie"
-
-#: components/Schedule/shared/ScheduleFormFields.js:160
-msgid "Exceptions"
-msgstr "Uitzonderingen"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:47
-msgid "Execute regardless of the parent node's final state."
-msgstr "Uitvoeren ongeacht de eindtoestand van het bovenliggende knooppunt."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:40
-msgid "Execute when the parent node results in a failure state."
-msgstr "Uitvoeren wanneer het bovenliggende knooppunt in een storingstoestand komt."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:33
-msgid "Execute when the parent node results in a successful state."
-msgstr "Uitvoeren wanneer het bovenliggende knooppunt in een succesvolle status resulteert."
-
-#: screens/InstanceGroup/Instances/InstanceList.js:208
-#: screens/Instances/InstanceList/InstanceList.js:152
-msgid "Execution"
-msgstr "Uitvoering"
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:90
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:91
-#: components/AdHocCommands/AdHocPreviewStep.js:58
-#: components/AdHocCommands/useAdHocExecutionEnvironmentStep.js:15
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:41
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:105
-#: components/LaunchPrompt/steps/useExecutionEnvironmentStep.js:29
-#: components/Lookup/ExecutionEnvironmentLookup.js:159
-#: components/Lookup/ExecutionEnvironmentLookup.js:191
-#: components/Lookup/ExecutionEnvironmentLookup.js:208
-#: components/PromptDetail/PromptDetail.js:220
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:451
-msgid "Execution Environment"
-msgstr "Uitvoeringsomgeving"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:70
-#: components/TemplateList/TemplateListItem.js:160
-msgid "Execution Environment Missing"
-msgstr "Uitvoeringsomgeving ontbreekt"
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:103
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:107
-#: routeConfig.js:147
-#: screens/ActivityStream/ActivityStream.js:217
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:129
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:191
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:13
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:22
-#: screens/Organization/Organization.js:127
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:78
-#: screens/Organization/Organizations.js:34
-#: util/getRelatedResourceDeleteDetails.js:81
-#: util/getRelatedResourceDeleteDetails.js:188
-msgid "Execution Environments"
-msgstr "Uitvoeringsomgevingen"
-
-#: screens/Job/JobDetail/JobDetail.js:345
-msgid "Execution Node"
-msgstr "Uitvoeringsknooppunt"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:103
-msgid "Execution environment copied successfully"
-msgstr "Uitvoeringsomgeving gekopieerd"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:112
-msgid "Execution environment is missing or deleted."
-msgstr "Uitvoeringsomgeving ontbreekt of is verwijderd."
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:83
-msgid "Execution environment not found."
-msgstr "Uitvoeringsomgeving niet gevonden."
-
-#: screens/TopologyView/Legend.js:86
-msgid "Execution node"
-msgstr "Uitvoeringsknooppunt"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:23
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:26
-msgid "Exit Without Saving"
-msgstr "Afsluiten zonder op te slaan"
-
-#: components/ExpandCollapse/ExpandCollapse.js:52
-msgid "Expand"
-msgstr "Uitbreiden"
-
-#: components/DataListToolbar/DataListToolbar.js:105
-msgid "Expand all rows"
-msgstr "Alle rijen uitklappen"
-
-#: components/CodeEditor/VariablesDetail.js:212
-#: components/CodeEditor/VariablesField.js:248
-msgid "Expand input"
-msgstr "Input uitbreiden"
-
-#: screens/Job/JobOutput/PageControls.js:50
-msgid "Expand job events"
-msgstr "Taakgebeurtenissen uitklappen"
-
-#: screens/Job/JobOutput/shared/JobEventLineToggle.js:37
-msgid "Expand section"
-msgstr "Sectie uitklappen"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:46
-msgid "Expected at least one of client_email, project_id or private_key to be present in the file."
-msgstr "Minstens één van client_email, project_id of private_key werd verwacht aanwezig te zijn in het bestand."
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:137
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:34
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:148
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:172
-#: screens/User/UserTokenDetail/UserTokenDetail.js:56
-#: screens/User/UserTokenList/UserTokenList.js:146
-#: screens/User/UserTokenList/UserTokenList.js:190
-#: screens/User/UserTokenList/UserTokenListItem.js:35
-#: screens/User/UserTokens/UserTokens.js:89
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:151
-msgid "Expires"
-msgstr "Verloopt"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:146
-msgid "Expires on"
-msgstr "Verloopt op"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:156
-msgid "Expires on UTC"
-msgstr "Verloopt op UTC"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:50
-msgid "Expires on {0}"
-msgstr "Verloopt op {0}"
-
-#: components/JobList/JobListItem.js:307
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:181
-msgid "Explanation"
-msgstr "Uitleg"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:113
-msgid "External Secret Management System"
-msgstr "Extern geheimbeheersysteem"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:272
-#: components/AdHocCommands/AdHocDetailsStep.js:273
-msgid "Extra variables"
-msgstr "Extra variabelen"
-
-#: components/Sparkline/Sparkline.js:35
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:164
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:43
-#: screens/Project/ProjectDetail/ProjectDetail.js:139
-#: screens/Project/ProjectList/ProjectListItem.js:77
-msgid "FINISHED:"
-msgstr "VOLTOOID:"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:73
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:137
-msgid "Fact Storage"
-msgstr "Feitenopslag"
-
-#: screens/Template/shared/JobTemplate.helptext.js:39
-msgid "Fact storage: If enabled, this will store gathered facts so they can be viewed at the host level. Facts are persisted and injected into the fact cache at runtime.."
-msgstr "Indien ingeschakeld, worden de verzamelde feiten opgeslagen zodat ze kunnen worden weergegeven op hostniveau. Feiten worden bewaard en\n"
-"geïnjecteerd in de feitencache tijdens runtime."
-
-#: screens/Host/Host.js:63
-#: screens/Host/HostFacts/HostFacts.js:45
-#: screens/Host/Hosts.js:28
-#: screens/Inventory/Inventories.js:71
-#: screens/Inventory/InventoryHost/InventoryHost.js:78
-#: screens/Inventory/InventoryHostFacts/InventoryHostFacts.js:39
-msgid "Facts"
-msgstr "Feiten"
-
-#: components/JobList/JobList.js:232
-#: components/StatusLabel/StatusLabel.js:45
-#: components/Workflow/WorkflowNodeHelp.js:105
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:90
-#: screens/Dashboard/shared/ChartTooltip.js:66
-#: screens/Job/JobOutput/shared/HostStatusBar.js:47
-#: screens/Job/JobOutput/shared/OutputToolbar.js:113
-msgid "Failed"
-msgstr "Mislukt"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:112
-msgid "Failed Host Count"
-msgstr "Aantal mislukte hosts"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:114
-msgid "Failed Hosts"
-msgstr "Mislukte hosts"
-
-#: components/LaunchButton/ReLaunchDropDown.js:61
-#: screens/Dashboard/Dashboard.js:87
-msgid "Failed hosts"
-msgstr "Mislukte hosts"
-
-#: screens/Dashboard/DashboardGraph.js:170
-msgid "Failed jobs"
-msgstr "Mislukte taken"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:56
-msgid "Failed to approve {0}."
-msgstr "Niet goedgekeurd {0}."
-
-#: components/ResourceAccessList/ResourceAccessList.js:281
-msgid "Failed to assign roles properly"
-msgstr "Kan rollen niet goed toewijzen"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:250
-#: screens/User/UserRoles/UserRolesList.js:246
-msgid "Failed to associate role"
-msgstr "Kan rol niet koppelen"
-
-#: screens/Host/HostGroups/HostGroupsList.js:248
-#: screens/InstanceGroup/Instances/InstanceList.js:311
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:288
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:265
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:268
-#: screens/User/UserTeams/UserTeamList.js:263
-msgid "Failed to associate."
-msgstr "Kan niet koppelen."
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:301
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:111
-msgid "Failed to cancel Inventory Source Sync"
-msgstr "Kan de synchronisatie van de inventarisbron niet annuleren"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:322
-#: screens/Project/ProjectList/ProjectListItem.js:232
-msgid "Failed to cancel Project Sync"
-msgstr "Kan projectsynchronisatie niet annuleren"
-
-#: components/JobList/JobList.js:329
-msgid "Failed to cancel one or more jobs."
-msgstr "Kan een of meer taken niet annuleren."
-
-#: components/JobList/JobListItem.js:114
-#: screens/Job/JobDetail/JobDetail.js:601
-#: screens/Job/JobOutput/shared/OutputToolbar.js:138
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:90
-msgid "Failed to cancel {0}"
-msgstr "Kan {0} niet annuleren"
-
-#: screens/Credential/CredentialList/CredentialListItem.js:88
-msgid "Failed to copy credential."
-msgstr "Kan toegangsgegevens niet kopiëren."
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:112
-msgid "Failed to copy execution environment"
-msgstr "Kan uitvoeringsomgeving niet kopiëren"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:162
-msgid "Failed to copy inventory."
-msgstr "Kan inventaris niet kopiëren."
-
-#: screens/Project/ProjectList/ProjectListItem.js:270
-msgid "Failed to copy project."
-msgstr "Kan project niet kopiëren."
-
-#: components/TemplateList/TemplateListItem.js:253
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:160
-msgid "Failed to copy template."
-msgstr "Kan sjabloon niet kopiëren."
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:139
-msgid "Failed to delete application."
-msgstr "Kan toepassing niet verwijderen."
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:317
-msgid "Failed to delete credential."
-msgstr "Kan toegangsgegevens niet verwijderen."
-
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:82
-msgid "Failed to delete group {0}."
-msgstr "Kan groep {0} niet verwijderen."
-
-#: screens/Host/HostDetail/HostDetail.js:126
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:121
-msgid "Failed to delete host."
-msgstr "Kan host niet verwijderen."
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:327
-msgid "Failed to delete inventory source {name}."
-msgstr "Kan inventarisbron {name} niet verwijderen."
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:213
-msgid "Failed to delete inventory."
-msgstr "Kan inventaris niet verwijderen."
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:557
-msgid "Failed to delete job template."
-msgstr "Kan taaksjabloon niet verwijderen."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:448
-msgid "Failed to delete notification."
-msgstr "Kan bericht niet verwijderen."
-
-#: screens/Application/ApplicationsList/ApplicationsList.js:188
-msgid "Failed to delete one or more applications."
-msgstr "Een of meer toepassingen kunnen niet worden verwijderd."
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:208
-msgid "Failed to delete one or more credential types."
-msgstr "Een of meer typen toegangsgegevens kunnen niet worden verwijderd."
-
-#: screens/Credential/CredentialList/CredentialList.js:217
-msgid "Failed to delete one or more credentials."
-msgstr "Een of meer toegangsgegevens kunnen niet worden verwijderd."
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:233
-msgid "Failed to delete one or more execution environments"
-msgstr "Een of meer uitvoeringsomgevingen kunnen niet worden verwijderd"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:155
-msgid "Failed to delete one or more groups."
-msgstr "Een of meer groepen kunnen niet worden verwijderd."
-
-#: screens/Host/HostList/HostList.js:236
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:204
-msgid "Failed to delete one or more hosts."
-msgstr "Een of meer hosts kunnen niet worden verwijderd."
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:225
-msgid "Failed to delete one or more instance groups."
-msgstr "Een of meer instantiegroepen kunnen niet worden verwijderd."
-
-#: screens/Inventory/InventoryList/InventoryList.js:288
-msgid "Failed to delete one or more inventories."
-msgstr "Een of meer inventarissen kunnen niet worden verwijderd."
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:255
-msgid "Failed to delete one or more inventory sources."
-msgstr "Een of meer inventarisbronnen kunnen niet worden verwijderd."
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:244
-msgid "Failed to delete one or more job templates."
-msgstr "Een of meer taaksjablonen kunnen niet worden verwijderd."
-
-#: components/JobList/JobList.js:318
-msgid "Failed to delete one or more jobs."
-msgstr "Een of meer taken kunnen niet worden verwijderd."
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:236
-msgid "Failed to delete one or more notification template."
-msgstr "Een of meer berichtsjablonen kunnen niet worden verwijderd."
-
-#: screens/Organization/OrganizationList/OrganizationList.js:198
-msgid "Failed to delete one or more organizations."
-msgstr "Een of meer organisaties kunnen niet worden verwijderd."
-
-#: screens/Project/ProjectList/ProjectList.js:294
-msgid "Failed to delete one or more projects."
-msgstr "Een of meer projecten kunnen niet worden verwijderd."
-
-#: components/Schedule/ScheduleList/ScheduleList.js:242
-msgid "Failed to delete one or more schedules."
-msgstr "Een of meer schema's kunnen niet worden verwijderd."
-
-#: screens/Team/TeamList/TeamList.js:195
-msgid "Failed to delete one or more teams."
-msgstr "Een of meer teams kunnen niet worden verwijderd."
-
-#: components/TemplateList/TemplateList.js:302
-msgid "Failed to delete one or more templates."
-msgstr "Een of meer sjablonen kunnen niet worden verwijderd."
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:158
-msgid "Failed to delete one or more tokens."
-msgstr "Een of meer tokens kunnen niet worden verwijderd."
-
-#: screens/User/UserTokenList/UserTokenList.js:217
-msgid "Failed to delete one or more user tokens."
-msgstr "Een of meer gebruikerstokens kunnen niet worden verwijderd."
-
-#: screens/User/UserList/UserList.js:192
-msgid "Failed to delete one or more users."
-msgstr "Een of meer gebruikers kunnen niet worden verwijderd."
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:192
-msgid "Failed to delete one or more workflow approval."
-msgstr "Een of meer workflowgoedkeuringen kunnen niet worden verwijderd."
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:211
-msgid "Failed to delete organization."
-msgstr "Kan organisatie niet verwijderen."
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:351
-msgid "Failed to delete project."
-msgstr "Kan project niet verwijderen."
-
-#: components/ResourceAccessList/ResourceAccessList.js:292
-msgid "Failed to delete role"
-msgstr "Kan rol niet verwijderen"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:261
-#: screens/User/UserRoles/UserRolesList.js:257
-msgid "Failed to delete role."
-msgstr "Kan rol niet verwijderen."
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:657
-msgid "Failed to delete schedule."
-msgstr "Kan schema niet verwijderen."
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:186
-msgid "Failed to delete smart inventory."
-msgstr "Kan Smart-inventaris niet verwijderen."
-
-#: screens/Team/TeamDetail/TeamDetail.js:81
-msgid "Failed to delete team."
-msgstr "Kan team niet verwijderen."
-
-#: screens/User/UserDetail/UserDetail.js:118
-msgid "Failed to delete user."
-msgstr "Kan gebruiker niet verwijderen."
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:351
-msgid "Failed to delete workflow approval."
-msgstr "Kan workflowgoedkeuring niet verwijderen."
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:280
-msgid "Failed to delete workflow job template."
-msgstr "Kan workflow-taaksjabloon niet verwijderen."
-
-#: screens/Host/HostDetail/HostDetail.js:59
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:59
-msgid "Failed to delete {name}."
-msgstr "Kan {name} niet verwijderen."
-
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:51
-msgid "Failed to deny {0}."
-msgstr "Kan {0} niet verwijderen."
-
-#: screens/Host/HostGroups/HostGroupsList.js:249
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:266
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:269
-msgid "Failed to disassociate one or more groups."
-msgstr "Een of meer groepen kunnen niet worden losgekoppeld."
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:299
-msgid "Failed to disassociate one or more hosts."
-msgstr "Een of meer hosts kunnen niet worden losgekoppeld."
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:315
-#: screens/InstanceGroup/Instances/InstanceList.js:313
-#: screens/Instances/InstanceDetail/InstanceDetail.js:365
-msgid "Failed to disassociate one or more instances."
-msgstr "Een of meer instanties kunnen niet worden losgekoppeld."
-
-#: screens/User/UserTeams/UserTeamList.js:264
-msgid "Failed to disassociate one or more teams."
-msgstr "Een of meer teams kunnen niet worden losgekoppeld."
-
-#: screens/Login/Login.js:243
-msgid "Failed to fetch custom login configuration settings. System defaults will be shown instead."
-msgstr "Kan de aangepaste configuratie-instellingen voor inloggen niet ophalen. De standaardsysteeminstellingen worden in plaats daarvan getoond."
-
-#: screens/Project/ProjectList/ProjectList.js:306
-msgid "Failed to fetch the updated project data."
-msgstr "Kan de bijgewerkte projectgegevens niet ophalen."
-
-#: screens/TopologyView/MeshGraph.js:409
-msgid "Failed to get instance."
-msgstr "Kon dashboard niet weergeven:"
-
-#: components/AdHocCommands/AdHocCommands.js:112
-#: components/LaunchButton/LaunchButton.js:188
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:128
-msgid "Failed to launch job."
-msgstr "Kan de taak niet starten."
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:378
-#: screens/Instances/InstanceList/InstanceList.js:246
-msgid "Failed to remove one or more instances."
-msgstr "Een of meer instanties kunnen niet worden losgekoppeld."
-
-#: contexts/Config.js:98
-msgid "Failed to retrieve configuration."
-msgstr "Kan de configuratie niet ophalen."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:376
-msgid "Failed to retrieve full node resource object."
-msgstr "Kan geen volledig bronobject van knooppunt ophalen."
-
-#: screens/InstanceGroup/Instances/InstanceList.js:315
-#: screens/Instances/InstanceList/InstanceList.js:234
-msgid "Failed to run a health check on one or more instances."
-msgstr "Kan geen gezondheidscontrole uitvoeren op een of meer instanties."
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:172
-msgid "Failed to send test notification."
-msgstr "Kan testbericht niet verzenden."
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:52
-msgid "Failed to sync inventory source."
-msgstr "Kan inventarisbron niet synchroniseren."
-
-#: screens/Project/shared/ProjectSyncButton.js:63
-msgid "Failed to sync project."
-msgstr "Kan project niet synchroniseren."
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:242
-msgid "Failed to sync some or all inventory sources."
-msgstr "Kan sommige of alle inventarisbronnen niet synchroniseren."
-
-#: components/HostToggle/HostToggle.js:80
-msgid "Failed to toggle host."
-msgstr "Kan niet van host wisselen."
-
-#: components/InstanceToggle/InstanceToggle.js:71
-msgid "Failed to toggle instance."
-msgstr "Kan niet van instantie wisselen."
-
-#: components/NotificationList/NotificationList.js:250
-msgid "Failed to toggle notification."
-msgstr "Kan niet van bericht wisselen."
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:77
-msgid "Failed to toggle schedule."
-msgstr "Kan niet van schema wisselen."
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:314
-#: screens/InstanceGroup/Instances/InstanceListItem.js:222
-#: screens/Instances/InstanceDetail/InstanceDetail.js:364
-#: screens/Instances/InstanceList/InstanceListItem.js:238
-msgid "Failed to update capacity adjustment."
-msgstr "Kan de capaciteitsaanpassing niet bijwerken."
-
-#: screens/TopologyView/Tooltip.js:204
-msgid "Failed to update instance."
-msgstr "Kan de vragenlijst niet bijwerken."
-
-#: screens/Template/TemplateSurvey.js:133
-msgid "Failed to update survey."
-msgstr "Kan de vragenlijst niet bijwerken."
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:88
-msgid "Failed to user token."
-msgstr "Kan gebruikerstoken niet bijwerken."
-
-#: components/NotificationList/NotificationListItem.js:85
-#: components/NotificationList/NotificationListItem.js:86
-msgid "Failure"
-msgstr "Mislukking"
-
-#: screens/Job/JobOutput/EmptyOutput.js:45
-msgid "Failure Explanation:"
-msgstr "Storing Verklaring:"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:66
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:210
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:240
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:270
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:315
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:373
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:142
-msgid "False"
-msgstr "False"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:160
-#: components/Schedule/shared/FrequencyDetailSubform.js:103
-msgid "February"
-msgstr "Februari"
-
-#: components/Search/LookupTypeInput.js:52
-msgid "Field contains value."
-msgstr "Veld bevat waarde."
-
-#: components/Search/LookupTypeInput.js:80
-msgid "Field ends with value."
-msgstr "Veld eindigt op waarde."
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:76
-msgid "Field for passing a custom Kubernetes or OpenShift Pod specification."
-msgstr "Veld voor het opgeven van een aangepaste Kubernetes of OpenShift Pod-specificatie."
-
-#: components/Search/LookupTypeInput.js:94
-msgid "Field matches the given regular expression."
-msgstr "Het veld komt overeen met de opgegeven reguliere expressie."
-
-#: components/Search/LookupTypeInput.js:66
-msgid "Field starts with value."
-msgstr "Veld begint met waarde."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:416
-msgid "Fifth"
-msgstr "Vijfde"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:105
-msgid "File Difference"
-msgstr "Bestandsverschil"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:72
-msgid "File upload rejected. Please select a single .json file."
-msgstr "Bestand uploaden geweigerd. Selecteer één .json-bestand."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:96
-msgid "File, directory or script"
-msgstr "Bestand, map of script"
-
-#: components/Search/Search.js:198
-#: components/Search/Search.js:222
-msgid "Filter By {name}"
-msgstr "Filteren op {name}"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:88
-msgid "Filter by failed jobs"
-msgstr "Filteren op mislukte opdrachten"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:94
-msgid "Filter by successful jobs"
-msgstr "Recente succesvolle taken"
-
-#: components/JobList/JobList.js:248
-#: components/JobList/JobListItem.js:100
-msgid "Finish Time"
-msgstr "Voltooiingstijd"
-
-#: screens/Job/JobDetail/JobDetail.js:226
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:208
-msgid "Finished"
-msgstr "Voltooid"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:404
-msgid "First"
-msgstr "Eerste"
-
-#: components/AddRole/AddResourceRole.js:28
-#: components/AddRole/AddResourceRole.js:42
-#: components/ResourceAccessList/ResourceAccessList.js:178
-#: screens/User/UserDetail/UserDetail.js:64
-#: screens/User/UserList/UserList.js:124
-#: screens/User/UserList/UserList.js:161
-#: screens/User/UserList/UserListItem.js:53
-#: screens/User/shared/UserForm.js:63
-msgid "First Name"
-msgstr "Voornaam"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:332
-msgid "First Run"
-msgstr "Eerste uitvoering"
-
-#: components/ResourceAccessList/ResourceAccessList.js:227
-#: components/ResourceAccessList/ResourceAccessListItem.js:67
-msgid "First name"
-msgstr "Voornaam"
-
-#: components/Search/AdvancedSearch.js:213
-#: components/Search/AdvancedSearch.js:227
-msgid "First, select a key"
-msgstr "Selecteer eerst een sleutel"
-
-#: components/Workflow/WorkflowTools.js:88
-msgid "Fit the graph to the available screen size"
-msgstr "Pas de grafiek aan de beschikbare schermgrootte aan"
-
-#: screens/TopologyView/Header.js:75
-#: screens/TopologyView/Header.js:78
-msgid "Fit to screen"
-msgstr "Aanpassen naar scherm"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:94
-msgid "Float"
-msgstr "Drijven"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:183
-msgid "Follow"
-msgstr "Volgen"
-
-#: screens/Job/Job.helptext.js:5
-#: screens/Template/shared/JobTemplate.helptext.js:6
-msgid "For job templates, select run to execute the playbook. Select check to only check playbook syntax, test environment setup, and report problems without executing the playbook."
-msgstr "Voor taaksjablonen selecteer \"uitvoeren\" om het draaiboek uit te voeren. Selecteer \"controleren\" om slechts de syntaxis van het draaiboek te controleren, de installatie van de omgeving te testen en problemen te rapporteren zonder het draaiboek uit te voeren."
-
-#: screens/Project/shared/Project.helptext.js:98
-msgid "For more information, refer to the"
-msgstr "Bekijk voor meer informatie de"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:160
-#: components/AdHocCommands/AdHocDetailsStep.js:161
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:55
-#: components/PromptDetail/PromptDetail.js:345
-#: components/PromptDetail/PromptJobTemplateDetail.js:150
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:475
-#: screens/Job/JobDetail/JobDetail.js:389
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:259
-#: screens/Template/shared/JobTemplateForm.js:409
-#: screens/TopologyView/Tooltip.js:282
-msgid "Forks"
-msgstr "Vorken"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:414
-msgid "Fourth"
-msgstr "Vierde"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:363
-#: components/Schedule/shared/ScheduleFormFields.js:146
-msgid "Frequency Details"
-msgstr "Frequentie-informatie"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:381
-msgid "Frequency Exception Details"
-msgstr "Frequentie Uitzondering Details"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:72
-#: components/Schedule/shared/FrequencyDetailSubform.js:185
-#: components/Schedule/shared/FrequencyDetailSubform.js:206
-#: components/Schedule/shared/buildRuleObj.js:91
-msgid "Frequency did not match an expected value"
-msgstr "Frequentie kwam niet overeen met een verwachte waarde"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:310
-msgid "Fri"
-msgstr "Vrij"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:81
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:187
-#: components/Schedule/shared/FrequencyDetailSubform.js:315
-#: components/Schedule/shared/FrequencyDetailSubform.js:453
-msgid "Friday"
-msgstr "Vrijdag"
-
-#: components/Search/RelatedLookupTypeInput.js:45
-msgid "Fuzzy search on id, name or description fields."
-msgstr "Fuzzy search op id, naam of beschrijvingsvelden."
-
-#: components/Search/RelatedLookupTypeInput.js:32
-msgid "Fuzzy search on name field."
-msgstr "Fuzzy search op naamveld."
-
-#: components/CredentialChip/CredentialChip.js:13
-msgid "GPG Public Key"
-msgstr "GPG openbare sleutel"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:153
-#: screens/Organization/shared/OrganizationForm.js:101
-msgid "Galaxy Credentials"
-msgstr "Galaxy-toegangsgegevens"
-
-#: screens/Credential/shared/CredentialForm.js:185
-msgid "Galaxy credentials must be owned by an Organization."
-msgstr "Galaxy-toegangsgegevens moeten eigendom zijn van een organisatie."
-
-#: screens/Job/JobOutput/JobOutputSearch.js:106
-msgid "Gathering Facts"
-msgstr "Feiten verzamelen"
-
-#: screens/Setting/Settings.js:72
-msgid "Generic OIDC"
-msgstr "Generieke OIDC"
-
-#: screens/Setting/SettingList.js:85
-msgid "Generic OIDC settings"
-msgstr "Algemene OIDC-instellingen"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:222
-msgid "Get subscription"
-msgstr "Abonnement ophalen"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:216
-msgid "Get subscriptions"
-msgstr "Abonnementen ophalen"
-
-#: components/Lookup/ProjectLookup.js:136
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:89
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:158
-#: screens/Job/JobDetail/JobDetail.js:75
-#: screens/Project/ProjectList/ProjectList.js:198
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:97
-msgid "Git"
-msgstr "Git"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:312
-#: screens/Template/shared/WebhookSubForm.js:106
-msgid "GitHub"
-msgstr "GitHub"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:85
-#: screens/Setting/Settings.js:51
-msgid "GitHub Default"
-msgstr "GitHub-standaard"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:100
-#: screens/Setting/Settings.js:60
-msgid "GitHub Enterprise"
-msgstr "GitHub Enterprise"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:105
-#: screens/Setting/Settings.js:63
-msgid "GitHub Enterprise Organization"
-msgstr "GitHub Enterprise-organisatie"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:110
-#: screens/Setting/Settings.js:66
-msgid "GitHub Enterprise Team"
-msgstr "GitHub Enterprise-team"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:90
-#: screens/Setting/Settings.js:54
-msgid "GitHub Organization"
-msgstr "GitHub-organisatie"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:95
-#: screens/Setting/Settings.js:57
-msgid "GitHub Team"
-msgstr "GitHub-team"
-
-#: screens/Setting/SettingList.js:61
-msgid "GitHub settings"
-msgstr "GitHub-instellingen"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:312
-#: screens/Template/shared/WebhookSubForm.js:112
-msgid "GitLab"
-msgstr "GitLab"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:79
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:84
-msgid "Globally Available"
-msgstr "Wereldwijd beschikbaar"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:134
-msgid "Globally available execution environment can not be reassigned to a specific Organization"
-msgstr "Wereldwijd beschikbare uitvoeringsomgeving kan niet opnieuw worden toegewezen aan een specifieke organisatie"
-
-#: components/Pagination/Pagination.js:29
-msgid "Go to first page"
-msgstr "Ga naar de eerste pagina"
-
-#: components/Pagination/Pagination.js:31
-msgid "Go to last page"
-msgstr "Ga naar de laatste pagina"
-
-#: components/Pagination/Pagination.js:32
-msgid "Go to next page"
-msgstr "Ga naar de volgende pagina"
-
-#: components/Pagination/Pagination.js:30
-msgid "Go to previous page"
-msgstr "Ga naar de vorige pagina"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:99
-msgid "Google Compute Engine"
-msgstr "Google Compute Engine"
-
-#: screens/Setting/SettingList.js:65
-msgid "Google OAuth 2 settings"
-msgstr "Google OAuth 2-instellingen"
-
-#: screens/Setting/Settings.js:69
-msgid "Google OAuth2"
-msgstr "Google OAuth2"
-
-#: components/NotificationList/NotificationList.js:194
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:135
-msgid "Grafana"
-msgstr "Grafana"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:160
-msgid "Grafana API key"
-msgstr "Grafana API-sleutel"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:187
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:151
-msgid "Grafana URL"
-msgstr "Grafana URL"
-
-#: components/Search/LookupTypeInput.js:106
-msgid "Greater than comparison."
-msgstr "Groter dan vergelijking."
-
-#: components/Search/LookupTypeInput.js:113
-msgid "Greater than or equal to comparison."
-msgstr "Groter dan of gelijk aan vergelijking."
-
-#: components/Lookup/HostFilterLookup.js:102
-msgid "Group"
-msgstr "Groep"
-
-#: screens/Inventory/Inventories.js:78
-msgid "Group details"
-msgstr "Groepsdetails"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:124
-msgid "Group type"
-msgstr "Type groep"
-
-#: screens/Host/Host.js:68
-#: screens/Host/HostGroups/HostGroupsList.js:231
-#: screens/Host/Hosts.js:29
-#: screens/Inventory/Inventories.js:72
-#: screens/Inventory/Inventories.js:74
-#: screens/Inventory/Inventory.js:66
-#: screens/Inventory/InventoryHost/InventoryHost.js:83
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:248
-#: screens/Inventory/InventoryList/InventoryListItem.js:127
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:251
-#: util/getRelatedResourceDeleteDetails.js:119
-msgid "Groups"
-msgstr "Groepen"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:383
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:468
-msgid "HTTP Headers"
-msgstr "HTTP-koppen"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:378
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:481
-msgid "HTTP Method"
-msgstr "HTTP-methode"
-
-#: components/HealthCheckAlert/HealthCheckAlert.js:22
-msgid "Health check request(s) submitted. Please wait and reload the page."
-msgstr "Gezondheidscontrole verzoek(en) ingediend. Wacht even en laad de pagina opnieuw."
-
-#: components/StatusLabel/StatusLabel.js:42
-msgid "Healthy"
-msgstr "Gezond"
-
-#: components/AppContainer/PageHeaderToolbar.js:116
-msgid "Help"
-msgstr "Help"
-
-#: components/FormField/PasswordInput.js:35
-msgid "Hide"
-msgstr "Verbergen"
-
-#: components/LaunchPrompt/LaunchPrompt.js:135
-#: components/Schedule/shared/SchedulePromptableFields.js:102
-msgid "Hide description"
-msgstr "Omschrijving verbergen"
-
-#: components/NotificationList/NotificationList.js:195
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:136
-msgid "Hipchat"
-msgstr "Hipchat"
-
-#: screens/Instances/InstanceList/InstanceList.js:154
-msgid "Hop"
-msgstr "Hop"
-
-#: screens/TopologyView/Legend.js:103
-msgid "Hop node"
-msgstr "Hop-knooppunt"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:211
-#: screens/Job/JobOutput/HostEventModal.js:109
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:149
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:78
-msgid "Host"
-msgstr "Host"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:107
-msgid "Host Async Failure"
-msgstr "Host Async mislukking"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:108
-msgid "Host Async OK"
-msgstr "Host Async OK"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:159
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:297
-#: screens/Template/shared/JobTemplateForm.js:633
-msgid "Host Config Key"
-msgstr "Configuratiesleutel host"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:96
-msgid "Host Count"
-msgstr "Aantal hosts"
-
-#: screens/Job/JobOutput/HostEventModal.js:88
-msgid "Host Details"
-msgstr "Hostdetails"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:109
-msgid "Host Failed"
-msgstr "Host is mislukt"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:110
-msgid "Host Failure"
-msgstr "Hostmislukking"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:242
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:145
-msgid "Host Filter"
-msgstr "Hostfilter"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:201
-#: screens/Instances/InstanceDetail/InstanceDetail.js:191
-#: screens/Instances/Shared/InstanceForm.js:18
-msgid "Host Name"
-msgstr "Hostnaam"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:111
-msgid "Host OK"
-msgstr "Host OK"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:112
-msgid "Host Polling"
-msgstr "Hostpolling"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:113
-msgid "Host Retry"
-msgstr "Host opnieuw proberen"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:114
-msgid "Host Skipped"
-msgstr "Host overgeslagen"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:115
-msgid "Host Started"
-msgstr "Host gestart"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:116
-msgid "Host Unreachable"
-msgstr "Host onbereikbaar"
-
-#: screens/Inventory/Inventories.js:69
-msgid "Host details"
-msgstr "Hostdetails"
-
-#: screens/Job/JobOutput/HostEventModal.js:89
-msgid "Host details modal"
-msgstr "Modus hostdetails"
-
-#: screens/Host/Host.js:96
-#: screens/Inventory/InventoryHost/InventoryHost.js:100
-msgid "Host not found."
-msgstr "Host niet gevonden."
-
-#: screens/Job/JobOutput/shared/HostStatusBar.js:76
-msgid "Host status information for this job is unavailable."
-msgstr "Statusinformatie van de host is niet beschikbaar voor deze taak."
-
-#: routeConfig.js:85
-#: screens/ActivityStream/ActivityStream.js:176
-#: screens/Dashboard/Dashboard.js:81
-#: screens/Host/HostList/HostList.js:143
-#: screens/Host/HostList/HostList.js:191
-#: screens/Host/Hosts.js:14
-#: screens/Host/Hosts.js:23
-#: screens/Inventory/Inventories.js:65
-#: screens/Inventory/Inventories.js:79
-#: screens/Inventory/Inventory.js:67
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:67
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:189
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:272
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:112
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:172
-#: screens/Inventory/SmartInventory.js:68
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:71
-#: screens/Job/JobOutput/shared/OutputToolbar.js:97
-#: util/getRelatedResourceDeleteDetails.js:123
-msgid "Hosts"
-msgstr "Hosts"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:92
-msgid "Hosts automated"
-msgstr "Geautomatiseerde hosts"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:118
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:125
-msgid "Hosts available"
-msgstr "Beschikbare hosts"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:107
-msgid "Hosts imported"
-msgstr "Geïmporteerde hosts"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:112
-msgid "Hosts remaining"
-msgstr "Resterende hosts"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:183
-#: components/Schedule/shared/FrequencyDetailSubform.js:175
-#: components/Schedule/shared/ScheduleFormFields.js:126
-#: components/Schedule/shared/ScheduleFormFields.js:186
-msgid "Hour"
-msgstr "Uur"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:209
-#: screens/Instances/InstanceList/InstanceList.js:153
-msgid "Hybrid"
-msgstr "Hybride"
-
-#: screens/TopologyView/Legend.js:95
-msgid "Hybrid node"
-msgstr "Hybride knooppunt"
-
-#: components/JobList/JobList.js:200
-#: components/Lookup/HostFilterLookup.js:98
-#: screens/Team/TeamRoles/TeamRolesList.js:155
-msgid "ID"
-msgstr "ID"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:193
-msgid "ID of the Dashboard"
-msgstr "ID van het dashboard"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:198
-msgid "ID of the Panel"
-msgstr "ID van het paneel"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:167
-msgid "ID of the dashboard (optional)"
-msgstr "ID van het dashboard (optioneel)"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:173
-msgid "ID of the panel (optional)"
-msgstr "ID van het paneel (optioneel)"
-
-#: screens/TopologyView/Tooltip.js:265
-msgid "IP address"
-msgstr "IP-adres"
-
-#: components/NotificationList/NotificationList.js:196
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:137
-msgid "IRC"
-msgstr "IRC"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:228
-msgid "IRC Nick"
-msgstr "IRC-bijnaam"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:223
-msgid "IRC Server Address"
-msgstr "IRC-serveradres"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:218
-msgid "IRC Server Port"
-msgstr "IRC-serverpoort"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:223
-msgid "IRC nick"
-msgstr "IRC-bijnaam"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:215
-msgid "IRC server address"
-msgstr "IRC-serveradres"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:201
-msgid "IRC server password"
-msgstr "IRC-serverwachtwoord"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:206
-msgid "IRC server port"
-msgstr "IRC-serverpoort"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:263
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:308
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:272
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:343
-msgid "Icon URL"
-msgstr "Icoon-URL"
-
-#: screens/Inventory/shared/Inventory.helptext.js:100
-msgid ""
-"If checked, all variables for child groups\n"
-"and hosts will be removed and replaced by those found\n"
-"on the external source."
-msgstr "Als dit vakje is ingeschakeld, worden alle variabelen voor onderliggende groepen en hosts verwijderd en worden ze vervangen door de variabelen die aangetroffen worden in de externe bron."
-
-#: screens/Inventory/shared/Inventory.helptext.js:84
-msgid ""
-"If checked, any hosts and groups that were\n"
-"previously present on the external source but are now removed\n"
-"will be removed from the inventory. Hosts and groups\n"
-"that were not managed by the inventory source will be promoted\n"
-"to the next manually created group or if there is no manually\n"
-"created group to promote them into, they will be left in the \"all\"\n"
-"default group for the inventory."
-msgstr "Als dit vakje is ingeschakeld, worden alle groepen en hosts die eerder aanwezig waren in de externe bron, maar die nu verwijderd zijn, verwijderd uit de inventaris. Hosts en groepen die niet beheerd werden door de inventarisbron, worden omhoog verplaatst naar de volgende handmatig gemaakte groep. Als er geen handmatig gemaakte groep is waar ze naartoe kunnen worden verplaatst, blijven ze staan in de standaard inventarisgroep 'Alle'."
-
-#: screens/Template/shared/JobTemplate.helptext.js:30
-msgid "If enabled, run this playbook as an administrator."
-msgstr "Als deze optie ingeschakeld is, wordt het draaiboek uitgevoerd als beheerder."
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:184
-msgid ""
-"If enabled, show the changes made\n"
-"by Ansible tasks, where supported. This is equivalent to Ansible’s\n"
-"--diff mode."
-msgstr "Als deze mogelijkheid ingeschakeld is, worden de wijzigingen die aangebracht zijn door Ansible-taken weergegeven, waar ondersteund. Dit staat gelijk aan de diff-modus van Ansible."
-
-#: screens/Template/shared/JobTemplate.helptext.js:19
-msgid "If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible's --diff mode."
-msgstr "Als deze mogelijkheid ingeschakeld is, worden de wijzigingen die aangebracht zijn door Ansible-taken weergegeven, waar ondersteund. Dit staat gelijk aan de diff-modus van Ansible."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:181
-msgid "If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible’s --diff mode."
-msgstr "Als deze mogelijkheid ingeschakeld is, worden de wijzigingen die aangebracht zijn door Ansible-taken weergegeven, waar ondersteund. Dit staat gelijk aan de diff-modus van Ansible."
-
-#: screens/Template/shared/JobTemplate.helptext.js:32
-msgid "If enabled, simultaneous runs of this job template will be allowed."
-msgstr "Indien deze mogelijkheid ingeschakeld is, zijn gelijktijdige uitvoeringen van dit taaksjabloon toegestaan."
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:16
-msgid "If enabled, simultaneous runs of this workflow job template will be allowed."
-msgstr "Indien deze mogelijkheid ingeschakeld is, zijn gelijktijdige uitvoeringen van deze workflow-taaksjabloon toegestaan."
-
-#: screens/Inventory/shared/Inventory.helptext.js:194
-msgid ""
-"If enabled, the inventory will prevent adding any organization instance groups to the list of preferred instances groups to run associated job templates on.\n"
-"Note: If this setting is enabled and you provided an empty list, the global instance groups will be applied."
-msgstr "Indien ingeschakeld, voorkomt de inventaris dat organisatie-instantiegroepen worden toegevoegd aan de lijst van voorkeursinstantiegroepen om geassocieerde taaksjablonen op uit te voeren.\n"
-"Opmerking: Als deze instelling is ingeschakeld en u een lege lijst hebt opgegeven, worden de globale instantiegroepen toegepast."
-
-#: screens/Template/shared/JobTemplate.helptext.js:33
-msgid ""
-"If enabled, the job template will prevent adding any inventory or organization instance groups to the list of preferred instances groups to run on.\n"
-"Note: If this setting is enabled and you provided an empty list, the global instance groups will be applied."
-msgstr "Indien ingeschakeld, zal de taaksjabloon voorkomen dat inventaris- of organisatie-instantiegroepen worden toegevoegd aan de lijst met voorkeursinstantiegroepen om op te draaien.\n"
-"Opmerking: Als deze instelling is ingeschakeld en u een lege lijst hebt opgegeven, worden de globale instantiegroepen toegepast."
-
-#: screens/Template/shared/JobTemplate.helptext.js:35
-msgid "If enabled, this will store gathered facts so they can be viewed at the host level. Facts are persisted and injected into the fact cache at runtime."
-msgstr "Indien ingeschakeld, worden de verzamelde feiten opgeslagen zodat ze kunnen worden weergegeven op hostniveau. Feiten worden bewaard en\n"
-"geïnjecteerd in de feitencache tijdens runtime."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:275
-msgid "If specified, this field will be shown on the node instead of the resource name when viewing the workflow"
-msgstr "Indien gespecificeerd, zal dit veld worden getoond op het knooppunt in plaats van de resourcenaam bij het bekijken van de workflow"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:180
-msgid "If you are ready to upgrade or renew, please <0>contact us.0>"
-msgstr "Neem zodra u klaar bent om te upgraden of te verlengen <0>contact met ons op.0>"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:63
-msgid ""
-"If you do not have a subscription, you can visit\n"
-"Red Hat to obtain a trial subscription."
-msgstr "Als u geen abonnement heeft, kunt u bij Red Hat terecht voor een proefabonnement."
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:46
-msgid "If you only want to remove access for this particular user, please remove them from the team."
-msgstr "Als u alleen de toegang voor deze specifieke gebruiker wilt verwijderen, verwijder deze dan uit het team."
-
-#: screens/Inventory/shared/Inventory.helptext.js:120
-#: screens/Inventory/shared/Inventory.helptext.js:139
-msgid ""
-"If you want the Inventory Source to update on\n"
-"launch and on project update, click on Update on launch, and also go to"
-msgstr "Als u wilt dat de inventarisbron wordt bijgewerkt bij\n"
-"het opstarten en bij projectupdates, klik op Update bij opstarten, en ga ook naar"
-
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:80
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:91
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:101
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:54
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:141
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:147
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:166
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:75
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:98
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:89
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:108
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.js:21
-msgid "Image"
-msgstr "Image"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:117
-msgid "Including File"
-msgstr "Inclusief bestand"
-
-#: components/HostToggle/HostToggle.js:16
-msgid ""
-"Indicates if a host is available and should be included in running\n"
-"jobs. For hosts that are part of an external inventory, this may be\n"
-"reset by the inventory sync process."
-msgstr "Geeft aan of een host beschikbaar is en opgenomen moet worden in lopende taken. Voor hosts die deel uitmaken van een externe inventaris, kan dit worden\n"
-"gereset worden door het inventarissynchronisatieproces."
-
-#: components/AppContainer/PageHeaderToolbar.js:103
-msgid "Info"
-msgstr "Info"
-
-#: screens/ActivityStream/ActivityStreamListItem.js:45
-msgid "Initiated By"
-msgstr "Gestart door"
-
-#: screens/ActivityStream/ActivityStream.js:253
-#: screens/ActivityStream/ActivityStream.js:263
-#: screens/ActivityStream/ActivityStreamDetailButton.js:44
-msgid "Initiated by"
-msgstr "Gestart door"
-
-#: screens/ActivityStream/ActivityStream.js:243
-msgid "Initiated by (username)"
-msgstr "Gestart door (gebruikersnaam)"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:82
-#: screens/CredentialType/shared/CredentialTypeForm.js:46
-msgid "Injector configuration"
-msgstr "Configuratie-injector"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:74
-#: screens/CredentialType/shared/CredentialTypeForm.js:38
-msgid "Input configuration"
-msgstr "Configuratie-input"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:79
-msgid "Input schema which defines a set of ordered fields for that type."
-msgstr "Invoerschema dat een reeks geordende velden voor dat type definieert."
-
-#: screens/Project/shared/ProjectSubForms/InsightsSubForm.js:31
-msgid "Insights Credential"
-msgstr "Toegangsgegevens voor Insights"
-
-#: components/Lookup/HostFilterLookup.js:123
-msgid "Insights system ID"
-msgstr "Systeem-ID Insights"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:249
-msgid "Install Bundle"
-msgstr "Bundel installeren"
-
-#: components/StatusLabel/StatusLabel.js:58
-#: screens/TopologyView/Legend.js:136
-msgid "Installed"
-msgstr "Geïnstalleerd"
-
-#: screens/Metrics/Metrics.js:187
-msgid "Instance"
-msgstr "Instantie"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:135
-msgid "Instance Filters"
-msgstr "Instantiefilters"
-
-#: screens/Job/JobDetail/JobDetail.js:358
-msgid "Instance Group"
-msgstr "Instantiegroep"
-
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:96
-#: components/LaunchPrompt/steps/useInstanceGroupsStep.js:31
-#: components/Lookup/InstanceGroupsLookup.js:74
-#: components/Lookup/InstanceGroupsLookup.js:121
-#: components/Lookup/InstanceGroupsLookup.js:141
-#: components/Lookup/InstanceGroupsLookup.js:151
-#: components/PromptDetail/PromptDetail.js:227
-#: components/PromptDetail/PromptJobTemplateDetail.js:228
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:499
-#: routeConfig.js:132
-#: screens/ActivityStream/ActivityStream.js:205
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:106
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:179
-#: screens/InstanceGroup/InstanceGroups.js:16
-#: screens/InstanceGroup/InstanceGroups.js:26
-#: screens/Instances/InstanceDetail/InstanceDetail.js:217
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:107
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:127
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:422
-#: util/getRelatedResourceDeleteDetails.js:282
-msgid "Instance Groups"
-msgstr "Instantiegroepen"
-
-#: components/Lookup/HostFilterLookup.js:115
-msgid "Instance ID"
-msgstr "Instantie-id"
-
-#: screens/Instances/Shared/InstanceForm.js:32
-msgid "Instance State"
-msgstr "Instantiestaat"
-
-#: screens/Instances/Shared/InstanceForm.js:48
-msgid "Instance Type"
-msgstr "Instantietype"
-
-#: screens/InstanceGroup/InstanceGroups.js:33
-msgid "Instance details"
-msgstr "Instantiedetails"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:58
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:69
-msgid "Instance group"
-msgstr "Instantiegroep"
-
-#: screens/InstanceGroup/InstanceGroup.js:92
-msgid "Instance group not found."
-msgstr "Kan instantiegroep niet vinden."
-
-#: screens/InstanceGroup/Instances/InstanceListItem.js:165
-#: screens/Instances/InstanceList/InstanceListItem.js:176
-msgid "Instance group used capacity"
-msgstr "Gebruikte capaciteit instantiegroep"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:122
-#: screens/TopologyView/Tooltip.js:273
-msgid "Instance groups"
-msgstr "Instantiegroepen"
-
-#: screens/TopologyView/Tooltip.js:234
-msgid "Instance status"
-msgstr "Instantiestaat"
-
-#: screens/TopologyView/Tooltip.js:240
-msgid "Instance type"
-msgstr "instantietype"
-
-#: routeConfig.js:137
-#: screens/ActivityStream/ActivityStream.js:203
-#: screens/InstanceGroup/InstanceGroup.js:74
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:198
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:73
-#: screens/InstanceGroup/InstanceGroups.js:31
-#: screens/InstanceGroup/Instances/InstanceList.js:192
-#: screens/InstanceGroup/Instances/InstanceList.js:290
-#: screens/Instances/InstanceList/InstanceList.js:136
-#: screens/Instances/Instances.js:13
-#: screens/Instances/Instances.js:22
-msgid "Instances"
-msgstr "Instanties"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:93
-msgid "Integer"
-msgstr "Geheel getal"
-
-#: util/validators.js:94
-msgid "Invalid email address"
-msgstr "Ongeldig e-mailadres"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:116
-msgid "Invalid file format. Please upload a valid Red Hat Subscription Manifest."
-msgstr "Ongeldige bestandsindeling. Upload een geldig Red Hat-abonnementsmanifest."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:178
-msgid "Invalid link target. Unable to link to children or ancestor nodes. Graph cycles are not supported."
-msgstr "Ongeldig linkdoel. Kan niet linken aan onder- of bovenliggende knooppunten. Grafiekcycli worden niet ondersteund."
-
-#: util/validators.js:33
-msgid "Invalid time format"
-msgstr "Ongeldige tijdsindeling"
-
-#: screens/Login/Login.js:153
-msgid "Invalid username or password. Please try again."
-msgstr "Ongeldige gebruikersnaam of wachtwoord. Probeer het opnieuw."
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:119
-#: routeConfig.js:80
-#: screens/ActivityStream/ActivityStream.js:173
-#: screens/Dashboard/Dashboard.js:92
-#: screens/Inventory/Inventories.js:17
-#: screens/Inventory/InventoryList/InventoryList.js:174
-#: screens/Inventory/InventoryList/InventoryList.js:237
-#: util/getRelatedResourceDeleteDetails.js:202
-#: util/getRelatedResourceDeleteDetails.js:270
-msgid "Inventories"
-msgstr "Inventarissen"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:153
-msgid "Inventories with sources cannot be copied"
-msgstr "Inventarissen met bronnen kunnen niet gekopieerd worden"
-
-#: components/HostForm/HostForm.js:48
-#: components/JobList/JobListItem.js:223
-#: components/LaunchPrompt/steps/InventoryStep.js:105
-#: components/LaunchPrompt/steps/useInventoryStep.js:48
-#: components/Lookup/HostFilterLookup.js:424
-#: components/Lookup/HostListItem.js:10
-#: components/Lookup/InventoryLookup.js:119
-#: components/Lookup/InventoryLookup.js:128
-#: components/Lookup/InventoryLookup.js:169
-#: components/Lookup/InventoryLookup.js:184
-#: components/Lookup/InventoryLookup.js:224
-#: components/PromptDetail/PromptDetail.js:214
-#: components/PromptDetail/PromptInventorySourceDetail.js:76
-#: components/PromptDetail/PromptJobTemplateDetail.js:119
-#: components/PromptDetail/PromptJobTemplateDetail.js:129
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:79
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:430
-#: components/TemplateList/TemplateListItem.js:285
-#: components/TemplateList/TemplateListItem.js:295
-#: screens/Host/HostDetail/HostDetail.js:77
-#: screens/Host/HostList/HostList.js:171
-#: screens/Host/HostList/HostListItem.js:61
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:94
-#: screens/Inventory/InventoryList/InventoryList.js:186
-#: screens/Inventory/InventoryList/InventoryListItem.js:117
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:38
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:113
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:41
-#: screens/Job/JobDetail/JobDetail.js:107
-#: screens/Job/JobDetail/JobDetail.js:122
-#: screens/Job/JobDetail/JobDetail.js:129
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:214
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:224
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:141
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:33
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:252
-msgid "Inventory"
-msgstr "Inventaris"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:104
-msgid "Inventory (Name)"
-msgstr "Inventaris (naam)"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:99
-msgid "Inventory File"
-msgstr "Inventarisbestand"
-
-#: components/Lookup/HostFilterLookup.js:106
-msgid "Inventory ID"
-msgstr "Inventaris-id"
-
-#: screens/Job/JobDetail/JobDetail.js:282
-msgid "Inventory Source"
-msgstr "Inventarisbron"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:73
-msgid "Inventory Source Sync"
-msgstr "Synchronisatie inventarisbronnen"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:299
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:110
-msgid "Inventory Source Sync Error"
-msgstr "Fout tijdens synchronisatie inventarisbronnen"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:176
-#: screens/Inventory/InventorySources/InventorySourceList.js:193
-#: util/getRelatedResourceDeleteDetails.js:67
-#: util/getRelatedResourceDeleteDetails.js:147
-msgid "Inventory Sources"
-msgstr "Inventarisbronnen"
-
-#: components/JobList/JobList.js:212
-#: components/JobList/JobListItem.js:43
-#: components/Schedule/ScheduleList/ScheduleListItem.js:36
-#: components/Workflow/WorkflowLegend.js:100
-#: screens/Job/JobDetail/JobDetail.js:66
-msgid "Inventory Sync"
-msgstr "Inventarissynchronisatie"
-
-#: screens/Inventory/InventoryList/InventoryList.js:183
-msgid "Inventory Type"
-msgstr "Type inventaris"
-
-#: components/Workflow/WorkflowNodeHelp.js:71
-msgid "Inventory Update"
-msgstr "Inventarisupdate"
-
-#: screens/Inventory/InventoryList/InventoryList.js:121
-msgid "Inventory copied successfully"
-msgstr "Inventaris gekopieerd"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:226
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:109
-msgid "Inventory file"
-msgstr "Inventarisbestand"
-
-#: screens/Inventory/Inventory.js:94
-msgid "Inventory not found."
-msgstr "Inventaris niet gevonden."
-
-#: screens/Dashboard/DashboardGraph.js:140
-msgid "Inventory sync"
-msgstr "Inventarissynchronisatie"
-
-#: screens/Dashboard/Dashboard.js:98
-msgid "Inventory sync failures"
-msgstr "Fout tijdens inventarissynchronisatie"
-
-#: components/DataListToolbar/DataListToolbar.js:110
-msgid "Is expanded"
-msgstr "Is uitgeklapt"
-
-#: components/DataListToolbar/DataListToolbar.js:112
-msgid "Is not expanded"
-msgstr "Is niet uitgeklapt"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:118
-msgid "Item Failed"
-msgstr "Item mislukt"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:119
-msgid "Item OK"
-msgstr "Item OK"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:120
-msgid "Item Skipped"
-msgstr "Item overgeslagen"
-
-#: components/AssociateModal/AssociateModal.js:20
-#: components/PaginatedTable/PaginatedTable.js:42
-msgid "Items"
-msgstr "Items"
-
-#: components/Pagination/Pagination.js:27
-msgid "Items per page"
-msgstr "Items per pagina"
-
-#: components/Sparkline/Sparkline.js:28
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:157
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:36
-#: screens/Project/ProjectDetail/ProjectDetail.js:132
-#: screens/Project/ProjectList/ProjectListItem.js:70
-msgid "JOB ID:"
-msgstr "TAAK-ID:"
-
-#: screens/Job/JobOutput/HostEventModal.js:136
-msgid "JSON"
-msgstr "JSON"
-
-#: screens/Job/JobOutput/HostEventModal.js:137
-msgid "JSON tab"
-msgstr "JSON-tabblad"
-
-#: screens/Inventory/shared/Inventory.helptext.js:49
-msgid "JSON:"
-msgstr "JSON:"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:159
-#: components/Schedule/shared/FrequencyDetailSubform.js:98
-msgid "January"
-msgstr "Januari"
-
-#: components/JobList/JobListItem.js:112
-#: screens/Job/JobDetail/JobDetail.js:599
-#: screens/Job/JobOutput/JobOutput.js:845
-#: screens/Job/JobOutput/JobOutput.js:846
-#: screens/Job/JobOutput/shared/OutputToolbar.js:136
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:88
-msgid "Job Cancel Error"
-msgstr "Fout bij annuleren taak"
-
-#: screens/Job/JobDetail/JobDetail.js:621
-#: screens/Job/JobOutput/JobOutput.js:834
-#: screens/Job/JobOutput/JobOutput.js:835
-msgid "Job Delete Error"
-msgstr "Fout bij verwijderen taak"
-
-#: screens/Job/JobDetail/JobDetail.js:206
-msgid "Job ID"
-msgstr "Taak-id"
-
-#: screens/Dashboard/shared/LineChart.js:128
-msgid "Job Runs"
-msgstr "Taakuitvoeringen"
-
-#: components/JobList/JobListItem.js:314
-#: screens/Job/JobDetail/JobDetail.js:374
-msgid "Job Slice"
-msgstr "Taken verdelen"
-
-#: components/JobList/JobListItem.js:319
-#: screens/Job/JobDetail/JobDetail.js:382
-msgid "Job Slice Parent"
-msgstr "Ouder taken verdelen"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:76
-#: components/PromptDetail/PromptDetail.js:349
-#: components/PromptDetail/PromptJobTemplateDetail.js:158
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:494
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:289
-#: screens/Template/shared/JobTemplateForm.js:453
-msgid "Job Slicing"
-msgstr "Taken verdelen"
-
-#: components/Workflow/WorkflowNodeHelp.js:164
-msgid "Job Status"
-msgstr "Taakstatus"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:97
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:98
-#: components/PromptDetail/PromptDetail.js:267
-#: components/PromptDetail/PromptJobTemplateDetail.js:247
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:571
-#: screens/Job/JobDetail/JobDetail.js:474
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:449
-#: screens/Template/shared/JobTemplateForm.js:521
-#: screens/Template/shared/WorkflowJobTemplateForm.js:218
-msgid "Job Tags"
-msgstr "Taaktags"
-
-#: components/JobList/JobListItem.js:191
-#: components/TemplateList/TemplateList.js:217
-#: components/Workflow/WorkflowLegend.js:92
-#: components/Workflow/WorkflowNodeHelp.js:59
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:97
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:19
-#: screens/Job/JobDetail/JobDetail.js:233
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:79
-msgid "Job Template"
-msgstr "Taaksjabloon"
-
-#: components/LaunchPrompt/steps/credentialsValidator.js:38
-msgid "Job Template default credentials must be replaced with one of the same type. Please select a credential for the following types in order to proceed: {0}"
-msgstr "De standaardreferenties van de taaksjabloon moeten worden vervangen door een van hetzelfde type. Selecteer een toegangsgegeven voor de volgende typen om verder te gaan: {0}"
-
-#: screens/Credential/Credential.js:79
-#: screens/Credential/Credentials.js:30
-#: screens/Inventory/Inventories.js:62
-#: screens/Inventory/Inventory.js:74
-#: screens/Inventory/SmartInventory.js:74
-#: screens/Project/Project.js:107
-#: screens/Project/Projects.js:29
-#: util/getRelatedResourceDeleteDetails.js:56
-#: util/getRelatedResourceDeleteDetails.js:101
-#: util/getRelatedResourceDeleteDetails.js:133
-msgid "Job Templates"
-msgstr "Taaksjablonen"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.js:25
-msgid "Job Templates with a missing inventory or project cannot be selected when creating or editing nodes. Select another template or fix the missing fields to proceed."
-msgstr "Taaksjablonen met een ontbrekende inventaris of een ontbrekend project kunnen niet worden geselecteerd tijdens het maken of bewerken van knooppunten. Selecteer een andere sjabloon of herstel de ontbrekende velden om verder te gaan."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useWorkflowNodeSteps.js:357
-msgid "Job Templates with credentials that prompt for passwords cannot be selected when creating or editing nodes"
-msgstr "Taaksjablonen met toegangsgegevens die om een wachtwoord vragen, kunnen niet worden geselecteerd tijdens het maken of bewerken van knooppunten"
-
-#: components/JobList/JobList.js:208
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:146
-#: components/PromptDetail/PromptDetail.js:185
-#: components/PromptDetail/PromptJobTemplateDetail.js:102
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:423
-#: screens/Job/JobDetail/JobDetail.js:267
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:192
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:138
-#: screens/Template/shared/JobTemplateForm.js:256
-msgid "Job Type"
-msgstr "Soort taak"
-
-#: screens/Dashboard/Dashboard.js:125
-msgid "Job status"
-msgstr "Taakstatus"
-
-#: screens/Dashboard/Dashboard.js:123
-msgid "Job status graph tab"
-msgstr "Grafiektabblad Taakstatus"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:156
-#: components/RelatedTemplateList/RelatedTemplateList.js:206
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:15
-msgid "Job templates"
-msgstr "Taaksjablonen"
-
-#: components/JobList/JobList.js:191
-#: components/JobList/JobList.js:274
-#: routeConfig.js:39
-#: screens/ActivityStream/ActivityStream.js:150
-#: screens/Dashboard/shared/LineChart.js:64
-#: screens/Host/Host.js:73
-#: screens/Host/Hosts.js:30
-#: screens/InstanceGroup/ContainerGroup.js:71
-#: screens/InstanceGroup/InstanceGroup.js:79
-#: screens/InstanceGroup/InstanceGroups.js:34
-#: screens/InstanceGroup/InstanceGroups.js:39
-#: screens/Inventory/Inventories.js:60
-#: screens/Inventory/Inventories.js:70
-#: screens/Inventory/Inventory.js:70
-#: screens/Inventory/InventoryHost/InventoryHost.js:88
-#: screens/Inventory/SmartInventory.js:70
-#: screens/Job/Jobs.js:22
-#: screens/Job/Jobs.js:32
-#: screens/Setting/SettingList.js:91
-#: screens/Setting/Settings.js:75
-#: screens/Template/Template.js:155
-#: screens/Template/Templates.js:47
-#: screens/Template/WorkflowJobTemplate.js:141
-msgid "Jobs"
-msgstr "Taken"
-
-#: screens/Setting/SettingList.js:96
-msgid "Jobs settings"
-msgstr "Taakinstellingen"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:165
-#: components/Schedule/shared/FrequencyDetailSubform.js:128
-msgid "July"
-msgstr "Juli"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:164
-#: components/Schedule/shared/FrequencyDetailSubform.js:123
-msgid "June"
-msgstr "Juni"
-
-#: components/Search/AdvancedSearch.js:262
-msgid "Key"
-msgstr "Sleutel"
-
-#: components/Search/AdvancedSearch.js:253
-msgid "Key select"
-msgstr "Sleutel selecteren"
-
-#: components/Search/AdvancedSearch.js:256
-msgid "Key typeahead"
-msgstr "Sleutel typeahead"
-
-#: screens/ActivityStream/ActivityStream.js:238
-msgid "Keyword"
-msgstr "Trefwoord"
-
-#: screens/User/UserDetail/UserDetail.js:56
-#: screens/User/UserList/UserListItem.js:44
-msgid "LDAP"
-msgstr "LDAP"
-
-#: screens/Setting/Settings.js:80
-msgid "LDAP 1"
-msgstr "LDAP 1"
-
-#: screens/Setting/Settings.js:81
-msgid "LDAP 2"
-msgstr "LDAP 2"
-
-#: screens/Setting/Settings.js:82
-msgid "LDAP 3"
-msgstr "LDAP 3"
-
-#: screens/Setting/Settings.js:83
-msgid "LDAP 4"
-msgstr "LDAP 4"
-
-#: screens/Setting/Settings.js:84
-msgid "LDAP 5"
-msgstr "LDAP 5"
-
-#: screens/Setting/Settings.js:79
-msgid "LDAP Default"
-msgstr "LDAP-standaard"
-
-#: screens/Setting/SettingList.js:69
-msgid "LDAP settings"
-msgstr "LDAP-instellingen"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:107
-msgid "LDAP1"
-msgstr "LDAP1"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:112
-msgid "LDAP2"
-msgstr "LDAP2"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:117
-msgid "LDAP3"
-msgstr "LDAP3"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:122
-msgid "LDAP4"
-msgstr "LDAP4"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:127
-msgid "LDAP5"
-msgstr "LDAP5"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:178
-#: components/TemplateList/TemplateList.js:234
-msgid "Label"
-msgstr "Label"
-
-#: components/JobList/JobList.js:204
-msgid "Label Name"
-msgstr "Labelnaam"
-
-#: components/JobList/JobListItem.js:284
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:223
-#: components/PromptDetail/PromptDetail.js:323
-#: components/PromptDetail/PromptJobTemplateDetail.js:209
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:116
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:551
-#: components/TemplateList/TemplateListItem.js:347
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:148
-#: screens/Inventory/shared/InventoryForm.js:83
-#: screens/Job/JobDetail/JobDetail.js:453
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:401
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:206
-#: screens/Template/shared/JobTemplateForm.js:387
-#: screens/Template/shared/WorkflowJobTemplateForm.js:195
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:273
-msgid "Labels"
-msgstr "Labels"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:417
-msgid "Last"
-msgstr "Laatste"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:220
-#: screens/InstanceGroup/Instances/InstanceListItem.js:133
-#: screens/InstanceGroup/Instances/InstanceListItem.js:208
-#: screens/Instances/InstanceDetail/InstanceDetail.js:243
-#: screens/Instances/InstanceList/InstanceListItem.js:138
-#: screens/Instances/InstanceList/InstanceListItem.js:223
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:47
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:87
-msgid "Last Health Check"
-msgstr "Laatste gezondheidscontrole"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:183
-#: screens/Project/ProjectDetail/ProjectDetail.js:161
-msgid "Last Job Status"
-msgstr "Laatste taakstatus"
-
-#: screens/User/UserDetail/UserDetail.js:80
-msgid "Last Login"
-msgstr "Laatste login"
-
-#: components/PromptDetail/PromptDetail.js:161
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:411
-#: components/TemplateList/TemplateListItem.js:316
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:106
-#: screens/Application/ApplicationsList/ApplicationListItem.js:45
-#: screens/Application/ApplicationsList/ApplicationsList.js:159
-#: screens/Credential/CredentialDetail/CredentialDetail.js:263
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:95
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:108
-#: screens/Host/HostDetail/HostDetail.js:89
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:72
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:98
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:178
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:45
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:84
-#: screens/Job/JobDetail/JobDetail.js:538
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:398
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:120
-#: screens/Project/ProjectDetail/ProjectDetail.js:297
-#: screens/Team/TeamDetail/TeamDetail.js:48
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:358
-#: screens/User/UserDetail/UserDetail.js:84
-#: screens/User/UserTokenDetail/UserTokenDetail.js:66
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:204
-msgid "Last Modified"
-msgstr "Laatst aangepast"
-
-#: components/AddRole/AddResourceRole.js:32
-#: components/AddRole/AddResourceRole.js:46
-#: components/ResourceAccessList/ResourceAccessList.js:182
-#: screens/User/UserDetail/UserDetail.js:65
-#: screens/User/UserList/UserList.js:128
-#: screens/User/UserList/UserList.js:162
-#: screens/User/UserList/UserListItem.js:54
-#: screens/User/shared/UserForm.js:69
-msgid "Last Name"
-msgstr "Achternaam"
-
-#: components/TemplateList/TemplateList.js:245
-#: components/TemplateList/TemplateListItem.js:194
-msgid "Last Ran"
-msgstr "Laatst uitgevoerd"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:341
-msgid "Last Run"
-msgstr "Laatste uitvoering"
-
-#: components/Lookup/HostFilterLookup.js:119
-msgid "Last job"
-msgstr "Laatste taak"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:280
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:151
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:49
-#: screens/Project/ProjectList/ProjectListItem.js:308
-#: screens/TopologyView/Tooltip.js:331
-msgid "Last modified"
-msgstr "Laatste wijziging"
-
-#: components/ResourceAccessList/ResourceAccessList.js:228
-#: components/ResourceAccessList/ResourceAccessListItem.js:68
-msgid "Last name"
-msgstr "Achternaam"
-
-#: screens/TopologyView/Tooltip.js:337
-msgid "Last seen"
-msgstr "Laatste synchronisatie"
-
-#: screens/Project/ProjectList/ProjectListItem.js:313
-msgid "Last used"
-msgstr "Laatst gebruikt"
-
-#: components/AdHocCommands/useAdHocPreviewStep.js:22
-#: components/LaunchPrompt/steps/usePreviewStep.js:35
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:54
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:57
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:520
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:529
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:245
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:254
-msgid "Launch"
-msgstr "Starten"
-
-#: components/TemplateList/TemplateListItem.js:214
-msgid "Launch Template"
-msgstr "Sjabloon opstarten"
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:32
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:34
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:46
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:47
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:87
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:90
-msgid "Launch management job"
-msgstr "Beheertaak opstarten"
-
-#: components/TemplateList/TemplateListItem.js:222
-msgid "Launch template"
-msgstr "Sjabloon opstarten"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:119
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:120
-msgid "Launch workflow"
-msgstr "Workflow opstarten"
-
-#: components/LaunchPrompt/LaunchPrompt.js:130
-msgid "Launch | {0}"
-msgstr "Starten | {0}"
-
-#: components/DetailList/LaunchedByDetail.js:54
-msgid "Launched By"
-msgstr "Gestart door"
-
-#: components/JobList/JobList.js:220
-msgid "Launched By (Username)"
-msgstr "Opgestart door (gebruikersnaam)"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:120
-msgid "Learn more about Automation Analytics"
-msgstr "Meer informatie over Automatiseringsanalyse"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:69
-msgid "Leave this field blank to make the execution environment globally available."
-msgstr "Laat dit veld leeg om de uitvoeringsomgeving globaal beschikbaar te maken."
-
-#: components/Workflow/WorkflowLegend.js:86
-#: screens/Metrics/LineChart.js:120
-#: screens/TopologyView/Header.js:102
-#: screens/TopologyView/Legend.js:67
-msgid "Legend"
-msgstr "Legenda"
-
-#: components/Search/LookupTypeInput.js:120
-msgid "Less than comparison."
-msgstr "Minder dan vergelijking."
-
-#: components/Search/LookupTypeInput.js:127
-msgid "Less than or equal to comparison."
-msgstr "Minder dan of gelijk aan vergelijking."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:140
-#: components/AdHocCommands/AdHocDetailsStep.js:141
-#: components/JobList/JobList.js:238
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:65
-#: components/PromptDetail/PromptDetail.js:255
-#: components/PromptDetail/PromptJobTemplateDetail.js:153
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:90
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:473
-#: screens/Job/JobDetail/JobDetail.js:325
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:265
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:151
-#: screens/Template/shared/JobTemplateForm.js:429
-#: screens/Template/shared/WorkflowJobTemplateForm.js:159
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:238
-msgid "Limit"
-msgstr "Limiet"
-
-#: screens/TopologyView/Legend.js:237
-msgid "Link state types"
-msgstr "Typen verbindingstoestanden"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:261
-msgid "Link to an available node"
-msgstr "Link naar een beschikbaar knooppunt"
-
-#: screens/Instances/Shared/InstanceForm.js:40
-msgid "Listener Port"
-msgstr "Luisterpoort"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:353
-msgid "Loading"
-msgstr "Laden"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:49
-msgid "Local"
-msgstr "Lokaal"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:343
-msgid "Local Time Zone"
-msgstr "Lokale tijdzone"
-
-#: components/Schedule/shared/ScheduleFormFields.js:95
-msgid "Local time zone"
-msgstr "Lokale tijdzone"
-
-#: screens/Login/Login.js:217
-msgid "Log In"
-msgstr "Inloggen"
-
-#: screens/Setting/Settings.js:97
-msgid "Logging"
-msgstr "Logboekregistratie"
-
-#: screens/Setting/SettingList.js:115
-msgid "Logging settings"
-msgstr "Instellingen voor logboekregistratie"
-
-#: components/AppContainer/AppContainer.js:81
-#: components/AppContainer/AppContainer.js:150
-#: components/AppContainer/PageHeaderToolbar.js:169
-msgid "Logout"
-msgstr "Afmelden"
-
-#: components/Lookup/HostFilterLookup.js:367
-#: components/Lookup/Lookup.js:187
-msgid "Lookup modal"
-msgstr "Opzoekmodus"
-
-#: components/Search/LookupTypeInput.js:22
-msgid "Lookup select"
-msgstr "Opzoeken selecteren"
-
-#: components/Search/LookupTypeInput.js:31
-msgid "Lookup type"
-msgstr "Type opzoeken"
-
-#: components/Search/LookupTypeInput.js:25
-msgid "Lookup typeahead"
-msgstr "Typeahead opzoeken"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:155
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:34
-#: screens/Project/ProjectDetail/ProjectDetail.js:130
-#: screens/Project/ProjectList/ProjectListItem.js:68
-msgid "MOST RECENT SYNC"
-msgstr "MEEST RECENTE SYNCHRONISATIE"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:97
-#: components/AdHocCommands/AdHocCredentialStep.js:98
-#: components/AdHocCommands/AdHocCredentialStep.js:112
-#: screens/Job/JobDetail/JobDetail.js:407
-msgid "Machine Credential"
-msgstr "Toegangsgegevens machine"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:65
-msgid "Managed"
-msgstr "Beheerd"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:147
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:169
-msgid "Managed nodes"
-msgstr "Beheerde knooppunten"
-
-#: components/JobList/JobList.js:215
-#: components/JobList/JobListItem.js:46
-#: components/Schedule/ScheduleList/ScheduleListItem.js:39
-#: components/Workflow/WorkflowLegend.js:108
-#: components/Workflow/WorkflowNodeHelp.js:79
-#: screens/Job/JobDetail/JobDetail.js:69
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:102
-msgid "Management Job"
-msgstr "Beheertaak"
-
-#: routeConfig.js:127
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:84
-msgid "Management Jobs"
-msgstr "Beheerderstaken"
-
-#: screens/ManagementJob/ManagementJobs.js:20
-msgid "Management job"
-msgstr "Beheertaak"
-
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:109
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:110
-msgid "Management job launch error"
-msgstr "Fout bij opstarten van beheertaak"
-
-#: screens/ManagementJob/ManagementJob.js:133
-msgid "Management job not found."
-msgstr "Beheertaak niet gevonden."
-
-#: screens/ManagementJob/ManagementJobs.js:13
-msgid "Management jobs"
-msgstr "Beheertaken"
-
-#: components/Lookup/ProjectLookup.js:135
-#: components/PromptDetail/PromptProjectDetail.js:98
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:88
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:157
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:215
-#: screens/InstanceGroup/Instances/InstanceListItem.js:204
-#: screens/Instances/InstanceDetail/InstanceDetail.js:209
-#: screens/Instances/InstanceList/InstanceListItem.js:219
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:83
-#: screens/Job/JobDetail/JobDetail.js:74
-#: screens/Project/ProjectDetail/ProjectDetail.js:192
-#: screens/Project/ProjectList/ProjectList.js:197
-#: screens/Project/ProjectList/ProjectListItem.js:219
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:96
-msgid "Manual"
-msgstr "Handmatig"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:161
-#: components/Schedule/shared/FrequencyDetailSubform.js:108
-msgid "March"
-msgstr "Maart"
-
-#: components/NotificationList/NotificationList.js:197
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:138
-msgid "Mattermost"
-msgstr "Mattermost"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:98
-#: screens/Organization/shared/OrganizationForm.js:71
-msgid "Max Hosts"
-msgstr "Max. hosts"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:220
-msgid "Maximum"
-msgstr "Maximum"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:204
-msgid "Maximum length"
-msgstr "Maximumlengte"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:163
-#: components/Schedule/shared/FrequencyDetailSubform.js:118
-msgid "May"
-msgstr "Mei"
-
-#: screens/Organization/OrganizationList/OrganizationList.js:144
-#: screens/Organization/OrganizationList/OrganizationListItem.js:63
-msgid "Members"
-msgstr "Leden"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:47
-msgid "Metadata"
-msgstr "Metadata"
-
-#: screens/Metrics/Metrics.js:207
-msgid "Metric"
-msgstr "Metrisch"
-
-#: screens/Metrics/Metrics.js:179
-msgid "Metrics"
-msgstr "Meetwaarden"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:100
-msgid "Microsoft Azure Resource Manager"
-msgstr "Microsoft Azure Resource Manager"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:214
-msgid "Minimum"
-msgstr "Minimum"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:198
-msgid "Minimum length"
-msgstr "Minimumlengte"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:65
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:31
-msgid ""
-"Minimum number of instances that will be automatically\n"
-"assigned to this group when new instances come online."
-msgstr "Minimum aantal instanties dat automatisch toegewezen wordt aan deze groep wanneer nieuwe instanties online komen."
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:71
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:41
-msgid ""
-"Minimum percentage of all instances that will be automatically\n"
-"assigned to this group when new instances come online."
-msgstr "Minimumpercentage van alle instanties die automatisch toegewezen worden aan deze groep wanneer nieuwe instanties online komen."
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:182
-#: components/Schedule/shared/FrequencyDetailSubform.js:173
-#: components/Schedule/shared/ScheduleFormFields.js:125
-#: components/Schedule/shared/ScheduleFormFields.js:185
-msgid "Minute"
-msgstr "Minuut"
-
-#: screens/Setting/Settings.js:100
-msgid "Miscellaneous Authentication"
-msgstr "Diversen authenticatie"
-
-#: screens/Setting/SettingList.js:111
-msgid "Miscellaneous Authentication settings"
-msgstr "Instellingen diversen authenticatie"
-
-#: screens/Setting/Settings.js:103
-msgid "Miscellaneous System"
-msgstr "Divers systeem"
-
-#: screens/Setting/SettingList.js:107
-msgid "Miscellaneous System settings"
-msgstr "Diverse systeeminstellingen"
-
-#: components/Workflow/WorkflowNodeHelp.js:120
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:87
-msgid "Missing"
-msgstr "Ontbrekend"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:66
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:109
-msgid "Missing resource"
-msgstr "Ontbrekende bron"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:192
-#: screens/User/UserTokenList/UserTokenList.js:154
-msgid "Modified"
-msgstr "Gewijzigd"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:126
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:116
-#: components/AddRole/AddResourceRole.js:61
-#: components/AssociateModal/AssociateModal.js:148
-#: components/LaunchPrompt/steps/CredentialsStep.js:177
-#: components/LaunchPrompt/steps/InventoryStep.js:93
-#: components/Lookup/CredentialLookup.js:198
-#: components/Lookup/InventoryLookup.js:156
-#: components/Lookup/InventoryLookup.js:211
-#: components/Lookup/MultiCredentialsLookup.js:198
-#: components/Lookup/OrganizationLookup.js:138
-#: components/Lookup/ProjectLookup.js:147
-#: components/NotificationList/NotificationList.js:210
-#: components/RelatedTemplateList/RelatedTemplateList.js:170
-#: components/Schedule/ScheduleList/ScheduleList.js:202
-#: components/TemplateList/TemplateList.js:230
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:31
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:62
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:100
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:131
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:169
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:200
-#: screens/Credential/CredentialList/CredentialList.js:154
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:100
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:136
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:106
-#: screens/Host/HostGroups/HostGroupsList.js:168
-#: screens/Host/HostList/HostList.js:161
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:203
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:133
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:178
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:132
-#: screens/Inventory/InventoryList/InventoryList.js:203
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:189
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:98
-#: screens/Organization/OrganizationList/OrganizationList.js:135
-#: screens/Project/ProjectList/ProjectList.js:209
-#: screens/Team/TeamList/TeamList.js:134
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:165
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:108
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:112
-msgid "Modified By (Username)"
-msgstr "Gewijzigd door (gebruikersnaam)"
-
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:85
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:151
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:77
-msgid "Modified by (username)"
-msgstr "Gewijzigd door (gebruikersnaam)"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:59
-#: screens/Job/JobOutput/HostEventModal.js:125
-msgid "Module"
-msgstr "Module"
-
-#: screens/Job/JobDetail/JobDetail.js:530
-msgid "Module Arguments"
-msgstr "Module-argumenten"
-
-#: screens/Job/JobDetail/JobDetail.js:524
-msgid "Module Name"
-msgstr "Naam van de module"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:266
-msgid "Mon"
-msgstr "Ma"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:77
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:183
-#: components/Schedule/shared/FrequencyDetailSubform.js:271
-#: components/Schedule/shared/FrequencyDetailSubform.js:433
-msgid "Monday"
-msgstr "Maandag"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:186
-#: components/Schedule/shared/FrequencyDetailSubform.js:181
-#: components/Schedule/shared/ScheduleFormFields.js:129
-#: components/Schedule/shared/ScheduleFormFields.js:189
-msgid "Month"
-msgstr "Maand"
-
-#: components/Popover/Popover.js:32
-msgid "More information"
-msgstr "Meer informatie"
-
-#: screens/Setting/shared/SharedFields.js:73
-msgid "More information for"
-msgstr "Meer informatie voor"
-
-#: screens/Template/Survey/SurveyReorderModal.js:162
-#: screens/Template/Survey/SurveyReorderModal.js:163
-msgid "Multi-Select"
-msgstr "Multi-Select"
-
-#: screens/Template/Survey/SurveyReorderModal.js:146
-#: screens/Template/Survey/SurveyReorderModal.js:147
-msgid "Multiple Choice"
-msgstr "Meerkeuze"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:91
-msgid "Multiple Choice (multiple select)"
-msgstr "Meerkeuze-opties (meerdere keuzes mogelijk)"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:86
-msgid "Multiple Choice (single select)"
-msgstr "Meerkeuze-opties (één keuze mogelijk)"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:258
-msgid "Multiple Choice Options"
-msgstr "Meerkeuze-opties"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:117
-#: components/AdHocCommands/AdHocCredentialStep.js:132
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:107
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:122
-#: components/AddRole/AddResourceRole.js:52
-#: components/AddRole/AddResourceRole.js:68
-#: components/AssociateModal/AssociateModal.js:139
-#: components/AssociateModal/AssociateModal.js:154
-#: components/HostForm/HostForm.js:96
-#: components/JobList/JobList.js:195
-#: components/JobList/JobList.js:244
-#: components/JobList/JobListItem.js:86
-#: components/LaunchPrompt/steps/CredentialsStep.js:168
-#: components/LaunchPrompt/steps/CredentialsStep.js:183
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:76
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:86
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:97
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:78
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:89
-#: components/LaunchPrompt/steps/InventoryStep.js:84
-#: components/LaunchPrompt/steps/InventoryStep.js:99
-#: components/Lookup/ApplicationLookup.js:100
-#: components/Lookup/ApplicationLookup.js:111
-#: components/Lookup/CredentialLookup.js:189
-#: components/Lookup/CredentialLookup.js:204
-#: components/Lookup/ExecutionEnvironmentLookup.js:177
-#: components/Lookup/ExecutionEnvironmentLookup.js:184
-#: components/Lookup/HostFilterLookup.js:93
-#: components/Lookup/HostFilterLookup.js:422
-#: components/Lookup/HostListItem.js:8
-#: components/Lookup/InstanceGroupsLookup.js:103
-#: components/Lookup/InstanceGroupsLookup.js:114
-#: components/Lookup/InventoryLookup.js:147
-#: components/Lookup/InventoryLookup.js:162
-#: components/Lookup/InventoryLookup.js:202
-#: components/Lookup/InventoryLookup.js:217
-#: components/Lookup/MultiCredentialsLookup.js:189
-#: components/Lookup/MultiCredentialsLookup.js:204
-#: components/Lookup/OrganizationLookup.js:129
-#: components/Lookup/OrganizationLookup.js:144
-#: components/Lookup/ProjectLookup.js:127
-#: components/Lookup/ProjectLookup.js:157
-#: components/NotificationList/NotificationList.js:181
-#: components/NotificationList/NotificationList.js:218
-#: components/NotificationList/NotificationListItem.js:28
-#: components/OptionsList/OptionsList.js:57
-#: components/PaginatedTable/PaginatedTable.js:72
-#: components/PromptDetail/PromptDetail.js:114
-#: components/RelatedTemplateList/RelatedTemplateList.js:161
-#: components/RelatedTemplateList/RelatedTemplateList.js:186
-#: components/ResourceAccessList/ResourceAccessListItem.js:58
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:325
-#: components/Schedule/ScheduleList/ScheduleList.js:168
-#: components/Schedule/ScheduleList/ScheduleList.js:189
-#: components/Schedule/ScheduleList/ScheduleListItem.js:86
-#: components/Schedule/shared/ScheduleFormFields.js:72
-#: components/TemplateList/TemplateList.js:205
-#: components/TemplateList/TemplateList.js:242
-#: components/TemplateList/TemplateListItem.js:142
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:18
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:37
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:49
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:68
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:80
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:110
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:122
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:137
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:149
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:179
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:191
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:206
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:60
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:109
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:135
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:28
-#: screens/Application/Applications.js:81
-#: screens/Application/ApplicationsList/ApplicationListItem.js:33
-#: screens/Application/ApplicationsList/ApplicationsList.js:118
-#: screens/Application/ApplicationsList/ApplicationsList.js:155
-#: screens/Application/shared/ApplicationForm.js:54
-#: screens/Credential/CredentialDetail/CredentialDetail.js:217
-#: screens/Credential/CredentialList/CredentialList.js:141
-#: screens/Credential/CredentialList/CredentialList.js:164
-#: screens/Credential/CredentialList/CredentialListItem.js:58
-#: screens/Credential/shared/CredentialForm.js:161
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:71
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:91
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:68
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:123
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:176
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:33
-#: screens/CredentialType/shared/CredentialTypeForm.js:21
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:49
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:136
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:165
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:69
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:89
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:115
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:12
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:89
-#: screens/Host/HostDetail/HostDetail.js:69
-#: screens/Host/HostGroups/HostGroupItem.js:28
-#: screens/Host/HostGroups/HostGroupsList.js:159
-#: screens/Host/HostGroups/HostGroupsList.js:176
-#: screens/Host/HostList/HostList.js:148
-#: screens/Host/HostList/HostList.js:169
-#: screens/Host/HostList/HostListItem.js:50
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:41
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:49
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:161
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:194
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:61
-#: screens/InstanceGroup/Instances/InstanceList.js:199
-#: screens/InstanceGroup/Instances/InstanceList.js:215
-#: screens/InstanceGroup/Instances/InstanceList.js:266
-#: screens/InstanceGroup/Instances/InstanceList.js:299
-#: screens/InstanceGroup/Instances/InstanceListItem.js:124
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:44
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:19
-#: screens/Instances/InstanceList/InstanceList.js:143
-#: screens/Instances/InstanceList/InstanceList.js:160
-#: screens/Instances/InstanceList/InstanceList.js:201
-#: screens/Instances/InstanceList/InstanceListItem.js:128
-#: screens/Instances/InstancePeers/InstancePeerList.js:80
-#: screens/Instances/InstancePeers/InstancePeerList.js:87
-#: screens/Instances/InstancePeers/InstancePeerList.js:96
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:37
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:89
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:31
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:194
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:209
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:215
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:34
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:119
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:141
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:74
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:36
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:169
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:186
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:33
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:119
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:138
-#: screens/Inventory/InventoryList/InventoryList.js:178
-#: screens/Inventory/InventoryList/InventoryList.js:209
-#: screens/Inventory/InventoryList/InventoryList.js:218
-#: screens/Inventory/InventoryList/InventoryListItem.js:92
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:180
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:195
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:232
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:181
-#: screens/Inventory/InventorySources/InventorySourceList.js:211
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:71
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:98
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:30
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:76
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:111
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:33
-#: screens/Inventory/shared/InventoryForm.js:50
-#: screens/Inventory/shared/InventoryGroupForm.js:32
-#: screens/Inventory/shared/InventorySourceForm.js:100
-#: screens/Inventory/shared/SmartInventoryForm.js:47
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:90
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:100
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:67
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:107
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:122
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:178
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:112
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:41
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:91
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:84
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:107
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.js:16
-#: screens/Organization/OrganizationList/OrganizationList.js:122
-#: screens/Organization/OrganizationList/OrganizationList.js:143
-#: screens/Organization/OrganizationList/OrganizationListItem.js:45
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:68
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:85
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:14
-#: screens/Organization/shared/OrganizationForm.js:56
-#: screens/Project/ProjectDetail/ProjectDetail.js:176
-#: screens/Project/ProjectList/ProjectList.js:185
-#: screens/Project/ProjectList/ProjectList.js:221
-#: screens/Project/ProjectList/ProjectListItem.js:179
-#: screens/Project/shared/ProjectForm.js:214
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:146
-#: screens/Team/TeamDetail/TeamDetail.js:37
-#: screens/Team/TeamList/TeamList.js:117
-#: screens/Team/TeamList/TeamList.js:142
-#: screens/Team/TeamList/TeamListItem.js:33
-#: screens/Team/shared/TeamForm.js:29
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:185
-#: screens/Template/Survey/SurveyList.js:102
-#: screens/Template/Survey/SurveyList.js:102
-#: screens/Template/Survey/SurveyListItem.js:39
-#: screens/Template/Survey/SurveyReorderModal.js:218
-#: screens/Template/Survey/SurveyReorderModal.js:218
-#: screens/Template/Survey/SurveyReorderModal.js:238
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:111
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:69
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:88
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:120
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:152
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:178
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:88
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.js:74
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.js:94
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:75
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:95
-#: screens/Template/shared/JobTemplateForm.js:243
-#: screens/Template/shared/WorkflowJobTemplateForm.js:109
-#: screens/User/UserOrganizations/UserOrganizationList.js:75
-#: screens/User/UserOrganizations/UserOrganizationList.js:79
-#: screens/User/UserOrganizations/UserOrganizationListItem.js:13
-#: screens/User/UserRoles/UserRolesList.js:155
-#: screens/User/UserRoles/UserRolesListItem.js:12
-#: screens/User/UserTeams/UserTeamList.js:180
-#: screens/User/UserTeams/UserTeamList.js:232
-#: screens/User/UserTeams/UserTeamListItem.js:18
-#: screens/User/UserTokenList/UserTokenListItem.js:22
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:140
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:123
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:163
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:43
-msgid "Name"
-msgstr "Naam"
-
-#: components/AppContainer/AppContainer.js:95
-msgid "Navigation"
-msgstr "Navigatie"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:221
-#: components/Schedule/shared/FrequencyDetailSubform.js:512
-#: screens/Dashboard/shared/ChartTooltip.js:106
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:57
-msgid "Never"
-msgstr "Nooit"
-
-#: components/Workflow/WorkflowNodeHelp.js:114
-msgid "Never Updated"
-msgstr "Nooit bijgewerkt"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:47
-msgid "Never expires"
-msgstr "Verloopt nooit"
-
-#: components/JobList/JobList.js:227
-#: components/Workflow/WorkflowNodeHelp.js:90
-msgid "New"
-msgstr "Nieuw"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:51
-#: components/AdHocCommands/useAdHocCredentialStep.js:29
-#: components/AdHocCommands/useAdHocDetailsStep.js:40
-#: components/AdHocCommands/useAdHocExecutionEnvironmentStep.js:22
-#: components/AddRole/AddResourceRole.js:196
-#: components/AddRole/AddResourceRole.js:231
-#: components/LaunchPrompt/LaunchPrompt.js:160
-#: components/Schedule/shared/SchedulePromptableFields.js:127
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:66
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:59
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:132
-msgid "Next"
-msgstr "Volgende"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:337
-#: components/Schedule/ScheduleList/ScheduleList.js:171
-#: components/Schedule/ScheduleList/ScheduleListItem.js:118
-#: components/Schedule/ScheduleList/ScheduleListItem.js:122
-msgid "Next Run"
-msgstr "Volgende uitvoering"
-
-#: components/Search/Search.js:232
-msgid "No"
-msgstr "Geen"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:121
-msgid "No Hosts Matched"
-msgstr "Geen overeenkomende hosts"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:122
-#: screens/Job/JobOutput/JobOutputSearch.js:123
-msgid "No Hosts Remaining"
-msgstr "Geen resterende hosts"
-
-#: screens/Job/JobOutput/HostEventModal.js:150
-msgid "No JSON Available"
-msgstr "Geen JSON beschikbaar"
-
-#: screens/Dashboard/shared/ChartTooltip.js:82
-msgid "No Jobs"
-msgstr "Geen taken"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:72
-msgid "No inventory sync failures."
-msgstr "Geen fouten bij inventarissynchronisatie."
-
-#: components/ContentEmpty/ContentEmpty.js:20
-msgid "No items found."
-msgstr "Geen items gevonden."
-
-#: screens/Host/HostList/HostListItem.js:100
-msgid "No job data available"
-msgstr "Geen taakgegevens beschikbaar"
-
-#: screens/Job/JobOutput/EmptyOutput.js:52
-msgid "No output found for this job."
-msgstr "Geen output gevonden voor deze taak."
-
-#: screens/Job/JobOutput/HostEventModal.js:126
-msgid "No result found"
-msgstr "Geen resultaat gevonden"
-
-#: components/LabelSelect/LabelSelect.js:130
-#: components/LaunchPrompt/steps/SurveyStep.js:136
-#: components/LaunchPrompt/steps/SurveyStep.js:195
-#: components/MultiSelect/TagMultiSelect.js:60
-#: components/Search/AdvancedSearch.js:151
-#: components/Search/AdvancedSearch.js:266
-#: components/Search/LookupTypeInput.js:33
-#: components/Search/RelatedLookupTypeInput.js:26
-#: components/Search/Search.js:153
-#: components/Search/Search.js:202
-#: components/Search/Search.js:226
-#: screens/ActivityStream/ActivityStream.js:142
-#: screens/Credential/shared/CredentialForm.js:143
-#: screens/Credential/shared/CredentialFormFields/BecomeMethodField.js:65
-#: screens/Dashboard/DashboardGraph.js:106
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:137
-#: screens/Template/Survey/SurveyReorderModal.js:166
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:260
-#: screens/Template/shared/PlaybookSelect.js:72
-msgid "No results found"
-msgstr "Geen resultaten gevonden"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:116
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:137
-msgid "No subscriptions found"
-msgstr "Geen abonnementen gevonden"
-
-#: screens/Template/Survey/SurveyList.js:147
-msgid "No survey questions found."
-msgstr "Geen vragenlijstvragen gevonden."
-
-#: screens/Job/JobDetail/JobDetail.js:399
-msgid "No timeout specified"
-msgstr "Geen time-out gespecificeerd"
-
-#: components/PaginatedTable/PaginatedTable.js:80
-msgid "No {pluralizedItemName} Found"
-msgstr "{pluralizedItemName} niet gevonden"
-
-#: components/Workflow/WorkflowNodeHelp.js:148
-#: components/Workflow/WorkflowNodeHelp.js:184
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:273
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:274
-msgid "Node Alias"
-msgstr "Knooppunt alias"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:223
-#: screens/InstanceGroup/Instances/InstanceList.js:204
-#: screens/InstanceGroup/Instances/InstanceList.js:268
-#: screens/InstanceGroup/Instances/InstanceList.js:300
-#: screens/InstanceGroup/Instances/InstanceListItem.js:142
-#: screens/Instances/InstanceDetail/InstanceDetail.js:204
-#: screens/Instances/InstanceList/InstanceList.js:148
-#: screens/Instances/InstanceList/InstanceList.js:203
-#: screens/Instances/InstanceList/InstanceListItem.js:150
-#: screens/Instances/InstancePeers/InstancePeerList.js:98
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:59
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:118
-msgid "Node Type"
-msgstr "Type knooppunt"
-
-#: screens/TopologyView/Legend.js:107
-msgid "Node state types"
-msgstr "Typen knooppuntstatus"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.js:75
-msgid "Node type"
-msgstr "Type knooppunt"
-
-#: screens/TopologyView/Legend.js:70
-msgid "Node types"
-msgstr "Typen knooppunten"
-
-#: components/Schedule/shared/ScheduleFormFields.js:180
-#: components/Schedule/shared/ScheduleFormFields.js:184
-#: components/Workflow/WorkflowNodeHelp.js:123
-msgid "None"
-msgstr "Geen"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:193
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:196
-msgid "None (Run Once)"
-msgstr "Geen (eenmaal uitgevoerd)"
-
-#: components/Schedule/shared/ScheduleFormFields.js:120
-#: components/Schedule/shared/ScheduleFormFields.js:124
-msgid "None (run once)"
-msgstr "Geen (eenmaal uitgevoerd)"
-
-#: screens/User/UserDetail/UserDetail.js:51
-#: screens/User/UserList/UserListItem.js:23
-#: screens/User/shared/UserForm.js:29
-msgid "Normal User"
-msgstr "Normale gebruiker"
-
-#: components/ContentError/ContentError.js:37
-msgid "Not Found"
-msgstr "Niet gevonden"
-
-#: screens/Setting/shared/SettingDetail.js:71
-#: screens/Setting/shared/SettingDetail.js:112
-msgid "Not configured"
-msgstr "Niet geconfigureerd"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:75
-msgid "Not configured for inventory sync."
-msgstr "Niet geconfigureerd voor inventarissynchronisatie."
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:248
-msgid ""
-"Note that only hosts directly in this group can\n"
-"be disassociated. Hosts in sub-groups must be disassociated\n"
-"directly from the sub-group level that they belong."
-msgstr "Let op: Alleen hosts die zich direct in deze groep bevinden, kunnen worden losgekoppeld. Hosts in subgroepen moeten rechtstreeks worden losgekoppeld van het subgroepniveau waar ze bij horen."
-
-#: screens/Host/HostGroups/HostGroupsList.js:212
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:230
-msgid ""
-"Note that you may still see the group in the list after\n"
-"disassociating if the host is also a member of that group’s\n"
-"children. This list shows all groups the host is associated\n"
-"with directly and indirectly."
-msgstr "Merk op dat u de groep na het ontkoppelen nog steeds in de lijst kunt zien als de host ook lid is van de onderliggende elementen van die groep. Deze lijst toont alle groepen waaraan de host is direct en indirect is gekoppeld."
-
-#: components/Lookup/InstanceGroupsLookup.js:90
-msgid "Note: The order in which these are selected sets the execution precedence. Select more than one to enable drag."
-msgstr "Opmerking: de volgorde waarin deze worden geselecteerd bepaalt de voorrang bij de uitvoering. Selecteer er meer dan één om slepen mogelijk te maken."
-
-#: screens/Organization/shared/OrganizationForm.js:116
-msgid "Note: The order of these credentials sets precedence for the sync and lookup of the content. Select more than one to enable drag."
-msgstr "Opmerking: de volgorde van deze toegangsgegevens bepaalt de voorrang voor de synchronisatie en het opzoeken van de inhoud. Selecteer er meer dan één om slepen mogelijk te maken."
-
-#: screens/Project/shared/Project.helptext.js:81
-msgid "Note: This field assumes the remote name is \"origin\"."
-msgstr "Opmerking: dit veld gaat ervan uit dat de naam op afstand \"oorsprong\" is."
-
-#: screens/Project/shared/Project.helptext.js:35
-msgid ""
-"Note: When using SSH protocol for GitHub or\n"
-"Bitbucket, enter an SSH key only, do not enter a username\n"
-"(other than git). Additionally, GitHub and Bitbucket do\n"
-"not support password authentication when using SSH. GIT\n"
-"read only protocol (git://) does not use username or\n"
-"password information."
-msgstr "Opmerking: als u een SSH-protocol gebruikt voor GitHub of Bitbucket, voer dan alleen een SSH-sleutel in. Voer geen gebruikersnaam in (behalve git). Daarnaast ondersteunen GitHub en Bitbucket geen wachtwoordauthenticatie bij gebruik van SSH. Het GIT-alleen-lezen-protocol (git://) gebruikt geen gebruikersnaam- of wachtwoordinformatie."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:331
-msgid "Notification Color"
-msgstr "Berichtkleur"
-
-#: screens/NotificationTemplate/NotificationTemplate.js:58
-#: screens/NotificationTemplate/NotificationTemplateAdd.js:50
-msgid "Notification Template not found."
-msgstr "Berichtsjabloon niet gevonden."
-
-#: screens/ActivityStream/ActivityStream.js:198
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:117
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:171
-#: screens/NotificationTemplate/NotificationTemplates.js:14
-#: screens/NotificationTemplate/NotificationTemplates.js:21
-#: util/getRelatedResourceDeleteDetails.js:181
-msgid "Notification Templates"
-msgstr "Berichtsjablonen"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:134
-msgid "Notification Type"
-msgstr "Berichttype"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:380
-msgid "Notification color"
-msgstr "Berichtkleur"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:193
-msgid "Notification sent successfully"
-msgstr "Bericht is verzonden"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:449
-msgid "Notification test failed."
-msgstr "Berichttest mislukt."
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:197
-msgid "Notification timed out"
-msgstr "Time-out voor bericht"
-
-#: components/NotificationList/NotificationList.js:190
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:131
-msgid "Notification type"
-msgstr "Berichttype"
-
-#: components/NotificationList/NotificationList.js:177
-#: routeConfig.js:122
-#: screens/Inventory/Inventories.js:93
-#: screens/Inventory/InventorySource/InventorySource.js:99
-#: screens/ManagementJob/ManagementJob.js:116
-#: screens/ManagementJob/ManagementJobs.js:22
-#: screens/Organization/Organization.js:135
-#: screens/Organization/Organizations.js:33
-#: screens/Project/Project.js:114
-#: screens/Project/Projects.js:28
-#: screens/Template/Template.js:141
-#: screens/Template/Templates.js:46
-#: screens/Template/WorkflowJobTemplate.js:123
-msgid "Notifications"
-msgstr "Berichten"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:169
-#: components/Schedule/shared/FrequencyDetailSubform.js:148
-msgid "November"
-msgstr "November"
-
-#: components/StatusLabel/StatusLabel.js:44
-#: components/Workflow/WorkflowNodeHelp.js:117
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:66
-#: screens/Job/JobOutput/shared/HostStatusBar.js:35
-msgid "OK"
-msgstr "OK"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:42
-#: components/Schedule/shared/FrequencyDetailSubform.js:549
-msgid "Occurrences"
-msgstr "Voorvallen"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:168
-#: components/Schedule/shared/FrequencyDetailSubform.js:143
-msgid "October"
-msgstr "Oktober"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:189
-#: components/HostToggle/HostToggle.js:61
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:192
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:195
-#: components/PromptDetail/PromptDetail.js:362
-#: components/PromptDetail/PromptJobTemplateDetail.js:156
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:489
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:58
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:45
-#: screens/Setting/shared/SettingDetail.js:98
-#: screens/Setting/shared/SharedFields.js:150
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:284
-#: screens/Template/shared/JobTemplateForm.js:504
-msgid "Off"
-msgstr "Uit"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:188
-#: components/HostToggle/HostToggle.js:60
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:192
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:194
-#: components/PromptDetail/PromptDetail.js:362
-#: components/PromptDetail/PromptJobTemplateDetail.js:156
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:489
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:57
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:45
-#: screens/Setting/shared/SettingDetail.js:98
-#: screens/Setting/shared/SharedFields.js:149
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:284
-#: screens/Template/shared/JobTemplateForm.js:504
-msgid "On"
-msgstr "Aan"
-
-#: components/Workflow/WorkflowLegend.js:126
-#: components/Workflow/WorkflowLinkHelp.js:30
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:39
-msgid "On Failure"
-msgstr "Bij mislukken"
-
-#: components/Workflow/WorkflowLegend.js:122
-#: components/Workflow/WorkflowLinkHelp.js:27
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:63
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:32
-msgid "On Success"
-msgstr "Bij slagen"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:536
-msgid "On date"
-msgstr "Aan-datum"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:99
-#: components/Schedule/shared/FrequencyDetailSubform.js:251
-msgid "On days"
-msgstr "Aan-dagen"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:18
-msgid ""
-"One Slack channel per line. The pound symbol (#)\n"
-"is required for channels. To respond to or start a thread to a specific message add the parent message Id to the channel where the parent message Id is 16 digits. A dot (.) must be manually inserted after the 10th digit. ie:#destination-channel, 1231257890.006423. See Slack"
-msgstr "Voer één Slack-kanaal per regel in. Het hekje (#) is vereist voor kanalen. Om op een thread te reageren of er een te beginnen voor een specifiek bericht, voert u de ID van het hoofdbericht toe aan het kanaal waar de ID van het hoofdbericht 16 tekens is. Een punt (.) moet handmatig worden ingevoerd na het 10e teken. bijv.: #destination-channel, 1231257890.006423. Zie Slack"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:154
-msgid "Only Group By"
-msgstr "Alleen ordenen op"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:103
-msgid "OpenStack"
-msgstr "OpenStack"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:111
-msgid "Option Details"
-msgstr "Optie Details"
-
-#: screens/Inventory/shared/Inventory.helptext.js:25
-msgid ""
-"Optional labels that describe this inventory,\n"
-"such as 'dev' or 'test'. Labels can be used to group and filter\n"
-"inventories and completed jobs."
-msgstr "Optionele labels die de inventaris beschrijven, zoals 'dev' of 'test'. Labels kunnen gebruikt worden om inventarissen en uitgevoerde taken te ordenen en filteren."
-
-#: screens/Job/Job.helptext.js:12
-#: screens/Template/shared/JobTemplate.helptext.js:13
-msgid "Optional labels that describe this job template, such as 'dev' or 'test'. Labels can be used to group and filter job templates and completed jobs."
-msgstr "Optionele labels die de taaksjabloon beschrijven, zoals 'dev' of 'test'. Labels kunnen gebruikt worden om taaksjablonen en uitgevoerde taken te ordenen en filteren."
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:11
-msgid ""
-"Optional labels that describe this workflow job template,\n"
-"such as 'dev' or 'test'. Labels can be used to group and filter\n"
-"workflow job templates and completed jobs."
-msgstr "Optionele labels die de taaksjabloon beschrijven, zoals 'dev' of 'test'. Labels kunnen gebruikt worden om taaksjablonen en uitgevoerde taken te ordenen en filteren."
-
-#: screens/Template/shared/JobTemplate.helptext.js:26
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:19
-msgid "Optionally select the credential to use to send status updates back to the webhook service."
-msgstr "Optioneel: selecteer de toegangsgegevens die u wilt gebruiken om statusupdates terug te sturen naar de webhookservice."
-
-#: components/NotificationList/NotificationList.js:220
-#: components/NotificationList/NotificationListItem.js:34
-#: screens/Credential/shared/TypeInputsSubForm.js:47
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:61
-#: screens/Instances/Shared/InstanceForm.js:54
-#: screens/Inventory/shared/InventoryForm.js:94
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:69
-#: screens/Template/shared/JobTemplateForm.js:545
-#: screens/Template/shared/WorkflowJobTemplateForm.js:241
-msgid "Options"
-msgstr "Opties"
-
-#: screens/Template/Survey/SurveyReorderModal.js:217
-#: screens/Template/Survey/SurveyReorderModal.js:217
-#: screens/Template/Survey/SurveyReorderModal.js:233
-msgid "Order"
-msgstr "Bestellen"
-
-#: components/Lookup/ApplicationLookup.js:119
-#: components/Lookup/OrganizationLookup.js:101
-#: components/Lookup/OrganizationLookup.js:107
-#: components/Lookup/OrganizationLookup.js:124
-#: components/PromptDetail/PromptInventorySourceDetail.js:62
-#: components/PromptDetail/PromptInventorySourceDetail.js:72
-#: components/PromptDetail/PromptJobTemplateDetail.js:105
-#: components/PromptDetail/PromptJobTemplateDetail.js:115
-#: components/PromptDetail/PromptProjectDetail.js:77
-#: components/PromptDetail/PromptProjectDetail.js:88
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:67
-#: components/TemplateList/TemplateList.js:244
-#: components/TemplateList/TemplateListItem.js:185
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:70
-#: screens/Application/ApplicationsList/ApplicationListItem.js:38
-#: screens/Application/ApplicationsList/ApplicationsList.js:157
-#: screens/Credential/CredentialDetail/CredentialDetail.js:230
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:70
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:155
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:167
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:76
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:96
-#: screens/Inventory/InventoryList/InventoryList.js:191
-#: screens/Inventory/InventoryList/InventoryList.js:221
-#: screens/Inventory/InventoryList/InventoryListItem.js:119
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:202
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:107
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:121
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:131
-#: screens/Project/ProjectDetail/ProjectDetail.js:180
-#: screens/Project/ProjectList/ProjectListItem.js:287
-#: screens/Project/ProjectList/ProjectListItem.js:298
-#: screens/Team/TeamDetail/TeamDetail.js:40
-#: screens/Team/TeamList/TeamList.js:143
-#: screens/Team/TeamList/TeamListItem.js:38
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:199
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:210
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:120
-#: screens/User/UserTeams/UserTeamList.js:181
-#: screens/User/UserTeams/UserTeamList.js:237
-#: screens/User/UserTeams/UserTeamListItem.js:23
-msgid "Organization"
-msgstr "Organisatie"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:100
-msgid "Organization (Name)"
-msgstr "Organisatie (naam)"
-
-#: screens/Team/TeamList/TeamList.js:126
-msgid "Organization Name"
-msgstr "Naam van organisatie"
-
-#: screens/Organization/Organization.js:154
-msgid "Organization not found."
-msgstr "Organisatie niet gevonden."
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:188
-#: routeConfig.js:96
-#: screens/ActivityStream/ActivityStream.js:181
-#: screens/Organization/OrganizationList/OrganizationList.js:117
-#: screens/Organization/OrganizationList/OrganizationList.js:163
-#: screens/Organization/Organizations.js:16
-#: screens/Organization/Organizations.js:26
-#: screens/User/User.js:66
-#: screens/User/UserOrganizations/UserOrganizationList.js:72
-#: screens/User/Users.js:33
-#: util/getRelatedResourceDeleteDetails.js:232
-#: util/getRelatedResourceDeleteDetails.js:266
-msgid "Organizations"
-msgstr "Organisaties"
-
-#: components/LaunchPrompt/steps/useOtherPromptsStep.js:90
-msgid "Other prompts"
-msgstr "Overige meldingen"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:79
-msgid "Out of compliance"
-msgstr "Niet compliant"
-
-#: screens/Job/Job.js:131
-#: screens/Job/JobOutput/HostEventModal.js:156
-#: screens/Job/Jobs.js:34
-msgid "Output"
-msgstr "Output"
-
-#: screens/Job/JobOutput/HostEventModal.js:157
-msgid "Output tab"
-msgstr "Output"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:80
-msgid "Overwrite"
-msgstr "Overschrijven"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:41
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:121
-msgid "Overwrite local groups and hosts from remote inventory source"
-msgstr "Lokale groepen en hosts overschrijven op grond van externe inventarisbron"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:46
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:127
-msgid "Overwrite local variables from remote inventory source"
-msgstr "Lokale variabelen overschrijven op grond van externe inventarisbron"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:86
-msgid "Overwrite variables"
-msgstr "Variabelen overschrijven"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:493
-msgid "POST"
-msgstr "BERICHT"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:494
-msgid "PUT"
-msgstr "PUT"
-
-#: components/NotificationList/NotificationList.js:198
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:139
-msgid "Pagerduty"
-msgstr "Pagerduty"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:279
-msgid "Pagerduty Subdomain"
-msgstr "Subdomein Pagerduty"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:298
-msgid "Pagerduty subdomain"
-msgstr "Subdomein Pagerduty"
-
-#: components/Pagination/Pagination.js:35
-msgid "Pagination"
-msgstr "Paginering"
-
-#: components/Workflow/WorkflowTools.js:165
-msgid "Pan Down"
-msgstr "Omlaag pannen"
-
-#: components/Workflow/WorkflowTools.js:132
-msgid "Pan Left"
-msgstr "Naar links pannen"
-
-#: components/Workflow/WorkflowTools.js:176
-msgid "Pan Right"
-msgstr "Naar rechts pannen"
-
-#: components/Workflow/WorkflowTools.js:143
-msgid "Pan Up"
-msgstr "Omhoog pannen"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:243
-msgid "Pass extra command line changes. There are two ansible command line parameters:"
-msgstr "Geef extra opdrachtregelwijzigingen in door. Er zijn twee opdrachtregelparameters voor Ansible:"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:14
-msgid "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. Refer to the Ansible Controller documentation for example syntax."
-msgstr "Geef extra commandoregelvariabelen op in het draaiboek. Dit is de commandoregelparameter -e of --extra-vars voor het ansible-draaiboek. Geef sleutel/waarde-paren op met YAML of JSON. Raadpleeg de documentatie van Ansible Tower voor voorbeeldsyntaxis."
-
-#: screens/Job/Job.helptext.js:13
-#: screens/Template/shared/JobTemplate.helptext.js:14
-msgid "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. Refer to the documentation for example syntax."
-msgstr "Geef extra opdrachtregelvariabelen op in het draaiboek. Dit is de opdrachtregelparameter -e of --extra-vars voor het Ansible-draaiboek. Geef sleutel/waarde-paren op met YAML of JSON. Raadpleeg de documentatie voor voorbeeldsyntaxis."
-
-#: screens/Login/Login.js:227
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:73
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:101
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:212
-#: screens/Template/Survey/SurveyQuestionForm.js:82
-#: screens/User/shared/UserForm.js:88
-msgid "Password"
-msgstr "Wachtwoord"
-
-#: screens/Dashboard/DashboardGraph.js:119
-msgid "Past 24 hours"
-msgstr "Afgelopen 24 uur"
-
-#: screens/Dashboard/DashboardGraph.js:110
-msgid "Past month"
-msgstr "Afgelopen maand"
-
-#: screens/Dashboard/DashboardGraph.js:113
-msgid "Past two weeks"
-msgstr "Afgelopen twee weken"
-
-#: screens/Dashboard/DashboardGraph.js:116
-msgid "Past week"
-msgstr "Afgelopen week"
-
-#: screens/Instances/Instance.js:51
-#: screens/Instances/InstancePeers/InstancePeerList.js:74
-msgid "Peers"
-msgstr "Collega's"
-
-#: components/JobList/JobList.js:228
-#: components/StatusLabel/StatusLabel.js:49
-#: components/Workflow/WorkflowNodeHelp.js:93
-msgid "Pending"
-msgstr "In afwachting"
-
-#: components/AppContainer/PageHeaderToolbar.js:76
-msgid "Pending Workflow Approvals"
-msgstr "In afwachting van workflowgoedkeuringen"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:128
-msgid "Pending delete"
-msgstr "In afwachting om verwijderd te worden"
-
-#: components/Lookup/HostFilterLookup.js:370
-msgid "Perform a search to define a host filter"
-msgstr "Voer een zoekopdracht uit om een hostfilter te definiëren"
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:73
-#: screens/User/UserTokenList/UserTokenList.js:105
-msgid "Personal Access Token"
-msgstr "Persoonlijke toegangstoken"
-
-#: screens/User/UserTokenList/UserTokenListItem.js:26
-msgid "Personal access token"
-msgstr "Persoonlijke toegangstoken"
-
-#: screens/Job/JobOutput/HostEventModal.js:122
-msgid "Play"
-msgstr "Afspelen"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:84
-msgid "Play Count"
-msgstr "Aantal afspelen"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:124
-msgid "Play Started"
-msgstr "Afspelen gestart"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:148
-#: screens/Job/JobDetail/JobDetail.js:319
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:253
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:43
-#: screens/Template/shared/JobTemplateForm.js:357
-msgid "Playbook"
-msgstr "Draaiboek"
-
-#: components/JobList/JobListItem.js:44
-#: screens/Job/JobDetail/JobDetail.js:67
-msgid "Playbook Check"
-msgstr "Draaiboek controleren"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:125
-msgid "Playbook Complete"
-msgstr "Draaiboek voltooid"
-
-#: components/PromptDetail/PromptProjectDetail.js:165
-#: screens/Project/ProjectDetail/ProjectDetail.js:288
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:72
-msgid "Playbook Directory"
-msgstr "Draaiboekmap"
-
-#: components/JobList/JobList.js:213
-#: components/JobList/JobListItem.js:44
-#: components/Schedule/ScheduleList/ScheduleListItem.js:37
-#: screens/Job/JobDetail/JobDetail.js:67
-msgid "Playbook Run"
-msgstr "Draaiboek uitvoering"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:126
-msgid "Playbook Started"
-msgstr "Draaiboek gestart"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:174
-#: components/TemplateList/TemplateList.js:222
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:23
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:54
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:157
-msgid "Playbook name"
-msgstr "Naam van draaiboek"
-
-#: screens/Dashboard/DashboardGraph.js:146
-msgid "Playbook run"
-msgstr "Uitvoering van draaiboek"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:85
-msgid "Plays"
-msgstr "Uitvoeringen van het draaiboek"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:149
-msgid "Please add a Schedule to populate this list."
-msgstr "Voeg een schema toe om deze lijst te vullen."
-
-#: components/Schedule/ScheduleList/ScheduleList.js:152
-msgid "Please add a Schedule to populate this list. Schedules can be added to a Template, Project, or Inventory Source."
-msgstr "Voeg een schema toe om deze lijst te vullen. Schema's kunnen worden toegevoegd aan een sjabloon, project of inventarisatiebron."
-
-#: screens/Template/Survey/SurveyList.js:146
-msgid "Please add survey questions."
-msgstr "Voeg vragenlijstvragen toe."
-
-#: components/PaginatedTable/PaginatedTable.js:93
-msgid "Please add {pluralizedItemName} to populate this list"
-msgstr "Voeg {pluralizedItemName} toe om deze lijst te vullen"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:43
-msgid "Please click the Start button to begin."
-msgstr "Klik op de startknop om te beginnen."
-
-#: components/Schedule/shared/ScheduleForm.js:421
-msgid "Please enter a number of occurrences."
-msgstr "Voer een aantal voorvallen in."
-
-#: util/validators.js:160
-msgid "Please enter a valid URL"
-msgstr "Voer een geldige URL in"
-
-#: screens/User/shared/UserTokenForm.js:20
-msgid "Please enter a value."
-msgstr "Voer een waarde in."
-
-#: screens/Login/Login.js:191
-msgid "Please log in"
-msgstr "Log in"
-
-#: components/JobList/JobList.js:190
-msgid "Please run a job to populate this list."
-msgstr "Voer een taak uit om deze lijst te vullen."
-
-#: components/Schedule/shared/ScheduleForm.js:417
-msgid "Please select a day number between 1 and 31."
-msgstr "Selecteer een getal tussen 1 en 31."
-
-#: screens/Template/shared/JobTemplateForm.js:174
-msgid "Please select an Inventory or check the Prompt on Launch option"
-msgstr "Selecteer een inventaris of schakel de optie Melding bij opstarten in"
-
-#: components/Schedule/shared/ScheduleForm.js:429
-msgid "Please select an end date/time that comes after the start date/time."
-msgstr "Kies een einddatum/-tijd die na de begindatum/-tijd komt."
-
-#: components/Lookup/HostFilterLookup.js:359
-msgid "Please select an organization before editing the host filter"
-msgstr "Selecteer een organisatie voordat u het hostfilter bewerkt"
-
-#: screens/Job/JobOutput/EmptyOutput.js:32
-msgid "Please try another search using the filter above"
-msgstr "Probeer een andere zoekopdracht met de bovenstaande filter"
-
-#: screens/TopologyView/ContentLoading.js:40
-msgid "Please wait until the topology view is populated..."
-msgstr "Wacht totdat de topologie-weergave is ingevuld..."
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:78
-msgid "Pod spec override"
-msgstr "Overschrijven Podspec"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:214
-#: screens/InstanceGroup/Instances/InstanceListItem.js:203
-#: screens/Instances/InstanceDetail/InstanceDetail.js:208
-#: screens/Instances/InstanceList/InstanceListItem.js:218
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:82
-msgid "Policy Type"
-msgstr "Beleidstype"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:63
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:26
-msgid "Policy instance minimum"
-msgstr "Beleid instantieminimum"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:70
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:36
-msgid "Policy instance percentage"
-msgstr "Beleid instantiepercentage"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginField.js:64
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginField.js:70
-msgid "Populate field from an external secret management system"
-msgstr "Vul veld vanuit een extern geheimbeheersysteem"
-
-#: components/Lookup/HostFilterLookup.js:349
-msgid ""
-"Populate the hosts for this inventory by using a search\n"
-"filter. Example: ansible_facts__ansible_distribution:\"RedHat\".\n"
-"Refer to the documentation for further syntax and\n"
-"examples. Refer to the Ansible Controller documentation for further syntax and\n"
-"examples."
-msgstr "Vul de hosts voor deze inventaris door gebruik te maken van een zoekfilter. Voorbeeld: ansible_facts.ansible_distribution: \"RedHat\".\n"
-"Raadpleeg de documentatie voor verdere syntaxis en\n"
-"voorbeelden. Raadpleeg de documentatie van Ansible Tower voor verdere syntaxis en\n"
-"voorbeelden."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:165
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:104
-msgid "Port"
-msgstr "Poort"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:231
-msgid "Preconditions for running this node when there are multiple parents. Refer to the"
-msgstr "Voorwaarden voor het uitvoeren van dit knooppunt wanneer er meerdere bovenliggende elementen zijn. Raadpleeg de"
-
-#: screens/Template/Survey/MultipleChoiceField.js:59
-msgid ""
-"Press 'Enter' to add more answer choices. One answer\n"
-"choice per line."
-msgstr "Druk op 'Enter' om meer antwoordkeuzen toe te voegen. Eén antwoordkeuze per regel."
-
-#: components/CodeEditor/CodeEditor.js:181
-msgid "Press Enter to edit. Press ESC to stop editing."
-msgstr "Druk op Enter om te bewerken. Druk op ESC om het bewerken te stoppen."
-
-#: components/SelectedList/DraggableSelectedList.js:85
-msgid ""
-"Press space or enter to begin dragging,\n"
-"and use the arrow keys to navigate up or down.\n"
-"Press enter to confirm the drag, or any other key to\n"
-"cancel the drag operation."
-msgstr "Druk op spatie of enter om te beginnen met slepen,\n"
-"en gebruik de pijltjestoetsen om omhoog of omlaag te navigeren.\n"
-"Druk op enter om het slepen te bevestigen, of op een andere toets om\n"
-"de sleepoperatie te annuleren."
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:71
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:130
-#: screens/Inventory/shared/InventoryForm.js:99
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:147
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:347
-#: screens/Template/shared/JobTemplateForm.js:603
-msgid "Prevent Instance Group Fallback"
-msgstr "Instance Group Fallback voorkomen"
-
-#: screens/Inventory/shared/Inventory.helptext.js:197
-msgid "Prevent Instance Group Fallback: If enabled, the inventory will prevent adding any organization instance groups to the list of preferred instances groups to run associated job templates on."
-msgstr "Instance Group Fallback voorkomen: Indien ingeschakeld, zal de inventaris voorkomen dat instantiegroepen van organisaties worden toegevoegd aan de lijst van voorkeursinstantiegroepen om geassocieerde taaksjablonen op uit te voeren."
-
-#: screens/Template/shared/JobTemplate.helptext.js:43
-msgid "Prevent Instance Group Fallback: If enabled, the job template will prevent adding any inventory or organization instance groups to the list of preferred instances groups to run on."
-msgstr "Instance Group Fallback voorkomen: Indien ingeschakeld, voorkomt de taaksjabloon dat inventaris- of organisatie-instantiegroepen worden toegevoegd aan de lijst van voorkeursinstantiegroepen om op te draaien."
-
-#: components/AdHocCommands/useAdHocPreviewStep.js:17
-#: components/LaunchPrompt/steps/usePreviewStep.js:23
-msgid "Preview"
-msgstr "Voorvertoning"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:103
-msgid "Private key passphrase"
-msgstr "Privésleutel wachtwoordzin"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:58
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:122
-#: screens/Template/shared/JobTemplateForm.js:551
-msgid "Privilege Escalation"
-msgstr "Verhoging van rechten"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:111
-msgid "Privilege escalation password"
-msgstr "Wachtwoord verhoging van rechten"
-
-#: screens/Template/shared/JobTemplate.helptext.js:40
-msgid "Privilege escalation: If enabled, run this playbook as an administrator."
-msgstr "Als deze optie ingeschakeld is, wordt het draaiboek uitgevoerd als beheerder."
-
-#: components/JobList/JobListItem.js:239
-#: components/Lookup/ProjectLookup.js:104
-#: components/Lookup/ProjectLookup.js:109
-#: components/Lookup/ProjectLookup.js:166
-#: components/PromptDetail/PromptInventorySourceDetail.js:87
-#: components/PromptDetail/PromptJobTemplateDetail.js:133
-#: components/PromptDetail/PromptJobTemplateDetail.js:141
-#: components/TemplateList/TemplateListItem.js:300
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:216
-#: screens/Job/JobDetail/JobDetail.js:172
-#: screens/Job/JobDetail/JobDetail.js:198
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:229
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:239
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:38
-msgid "Project"
-msgstr "Project"
-
-#: components/PromptDetail/PromptProjectDetail.js:158
-#: screens/Project/ProjectDetail/ProjectDetail.js:281
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:61
-msgid "Project Base Path"
-msgstr "Basispad project"
-
-#: components/Workflow/WorkflowLegend.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:85
-msgid "Project Sync"
-msgstr "Projectsynchronisatie"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:320
-#: screens/Project/ProjectList/ProjectListItem.js:229
-msgid "Project Sync Error"
-msgstr "Fout tijdens projectsynchronisatie"
-
-#: components/Workflow/WorkflowNodeHelp.js:67
-msgid "Project Update"
-msgstr "Projectupdate"
-
-#: screens/Job/JobDetail/JobDetail.js:180
-msgid "Project Update Status"
-msgstr "Projectupdate"
-
-#: screens/Job/Job.helptext.js:22
-msgid "Project checkout results"
-msgstr "Resultaten project-checkout weergeven"
-
-#: screens/Project/ProjectList/ProjectList.js:132
-msgid "Project copied successfully"
-msgstr "Project gekopieerd"
-
-#: screens/Project/Project.js:136
-msgid "Project not found."
-msgstr "Feit niet gevonden."
-
-#: screens/Dashboard/Dashboard.js:109
-msgid "Project sync failures"
-msgstr "Mislukte projectsynchronisaties"
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:146
-#: routeConfig.js:75
-#: screens/ActivityStream/ActivityStream.js:170
-#: screens/Dashboard/Dashboard.js:103
-#: screens/Project/ProjectList/ProjectList.js:180
-#: screens/Project/ProjectList/ProjectList.js:249
-#: screens/Project/Projects.js:12
-#: screens/Project/Projects.js:22
-#: util/getRelatedResourceDeleteDetails.js:60
-#: util/getRelatedResourceDeleteDetails.js:195
-#: util/getRelatedResourceDeleteDetails.js:225
-msgid "Projects"
-msgstr "Projecten"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:139
-msgid "Promote Child Groups and Hosts"
-msgstr "Onderliggende groepen en hosts promoveren"
-
-#: components/Schedule/shared/ScheduleForm.js:540
-#: components/Schedule/shared/ScheduleForm.js:543
-msgid "Prompt"
-msgstr "Melding"
-
-#: components/PromptDetail/PromptDetail.js:182
-msgid "Prompt Overrides"
-msgstr "Meldingsoverschrijvingen"
-
-#: components/CodeEditor/VariablesField.js:241
-#: components/FieldWithPrompt/FieldWithPrompt.js:46
-#: screens/Credential/CredentialDetail/CredentialDetail.js:175
-msgid "Prompt on launch"
-msgstr "Melding bij opstarten"
-
-#: components/Schedule/shared/SchedulePromptableFields.js:97
-msgid "Prompt | {0}"
-msgstr "Melding | {0}"
-
-#: components/PromptDetail/PromptDetail.js:180
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:418
-msgid "Prompted Values"
-msgstr "Invoerwaarden"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:6
-msgid ""
-"Provide a host pattern to further constrain\n"
-"the list of hosts that will be managed or affected by the\n"
-"playbook. Multiple patterns are allowed. Refer to Ansible\n"
-"documentation for more information and examples on patterns."
-msgstr "Geef een hostpatroon op om de lijst van hosts die beheerd of beïnvloed worden door het draaiboek verder te beperken. Meerdere patronen zijn toegestaan. Raadpleeg de documentatie van Ansible voor meer informatie over en voorbeelden van patronen."
-
-#: screens/Job/Job.helptext.js:14
-#: screens/Template/shared/JobTemplate.helptext.js:15
-msgid "Provide a host pattern to further constrain the list of hosts that will be managed or affected by the playbook. Multiple patterns are allowed. Refer to Ansible documentation for more information and examples on patterns."
-msgstr "Geef een hostpatroon op om de lijst van hosts die beheerd of beïnvloed worden door het draaiboek verder te beperken. Meerdere patronen zijn toegestaan. Raadpleeg de documentatie van Ansible voor meer informatie over en voorbeelden van patronen."
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:179
-msgid "Provide a value for this field or select the Prompt on launch option."
-msgstr "Geef een waarde op voor dit veld of selecteer de optie Melding bij opstarten."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:247
-msgid ""
-"Provide key/value pairs using either\n"
-"YAML or JSON."
-msgstr "Geef sleutel/waardeparen op met behulp van YAML of JSON."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:191
-msgid ""
-"Provide your Red Hat or Red Hat Satellite credentials\n"
-"below and you can choose from a list of your available subscriptions.\n"
-"The credentials you use will be stored for future use in\n"
-"retrieving renewal or expanded subscriptions."
-msgstr "Geef uw Red Hat- of Red Hat Satellite-gegevens hieronder door en u kunt kiezen uit een lijst met beschikbare abonnementen. De toegangsgegevens die u gebruikt, worden opgeslagen voor toekomstig gebruik bij het ophalen van verlengingen of uitbreidingen van abonnementen."
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:83
-msgid "Provide your Red Hat or Red Hat Satellite credentials to enable Automation Analytics."
-msgstr "Geef uw Red Hat- of Red Hat Satellite-toegangsgegevens op om Automatiseringsanalyse in te schakelen."
-
-#: components/StatusLabel/StatusLabel.js:59
-#: screens/TopologyView/Legend.js:150
-msgid "Provisioning"
-msgstr "Voorziening"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:162
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:302
-#: screens/Template/shared/JobTemplateForm.js:620
-msgid "Provisioning Callback URL"
-msgstr "Provisioning terugkoppelings-URL"
-
-#: screens/Template/shared/JobTemplateForm.js:615
-msgid "Provisioning Callback details"
-msgstr "Provisioning terugkoppelingsdetails"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:63
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:127
-#: screens/Template/shared/JobTemplateForm.js:555
-#: screens/Template/shared/JobTemplateForm.js:558
-msgid "Provisioning Callbacks"
-msgstr "Provisioning terugkoppelingen"
-
-#: screens/Template/shared/JobTemplate.helptext.js:41
-msgid "Provisioning callbacks: Enables creation of a provisioning callback URL. Using the URL a host can contact Ansible AWX and request a configuration update using this job template."
-msgstr "Maakt het mogelijk een provisioning terugkoppelings-URL aan te maken. Met deze URL kan een host contact opnemen met \n"
-"en een verzoek voor een configuratie-update indienen met behulp van deze taaksjabloon."
-
-#: components/StatusLabel/StatusLabel.js:62
-msgid "Provisioning fail"
-msgstr "Bevoorrading mislukt"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:86
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:114
-msgid "Pull"
-msgstr "Pullen"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:163
-msgid "Question"
-msgstr "Vraag"
-
-#: screens/Setting/Settings.js:106
-msgid "RADIUS"
-msgstr "RADIUS"
-
-#: screens/Setting/SettingList.js:73
-msgid "RADIUS settings"
-msgstr "RADIUS-instellingen"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:244
-#: screens/InstanceGroup/Instances/InstanceListItem.js:161
-#: screens/Instances/InstanceDetail/InstanceDetail.js:287
-#: screens/Instances/InstanceList/InstanceListItem.js:171
-#: screens/TopologyView/Tooltip.js:307
-msgid "RAM {0}"
-msgstr "RAM {0}"
-
-#: screens/User/shared/UserTokenForm.js:76
-msgid "Read"
-msgstr "Lezen"
-
-#: components/StatusLabel/StatusLabel.js:57
-#: screens/TopologyView/Legend.js:122
-msgid "Ready"
-msgstr "Klaar"
-
-#: screens/Dashboard/Dashboard.js:133
-msgid "Recent Jobs"
-msgstr "Recente taken"
-
-#: screens/Dashboard/Dashboard.js:131
-msgid "Recent Jobs list tab"
-msgstr "Tabblad Lijst met recente takenlijst"
-
-#: screens/Dashboard/Dashboard.js:145
-msgid "Recent Templates"
-msgstr "Recente sjablonen"
-
-#: screens/Dashboard/Dashboard.js:143
-msgid "Recent Templates list tab"
-msgstr "Tabblad Lijst met recente sjablonen"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:188
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:112
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:38
-msgid "Recent jobs"
-msgstr "Recente taken"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:154
-msgid "Recipient List"
-msgstr "Lijst met ontvangers"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:86
-msgid "Recipient list"
-msgstr "Lijst met ontvangers"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:105
-msgid "Red Hat Ansible Automation Platform"
-msgstr "Automatiseringsplatform voor Red Hat Ansible"
-
-#: components/Lookup/ProjectLookup.js:139
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:92
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:161
-#: screens/Job/JobDetail/JobDetail.js:77
-#: screens/Project/ProjectList/ProjectList.js:201
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:100
-msgid "Red Hat Insights"
-msgstr "Red Hat Insights"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:102
-msgid "Red Hat Satellite 6"
-msgstr "Red Hat Satellite 6"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:104
-msgid "Red Hat Virtualization"
-msgstr "Red Hat-virtualizering"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:117
-msgid "Red Hat subscription manifest"
-msgstr "Red Hat-abonnementsmanifest"
-
-#: components/About/About.js:36
-msgid "Red Hat, Inc."
-msgstr "Red Hat, Inc."
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:94
-#: screens/Application/shared/ApplicationForm.js:107
-msgid "Redirect URIs"
-msgstr "URI's doorverwijzen"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:259
-msgid "Redirecting to dashboard"
-msgstr "Doorverwijzen naar dashboard"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:263
-msgid "Redirecting to subscription detail"
-msgstr "Doorverwijzen naar abonnementsdetails"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:261
-#: screens/Template/shared/JobTemplate.helptext.js:55
-msgid "Refer to the"
-msgstr "Raadpleeg de"
-
-#: screens/Job/Job.helptext.js:27
-#: screens/Template/shared/JobTemplate.helptext.js:50
-msgid "Refer to the Ansible documentation for details about the configuration file."
-msgstr "Raadpleeg de documentatie van Ansible voor meer informatie over het configuratiebestand."
-
-#: screens/User/UserTokens/UserTokens.js:77
-msgid "Refresh Token"
-msgstr "Token verversen"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:81
-msgid "Refresh Token Expiration"
-msgstr "Vernieuwingstoken vervallen"
-
-#: screens/Project/ProjectList/ProjectListItem.js:132
-msgid "Refresh for revision"
-msgstr "Synchroniseren voor herziening"
-
-#: screens/Project/ProjectList/ProjectListItem.js:134
-msgid "Refresh project revision"
-msgstr "Herziening vernieuwing project"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:116
-msgid "Regions"
-msgstr "Regio's"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:92
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:143
-msgid "Registry credential"
-msgstr "Toegangsgegevens registreren"
-
-#: screens/Inventory/shared/Inventory.helptext.js:156
-msgid "Regular expression where only matching host names will be imported. The filter is applied as a post-processing step after any inventory plugin filters are applied."
-msgstr "Reguliere expressie waarbij alleen overeenkomende hostnamen worden geïmporteerd. Het filter wordt toegepast als een nabewerkingsstap nadat eventuele filters voor inventarisplugins zijn toegepast."
-
-#: screens/Inventory/Inventories.js:81
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:62
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:175
-msgid "Related Groups"
-msgstr "Gerelateerde groepen"
-
-#: components/Search/AdvancedSearch.js:287
-msgid "Related Keys"
-msgstr "Verwante sleutels"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:169
-#: components/Schedule/ScheduleList/ScheduleListItem.js:105
-msgid "Related resource"
-msgstr "Verwante bron"
-
-#: components/Search/RelatedLookupTypeInput.js:16
-#: components/Search/RelatedLookupTypeInput.js:24
-msgid "Related search type"
-msgstr "Verwant zoektype"
-
-#: components/Search/RelatedLookupTypeInput.js:19
-msgid "Related search type typeahead"
-msgstr "Verwante zoekopdracht typeahead"
-
-#: components/JobList/JobListItem.js:146
-#: components/LaunchButton/ReLaunchDropDown.js:82
-#: screens/Job/JobDetail/JobDetail.js:580
-#: screens/Job/JobDetail/JobDetail.js:588
-#: screens/Job/JobOutput/shared/OutputToolbar.js:167
-msgid "Relaunch"
-msgstr "Opnieuw starten"
-
-#: components/JobList/JobListItem.js:126
-#: screens/Job/JobOutput/shared/OutputToolbar.js:147
-msgid "Relaunch Job"
-msgstr "Taak opnieuw starten"
-
-#: components/LaunchButton/ReLaunchDropDown.js:41
-msgid "Relaunch all hosts"
-msgstr "Alle hosts opnieuw starten"
-
-#: components/LaunchButton/ReLaunchDropDown.js:54
-msgid "Relaunch failed hosts"
-msgstr "Mislukte hosts opnieuw starten"
-
-#: components/LaunchButton/ReLaunchDropDown.js:30
-#: components/LaunchButton/ReLaunchDropDown.js:35
-msgid "Relaunch on"
-msgstr "Opnieuw starten bij"
-
-#: components/JobList/JobListItem.js:125
-#: screens/Job/JobOutput/shared/OutputToolbar.js:146
-msgid "Relaunch using host parameters"
-msgstr "Opnieuw opstarten met hostparameters"
-
-#: components/HealthCheckAlert/HealthCheckAlert.js:27
-msgid "Reload"
-msgstr "Herladen"
-
-#: screens/Job/JobOutput/JobOutput.js:723
-msgid "Reload output"
-msgstr "Download output"
-
-#: components/Lookup/ProjectLookup.js:138
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:91
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:160
-#: screens/Job/JobDetail/JobDetail.js:78
-#: screens/Project/ProjectList/ProjectList.js:200
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:99
-msgid "Remote Archive"
-msgstr "Extern archief"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:374
-#: screens/Instances/InstanceList/InstanceList.js:242
-msgid "Removal Error"
-msgstr "Verwijderingsfout"
-
-#: components/SelectedList/DraggableSelectedList.js:105
-#: screens/Instances/Shared/RemoveInstanceButton.js:75
-#: screens/Instances/Shared/RemoveInstanceButton.js:129
-#: screens/Instances/Shared/RemoveInstanceButton.js:143
-#: screens/Instances/Shared/RemoveInstanceButton.js:163
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:21
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:29
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:40
-msgid "Remove"
-msgstr "Verwijderen"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:36
-msgid "Remove All Nodes"
-msgstr "Alle knooppunten verwijderen"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:152
-msgid "Remove Instances"
-msgstr "Instanties verwijderen"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:17
-msgid "Remove Link"
-msgstr "Link verwijderen"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:28
-msgid "Remove Node {nodeName}"
-msgstr "Knooppunt {nodeName} verwijderen"
-
-#: screens/Project/shared/Project.helptext.js:113
-msgid "Remove any local modifications prior to performing an update."
-msgstr "Verwijder alle plaatselijke aanpassingen voordat een update uitgevoerd wordt."
-
-#: components/Search/AdvancedSearch.js:206
-msgid "Remove the current search related to ansible facts to enable another search using this key."
-msgstr "Verwijder de huidige zoekopdracht die gerelateerd is aan ansible-feiten om een andere zoekopdracht met deze sleutel mogelijk te maken."
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:14
-msgid "Remove {0} Access"
-msgstr "Toegang {0} verwijderen"
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:45
-msgid "Remove {0} chip"
-msgstr "Chip {0} verwijderen"
-
-#: screens/TopologyView/Legend.js:285
-msgid "Removing"
-msgstr "Verwijderen van"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:48
-msgid "Removing this link will orphan the rest of the branch and cause it to be executed immediately on launch."
-msgstr "Als u deze link verwijdert, wordt de rest van de vertakking zwevend en wordt deze onmiddellijk bij lancering uitgevoerd."
-
-#: components/SelectedList/DraggableSelectedList.js:83
-msgid "Reorder"
-msgstr "Nabestellen"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:349
-msgid "Repeat Frequency"
-msgstr "Frequentie herhalen"
-
-#: components/Schedule/shared/ScheduleFormFields.js:113
-msgid "Repeat frequency"
-msgstr "Frequentie herhalen"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:52
-msgid "Replace"
-msgstr "Vervangen"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:60
-msgid "Replace field with new value"
-msgstr "Veld vervangen door nieuwe waarde"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:67
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:74
-msgid "Request subscription"
-msgstr "Abonnement aanvragen"
-
-#: screens/Template/Survey/SurveyListItem.js:51
-#: screens/Template/Survey/SurveyQuestionForm.js:188
-msgid "Required"
-msgstr "Vereist"
-
-#: screens/TopologyView/Header.js:87
-#: screens/TopologyView/Header.js:90
-msgid "Reset zoom"
-msgstr "Zoom resetten"
-
-#: components/Workflow/WorkflowNodeHelp.js:154
-#: components/Workflow/WorkflowNodeHelp.js:190
-#: screens/Team/TeamRoles/TeamRoleListItem.js:12
-#: screens/Team/TeamRoles/TeamRolesList.js:180
-msgid "Resource Name"
-msgstr "Bronnaam"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:246
-msgid "Resource deleted"
-msgstr "Bron verwijderd"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:170
-#: components/Schedule/ScheduleList/ScheduleListItem.js:111
-msgid "Resource type"
-msgstr "Brontype toevoegen"
-
-#: routeConfig.js:61
-#: screens/ActivityStream/ActivityStream.js:159
-msgid "Resources"
-msgstr "Hulpbronnen"
-
-#: components/TemplateList/TemplateListItem.js:149
-msgid "Resources are missing from this template."
-msgstr "Er ontbreken hulpbronnen uit dit sjabloon."
-
-#: screens/Setting/shared/RevertButton.js:43
-msgid "Restore initial value."
-msgstr "Oorspronkelijke waarde herstellen."
-
-#: screens/Inventory/shared/Inventory.helptext.js:153
-msgid ""
-"Retrieve the enabled state from the given dict of host variables.\n"
-"The enabled variable may be specified using dot notation, e.g: 'foo.bar'"
-msgstr "Haal de ingeschakelde status op uit het gegeven dictaat van de hostvariabelen. De ingeschakelde variabele kan worden gespecificeerd met behulp van puntnotatie, bijvoorbeeld: 'foo.bar'"
-
-#: components/JobCancelButton/JobCancelButton.js:96
-#: components/JobCancelButton/JobCancelButton.js:100
-#: components/JobList/JobListCancelButton.js:160
-#: components/JobList/JobListCancelButton.js:163
-#: screens/Job/JobOutput/JobOutput.js:819
-#: screens/Job/JobOutput/JobOutput.js:822
-msgid "Return"
-msgstr "Teruggeven"
-
-#: screens/Job/JobOutput/EmptyOutput.js:40
-msgid "Return to"
-msgstr "Teruggeven"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:129
-msgid "Return to subscription management."
-msgstr "Terug naar abonnementenbeheer."
-
-#: components/Search/AdvancedSearch.js:171
-msgid "Returns results that have values other than this one as well as other filters."
-msgstr "Retourneert resultaten die andere waarden hebben dan deze, evenals andere filters."
-
-#: components/Search/AdvancedSearch.js:158
-msgid "Returns results that satisfy this one as well as other filters. This is the default set type if nothing is selected."
-msgstr "Retourneert resultaten die voldoen aan dit filter en aan andere filters. Dit is het standaard ingestelde type als er niets is geselecteerd."
-
-#: components/Search/AdvancedSearch.js:164
-msgid "Returns results that satisfy this one or any other filters."
-msgstr "Retourneert resultaten die voldoen aan dit filter of aan andere filters."
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:52
-#: screens/Setting/shared/RevertButton.js:53
-#: screens/Setting/shared/RevertButton.js:62
-msgid "Revert"
-msgstr "Terugzetten"
-
-#: screens/Setting/shared/RevertAllAlert.js:23
-msgid "Revert all"
-msgstr "Alles terugzetten"
-
-#: screens/Setting/shared/RevertFormActionGroup.js:21
-#: screens/Setting/shared/RevertFormActionGroup.js:27
-msgid "Revert all to default"
-msgstr "Alles terugzetten naar standaardinstellingen"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:59
-msgid "Revert field to previously saved value"
-msgstr "Veld terugzetten op eerder opgeslagen waarde"
-
-#: screens/Setting/shared/RevertAllAlert.js:11
-msgid "Revert settings"
-msgstr "Instellingen terugzetten"
-
-#: screens/Setting/shared/RevertButton.js:42
-msgid "Revert to factory default."
-msgstr "Terugzetten op fabrieksinstellingen."
-
-#: screens/Job/JobDetail/JobDetail.js:314
-#: screens/Project/ProjectList/ProjectList.js:224
-#: screens/Project/ProjectList/ProjectListItem.js:221
-msgid "Revision"
-msgstr "Herziening"
-
-#: screens/Project/shared/ProjectSubForms/SvnSubForm.js:22
-msgid "Revision #"
-msgstr "Herziening #"
-
-#: components/NotificationList/NotificationList.js:199
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:140
-msgid "Rocket.Chat"
-msgstr "Rocket.Chat"
-
-#: screens/Team/TeamRoles/TeamRoleListItem.js:20
-#: screens/Team/TeamRoles/TeamRolesList.js:148
-#: screens/Team/TeamRoles/TeamRolesList.js:182
-#: screens/User/UserList/UserList.js:163
-#: screens/User/UserList/UserListItem.js:55
-#: screens/User/UserRoles/UserRolesList.js:146
-#: screens/User/UserRoles/UserRolesList.js:157
-#: screens/User/UserRoles/UserRolesListItem.js:26
-msgid "Role"
-msgstr "Rol"
-
-#: components/ResourceAccessList/ResourceAccessList.js:189
-#: components/ResourceAccessList/ResourceAccessList.js:202
-#: components/ResourceAccessList/ResourceAccessList.js:229
-#: components/ResourceAccessList/ResourceAccessListItem.js:69
-#: screens/Team/Team.js:59
-#: screens/Team/Teams.js:32
-#: screens/User/User.js:71
-#: screens/User/Users.js:31
-msgid "Roles"
-msgstr "Rollen"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:134
-#: components/Workflow/WorkflowLinkHelp.js:39
-#: screens/Credential/shared/ExternalTestModal.js:89
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:49
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:23
-#: screens/Template/shared/JobTemplateForm.js:214
-msgid "Run"
-msgstr "Uitvoeren"
-
-#: components/AdHocCommands/AdHocCommands.js:131
-#: components/AdHocCommands/AdHocCommands.js:135
-#: components/AdHocCommands/AdHocCommands.js:141
-#: components/AdHocCommands/AdHocCommands.js:145
-#: screens/Job/JobDetail/JobDetail.js:68
-msgid "Run Command"
-msgstr "Opdracht uitvoeren"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:277
-#: screens/Instances/InstanceDetail/InstanceDetail.js:335
-msgid "Run a health check on the instance"
-msgstr "Een gezondheidscontrole op de instantie uitvoeren"
-
-#: components/AdHocCommands/AdHocCommands.js:125
-msgid "Run ad hoc command"
-msgstr "Ad-hoc-opdracht uitvoeren"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:48
-msgid "Run command"
-msgstr "Opdracht uitvoeren"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:93
-#: components/Schedule/shared/FrequencyDetailSubform.js:223
-msgid "Run every"
-msgstr "Uitvoeren om de"
-
-#: components/HealthCheckButton/HealthCheckButton.js:32
-#: components/HealthCheckButton/HealthCheckButton.js:45
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:286
-#: screens/Instances/InstanceDetail/InstanceDetail.js:344
-msgid "Run health check"
-msgstr "Gezondheidscontrole"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:129
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:138
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:175
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:197
-#: components/Schedule/shared/FrequencyDetailSubform.js:344
-msgid "Run on"
-msgstr "Uitvoeren op"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useRunTypeStep.js:32
-msgid "Run type"
-msgstr "Uitvoertype"
-
-#: components/JobList/JobList.js:230
-#: components/StatusLabel/StatusLabel.js:48
-#: components/TemplateList/TemplateListItem.js:118
-#: components/Workflow/WorkflowNodeHelp.js:99
-msgid "Running"
-msgstr "In uitvoering"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:127
-msgid "Running Handlers"
-msgstr "Handlers die worden uitgevoerd"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:217
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:196
-#: screens/InstanceGroup/Instances/InstanceListItem.js:194
-#: screens/Instances/InstanceDetail/InstanceDetail.js:212
-#: screens/Instances/InstanceList/InstanceListItem.js:209
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:73
-msgid "Running Jobs"
-msgstr "Taken in uitvoering"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:284
-#: screens/Instances/InstanceDetail/InstanceDetail.js:342
-msgid "Running health check"
-msgstr "Laatste gezondheidscontrole"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:71
-msgid "Running jobs"
-msgstr "Taken in uitvoering"
-
-#: screens/Setting/Settings.js:109
-msgid "SAML"
-msgstr "SAML"
-
-#: screens/Setting/SettingList.js:77
-msgid "SAML settings"
-msgstr "SAML-instellingen"
-
-#: screens/Dashboard/DashboardGraph.js:143
-msgid "SCM update"
-msgstr "SCM-update"
-
-#: screens/User/UserDetail/UserDetail.js:58
-#: screens/User/UserList/UserListItem.js:49
-msgid "SOCIAL"
-msgstr "SOCIAAL"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:95
-msgid "SSH password"
-msgstr "SSH-wachtwoord"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:239
-msgid "SSL Connection"
-msgstr "SSL-verbinding"
-
-#: components/Workflow/WorkflowStartNode.js:60
-#: components/Workflow/workflowReducer.js:419
-msgid "START"
-msgstr "BEGINNEN"
-
-#: components/Sparkline/Sparkline.js:31
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:160
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:39
-#: screens/Project/ProjectDetail/ProjectDetail.js:135
-#: screens/Project/ProjectList/ProjectListItem.js:73
-msgid "STATUS:"
-msgstr "STATUS:"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:321
-msgid "Sat"
-msgstr "Zat"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:82
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:188
-#: components/Schedule/shared/FrequencyDetailSubform.js:326
-#: components/Schedule/shared/FrequencyDetailSubform.js:458
-msgid "Saturday"
-msgstr "Zaterdag"
-
-#: components/AddRole/AddResourceRole.js:247
-#: components/AssociateModal/AssociateModal.js:104
-#: components/AssociateModal/AssociateModal.js:110
-#: components/FormActionGroup/FormActionGroup.js:13
-#: components/FormActionGroup/FormActionGroup.js:19
-#: components/Schedule/shared/ScheduleForm.js:526
-#: components/Schedule/shared/ScheduleForm.js:532
-#: components/Schedule/shared/useSchedulePromptSteps.js:49
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:130
-#: screens/Credential/shared/CredentialForm.js:318
-#: screens/Credential/shared/CredentialForm.js:323
-#: screens/Setting/shared/RevertFormActionGroup.js:12
-#: screens/Setting/shared/RevertFormActionGroup.js:18
-#: screens/Template/Survey/SurveyReorderModal.js:205
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:35
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:131
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:158
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:162
-msgid "Save"
-msgstr "Opslaan"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:33
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:36
-msgid "Save & Exit"
-msgstr "Opslaan en afsluiten"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:32
-msgid "Save link changes"
-msgstr "Linkwijzigingen opslaan"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:254
-msgid "Save successful!"
-msgstr "Opslaan gelukt!"
-
-#: components/JobList/JobListItem.js:181
-#: components/JobList/JobListItem.js:187
-msgid "Schedule"
-msgstr "Schema"
-
-#: screens/Project/Projects.js:34
-#: screens/Template/Templates.js:54
-msgid "Schedule Details"
-msgstr "Details van schema"
-
-#: components/Schedule/shared/UnsupportedScheduleForm.js:15
-msgid "Schedule Rules"
-msgstr "Schema Regels"
-
-#: screens/Inventory/Inventories.js:92
-msgid "Schedule details"
-msgstr "Details van schema"
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:49
-msgid "Schedule is active"
-msgstr "Schema is actief"
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:49
-msgid "Schedule is inactive"
-msgstr "Schema is actief"
-
-#: components/Schedule/shared/ScheduleForm.js:394
-msgid "Schedule is missing rrule"
-msgstr "Er ontbreekt een regel in het schema"
-
-#: components/Schedule/Schedule.js:82
-msgid "Schedule not found."
-msgstr "Schema niet gevonden."
-
-#: components/Schedule/ScheduleList/ScheduleList.js:163
-#: components/Schedule/ScheduleList/ScheduleList.js:229
-#: routeConfig.js:44
-#: screens/ActivityStream/ActivityStream.js:153
-#: screens/Inventory/Inventories.js:89
-#: screens/Inventory/InventorySource/InventorySource.js:88
-#: screens/ManagementJob/ManagementJob.js:108
-#: screens/ManagementJob/ManagementJobs.js:23
-#: screens/Project/Project.js:120
-#: screens/Project/Projects.js:31
-#: screens/Schedule/AllSchedules.js:21
-#: screens/Template/Template.js:148
-#: screens/Template/Templates.js:51
-#: screens/Template/WorkflowJobTemplate.js:130
-msgid "Schedules"
-msgstr "Schema's"
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:136
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:33
-#: screens/User/UserTokenDetail/UserTokenDetail.js:50
-#: screens/User/UserTokenList/UserTokenList.js:142
-#: screens/User/UserTokenList/UserTokenList.js:189
-#: screens/User/UserTokenList/UserTokenListItem.js:32
-#: screens/User/shared/UserTokenForm.js:68
-msgid "Scope"
-msgstr "Bereik"
-
-#: screens/User/shared/User.helptext.js:5
-msgid "Scope for the token's access"
-msgstr "Geef een bereik op voor de toegang van de token"
-
-#: screens/Job/JobOutput/PageControls.js:79
-msgid "Scroll first"
-msgstr "Eerste scrollen"
-
-#: screens/Job/JobOutput/PageControls.js:87
-msgid "Scroll last"
-msgstr "Laatste scrollen"
-
-#: screens/Job/JobOutput/PageControls.js:71
-msgid "Scroll next"
-msgstr "Volgende scrollen"
-
-#: screens/Job/JobOutput/PageControls.js:63
-msgid "Scroll previous"
-msgstr "Vorige scrollen"
-
-#: components/Lookup/HostFilterLookup.js:290
-#: components/Lookup/Lookup.js:143
-msgid "Search"
-msgstr "Zoeken"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:151
-msgid "Search is disabled while the job is running"
-msgstr "Zoeken is uitgeschakeld terwijl de taak wordt uitgevoerd"
-
-#: components/Search/AdvancedSearch.js:311
-#: components/Search/Search.js:259
-msgid "Search submit button"
-msgstr "Knop Zoekopdracht verzenden"
-
-#: components/Search/Search.js:248
-msgid "Search text input"
-msgstr "Input voor tekst zoeken"
-
-#: components/Lookup/HostFilterLookup.js:398
-msgid "Searching by ansible_facts requires special syntax. Refer to the"
-msgstr "Zoeken op ansible_facts vereist speciale syntax. Raadpleeg de"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:408
-msgid "Second"
-msgstr "Seconde"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:103
-#: components/PromptDetail/PromptProjectDetail.js:153
-#: screens/Project/ProjectDetail/ProjectDetail.js:269
-msgid "Seconds"
-msgstr "Seconden"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:34
-msgid "See Django"
-msgstr "Zie Django"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:35
-#: components/LaunchPrompt/steps/PreviewStep.js:61
-msgid "See errors on the left"
-msgstr "Zie fouten links"
-
-#: components/JobList/JobListItem.js:84
-#: components/Lookup/HostFilterLookup.js:380
-#: components/Lookup/Lookup.js:200
-#: components/Pagination/Pagination.js:33
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:98
-msgid "Select"
-msgstr "Selecteren"
-
-#: screens/Credential/shared/CredentialForm.js:129
-msgid "Select Credential Type"
-msgstr "Type toegangsgegevens selecteren"
-
-#: screens/Host/HostGroups/HostGroupsList.js:237
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:254
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:257
-msgid "Select Groups"
-msgstr "Groepen selecteren"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:278
-msgid "Select Hosts"
-msgstr "Hosts selecteren"
-
-#: components/AnsibleSelect/AnsibleSelect.js:38
-msgid "Select Input"
-msgstr "Input selecteren"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:295
-msgid "Select Instances"
-msgstr "Instanties selecteren"
-
-#: components/AssociateModal/AssociateModal.js:22
-msgid "Select Items"
-msgstr "Items selecteren"
-
-#: components/AddRole/AddResourceRole.js:201
-msgid "Select Items from List"
-msgstr "Items in lijst selecteren"
-
-#: components/LabelSelect/LabelSelect.js:127
-msgid "Select Labels"
-msgstr "Labels selecteren"
-
-#: components/AddRole/AddResourceRole.js:236
-msgid "Select Roles to Apply"
-msgstr "Rollen selecteren om toe te passen"
-
-#: screens/User/UserTeams/UserTeamList.js:251
-msgid "Select Teams"
-msgstr "Teams selecteren"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:25
-msgid "Select a JSON formatted service account key to autopopulate the following fields."
-msgstr "Selecteer een JSON-geformatteerde serviceaccountsleutel om de volgende velden automatisch in te vullen."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:122
-msgid "Select a Node Type"
-msgstr "Selecteer een knooppunttype"
-
-#: components/AddRole/AddResourceRole.js:170
-msgid "Select a Resource Type"
-msgstr "Selecteer een brontype"
-
-#: screens/Job/Job.helptext.js:21
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:10
-msgid "Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch."
-msgstr "Selecteer een vertakking voor de workflow. Deze vertakking wordt toegepast op alle jobsjabloonknooppunten die vragen naar een vertakking."
-
-#: screens/Credential/shared/CredentialForm.js:139
-msgid "Select a credential Type"
-msgstr "Type toegangsgegevens selecteren"
-
-#: components/JobList/JobListCancelButton.js:98
-msgid "Select a job to cancel"
-msgstr "Taak selecteren om deze te annuleren"
-
-#: screens/Metrics/Metrics.js:211
-msgid "Select a metric"
-msgstr "Metriek selecteren"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:75
-msgid "Select a module"
-msgstr "Module selecteren"
-
-#: screens/Template/shared/PlaybookSelect.js:60
-#: screens/Template/shared/PlaybookSelect.js:61
-msgid "Select a playbook"
-msgstr "Draaiboek selecteren"
-
-#: screens/Template/shared/JobTemplateForm.js:323
-msgid "Select a project before editing the execution environment."
-msgstr "Selecteer een project voordat u de uitvoeringsomgeving bewerkt."
-
-#: screens/Template/Survey/SurveyToolbar.js:82
-msgid "Select a question to delete"
-msgstr "Selecteer een vraag om te verwijderen"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:160
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:103
-msgid "Select a row to delete"
-msgstr "Rij selecteren om deze te verwijderen"
-
-#: components/DisassociateButton/DisassociateButton.js:75
-msgid "Select a row to disassociate"
-msgstr "Rij selecteren om deze te ontkoppelen"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:77
-msgid "Select a row to remove"
-msgstr "Rij selecteren om deze te weigeren"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:87
-msgid "Select a subscription"
-msgstr "Abonnement selecteren"
-
-#: components/HostForm/HostForm.js:39
-#: components/Schedule/shared/FrequencyDetailSubform.js:71
-#: components/Schedule/shared/FrequencyDetailSubform.js:78
-#: components/Schedule/shared/FrequencyDetailSubform.js:88
-#: components/Schedule/shared/ScheduleFormFields.js:33
-#: components/Schedule/shared/ScheduleFormFields.js:37
-#: components/Schedule/shared/ScheduleFormFields.js:61
-#: screens/Credential/shared/CredentialForm.js:44
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:79
-#: screens/Inventory/shared/InventoryForm.js:72
-#: screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.js:46
-#: screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/GCESubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.js:46
-#: screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:97
-#: screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.js:44
-#: screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.js:46
-#: screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.js:46
-#: screens/Inventory/shared/SmartInventoryForm.js:67
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:24
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:61
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:436
-#: screens/Project/shared/ProjectForm.js:234
-#: screens/Project/shared/ProjectSubForms/InsightsSubForm.js:39
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:37
-#: screens/Team/shared/TeamForm.js:49
-#: screens/Template/Survey/SurveyQuestionForm.js:30
-#: screens/Template/shared/WorkflowJobTemplateForm.js:130
-#: screens/User/shared/UserForm.js:139
-#: util/validators.js:201
-msgid "Select a value for this field"
-msgstr "Waarde voor dit veld selecteren"
-
-#: screens/Template/shared/JobTemplate.helptext.js:23
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:20
-msgid "Select a webhook service."
-msgstr "Selecteer een webhookservice."
-
-#: components/DataListToolbar/DataListToolbar.js:121
-#: components/DataListToolbar/DataListToolbar.js:125
-#: screens/Template/Survey/SurveyToolbar.js:49
-msgid "Select all"
-msgstr "Alles selecteren"
-
-#: screens/ActivityStream/ActivityStream.js:129
-msgid "Select an activity type"
-msgstr "Type activiteit selecteren"
-
-#: screens/Metrics/Metrics.js:200
-msgid "Select an instance"
-msgstr "Selecteer een instantie"
-
-#: screens/Metrics/Metrics.js:242
-msgid "Select an instance and a metric to show chart"
-msgstr "Instantie en metriek selecteren om grafiek te tonen"
-
-#: components/HealthCheckButton/HealthCheckButton.js:19
-msgid "Select an instance to run a health check."
-msgstr "Selecteer een instantie om een gezondheidscontrole uit te voeren."
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:5
-msgid "Select an inventory for the workflow. This inventory is applied to all workflow nodes that prompt for an inventory."
-msgstr "Selecteer een inventaris voor de workflow. Deze inventaris wordt toegepast op alle workflowknooppunten die vragen naar een inventaris."
-
-#: components/LaunchPrompt/steps/SurveyStep.js:131
-msgid "Select an option"
-msgstr "Kies een optie"
-
-#: screens/Project/shared/ProjectForm.js:245
-msgid "Select an organization before editing the default execution environment."
-msgstr "Selecteer een organisatie voordat u de standaard uitvoeringsomgeving bewerkt."
-
-#: screens/Job/Job.helptext.js:11
-#: screens/Template/shared/JobTemplate.helptext.js:12
-msgid "Select credentials for accessing the nodes this job will be ran against. You can only select one credential of each type. For machine credentials (SSH), checking \"Prompt on launch\" without selecting credentials will require you to select a machine credential at run time. If you select credentials and check \"Prompt on launch\", the selected credential(s) become the defaults that can be updated at run time."
-msgstr "Selecteer toegangsgegevens om toegang te krijgen tot de knooppunten waartegen deze taak uitgevoerd zal worden. U kunt slechts één set toegangsgegevens van iedere soort kiezen. In het geval van machine-toegangsgegevens (SSH) moet u, als u 'melding bij opstarten' aanvinkt zonder toegangsgegevens te kiezen, bij het opstarten de machinetoegangsgegevens kiezen. Als u toegangsgegevens selecteert en 'melding bij opstarten' aanvinkt, worden de geselecteerde toegangsgegevens de standaardtoegangsgegevens en kunnen deze bij het opstarten gewijzigd worden."
-
-#: components/Schedule/shared/ScheduleFormFields.js:120
-#: components/Schedule/shared/ScheduleFormFields.js:179
-msgid "Select frequency"
-msgstr "Frequentie herhalen"
-
-#: screens/Project/shared/Project.helptext.js:18
-msgid ""
-"Select from the list of directories found in\n"
-"the Project Base Path. Together the base path and the playbook\n"
-"directory provide the full path used to locate playbooks."
-msgstr "Kies uit de lijst mappen die in het basispad van het project gevonden zijn. Het basispad en de map van het draaiboek vormen samen het volledige pad dat gebruikt wordt op draaiboeken te vinden."
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:98
-msgid "Select items from list"
-msgstr "Items in lijst selecteren"
-
-#: screens/Dashboard/DashboardGraph.js:124
-#: screens/Dashboard/DashboardGraph.js:125
-msgid "Select job type"
-msgstr "Type taak selecteren"
-
-#: components/LaunchPrompt/steps/SurveyStep.js:179
-msgid "Select option(s)"
-msgstr "Optie(s) selecteren"
-
-#: screens/Dashboard/DashboardGraph.js:95
-#: screens/Dashboard/DashboardGraph.js:96
-#: screens/Dashboard/DashboardGraph.js:97
-msgid "Select period"
-msgstr "Periode selecteren"
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:117
-msgid "Select roles to apply"
-msgstr "Rollen selecteren om toe te passen"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:128
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:129
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:130
-msgid "Select source path"
-msgstr "Bronpad selecteren"
-
-#: screens/Dashboard/DashboardGraph.js:151
-#: screens/Dashboard/DashboardGraph.js:152
-msgid "Select status"
-msgstr "Status selecteren"
-
-#: components/MultiSelect/TagMultiSelect.js:59
-msgid "Select tags"
-msgstr "Tags selecteren"
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:94
-msgid "Select the Execution Environment you want this command to run inside."
-msgstr "Selecteer de uitvoeromgeving waarbinnen u deze opdracht wilt uitvoeren."
-
-#: screens/Inventory/shared/SmartInventoryForm.js:87
-msgid "Select the Instance Groups for this Inventory to run on."
-msgstr "Selecteer de instantiegroepen waar deze inventaris op uitgevoerd wordt."
-
-#: screens/Job/Job.helptext.js:18
-#: screens/Template/shared/JobTemplate.helptext.js:20
-msgid "Select the Instance Groups for this Job Template to run on."
-msgstr "Selecteer de instantiegroepen waar dit taaksjabloon op uitgevoerd wordt."
-
-#: screens/Organization/shared/OrganizationForm.js:83
-msgid "Select the Instance Groups for this Organization to run on."
-msgstr "Selecteer de instantiegroepen waar de organisatie op uitgevoerd wordt."
-
-#: components/AdHocCommands/AdHocCredentialStep.js:104
-msgid "Select the credential you want to use when accessing the remote hosts to run the command. Choose the credential containing the username and SSH key or password that Ansible will need to log into the remote hosts."
-msgstr "Selecteer de toegangsgegevens die u wilt gebruiken bij het aanspreken van externe hosts om de opdracht uit te voeren. Kies de toegangsgegevens die de gebruikersnaam en de SSH-sleutel of het wachtwoord bevatten die Ansible nodig heeft om aan te melden bij de hosts of afstand."
-
-#: components/Lookup/InventoryLookup.js:123
-msgid ""
-"Select the inventory containing the hosts\n"
-"you want this job to manage."
-msgstr "Selecteer de inventaris met de hosts waarvan u wilt dat deze taak ze beheert."
-
-#: screens/Job/Job.helptext.js:6
-#: screens/Template/shared/JobTemplate.helptext.js:7
-msgid "Select the inventory containing the hosts you want this job to manage."
-msgstr "Selecteer de inventaris met de hosts waarvan u wilt dat deze taak ze beheert."
-
-#: components/HostForm/HostForm.js:32
-#: components/HostForm/HostForm.js:51
-msgid "Select the inventory that this host will belong to."
-msgstr "Selecteer de inventaris waartoe deze host zal behoren."
-
-#: screens/Job/Job.helptext.js:10
-#: screens/Template/shared/JobTemplate.helptext.js:11
-msgid "Select the playbook to be executed by this job."
-msgstr "Selecteer het draaiboek dat uitgevoerd moet worden door deze taak."
-
-#: screens/Instances/Shared/InstanceForm.js:43
-msgid "Select the port that Receptor will listen on for incoming connections. Default is 27199."
-msgstr "Selecteer de poort waarop Receptor zal luisteren voor inkomende verbindingen. De standaardinstelling is 27199."
-
-#: screens/Template/shared/JobTemplate.helptext.js:8
-msgid "Select the project containing the playbook you want this job to execute."
-msgstr "Selecteer het project dat het draaiboek bevat waarvan u wilt dat deze taak hem uitvoert."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:79
-msgid "Select your Ansible Automation Platform subscription to use."
-msgstr "Selecteer het Ansible Automation Platform-abonnement dat u wilt gebruiken."
-
-#: components/Lookup/Lookup.js:186
-msgid "Select {0}"
-msgstr "Selecteer {0}"
-
-#: components/AddRole/AddResourceRole.js:212
-#: components/AddRole/AddResourceRole.js:224
-#: components/AddRole/AddResourceRole.js:242
-#: components/AddRole/SelectRoleStep.js:27
-#: components/CheckboxListItem/CheckboxListItem.js:44
-#: components/Lookup/InstanceGroupsLookup.js:87
-#: components/OptionsList/OptionsList.js:74
-#: components/Schedule/ScheduleList/ScheduleListItem.js:84
-#: components/TemplateList/TemplateListItem.js:140
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:107
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:125
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:26
-#: screens/Application/ApplicationsList/ApplicationListItem.js:31
-#: screens/Credential/CredentialList/CredentialListItem.js:56
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:31
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:65
-#: screens/Host/HostGroups/HostGroupItem.js:26
-#: screens/Host/HostList/HostListItem.js:48
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:59
-#: screens/InstanceGroup/Instances/InstanceListItem.js:122
-#: screens/Instances/InstanceList/InstanceListItem.js:126
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:42
-#: screens/Inventory/InventoryList/InventoryListItem.js:90
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:37
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:110
-#: screens/Organization/OrganizationList/OrganizationListItem.js:43
-#: screens/Organization/shared/OrganizationForm.js:113
-#: screens/Project/ProjectList/ProjectListItem.js:177
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:242
-#: screens/Team/TeamList/TeamListItem.js:31
-#: screens/Template/Survey/SurveyListItem.js:34
-#: screens/User/UserTokenList/UserTokenListItem.js:19
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:41
-msgid "Selected"
-msgstr "Geselecteerd"
-
-#: components/LaunchPrompt/steps/CredentialsStep.js:142
-#: components/LaunchPrompt/steps/CredentialsStep.js:147
-#: components/Lookup/MultiCredentialsLookup.js:162
-#: components/Lookup/MultiCredentialsLookup.js:167
-msgid "Selected Category"
-msgstr "Geselecteerde categorie"
-
-#: components/Schedule/shared/ScheduleForm.js:446
-#: components/Schedule/shared/ScheduleForm.js:447
-msgid "Selected date range must have at least 1 schedule occurrence."
-msgstr "Het geselecteerde datumbereik moet ten minste 1 geplande gebeurtenis hebben."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:160
-msgid "Sender Email"
-msgstr "Afzender e-mail"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:96
-msgid "Sender e-mail"
-msgstr "Afzender e-mailbericht"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:167
-#: components/Schedule/shared/FrequencyDetailSubform.js:138
-msgid "September"
-msgstr "September"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:24
-msgid "Service account JSON file"
-msgstr "JSON-bestand service-account"
-
-#: screens/Inventory/shared/InventorySourceForm.js:46
-#: screens/Project/shared/ProjectForm.js:112
-msgid "Set a value for this field"
-msgstr "Waarde instellen voor dit veld"
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:70
-msgid "Set how many days of data should be retained."
-msgstr "Stel in hoeveel dagen aan gegevens er moet worden bewaard."
-
-#: screens/Setting/SettingList.js:122
-msgid "Set preferences for data collection, logos, and logins"
-msgstr "Stel voorkeuren in voor gegevensverzameling, logo's en aanmeldingen"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:131
-msgid "Set source path to"
-msgstr "Stel bronpad in op"
-
-#: components/InstanceToggle/InstanceToggle.js:48
-#: screens/Instances/Shared/InstanceForm.js:59
-msgid "Set the instance enabled or disabled. If disabled, jobs will not be assigned to this instance."
-msgstr "Zet de instantie aan of uit. Indien uitgeschakeld, zullen er geen taken aan deze instantie worden toegewezen."
-
-#: screens/Application/shared/Application.helptext.js:5
-msgid "Set to Public or Confidential depending on how secure the client device is."
-msgstr "Ingesteld op openbaar of vertrouwelijk, afhankelijk van de beveiliging van het toestel van de klant."
-
-#: components/Search/AdvancedSearch.js:149
-msgid "Set type"
-msgstr "Type instellen"
-
-#: components/Search/AdvancedSearch.js:239
-msgid "Set type disabled for related search field fuzzy searches"
-msgstr "Zet type op uitgeschakeld voor verwant zoekveld fuzzy zoekopdrachten"
-
-#: components/Search/AdvancedSearch.js:140
-msgid "Set type select"
-msgstr "Type instellen selecteren"
-
-#: components/Search/AdvancedSearch.js:143
-msgid "Set type typeahead"
-msgstr "Typeahead type instellen"
-
-#: components/Workflow/WorkflowTools.js:154
-msgid "Set zoom to 100% and center graph"
-msgstr "Zoom instellen op 100% en grafiek centreren"
-
-#: screens/Instances/Shared/InstanceForm.js:35
-msgid "Sets the current life cycle stage of this instance. Default is \"installed.\""
-msgstr "Stelt het huidige levenscyclusstadium van deze instantie in. Standaard is \"geïnstalleerd\"."
-
-#: screens/Instances/Shared/InstanceForm.js:51
-msgid "Sets the role that this instance will play within mesh topology. Default is \"execution.\""
-msgstr "Stelt de rol in die deze instantie zal spelen binnen de netwerktopologie. Standaard is \"uitvoering\"."
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:46
-msgid "Setting category"
-msgstr "Categorie instellen"
-
-#: screens/Setting/shared/RevertButton.js:46
-msgid "Setting matches factory default."
-msgstr "De instelling komt overeen met de fabrieksinstelling."
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:49
-msgid "Setting name"
-msgstr "Naam instellen"
-
-#: routeConfig.js:159
-#: routeConfig.js:163
-#: screens/ActivityStream/ActivityStream.js:220
-#: screens/ActivityStream/ActivityStream.js:222
-#: screens/Setting/Settings.js:43
-msgid "Settings"
-msgstr "Instellingen"
-
-#: components/FormField/PasswordInput.js:35
-msgid "Show"
-msgstr "Tonen"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:182
-#: components/PromptDetail/PromptDetail.js:361
-#: components/PromptDetail/PromptJobTemplateDetail.js:156
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:488
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:283
-#: screens/Template/shared/JobTemplateForm.js:497
-msgid "Show Changes"
-msgstr "Wijzigingen tonen"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:177
-#: components/AdHocCommands/AdHocDetailsStep.js:178
-msgid "Show changes"
-msgstr "Wijzigingen tonen"
-
-#: components/LaunchPrompt/LaunchPrompt.js:135
-#: components/Schedule/shared/SchedulePromptableFields.js:102
-msgid "Show description"
-msgstr "Beschrijving tonen"
-
-#: components/ChipGroup/ChipGroup.js:12
-msgid "Show less"
-msgstr "Minder tonen"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:126
-msgid "Show only root groups"
-msgstr "Alleen wortelgroepen tonen"
-
-#: screens/Login/Login.js:262
-msgid "Sign in with Azure AD"
-msgstr "Aanmelden met Azure AD"
-
-#: screens/Login/Login.js:276
-msgid "Sign in with GitHub"
-msgstr "Aanmelden met GitHub"
-
-#: screens/Login/Login.js:318
-msgid "Sign in with GitHub Enterprise"
-msgstr "Aanmelden met GitHub Enterprise"
-
-#: screens/Login/Login.js:333
-msgid "Sign in with GitHub Enterprise Organizations"
-msgstr "Aanmelden met GitHub Enterprise-organisaties"
-
-#: screens/Login/Login.js:349
-msgid "Sign in with GitHub Enterprise Teams"
-msgstr "Aanmelden met GitHub Enterprise-teams"
-
-#: screens/Login/Login.js:290
-msgid "Sign in with GitHub Organizations"
-msgstr "Aanmelden met GitHub-organisaties"
-
-#: screens/Login/Login.js:304
-msgid "Sign in with GitHub Teams"
-msgstr "Aanmelden met GitHub-teams"
-
-#: screens/Login/Login.js:364
-msgid "Sign in with Google"
-msgstr "Aanmelden met Google"
-
-#: screens/Login/Login.js:378
-msgid "Sign in with OIDC"
-msgstr "Aanmelden met SAML "
-
-#: screens/Login/Login.js:397
-msgid "Sign in with SAML"
-msgstr "Aanmelden met SAML"
-
-#: screens/Login/Login.js:396
-msgid "Sign in with SAML {samlIDP}"
-msgstr "Aanmelden met SAML {samlIDP}"
-
-#: components/Search/Search.js:145
-#: components/Search/Search.js:146
-msgid "Simple key select"
-msgstr "Eenvoudige sleutel selecteren"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:106
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:107
-#: components/PromptDetail/PromptDetail.js:295
-#: components/PromptDetail/PromptJobTemplateDetail.js:267
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:595
-#: screens/Job/JobDetail/JobDetail.js:500
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:475
-#: screens/Template/shared/JobTemplateForm.js:533
-#: screens/Template/shared/WorkflowJobTemplateForm.js:230
-msgid "Skip Tags"
-msgstr "Tags overslaan"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:93
-#: components/Schedule/shared/FrequencyDetailSubform.js:223
-msgid "Skip every"
-msgstr "Sla elke"
-
-#: screens/Job/Job.helptext.js:20
-#: screens/Template/shared/JobTemplate.helptext.js:22
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:22
-msgid "Skip tags are useful when you have a large playbook, and you want to skip specific parts of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags."
-msgstr "Tags overslaan is nuttig wanneer u een groot draaiboek heeft en specifieke delen van het draaiboek of een taak wilt overslaan. Gebruik een komma om meerdere tags van elkaar te scheiden. Raadpleeg de documentatie voor meer informatie over het gebruik van tags."
-
-#: screens/Job/JobOutput/shared/HostStatusBar.js:39
-msgid "Skipped"
-msgstr "Overgeslagen"
-
-#: components/StatusLabel/StatusLabel.js:50
-msgid "Skipped'"
-msgstr "Overgeslagen'"
-
-#: components/NotificationList/NotificationList.js:200
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:141
-msgid "Slack"
-msgstr "Slack"
-
-#: screens/Host/HostList/SmartInventoryButton.js:39
-#: screens/Host/HostList/SmartInventoryButton.js:48
-#: screens/Host/HostList/SmartInventoryButton.js:52
-#: screens/Inventory/InventoryList/InventoryList.js:187
-#: screens/Inventory/InventoryList/InventoryListItem.js:117
-msgid "Smart Inventory"
-msgstr "Smart-inventaris"
-
-#: screens/Inventory/SmartInventory.js:94
-msgid "Smart Inventory not found."
-msgstr "Smart-inventaris niet gevonden."
-
-#: components/Lookup/HostFilterLookup.js:345
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:116
-msgid "Smart host filter"
-msgstr "Smart-hostfilter"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:105
-msgid "Smart inventory"
-msgstr "Smart-inventaris"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:32
-#: components/LaunchPrompt/steps/PreviewStep.js:58
-msgid "Some of the previous step(s) have errors"
-msgstr "Sommige van de vorige stappen bevatten fouten"
-
-#: screens/Host/HostList/SmartInventoryButton.js:17
-msgid "Some search modifiers like not__ and __search are not supported in Smart Inventory host filters. Remove these to create a new Smart Inventory with this filter."
-msgstr "Sommige zoekmodifiers zoals not__ en __search worden niet ondersteund in Smart Inventory hostfilters. Verwijder deze om een nieuwe Smart Inventory te maken met dit filter."
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.js:39
-msgid "Something went wrong with the request to test this credential and metadata."
-msgstr "Er is iets misgegaan met het verzoek om deze toegangsgegevens en metadata te testen."
-
-#: components/ContentError/ContentError.js:37
-msgid "Something went wrong..."
-msgstr "Er is iets misgegaan..."
-
-#: components/Sort/Sort.js:139
-msgid "Sort"
-msgstr "Sorteren"
-
-#: components/JobList/JobListItem.js:169
-#: components/PromptDetail/PromptInventorySourceDetail.js:84
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:199
-#: screens/Inventory/shared/InventorySourceForm.js:130
-#: screens/Job/JobDetail/JobDetail.js:172
-#: screens/Job/JobDetail/JobDetail.js:294
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:93
-msgid "Source"
-msgstr "Bron"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:44
-#: components/PromptDetail/PromptDetail.js:250
-#: components/PromptDetail/PromptJobTemplateDetail.js:147
-#: components/PromptDetail/PromptProjectDetail.js:106
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:89
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:467
-#: screens/Job/JobDetail/JobDetail.js:307
-#: screens/Project/ProjectDetail/ProjectDetail.js:230
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:248
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:133
-#: screens/Template/shared/JobTemplateForm.js:335
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:245
-msgid "Source Control Branch"
-msgstr "Vertakking broncontrole"
-
-#: screens/Project/shared/ProjectSubForms/GitSubForm.js:29
-msgid "Source Control Branch/Tag/Commit"
-msgstr "Vertakking/tag/binding broncontrole"
-
-#: components/PromptDetail/PromptProjectDetail.js:117
-#: screens/Project/ProjectDetail/ProjectDetail.js:256
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:56
-msgid "Source Control Credential"
-msgstr "Toegangsgegevens bronbeheer"
-
-#: components/PromptDetail/PromptProjectDetail.js:111
-#: screens/Project/ProjectDetail/ProjectDetail.js:235
-#: screens/Project/shared/ProjectSubForms/GitSubForm.js:32
-msgid "Source Control Refspec"
-msgstr "Refspec broncontrole"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:195
-msgid "Source Control Revision"
-msgstr "Herziening broncontrole"
-
-#: components/PromptDetail/PromptProjectDetail.js:96
-#: screens/Job/JobDetail/JobDetail.js:273
-#: screens/Project/ProjectDetail/ProjectDetail.js:191
-#: screens/Project/shared/ProjectForm.js:259
-msgid "Source Control Type"
-msgstr "Type broncontrole"
-
-#: components/Lookup/ProjectLookup.js:143
-#: components/PromptDetail/PromptProjectDetail.js:101
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:96
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:165
-#: screens/Project/ProjectDetail/ProjectDetail.js:225
-#: screens/Project/ProjectList/ProjectList.js:205
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:16
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:104
-msgid "Source Control URL"
-msgstr "URL broncontrole"
-
-#: components/JobList/JobList.js:211
-#: components/JobList/JobListItem.js:42
-#: components/Schedule/ScheduleList/ScheduleListItem.js:38
-#: screens/Job/JobDetail/JobDetail.js:65
-msgid "Source Control Update"
-msgstr "Update broncontrole"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:340
-msgid "Source Phone Number"
-msgstr "Brontelefoonnummer"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:176
-msgid "Source Variables"
-msgstr "Bronvariabelen"
-
-#: components/JobList/JobListItem.js:213
-#: screens/Job/JobDetail/JobDetail.js:257
-msgid "Source Workflow Job"
-msgstr "Taak bronworkflow"
-
-#: screens/Template/shared/WorkflowJobTemplateForm.js:177
-msgid "Source control branch"
-msgstr "Vertakking broncontrole"
-
-#: screens/Inventory/shared/InventorySourceForm.js:152
-msgid "Source details"
-msgstr "Broninformatie"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:403
-msgid "Source phone number"
-msgstr "Brontelefoonnummer"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:269
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:21
-msgid "Source variables"
-msgstr "Bronvariabelen"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:97
-msgid "Sourced from a project"
-msgstr "Afkomstig uit een project"
-
-#: screens/Inventory/Inventories.js:84
-#: screens/Inventory/Inventory.js:68
-msgid "Sources"
-msgstr "Bronnen"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:30
-msgid ""
-"Specify HTTP Headers in JSON format. Refer to\n"
-"the Ansible Controller documentation for example syntax."
-msgstr "Specificeer HTTP-koppen in JSON-formaat. Raadpleeg de documentatie van Ansible Tower voor voorbeeldsyntaxis."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:24
-msgid ""
-"Specify a notification color. Acceptable colors are hex\n"
-"color code (example: #3af or #789abc)."
-msgstr "Kies een berichtkleur. Mogelijke kleuren zijn kleuren uit de hexidecimale kleurencode (bijvoorbeeld: #3af of #789abc)."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:26
-msgid "Specify the conditions under which this node should be executed"
-msgstr "Specificeer de voorwaarden waaronder dit knooppunt moet worden uitgevoerd"
-
-#: screens/Job/JobOutput/HostEventModal.js:173
-msgid "Standard Error"
-msgstr "Standaardfout"
-
-#: screens/Job/JobOutput/HostEventModal.js:174
-msgid "Standard error tab"
-msgstr "Tabblad Standaardfout"
-
-#: components/NotificationList/NotificationListItem.js:57
-#: components/NotificationList/NotificationListItem.js:58
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:47
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:53
-msgid "Start"
-msgstr "Starten"
-
-#: components/JobList/JobList.js:247
-#: components/JobList/JobListItem.js:99
-msgid "Start Time"
-msgstr "Starttijd"
-
-#: components/Schedule/shared/DateTimePicker.js:51
-msgid "Start date"
-msgstr "Startdatum"
-
-#: components/Schedule/shared/ScheduleFormFields.js:87
-msgid "Start date/time"
-msgstr "Startdatum/-tijd"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:465
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:105
-msgid "Start message"
-msgstr "Startbericht"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:474
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:114
-msgid "Start message body"
-msgstr "Body startbericht"
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:33
-msgid "Start sync process"
-msgstr "Start het synchronisatieproces"
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:37
-msgid "Start sync source"
-msgstr "Start synchronisatie bron"
-
-#: components/Schedule/shared/DateTimePicker.js:61
-msgid "Start time"
-msgstr "Starttijd"
-
-#: screens/Job/JobDetail/JobDetail.js:220
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:165
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:63
-msgid "Started"
-msgstr "Gestart"
-
-#: components/JobList/JobList.js:224
-#: components/JobList/JobList.js:245
-#: components/JobList/JobListItem.js:95
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:206
-#: screens/InstanceGroup/Instances/InstanceList.js:267
-#: screens/InstanceGroup/Instances/InstanceListItem.js:129
-#: screens/Instances/InstanceDetail/InstanceDetail.js:196
-#: screens/Instances/InstanceList/InstanceList.js:202
-#: screens/Instances/InstanceList/InstanceListItem.js:134
-#: screens/Instances/InstancePeers/InstancePeerList.js:97
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:43
-#: screens/Inventory/InventoryList/InventoryListItem.js:101
-#: screens/Inventory/InventorySources/InventorySourceList.js:212
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:87
-#: screens/Job/JobDetail/JobDetail.js:210
-#: screens/Job/JobOutput/HostEventModal.js:118
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:115
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:179
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:117
-#: screens/Project/ProjectList/ProjectList.js:222
-#: screens/Project/ProjectList/ProjectListItem.js:197
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:61
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:162
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:166
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:66
-msgid "Status"
-msgstr "Status"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:91
-msgid "Stdout"
-msgstr "Stdout"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:37
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:49
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:212
-msgid "Submit"
-msgstr "Indienen"
-
-#: screens/Project/shared/Project.helptext.js:118
-msgid ""
-"Submodules will track the latest commit on\n"
-"their master branch (or other branch specified in\n"
-".gitmodules). If no, submodules will be kept at\n"
-"the revision specified by the main project.\n"
-"This is equivalent to specifying the --remote\n"
-"flag to git submodule update."
-msgstr "Submodules volgen de laatste binding op\n"
-"hun hoofdvertakking (of een andere vertakking die is gespecificeerd in\n"
-".gitmodules). Als dat niet zo is, dan worden de submodules bewaard tijdens de revisie die door het hoofdproject gespecificeerd is.\n"
-"Dit is gelijk aan het specificeren van de vlag --remote bij de update van de git-submodule."
-
-#: screens/Setting/SettingList.js:132
-#: screens/Setting/Settings.js:112
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:136
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:195
-msgid "Subscription"
-msgstr "Abonnement"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:41
-msgid "Subscription Details"
-msgstr "Details abonnement"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:194
-msgid "Subscription Management"
-msgstr "Abonnementenbeheer"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:82
-msgid "Subscription manifest"
-msgstr "Abonnementsmanifest"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:84
-msgid "Subscription selection modal"
-msgstr "Modus Abonnement selecteren"
-
-#: screens/Setting/SettingList.js:137
-msgid "Subscription settings"
-msgstr "Abonnementsinstellingen"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:131
-msgid "Subscription type"
-msgstr "Type abonnement"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:142
-msgid "Subscriptions table"
-msgstr "Tabel Abonnementen"
-
-#: components/Lookup/ProjectLookup.js:137
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:90
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:159
-#: screens/Job/JobDetail/JobDetail.js:76
-#: screens/Project/ProjectList/ProjectList.js:199
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:98
-msgid "Subversion"
-msgstr "Subversie"
-
-#: components/NotificationList/NotificationListItem.js:71
-#: components/NotificationList/NotificationListItem.js:72
-#: components/StatusLabel/StatusLabel.js:41
-msgid "Success"
-msgstr "Geslaagd"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:483
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:123
-msgid "Success message"
-msgstr "Succesbericht"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:492
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:132
-msgid "Success message body"
-msgstr "Body succesbericht"
-
-#: components/JobList/JobList.js:231
-#: components/StatusLabel/StatusLabel.js:43
-#: components/Workflow/WorkflowNodeHelp.js:102
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:96
-#: screens/Dashboard/shared/ChartTooltip.js:59
-msgid "Successful"
-msgstr "Geslaagd"
-
-#: screens/Dashboard/DashboardGraph.js:166
-msgid "Successful jobs"
-msgstr "Succesvolle taken"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:28
-msgid "Successfully Approved"
-msgstr "Succesvol goedgekeurd"
-
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:25
-msgid "Successfully Denied"
-msgstr "Succesvol geweigerd"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:201
-#: screens/Project/ProjectList/ProjectListItem.js:97
-msgid "Successfully copied to clipboard!"
-msgstr "Succesvol gekopieerd naar klembord!"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:255
-msgid "Sun"
-msgstr "Zon"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:83
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:182
-#: components/Schedule/shared/FrequencyDetailSubform.js:260
-#: components/Schedule/shared/FrequencyDetailSubform.js:428
-msgid "Sunday"
-msgstr "Zondag"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:26
-#: screens/Template/Template.js:159
-#: screens/Template/Templates.js:48
-#: screens/Template/WorkflowJobTemplate.js:145
-msgid "Survey"
-msgstr "Vragenlijst"
-
-#: screens/Template/Survey/SurveyToolbar.js:105
-msgid "Survey Disabled"
-msgstr "Enquête uitgeschakeld"
-
-#: screens/Template/Survey/SurveyToolbar.js:104
-msgid "Survey Enabled"
-msgstr "Enquête ingeschakeld"
-
-#: screens/Template/Survey/SurveyReorderModal.js:191
-msgid "Survey Question Order"
-msgstr "Volgorde vragen enquête"
-
-#: screens/Template/Survey/SurveyToolbar.js:102
-msgid "Survey Toggle"
-msgstr "Vragenlijst schakelen"
-
-#: screens/Template/Survey/SurveyReorderModal.js:192
-msgid "Survey preview modal"
-msgstr "Modus Voorbeeld van vragenlijst"
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:120
-#: screens/Inventory/shared/InventorySourceSyncButton.js:41
-#: screens/Project/shared/ProjectSyncButton.js:41
-#: screens/Project/shared/ProjectSyncButton.js:53
-msgid "Sync"
-msgstr "Synchroniseren"
-
-#: screens/Project/ProjectList/ProjectListItem.js:238
-#: screens/Project/shared/ProjectSyncButton.js:37
-#: screens/Project/shared/ProjectSyncButton.js:48
-msgid "Sync Project"
-msgstr "Project synchroniseren"
-
-#: screens/Inventory/InventoryList/InventoryList.js:219
-msgid "Sync Status"
-msgstr "Synchronisatiestatus"
-
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:19
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:29
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:32
-msgid "Sync all"
-msgstr "Alles synchroniseren"
-
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:25
-msgid "Sync all sources"
-msgstr "Alle bronnen synchroniseren"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:236
-msgid "Sync error"
-msgstr "Synchronisatiefout"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:213
-#: screens/Project/ProjectList/ProjectListItem.js:109
-msgid "Sync for revision"
-msgstr "Synchroniseren voor revisie"
-
-#: screens/Project/ProjectList/ProjectListItem.js:122
-msgid "Syncing"
-msgstr "Synchroniseren"
-
-#: screens/Setting/SettingList.js:102
-#: screens/User/UserRoles/UserRolesListItem.js:18
-msgid "System"
-msgstr "Systeem"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:128
-#: screens/User/UserDetail/UserDetail.js:47
-#: screens/User/UserList/UserListItem.js:19
-#: screens/User/UserRoles/UserRolesList.js:127
-#: screens/User/shared/UserForm.js:41
-msgid "System Administrator"
-msgstr "Systeembeheerder"
-
-#: screens/User/UserDetail/UserDetail.js:49
-#: screens/User/UserList/UserListItem.js:21
-#: screens/User/shared/UserForm.js:35
-msgid "System Auditor"
-msgstr "Systeemcontroleur"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:128
-msgid "System Warning"
-msgstr "Systeemwaarschuwing"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:131
-#: screens/User/UserRoles/UserRolesList.js:130
-msgid "System administrators have unrestricted access to all resources."
-msgstr "Systeembeheerders hebben onbeperkte toegang tot alle bronnen."
-
-#: screens/Setting/Settings.js:115
-msgid "TACACS+"
-msgstr "TACACS+"
-
-#: screens/Setting/SettingList.js:81
-msgid "TACACS+ settings"
-msgstr "TACACS+ instellingen"
-
-#: screens/Dashboard/Dashboard.js:117
-#: screens/Job/JobOutput/HostEventModal.js:94
-msgid "Tabs"
-msgstr "Tabbladen"
-
-#: screens/Job/Job.helptext.js:19
-#: screens/Template/shared/JobTemplate.helptext.js:21
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:21
-msgid "Tags are useful when you have a large playbook, and you want to run a specific part of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags."
-msgstr "Tags zijn nuttig wanneer u een groot draaiboek heeft en specifieke delen van het draaiboek of een taak wilt uitvoeren. Gebruik een komma om meerdere tags van elkaar te scheiden. Raadpleeg de documentatie voor meer informatie over het gebruik van tags."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:203
-msgid "Tags for the Annotation"
-msgstr "Tags voor de melding"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:179
-msgid "Tags for the annotation (optional)"
-msgstr "Tags voor de melding (optioneel)"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:248
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:298
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:366
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:252
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:329
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:453
-msgid "Target URL"
-msgstr "Doel-URL"
-
-#: screens/Job/JobOutput/HostEventModal.js:123
-msgid "Task"
-msgstr "Taak"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:90
-msgid "Task Count"
-msgstr "Aantal taken"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:129
-msgid "Task Started"
-msgstr "Taak gestart"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:91
-msgid "Tasks"
-msgstr "Taken"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:13
-msgid "Team"
-msgstr "Team"
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:85
-#: screens/Team/TeamRoles/TeamRolesList.js:144
-msgid "Team Roles"
-msgstr "Teamrollen"
-
-#: screens/Team/Team.js:75
-msgid "Team not found."
-msgstr "Taak niet gevonden."
-
-#: components/AddRole/AddResourceRole.js:188
-#: components/AddRole/AddResourceRole.js:189
-#: routeConfig.js:106
-#: screens/ActivityStream/ActivityStream.js:187
-#: screens/Organization/Organization.js:125
-#: screens/Organization/OrganizationList/OrganizationList.js:145
-#: screens/Organization/OrganizationList/OrganizationListItem.js:66
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:64
-#: screens/Organization/Organizations.js:32
-#: screens/Team/TeamList/TeamList.js:112
-#: screens/Team/TeamList/TeamList.js:166
-#: screens/Team/Teams.js:15
-#: screens/Team/Teams.js:25
-#: screens/User/User.js:70
-#: screens/User/UserTeams/UserTeamList.js:175
-#: screens/User/UserTeams/UserTeamList.js:246
-#: screens/User/Users.js:32
-#: util/getRelatedResourceDeleteDetails.js:174
-msgid "Teams"
-msgstr "Teams"
-
-#: screens/Setting/Jobs/JobsEdit/JobsEdit.js:130
-msgid "Template"
-msgstr "Sjabloon"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:115
-#: components/TemplateList/TemplateList.js:133
-msgid "Template copied successfully"
-msgstr "Sjabloon gekopieerd"
-
-#: screens/Template/Template.js:175
-#: screens/Template/WorkflowJobTemplate.js:175
-msgid "Template not found."
-msgstr "Sjabloon niet gevonden."
-
-#: components/TemplateList/TemplateList.js:200
-#: components/TemplateList/TemplateList.js:263
-#: routeConfig.js:65
-#: screens/ActivityStream/ActivityStream.js:164
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:70
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:83
-#: screens/Template/Templates.js:17
-#: util/getRelatedResourceDeleteDetails.js:218
-#: util/getRelatedResourceDeleteDetails.js:275
-msgid "Templates"
-msgstr "Sjablonen"
-
-#: screens/Credential/shared/CredentialForm.js:331
-#: screens/Credential/shared/CredentialForm.js:337
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:80
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:426
-msgid "Test"
-msgstr "Test"
-
-#: screens/Credential/shared/ExternalTestModal.js:77
-msgid "Test External Credential"
-msgstr "Test externe inloggegevens"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:128
-msgid "Test Notification"
-msgstr "Testbericht"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:125
-msgid "Test notification"
-msgstr "Testbericht"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.js:44
-msgid "Test passed"
-msgstr "Test geslaagd"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:80
-#: screens/Template/Survey/SurveyReorderModal.js:181
-msgid "Text"
-msgstr "Tekst"
-
-#: screens/Template/Survey/SurveyReorderModal.js:135
-msgid "Text Area"
-msgstr "Tekstgebied"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:81
-msgid "Textarea"
-msgstr "Tekstgebied"
-
-#: components/Lookup/Lookup.js:63
-msgid "That value was not found. Please enter or select a valid value."
-msgstr "De waarde is niet gevonden. Voer een geldige waarde in of selecteer er een."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:398
-msgid "The"
-msgstr "De"
-
-#: screens/Application/shared/Application.helptext.js:4
-msgid "The Grant type the user must use to acquire tokens for this application"
-msgstr "Het type toekenning dat de gebruiker moet gebruiken om tokens te verkrijgen voor deze toepassing"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:128
-msgid "The Instance Groups for this Organization to run on."
-msgstr "Selecteer de instantiegroepen waar de organisatie op uitgevoerd wordt."
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:219
-msgid "The Instance Groups to which this instance belongs."
-msgstr "De Instance Groups waartoe deze instantie behoort."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:6
-msgid ""
-"The amount of time (in seconds) before the email\n"
-"notification stops trying to reach the host and times out. Ranges\n"
-"from 1 to 120 seconds."
-msgstr "De tijd (in seconden) voordat de e-mailmelding de host probeert te bereiken en een time-out oplevert. Varieert van 1 tot 120 seconden."
-
-#: screens/Job/Job.helptext.js:17
-#: screens/Template/shared/JobTemplate.helptext.js:18
-msgid "The amount of time (in seconds) to run before the job is canceled. Defaults to 0 for no job timeout."
-msgstr "De tijd (in seconden) die het heeft geduurd voordat de taak werd geannuleerd. Standaard 0 voor geen taak time-out."
-
-#: screens/User/shared/User.helptext.js:4
-msgid "The application that this token belongs to, or leave this field empty to create a Personal Access Token."
-msgstr "Selecteer de toepassing waartoe dit token zal behoren, of laat dit veld leeg om een persoonlijk toegangstoken aan te maken."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:9
-msgid ""
-"The base URL of the Grafana server - the\n"
-"/api/annotations endpoint will be added automatically to the base\n"
-"Grafana URL."
-msgstr "De basis-URL van de Grafana-server - het /api/annotations-eindpunt wordt automatisch toegevoegd aan de basis-URL voor Grafana."
-
-#: screens/Template/shared/JobTemplate.helptext.js:9
-msgid "The container image to be used for execution."
-msgstr "De containerimage die gebruikt moet worden voor de uitvoering."
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:109
-msgid ""
-"The execution environment that will be used for jobs\n"
-"inside of this organization. This will be used a fallback when\n"
-"an execution environment has not been explicitly assigned at the\n"
-"project, job template or workflow level."
-msgstr "De uitvoeringsomgeving die zal worden gebruikt voor taken binnen deze organisatie. Dit wordt gebruikt als terugvalpunt wanneer er geen uitvoeringsomgeving expliciet is toegewezen op project-, taaksjabloon- of workflowniveau."
-
-#: screens/Organization/shared/OrganizationForm.js:93
-msgid "The execution environment that will be used for jobs inside of this organization. This will be used a fallback when an execution environment has not been explicitly assigned at the project, job template or workflow level."
-msgstr "De uitvoeringsomgeving die zal worden gebruikt voor taken binnen deze organisatie. Dit wordt gebruikt als terugvalpunt wanneer er geen uitvoeringsomgeving expliciet is toegewezen op project-, taaksjabloon- of workflowniveau."
-
-#: screens/Project/shared/Project.helptext.js:5
-msgid "The execution environment that will be used for jobs that use this project. This will be used as fallback when an execution environment has not been explicitly assigned at the job template or workflow level."
-msgstr "De uitvoeringsomgeving die zal worden gebruikt voor taken die dit project gebruiken. Dit wordt gebruikt als terugvalpunt wanneer er geen uitvoeringsomgeving expliciet is toegewezen op taaksjabloon- of workflowniveau."
-
-#: screens/Job/Job.helptext.js:9
-#: screens/Template/shared/JobTemplate.helptext.js:10
-msgid "The execution environment that will be used when launching this job template. The resolved execution environment can be overridden by explicitly assigning a different one to this job template."
-msgstr "De uitvoeringsomgeving die zal worden gebruikt bij het starten van\n"
-"dit taaksjabloon. De geselecteerde uitvoeringsomgeving kan worden opgeheven door\n"
-"door expliciet een andere omgeving aan dit taaksjabloon toe te wijzen."
-
-#: screens/Project/shared/Project.helptext.js:93
-msgid ""
-"The first fetches all references. The second\n"
-"fetches the Github pull request number 62, in this example\n"
-"the branch needs to be \"pull/62/head\"."
-msgstr "De eerste haalt alle referenties op. De tweede haalt het Github pullverzoek nummer 62 op, in dit voorbeeld moet de vertakking `pull/62/head` zijn."
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:7
-msgid "The full image location, including the container registry, image name, and version tag."
-msgstr "De volledige imagelocatie, inclusief het containerregister, de imagenaam en de versietag."
-
-#: screens/Inventory/shared/Inventory.helptext.js:191
-msgid ""
-"The inventory file\n"
-"to be synced by this source. You can select from\n"
-"the dropdown or enter a file within the input."
-msgstr "Selecteer het inventarisbestand dat gesynchroniseerd moet worden door deze bron. U kunt kiezen uit het uitklapbare menu of een bestand invoeren in het invoerveld."
-
-#: screens/Host/HostDetail/HostDetail.js:79
-msgid "The inventory that this host belongs to."
-msgstr "Selecteer de inventaris waartoe deze host zal behoren."
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:141
-msgid "The last {dayOfWeek}"
-msgstr "De laatste {dayOfWeek}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:200
-msgid "The last {weekday} of {month}"
-msgstr "De laatste {weekday} van {month}"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:100
-msgid ""
-"The maximum number of hosts allowed to be managed by\n"
-"this organization. Value defaults to 0 which means no limit.\n"
-"Refer to the Ansible documentation for more details."
-msgstr "Maximumaantal hosts dat beheerd mag worden door deze organisatie. De standaardwaarde is 0, wat betekent dat er geen limiet is. Raadpleeg de Ansible-documentatie voor meer informatie."
-
-#: screens/Organization/shared/OrganizationForm.js:72
-msgid ""
-"The maximum number of hosts allowed to be managed by this organization.\n"
-"Value defaults to 0 which means no limit. Refer to the Ansible\n"
-"documentation for more details."
-msgstr "Maximumaantal hosts dat beheerd mag worden door deze organisatie. De standaardwaarde is 0, wat betekent dat er geen limiet is. Raadpleeg de Ansible-documentatie voor meer informatie."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:26
-msgid ""
-"The number associated with the \"Messaging\n"
-"Service\" in Twilio with the format +18005550199."
-msgstr "Voer het telefoonnummer in dat hoort bij de 'Berichtenservice' in Twilio in de indeling +18005550199."
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:69
-msgid "The number of hosts you have automated against is below your subscription count."
-msgstr "Het aantal hosts waartegen u geautomatiseerd heeft is lager dan uw abonnement."
-
-#: screens/Job/Job.helptext.js:25
-#: screens/Template/shared/JobTemplate.helptext.js:48
-msgid "The number of parallel or simultaneous processes to use while executing the playbook. An empty value, or a value less than 1 will use the Ansible default which is usually 5. The default number of forks can be overwritten with a change to"
-msgstr "Het aantal parallelle of gelijktijdige processen dat tijdens de uitvoering van het draaiboek gebruikt wordt. Een lege waarde, of een waarde minder dan 1 zal de Ansible-standaard gebruiken die meestal 5 is. Het standaard aantal vorken kan overgeschreven worden met een wijziging naar"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:164
-msgid "The number of parallel or simultaneous processes to use while executing the playbook. Inputting no value will use the default value from the ansible configuration file. You can find more information"
-msgstr "Het aantal parallelle of gelijktijdige processen dat gebruikt wordt bij het uitvoeren van het draaiboek. Als u geen waarde invoert, wordt de standaardwaarde van het Ansible-configuratiebestand gebruikt. U vindt meer informatie"
-
-#: components/ContentError/ContentError.js:41
-#: screens/Job/Job.js:161
-msgid "The page you requested could not be found."
-msgstr "De door u opgevraagde pagina kan niet worden gevonden."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:144
-msgid "The pattern used to target hosts in the inventory. Leaving the field blank, all, and * will all target all hosts in the inventory. You can find more information about Ansible's host patterns"
-msgstr "Het patroon dat gebruikt wordt om hosts in de inventaris te targeten. Door het veld leeg te laten, worden met alle en * alle hosts in de inventaris getarget. U kunt meer informatie vinden over hostpatronen van Ansible"
-
-#: screens/Job/Job.helptext.js:7
-msgid "The project containing the playbook this job will execute."
-msgstr "Selecteer het project dat het draaiboek bevat waarvan u wilt dat deze taak hem uitvoert."
-
-#: screens/Job/Job.helptext.js:8
-msgid "The project from which this inventory update is sourced."
-msgstr "Het project waarvan deze bijgewerkte inventaris afkomstig is."
-
-#: screens/Project/ProjectList/ProjectListItem.js:120
-msgid "The project is currently syncing and the revision will be available after the sync is complete."
-msgstr "Het project wordt momenteel gesynchroniseerd en de revisie zal beschikbaar zijn nadat de synchronisatie is voltooid."
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:211
-#: screens/Project/ProjectList/ProjectListItem.js:107
-msgid "The project must be synced before a revision is available."
-msgstr "Het project moet zijn gesynchroniseerd voordat een revisie beschikbaar is."
-
-#: screens/Project/ProjectList/ProjectListItem.js:130
-msgid "The project revision is currently out of date. Please refresh to fetch the most recent revision."
-msgstr "De revisie van het project is momenteel verouderd. Vernieuw om de meest recente revisie op te halen."
-
-#: components/Workflow/WorkflowNodeHelp.js:138
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:131
-msgid "The resource associated with this node has been deleted."
-msgstr "De aan dit knooppunt gekoppelde bron is verwijderd."
-
-#: screens/Job/JobOutput/EmptyOutput.js:31
-msgid "The search filter did not produce any results…"
-msgstr "De zoekfilter leverde geen resultaten op…"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:180
-msgid ""
-"The suggested format for variable names is lowercase and\n"
-"underscore-separated (for example, foo_bar, user_id, host_name,\n"
-"etc.). Variable names with spaces are not allowed."
-msgstr "De voorgestelde indeling voor namen van variabelen: kleine letters en gescheiden door middel van een underscore (bijvoorbeeld foo_bar, user_id, host_name etc.) De naam van een variabele mag geen spaties bevatten."
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:50
-msgid ""
-"There are no available playbook directories in {project_base_dir}.\n"
-"Either that directory is empty, or all of the contents are already\n"
-"assigned to other projects. Create a new directory there and make\n"
-"sure the playbook files can be read by the \"awx\" system user,\n"
-"or have {brandName} directly retrieve your playbooks from\n"
-"source control using the Source Control Type option above."
-msgstr "Er zijn geen draaiboekmappen in {project_base_dir} beschikbaar.\n"
-"Die map leeg of alle inhoud ervan is al\n"
-"toegewezen aan andere projecten. Maak daar een nieuwe directory en zorg ervoor dat de draaiboekbestanden kunnen worden gelezen door de 'awx'-systeemgebruiker,\n"
-"of laat {brandName} uw draaiboeken direct ophalen uit broncontrole met behulp van de optie Type broncontrole hierboven."
-
-#: screens/Template/Survey/MultipleChoiceField.js:34
-msgid "There must be a value in at least one input"
-msgstr "Er moet een waarde zijn in ten minste één input"
-
-#: screens/Login/Login.js:155
-msgid "There was a problem logging in. Please try again."
-msgstr "Er is een probleem met inloggen. Probeer het opnieuw."
-
-#: components/ContentError/ContentError.js:42
-msgid "There was an error loading this content. Please reload the page."
-msgstr "Er is een fout opgetreden bij het laden van deze inhoud. Laad de pagina opnieuw."
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:56
-msgid "There was an error parsing the file. Please check the file formatting and try again."
-msgstr "Er is een fout opgetreden bij het parseren van het bestand. Controleer de opmaak van het bestand en probeer het opnieuw."
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:713
-msgid "There was an error saving the workflow."
-msgstr "Er is een fout opgetreden bij het opslaan van de workflow."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:69
-msgid "These are the modules that {brandName} supports running commands against."
-msgstr "Dit zijn de modules waar {brandName} commando's tegen kan uitvoeren."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:129
-msgid "These are the verbosity levels for standard out of the command run that are supported."
-msgstr "Dit zijn de verbositeitsniveaus voor standaardoutput van de commando-uitvoering die worden ondersteund."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:122
-#: screens/Job/Job.helptext.js:43
-msgid "These arguments are used with the specified module."
-msgstr "Deze argumenten worden gebruikt met de gespecificeerde module."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:111
-msgid "These arguments are used with the specified module. You can find information about {0} by clicking"
-msgstr "Deze argumenten worden gebruikt met de gespecificeerde module. U kunt informatie over {0} vinden door te klikken"
-
-#: screens/Job/Job.helptext.js:33
-msgid "These arguments are used with the specified module. You can find information about {moduleName} by clicking"
-msgstr "Deze argumenten worden gebruikt met de gespecificeerde module. U kunt informatie over {moduleName} vinden door te klikken"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:410
-msgid "Third"
-msgstr "Derde"
-
-#: screens/Template/shared/JobTemplateForm.js:157
-msgid "This Project needs to be updated"
-msgstr "Dit project moet worden bijgewerkt"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:286
-#: screens/Template/Survey/SurveyList.js:82
-msgid "This action will delete the following:"
-msgstr "Met deze actie wordt het volgende verwijderd:"
-
-#: screens/User/UserTeams/UserTeamList.js:217
-msgid "This action will disassociate all roles for this user from the selected teams."
-msgstr "Deze actie ontkoppelt alle rollen voor deze gebruiker van de geselecteerde teams."
-
-#: screens/Team/TeamRoles/TeamRolesList.js:236
-#: screens/User/UserRoles/UserRolesList.js:232
-msgid "This action will disassociate the following role from {0}:"
-msgstr "Deze actie ontkoppelt de volgende rol van {0}:"
-
-#: components/DisassociateButton/DisassociateButton.js:148
-msgid "This action will disassociate the following:"
-msgstr "Deze actie ontkoppelt het volgende:"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:178
-msgid "This action will remove the following instances:"
-msgstr "Deze actie zal de volgende instanties verwijderen:"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:113
-msgid "This container group is currently being by other resources. Are you sure you want to delete it?"
-msgstr "Deze containergroep wordt momenteel door andere bronnen gebruikt. Weet u zeker dat u hem wilt verwijderen?"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:304
-msgid "This credential is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "Deze toegangsgegevens worden momenteel door andere bronnen gebruikt. Weet u zeker dat u ze wilt verwijderen?"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:121
-msgid "This credential type is currently being used by some credentials and cannot be deleted"
-msgstr "Dit type toegangsgegevens wordt momenteel gebruikt door sommige toegangsgegevens en kan niet worden verwijderd"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:74
-msgid ""
-"This data is used to enhance\n"
-"future releases of the Software and to provide\n"
-"Automation Analytics."
-msgstr "Deze gegevens worden gebruikt om\n"
-"toekomstige versies van de Software te verbeteren en om\n"
-"Automatiseringsanalyse te bieden."
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:62
-msgid ""
-"This data is used to enhance\n"
-"future releases of the Tower Software and help\n"
-"streamline customer experience and success."
-msgstr "Deze gegevens worden gebruikt om toekomstige versies van de Tower-software en de ervaring en uitkomst voor klanten te verbeteren."
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:132
-msgid "This execution environment is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "Deze uitvoeringsomgeving wordt momenteel gebruikt door andere bronnen. Weet u zeker dat u deze wilt verwijderen?"
-
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:74
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:79
-msgid "This feature is deprecated and will be removed in a future release."
-msgstr "Deze functie is afgeschaft en zal worden verwijderd in een toekomstige versie."
-
-#: screens/Inventory/shared/Inventory.helptext.js:155
-msgid "This field is ignored unless an Enabled Variable is set. If the enabled variable matches this value, the host will be enabled on import."
-msgstr "Dit veld wordt genegeerd, tenzij er een Ingeschakelde variabele is ingesteld. Als de ingeschakelde variabele overeenkomt met deze waarde, wordt de host bij het importeren ingeschakeld."
-
-#: components/AdHocCommands/useAdHocCredentialPasswordStep.js:44
-#: components/LaunchPrompt/steps/useCredentialPasswordsStep.js:50
-msgid "This field may not be blank"
-msgstr "Dit veld mag niet leeg zijn"
-
-#: util/validators.js:127
-msgid "This field must be a number"
-msgstr "Dit veld moet een getal zijn"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:107
-msgid "This field must be a number and have a value between {0} and {1}"
-msgstr "Dit veld moet een getal zijn en een waarde hebben tussen {0} en {1}"
-
-#: util/validators.js:67
-msgid "This field must be a number and have a value between {min} and {max}"
-msgstr "Dit veld moet een getal zijn en een waarde hebben tussen {min} en {max}"
-
-#: util/validators.js:64
-msgid "This field must be a number and have a value greater than {min}"
-msgstr "Dit veld moet een getal zijn en een waarde hebben die hoger is dan {min}"
-
-#: util/validators.js:61
-msgid "This field must be a number and have a value less than {max}"
-msgstr "Dit veld moet een getal zijn en een waarde hebben die lager is dan {max}"
-
-#: util/validators.js:184
-msgid "This field must be a regular expression"
-msgstr "Dit veld moet een reguliere expressie zijn"
-
-#: util/validators.js:111
-#: util/validators.js:194
-msgid "This field must be an integer"
-msgstr "Dit veld moet een geheel getal zijn"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:99
-msgid "This field must be at least {0} characters"
-msgstr "Dit veld moet uit ten minste {0} tekens bestaan"
-
-#: util/validators.js:52
-msgid "This field must be at least {min} characters"
-msgstr "Dit veld moet uit ten minste {min} tekens bestaan"
-
-#: util/validators.js:197
-msgid "This field must be greater than 0"
-msgstr "Dit veld moet groter zijn dan 0"
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:52
-#: components/LaunchPrompt/steps/useSurveyStep.js:111
-#: screens/Template/shared/JobTemplateForm.js:154
-#: screens/User/shared/UserForm.js:92
-#: screens/User/shared/UserForm.js:103
-#: util/validators.js:5
-#: util/validators.js:76
-msgid "This field must not be blank"
-msgstr "Dit veld mag niet leeg zijn"
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:46
-msgid "This field must not be blank."
-msgstr "Dit veld mag niet leeg zijn"
-
-#: util/validators.js:101
-msgid "This field must not contain spaces"
-msgstr "Dit veld mag geen spaties bevatten"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:102
-msgid "This field must not exceed {0} characters"
-msgstr "Dit veld mag niet langer zijn dan {0} tekens"
-
-#: util/validators.js:43
-msgid "This field must not exceed {max} characters"
-msgstr "Dit veld mag niet langer zijn dan {max} tekens"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:51
-msgid "This field will be retrieved from an external secret management system using the specified credential."
-msgstr "Dit veld wordt met behulp van de opgegeven referentie opgehaald uit een extern geheimbeheersysteem."
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:82
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:89
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:104
-msgid "This has already been acted on"
-msgstr "Hieraan is reeds gevolg gegeven"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:125
-msgid "This instance group is currently being by other resources. Are you sure you want to delete it?"
-msgstr "Deze instantiegroep wordt momenteel door andere bronnen gebruikt. Weet u zeker dat u hem wilt verwijderen?"
-
-#: components/LaunchPrompt/steps/useInventoryStep.js:59
-msgid "This inventory is applied to all workflow nodes within this workflow ({0}) that prompt for an inventory."
-msgstr "Deze inventaris wordt toegepast op alle workflowknooppunten binnen deze workflow ({0}) die vragen naar een inventaris."
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:199
-msgid "This inventory is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "Deze inventaris wordt momenteel door andere bronnen gebruikt. Weet u zeker dat u hem wilt verwijderen?"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:313
-msgid "This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?"
-msgstr "Deze inventarisbron wordt momenteel door andere bronnen gebruikt die erop vertrouwen. Weet u zeker dat u hem wilt verwijderen?"
-
-#: screens/Application/Applications.js:77
-msgid "This is the only time the client secret will be shown."
-msgstr "Dit is de enige keer dat het cliëntgeheim wordt getoond."
-
-#: screens/User/UserTokens/UserTokens.js:59
-msgid "This is the only time the token value and associated refresh token value will be shown."
-msgstr "Dit is de enige keer dat de tokenwaarde en de bijbehorende ververste tokenwaarde worden getoond."
-
-#: screens/Job/JobOutput/EmptyOutput.js:37
-msgid "This job failed and has no output."
-msgstr "Deze opdracht is mislukt en heeft geen uitvoer."
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:543
-msgid "This job template is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "Deze taaksjabloon wordt momenteel door andere bronnen gebruikt. Weet u zeker dat u hem wilt verwijderen?"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:197
-msgid "This organization is currently being by other resources. Are you sure you want to delete it?"
-msgstr "Deze organisatie wordt momenteel door andere bronnen gebruikt. Weet u zeker dat u haar wilt verwijderen?"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:338
-msgid "This project is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "Dit project wordt momenteel gebruikt door andere bronnen. Weet u zeker dat u het wilt verwijderen?"
-
-#: screens/Project/shared/Project.helptext.js:59
-msgid "This project is currently on sync and cannot be clicked until sync process completed"
-msgstr "Dit project wordt momenteel gesynchroniseerd en er kan pas op worden geklikt nadat het synchronisatieproces is voltooid"
-
-#: components/Schedule/shared/ScheduleForm.js:460
-msgid "This schedule has no occurrences due to the selected exceptions."
-msgstr "Dit schema heeft geen voorvallen vanwege de geselecteerde uitzonderingen."
-
-#: components/Schedule/ScheduleList/ScheduleList.js:122
-msgid "This schedule is missing an Inventory"
-msgstr "In dit schema ontbreekt een Inventaris"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:147
-msgid "This schedule is missing required survey values"
-msgstr "In dit schema ontbreken de vereiste vragenlijstwaarden"
-
-#: components/Schedule/shared/UnsupportedScheduleForm.js:12
-msgid ""
-"This schedule uses complex rules that are not supported in the\n"
-"UI. Please use the API to manage this schedule."
-msgstr "Dit schema gebruikt complexe regels die niet worden ondersteund in de\n"
-"UI. Gebruik de API om deze agenda te beheren."
-
-#: components/LaunchPrompt/steps/StepName.js:26
-msgid "This step contains errors"
-msgstr "Deze stap bevat fouten"
-
-#: screens/User/shared/UserForm.js:150
-msgid "This value does not match the password you entered previously. Please confirm that password."
-msgstr "Deze waarde komt niet overeen met het wachtwoord dat u eerder ingevoerd heeft. Bevestig dat wachtwoord."
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:106
-msgid "This will cancel all subsequent nodes in this workflow"
-msgstr "Dit annuleert alle volgende knooppunten in deze werkstroom."
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:328
-msgid "This will cancel all subsequent nodes in this workflow."
-msgstr "Hierdoor worden alle volgende knooppunten in deze werkstroom geannuleerd."
-
-#: screens/Setting/shared/RevertAllAlert.js:36
-msgid ""
-"This will revert all configuration values on this page to\n"
-"their factory defaults. Are you sure you want to proceed?"
-msgstr "Hiermee worden alle configuratiewaarden op deze pagina teruggezet op de fabrieksinstellingen. Weet u zeker dat u verder wilt gaan?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:40
-msgid "This workflow does not have any nodes configured."
-msgstr "Er zijn voor deze workflow geen knooppunten geconfigureerd."
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:43
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:35
-msgid "This workflow has already been acted on"
-msgstr "Deze workflow is reeds in gang gezet"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:267
-msgid "This workflow job template is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "Deze sjabloon voor workflowtaken wordt momenteel gebruikt door andere bronnen. Weet u zeker dat u hem wilt verwijderen?"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:299
-msgid "Thu"
-msgstr "Do"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:80
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:186
-#: components/Schedule/shared/FrequencyDetailSubform.js:304
-#: components/Schedule/shared/FrequencyDetailSubform.js:448
-msgid "Thursday"
-msgstr "Donderdag"
-
-#: screens/ActivityStream/ActivityStream.js:249
-#: screens/ActivityStream/ActivityStream.js:261
-#: screens/ActivityStream/ActivityStreamDetailButton.js:41
-#: screens/ActivityStream/ActivityStreamListItem.js:42
-msgid "Time"
-msgstr "Tijd"
-
-#: screens/Project/shared/Project.helptext.js:128
-msgid ""
-"Time in seconds to consider a project\n"
-"to be current. During job runs and callbacks the task\n"
-"system will evaluate the timestamp of the latest project\n"
-"update. If it is older than Cache Timeout, it is not\n"
-"considered current, and a new project update will be\n"
-"performed."
-msgstr "Tijd in seconden waarmee een project actueel genoemd kan worden. Tijdens taken in uitvoering en terugkoppelingen wil het taaksysteem de tijdstempel van de meest recente projectupdate bekijken. Indien dit ouder is dan de Cache-timeout wordt het project niet gezien als actueel en moet er een nieuwe projectupdate uitgevoerd worden."
-
-#: screens/Inventory/shared/Inventory.helptext.js:147
-msgid ""
-"Time in seconds to consider an inventory sync\n"
-"to be current. During job runs and callbacks the task system will\n"
-"evaluate the timestamp of the latest sync. If it is older than\n"
-"Cache Timeout, it is not considered current, and a new\n"
-"inventory sync will be performed."
-msgstr "Tijd in seconden waarmee een inventarissynchronisatie actueel genoemd kan worden. Tijdens taken in uitvoering en terugkoppelingen zal het taaksysteem de tijdstempel van de meest recente synchronisatie bekijken. Indien dit ouder is dan de Cache-timeout wordt het project niet gezien als actueel en moet er een nieuwe inventarissynchronisatie uitgevoerd worden."
-
-#: components/StatusLabel/StatusLabel.js:51
-msgid "Timed out"
-msgstr "Er is een time-out opgetreden"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:86
-#: components/PromptDetail/PromptDetail.js:136
-#: components/PromptDetail/PromptDetail.js:355
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:484
-#: screens/Job/JobDetail/JobDetail.js:397
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:170
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:114
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:277
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:186
-#: screens/Template/shared/JobTemplateForm.js:475
-msgid "Timeout"
-msgstr "Time-out"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:193
-msgid "Timeout minutes"
-msgstr "Time-out minuten"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:207
-msgid "Timeout seconds"
-msgstr "Time-out seconden"
-
-#: screens/Host/HostList/SmartInventoryButton.js:20
-msgid "To create a smart inventory using ansible facts, go to the smart inventory screen."
-msgstr "Om een smart-inventaris aan te maken via ansible-feiten, gaat u naar het scherm smart-inventaris."
-
-#: screens/Template/Survey/SurveyReorderModal.js:194
-msgid "To reorder the survey questions drag and drop them in the desired location."
-msgstr "Om de enquêtevragen te herordenen, sleept u ze naar de gewenste locatie."
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:106
-msgid "Toggle Legend"
-msgstr "Legenda wisselen"
-
-#: components/FormField/PasswordInput.js:39
-msgid "Toggle Password"
-msgstr "Wachtwoord wisselen"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:116
-msgid "Toggle Tools"
-msgstr "Gereedschap wisselen"
-
-#: components/HostToggle/HostToggle.js:70
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:56
-msgid "Toggle host"
-msgstr "Host wisselen"
-
-#: components/InstanceToggle/InstanceToggle.js:61
-msgid "Toggle instance"
-msgstr "Instantie wisselen"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:80
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:82
-#: screens/TopologyView/Header.js:99
-msgid "Toggle legend"
-msgstr "Legenda wisselen"
-
-#: components/NotificationList/NotificationListItem.js:50
-msgid "Toggle notification approvals"
-msgstr "Berichtgoedkeuringen wisselen"
-
-#: components/NotificationList/NotificationListItem.js:92
-msgid "Toggle notification failure"
-msgstr "Berichtstoring wisselen"
-
-#: components/NotificationList/NotificationListItem.js:64
-msgid "Toggle notification start"
-msgstr "Berichtstart wisselen"
-
-#: components/NotificationList/NotificationListItem.js:78
-msgid "Toggle notification success"
-msgstr "Berichtsucces wisselen"
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:66
-msgid "Toggle schedule"
-msgstr "Schema wisselen"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:92
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:94
-msgid "Toggle tools"
-msgstr "Gereedschap wisselen"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:373
-#: screens/User/UserTokens/UserTokens.js:64
-msgid "Token"
-msgstr "Token"
-
-#: screens/User/UserTokens/UserTokens.js:50
-#: screens/User/UserTokens/UserTokens.js:53
-msgid "Token information"
-msgstr "Tokeninformatie"
-
-#: screens/User/UserToken/UserToken.js:73
-msgid "Token not found."
-msgstr "Token niet gevonden."
-
-#: screens/Application/Application/Application.js:80
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:105
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:128
-#: screens/Application/Applications.js:40
-#: screens/User/User.js:76
-#: screens/User/UserTokenList/UserTokenList.js:118
-#: screens/User/Users.js:34
-msgid "Tokens"
-msgstr "Tokens"
-
-#: components/Workflow/WorkflowTools.js:83
-msgid "Tools"
-msgstr "Gereedschap"
-
-#: routeConfig.js:152
-#: screens/TopologyView/TopologyView.js:40
-msgid "Topology View"
-msgstr "Topologie-weergave"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:218
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:197
-#: screens/InstanceGroup/Instances/InstanceListItem.js:199
-#: screens/Instances/InstanceDetail/InstanceDetail.js:213
-#: screens/Instances/InstanceList/InstanceListItem.js:214
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:78
-msgid "Total Jobs"
-msgstr "Totale taken"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:76
-msgid "Total Nodes"
-msgstr "Totaalaantal knooppunten"
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:103
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:119
-msgid "Total hosts"
-msgstr "Totaal gastheren"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:72
-msgid "Total jobs"
-msgstr "Totale taken"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:87
-msgid "Track submodules"
-msgstr "Submodules tracken"
-
-#: components/PromptDetail/PromptProjectDetail.js:56
-#: screens/Project/ProjectDetail/ProjectDetail.js:109
-msgid "Track submodules latest commit on branch"
-msgstr "Submodules laatste binding op vertakking tracken"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:141
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:168
-msgid "Trial"
-msgstr "Proefperiode"
-
-#: components/JobList/JobListItem.js:319
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:66
-#: screens/Job/JobDetail/JobDetail.js:383
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:210
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:240
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:270
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:315
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:373
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:142
-msgid "True"
-msgstr "True"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:277
-msgid "Tue"
-msgstr "Di"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:78
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:184
-#: components/Schedule/shared/FrequencyDetailSubform.js:282
-#: components/Schedule/shared/FrequencyDetailSubform.js:438
-msgid "Tuesday"
-msgstr "Dinsdag"
-
-#: components/NotificationList/NotificationList.js:201
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:142
-msgid "Twilio"
-msgstr "Twilio"
-
-#: components/JobList/JobList.js:246
-#: components/JobList/JobListItem.js:98
-#: components/Lookup/ProjectLookup.js:132
-#: components/NotificationList/NotificationList.js:219
-#: components/NotificationList/NotificationListItem.js:33
-#: components/PromptDetail/PromptDetail.js:124
-#: components/RelatedTemplateList/RelatedTemplateList.js:187
-#: components/TemplateList/TemplateList.js:214
-#: components/TemplateList/TemplateList.js:243
-#: components/TemplateList/TemplateListItem.js:184
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:85
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:154
-#: components/Workflow/WorkflowNodeHelp.js:160
-#: components/Workflow/WorkflowNodeHelp.js:196
-#: screens/Credential/CredentialList/CredentialList.js:165
-#: screens/Credential/CredentialList/CredentialListItem.js:63
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:94
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:116
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:17
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:46
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:54
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:195
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:66
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:94
-#: screens/Inventory/InventoryList/InventoryList.js:220
-#: screens/Inventory/InventoryList/InventoryListItem.js:116
-#: screens/Inventory/InventorySources/InventorySourceList.js:213
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:100
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:105
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:180
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:120
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:68
-#: screens/Project/ProjectList/ProjectList.js:194
-#: screens/Project/ProjectList/ProjectList.js:223
-#: screens/Project/ProjectList/ProjectListItem.js:218
-#: screens/Team/TeamRoles/TeamRoleListItem.js:17
-#: screens/Team/TeamRoles/TeamRolesList.js:181
-#: screens/Template/Survey/SurveyList.js:103
-#: screens/Template/Survey/SurveyList.js:103
-#: screens/Template/Survey/SurveyListItem.js:60
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:93
-#: screens/User/UserDetail/UserDetail.js:75
-#: screens/User/UserRoles/UserRolesList.js:156
-#: screens/User/UserRoles/UserRolesListItem.js:21
-msgid "Type"
-msgstr "Soort"
-
-#: screens/Credential/shared/TypeInputsSubForm.js:25
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:47
-#: screens/Project/shared/ProjectForm.js:298
-msgid "Type Details"
-msgstr "Soortdetails"
-
-#: screens/Template/Survey/MultipleChoiceField.js:56
-msgid ""
-"Type answer then click checkbox on right to select answer as\n"
-"default."
-msgstr "Typ het antwoord en klik dan op het selectievakje rechts om het antwoord als standaard te selecteren."
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:50
-msgid "UTC"
-msgstr "UTC"
-
-#: components/HostForm/HostForm.js:62
-msgid "Unable to change inventory on a host"
-msgstr "Kan inventaris op een host niet wijzigen"
-
-#: screens/Project/ProjectList/ProjectListItem.js:211
-msgid "Unable to load last job update"
-msgstr "Kan laatste taakupdate niet laden"
-
-#: components/StatusLabel/StatusLabel.js:61
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:260
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:87
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:46
-#: screens/InstanceGroup/Instances/InstanceListItem.js:78
-#: screens/Instances/InstanceDetail/InstanceDetail.js:306
-#: screens/Instances/InstanceList/InstanceListItem.js:77
-#: screens/TopologyView/Tooltip.js:121
-msgid "Unavailable"
-msgstr "Niet beschikbaar"
-
-#: screens/Setting/shared/RevertButton.js:53
-#: screens/Setting/shared/RevertButton.js:62
-msgid "Undo"
-msgstr "Ongedaan maken"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:183
-msgid "Unfollow"
-msgstr "Volgen ongedaan maken"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:126
-msgid "Unlimited"
-msgstr "Onbeperkt"
-
-#: components/StatusLabel/StatusLabel.js:47
-#: screens/Job/JobOutput/shared/HostStatusBar.js:51
-#: screens/Job/JobOutput/shared/OutputToolbar.js:103
-msgid "Unreachable"
-msgstr "Onbereikbaar"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:102
-msgid "Unreachable Host Count"
-msgstr "Aantal onbereikbare hosts"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:104
-msgid "Unreachable Hosts"
-msgstr "Hosts onbereikbaar"
-
-#: util/dates.js:74
-msgid "Unrecognized day string"
-msgstr "Tekenreeks niet-herkende dag"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:15
-msgid "Unsaved changes modal"
-msgstr "Modus Niet-opgeslagen wijzigingen"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:94
-msgid "Update Revision on Launch"
-msgstr "Herziening updaten bij opstarten"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:51
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:133
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:92
-msgid "Update on launch"
-msgstr "Update bij opstarten"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:75
-msgid "Update options"
-msgstr "Update-opties"
-
-#: components/PromptDetail/PromptProjectDetail.js:61
-#: screens/Project/ProjectDetail/ProjectDetail.js:115
-msgid "Update revision on job launch"
-msgstr "Herziening bijwerken bij starten taak"
-
-#: screens/Setting/SettingList.js:92
-msgid "Update settings pertaining to Jobs within {brandName}"
-msgstr "Instellingen bijwerken die betrekking hebben op taken binnen {brandName}"
-
-#: screens/Template/shared/WebhookSubForm.js:188
-msgid "Update webhook key"
-msgstr "Webhooksleutel bijwerken"
-
-#: components/Workflow/WorkflowNodeHelp.js:126
-msgid "Updating"
-msgstr "Bijwerken"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:118
-msgid "Upload a .zip file"
-msgstr ".zip-bestand uploaden"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:97
-msgid "Upload a Red Hat Subscription Manifest containing your subscription. To generate your subscription manifest, go to <0>subscription allocations0> on the Red Hat Customer Portal."
-msgstr "Upload een Red Hat-abonnementsmanifest met uw abonnement. Ga naar <0>abonnementstoewijzingen0> op het Red Hat-klantenportaal om uw abonnementsmanifest te genereren."
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:53
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:132
-msgid "Use SSL"
-msgstr "SSL gebruiken"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:58
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:137
-msgid "Use TLS"
-msgstr "TLS gebruiken"
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:72
-msgid ""
-"Use custom messages to change the content of\n"
-"notifications sent when a job starts, succeeds, or fails. Use\n"
-"curly braces to access information about the job:"
-msgstr "Gebruik aangepaste berichten om de inhoud te wijzigen van berichten die worden verzonden wanneer een taak start, slaagt of mislukt. Gebruik accolades om informatie over de taak te openen:"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:12
-msgid "Use one Annotation Tag per line, without commas."
-msgstr "Voer een opmerkingstas in per regel, zonder komma's."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:13
-msgid ""
-"Use one IRC channel or username per line. The pound\n"
-"symbol (#) for channels, and the at (@) symbol for users, are not\n"
-"required."
-msgstr "Voer één IRC-kanaal of gebruikersnaam per regel in. Het hekje (#) voor kanalen en het apenstaartje (@) voor gebruikers zijn hierbij niet vereist."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:5
-msgid "Use one email address per line to create a recipient list for this type of notification."
-msgstr "Voer één e-mailadres per regel in om een lijst met ontvangers te maken voor dit type bericht."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:28
-msgid ""
-"Use one phone number per line to specify where to\n"
-"route SMS messages. Phone numbers should be formatted +11231231234. For more information see Twilio documentation"
-msgstr "Voer één telefoonnummer per regel in om aan te geven waar\n"
-"sms-berichten te routeren. Telefoonnummers moeten worden ingedeeld als +11231231234. Voor meer informatie zie Twilio-documentatie"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:249
-#: screens/InstanceGroup/Instances/InstanceList.js:270
-#: screens/Instances/InstanceDetail/InstanceDetail.js:292
-#: screens/Instances/InstanceList/InstanceList.js:205
-msgid "Used Capacity"
-msgstr "Gebruikte capaciteit"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:253
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:257
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:78
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:86
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:42
-#: screens/InstanceGroup/Instances/InstanceListItem.js:74
-#: screens/Instances/InstanceDetail/InstanceDetail.js:297
-#: screens/Instances/InstanceDetail/InstanceDetail.js:303
-#: screens/Instances/InstanceList/InstanceListItem.js:73
-#: screens/TopologyView/Tooltip.js:117
-msgid "Used capacity"
-msgstr "Gebruikte capaciteit"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:13
-msgid "User"
-msgstr "Gebruiker"
-
-#: components/AppContainer/PageHeaderToolbar.js:160
-msgid "User Details"
-msgstr "Gebruikersdetails"
-
-#: screens/Setting/SettingList.js:121
-#: screens/Setting/Settings.js:118
-msgid "User Interface"
-msgstr "Gebruikersinterface"
-
-#: screens/Setting/SettingList.js:126
-msgid "User Interface settings"
-msgstr "Instellingen gebruikersinterface"
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:72
-#: screens/User/UserRoles/UserRolesList.js:142
-msgid "User Roles"
-msgstr "Gebruikersrollen"
-
-#: screens/User/UserDetail/UserDetail.js:72
-#: screens/User/shared/UserForm.js:119
-msgid "User Type"
-msgstr "Soort gebruiker"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:59
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:60
-msgid "User analytics"
-msgstr "Gebruikersanalyses"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:34
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:202
-msgid "User and Automation Analytics"
-msgstr "Gebruikers- en Automatiseringsanalyses"
-
-#: components/AppContainer/PageHeaderToolbar.js:154
-msgid "User details"
-msgstr "Gebruikersdetails"
-
-#: screens/User/User.js:96
-msgid "User not found."
-msgstr "Gebruiker niet gevonden."
-
-#: screens/User/UserTokenList/UserTokenList.js:180
-msgid "User tokens"
-msgstr "Gebruikerstokens"
-
-#: components/AddRole/AddResourceRole.js:23
-#: components/AddRole/AddResourceRole.js:38
-#: components/ResourceAccessList/ResourceAccessList.js:173
-#: components/ResourceAccessList/ResourceAccessList.js:226
-#: screens/Login/Login.js:230
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:144
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:253
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:303
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:361
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:67
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:260
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:337
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:442
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:92
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:204
-#: screens/User/UserDetail/UserDetail.js:68
-#: screens/User/UserList/UserList.js:120
-#: screens/User/UserList/UserList.js:160
-#: screens/User/UserList/UserListItem.js:38
-#: screens/User/shared/UserForm.js:76
-msgid "Username"
-msgstr "Gebruikersnaam"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:88
-msgid "Username / password"
-msgstr "Gebruikersnaam/wachtwoord"
-
-#: components/AddRole/AddResourceRole.js:178
-#: components/AddRole/AddResourceRole.js:179
-#: routeConfig.js:101
-#: screens/ActivityStream/ActivityStream.js:184
-#: screens/Team/Teams.js:30
-#: screens/User/UserList/UserList.js:110
-#: screens/User/UserList/UserList.js:153
-#: screens/User/Users.js:15
-#: screens/User/Users.js:26
-msgid "Users"
-msgstr "Gebruikers"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:101
-msgid "VMware vCenter"
-msgstr "VMware vCenter"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:69
-#: components/HostForm/HostForm.js:113
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:115
-#: components/PromptDetail/PromptDetail.js:168
-#: components/PromptDetail/PromptDetail.js:369
-#: components/PromptDetail/PromptJobTemplateDetail.js:286
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:135
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:620
-#: screens/Host/HostDetail/HostDetail.js:93
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:165
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:37
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:88
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:143
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:53
-#: screens/Inventory/shared/InventoryForm.js:108
-#: screens/Inventory/shared/InventoryGroupForm.js:46
-#: screens/Inventory/shared/SmartInventoryForm.js:93
-#: screens/Job/JobDetail/JobDetail.js:546
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:501
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:226
-#: screens/Template/shared/JobTemplateForm.js:402
-#: screens/Template/shared/WorkflowJobTemplateForm.js:212
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:293
-msgid "Variables"
-msgstr "Variabelen"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:130
-msgid "Variables Prompted"
-msgstr "Variabelen gevraagd"
-
-#: screens/Inventory/shared/Inventory.helptext.js:43
-msgid "Variables must be in JSON or YAML syntax. Use the radio button to toggle between the two."
-msgstr "Voer variabelen in met JSON- of YAML-syntaxis. Gebruik de radioknop om tussen de twee te wisselen."
-
-#: screens/Inventory/shared/Inventory.helptext.js:166
-msgid "Variables used to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>{sourceType}1> plugin configuration guide."
-msgstr "Voer variabelen in om de inventarisbron te configureren. Voor een gedetailleerde beschrijving om deze plug-in te configureren, zie <0>Inventarisplug-ins0> in de documentatie en de plug-inconfiguratiegids voor <1>{sourceType}1>."
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:121
-msgid "Vault password"
-msgstr "Wachtwoord kluis"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:121
-msgid "Vault password | {credId}"
-msgstr "Wachtwoord kluis | {credId}"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:131
-msgid "Verbose"
-msgstr "Uitgebreid"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:63
-#: components/PromptDetail/PromptDetail.js:260
-#: components/PromptDetail/PromptInventorySourceDetail.js:100
-#: components/PromptDetail/PromptJobTemplateDetail.js:154
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:478
-#: components/VerbositySelectField/VerbositySelectField.js:34
-#: components/VerbositySelectField/VerbositySelectField.js:45
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:232
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:47
-#: screens/Job/JobDetail/JobDetail.js:331
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:271
-msgid "Verbosity"
-msgstr "Verbositeit"
-
-#: screens/Setting/AzureAD/AzureAD.js:25
-msgid "View Azure AD settings"
-msgstr "Azure AD-instellingen weergeven"
-
-#: screens/Credential/Credential.js:143
-#: screens/Credential/Credential.js:155
-msgid "View Credential Details"
-msgstr "Details toegangsgegevens weergeven"
-
-#: components/Schedule/Schedule.js:151
-msgid "View Details"
-msgstr "Details weergeven"
-
-#: screens/Setting/GitHub/GitHub.js:58
-msgid "View GitHub Settings"
-msgstr "GitHub-instellingen weergeven"
-
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2.js:26
-msgid "View Google OAuth 2.0 settings"
-msgstr "Instellingen Google OAuth 2.0 weergeven"
-
-#: screens/Host/Host.js:137
-msgid "View Host Details"
-msgstr "Hostdetails weergeven"
-
-#: screens/Instances/Instance.js:78
-msgid "View Instance Details"
-msgstr "Instantiedetails weergeven"
-
-#: screens/Inventory/Inventory.js:192
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:142
-#: screens/Inventory/SmartInventory.js:175
-msgid "View Inventory Details"
-msgstr "Inventarisdetails weergeven"
-
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:92
-msgid "View Inventory Groups"
-msgstr "Inventarisgroepen weergeven"
-
-#: screens/Inventory/InventoryHost/InventoryHost.js:160
-msgid "View Inventory Host Details"
-msgstr "Hostdetails van inventaris weergeven"
-
-#: screens/Inventory/shared/Inventory.helptext.js:55
-msgid "View JSON examples at <0>www.json.org0>"
-msgstr "JSON-voorbeelden op <0>www.json.org0> weergeven"
-
-#: screens/Job/Job.js:212
-msgid "View Job Details"
-msgstr "Taakdetails weergeven"
-
-#: screens/Setting/Jobs/Jobs.js:25
-msgid "View Jobs settings"
-msgstr "Taakinstellingen weergeven"
-
-#: screens/Setting/LDAP/LDAP.js:38
-msgid "View LDAP Settings"
-msgstr "LDAP-instellingen weergeven"
-
-#: screens/Setting/Logging/Logging.js:32
-msgid "View Logging settings"
-msgstr "Logboekregistratie-instellingen weergeven"
-
-#: screens/Setting/MiscAuthentication/MiscAuthentication.js:32
-msgid "View Miscellaneous Authentication settings"
-msgstr "Instellingen diversen authenticatie weergeven"
-
-#: screens/Setting/MiscSystem/MiscSystem.js:32
-msgid "View Miscellaneous System settings"
-msgstr "Diverse systeeminstellingen weergeven"
-
-#: screens/Setting/OIDC/OIDC.js:25
-msgid "View OIDC settings"
-msgstr "OIDC-instellingen bekijken"
-
-#: screens/Organization/Organization.js:225
-msgid "View Organization Details"
-msgstr "Organisatiedetails weergeven"
-
-#: screens/Project/Project.js:200
-msgid "View Project Details"
-msgstr "Projectdetails weergeven"
-
-#: screens/Setting/RADIUS/RADIUS.js:25
-msgid "View RADIUS settings"
-msgstr "RADIUS-instellingen weergeven"
-
-#: screens/Setting/SAML/SAML.js:25
-msgid "View SAML settings"
-msgstr "SAML-instellingen weergeven"
-
-#: components/Schedule/Schedule.js:83
-#: components/Schedule/Schedule.js:101
-msgid "View Schedules"
-msgstr "Schema's weergeven"
-
-#: screens/Setting/Subscription/Subscription.js:30
-msgid "View Settings"
-msgstr "Instellingen weergeven"
-
-#: screens/Template/Template.js:159
-#: screens/Template/WorkflowJobTemplate.js:145
-msgid "View Survey"
-msgstr "Vragenlijst weergeven"
-
-#: screens/Setting/TACACS/TACACS.js:25
-msgid "View TACACS+ settings"
-msgstr "TACACS+ instellingen weergeven"
-
-#: screens/Team/Team.js:118
-msgid "View Team Details"
-msgstr "Teamdetails weergeven"
-
-#: screens/Template/Template.js:260
-#: screens/Template/WorkflowJobTemplate.js:275
-msgid "View Template Details"
-msgstr "Sjabloondetails weergeven"
-
-#: screens/User/UserToken/UserToken.js:100
-msgid "View Tokens"
-msgstr "Tokens weergeven"
-
-#: screens/User/User.js:141
-msgid "View User Details"
-msgstr "Gebruikersdetails weergeven"
-
-#: screens/Setting/UI/UI.js:26
-msgid "View User Interface settings"
-msgstr "Instellingen gebruikersinterface weergeven"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:105
-msgid "View Workflow Approval Details"
-msgstr "Details workflowgoedkeuring weergeven"
-
-#: screens/Inventory/shared/Inventory.helptext.js:66
-msgid "View YAML examples at <0>docs.ansible.com0>"
-msgstr "YAML-voorbeelden weergeven op <0>docs.ansible.com0>"
-
-#: components/ScreenHeader/ScreenHeader.js:65
-#: components/ScreenHeader/ScreenHeader.js:68
-msgid "View activity stream"
-msgstr "Activiteitenlogboek weergeven"
-
-#: screens/Credential/Credential.js:99
-msgid "View all Credentials."
-msgstr "Geef alle toegangsgegevens weer."
-
-#: screens/Host/Host.js:97
-msgid "View all Hosts."
-msgstr "Geef alle hosts weer."
-
-#: screens/Inventory/Inventory.js:95
-#: screens/Inventory/SmartInventory.js:95
-msgid "View all Inventories."
-msgstr "Geef alle inventarissen weer."
-
-#: screens/Inventory/InventoryHost/InventoryHost.js:101
-msgid "View all Inventory Hosts."
-msgstr "Geef alle inventarishosts weer."
-
-#: screens/Job/JobTypeRedirect.js:40
-msgid "View all Jobs"
-msgstr "Alle taken weergeven"
-
-#: screens/Job/Job.js:162
-msgid "View all Jobs."
-msgstr "Geef alle taken weer."
-
-#: screens/NotificationTemplate/NotificationTemplate.js:60
-#: screens/NotificationTemplate/NotificationTemplateAdd.js:52
-msgid "View all Notification Templates."
-msgstr "Geef alle berichtsjablonen weer."
-
-#: screens/Organization/Organization.js:155
-msgid "View all Organizations."
-msgstr "Geef alle organisaties weer."
-
-#: screens/Project/Project.js:137
-msgid "View all Projects."
-msgstr "Geef alle projecten weer."
-
-#: screens/Team/Team.js:76
-msgid "View all Teams."
-msgstr "Geef alle teams weer."
-
-#: screens/Template/Template.js:176
-#: screens/Template/WorkflowJobTemplate.js:176
-msgid "View all Templates."
-msgstr "Geef alle sjablonen weer."
-
-#: screens/User/User.js:97
-msgid "View all Users."
-msgstr "Geef alle gebruikers weer."
-
-#: screens/WorkflowApproval/WorkflowApproval.js:54
-msgid "View all Workflow Approvals."
-msgstr "Geef alle workflowgoedkeuringen weer."
-
-#: screens/Application/Application/Application.js:96
-msgid "View all applications."
-msgstr "Geef alle toepassingen weer."
-
-#: screens/CredentialType/CredentialType.js:78
-msgid "View all credential types"
-msgstr "Alle typen toegangsgegevens weergeven"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:85
-msgid "View all execution environments"
-msgstr "Alle uitvoeringsomgevingen weergeven"
-
-#: screens/InstanceGroup/ContainerGroup.js:86
-#: screens/InstanceGroup/InstanceGroup.js:94
-msgid "View all instance groups"
-msgstr "Alle instantiegroepen weergeven"
-
-#: screens/ManagementJob/ManagementJob.js:135
-msgid "View all management jobs"
-msgstr "Alle beheertaken weergeven"
-
-#: screens/Setting/Settings.js:204
-msgid "View all settings"
-msgstr "Alle instellingen weergeven"
-
-#: screens/User/UserToken/UserToken.js:74
-msgid "View all tokens."
-msgstr "Geef alle tokens weer."
-
-#: screens/Setting/SettingList.js:133
-msgid "View and edit your subscription information"
-msgstr "Uw abonnementsgegevens weergeven en bewerken"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:25
-#: screens/ActivityStream/ActivityStreamListItem.js:50
-msgid "View event details"
-msgstr "Evenementinformatie weergeven"
-
-#: screens/Inventory/InventorySource/InventorySource.js:167
-msgid "View inventory source details"
-msgstr "Details inventarisbron weergeven"
-
-#: components/Sparkline/Sparkline.js:44
-msgid "View job {0}"
-msgstr "Taak {0} weergeven"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:220
-msgid "View node details"
-msgstr "Details knooppunt weergeven"
-
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:85
-msgid "View smart inventory host details"
-msgstr "Hostdetails Smart-inventaris weergeven"
-
-#: routeConfig.js:30
-#: screens/ActivityStream/ActivityStream.js:145
-msgid "Views"
-msgstr "Weergaven"
-
-#: components/TemplateList/TemplateListItem.js:198
-#: components/TemplateList/TemplateListItem.js:204
-#: screens/Template/WorkflowJobTemplate.js:137
-msgid "Visualizer"
-msgstr "Visualizer"
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:44
-msgid "WARNING:"
-msgstr "WAARSCHUWING:"
-
-#: components/JobList/JobList.js:229
-#: components/StatusLabel/StatusLabel.js:52
-#: components/Workflow/WorkflowNodeHelp.js:96
-msgid "Waiting"
-msgstr "Wachten"
-
-#: screens/Job/JobOutput/EmptyOutput.js:35
-msgid "Waiting for job output…"
-msgstr "Wachten op output van taak…"
-
-#: components/Workflow/WorkflowLegend.js:118
-#: screens/Job/JobOutput/JobOutputSearch.js:132
-msgid "Warning"
-msgstr "Waarschuwing"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:14
-msgid "Warning: Unsaved Changes"
-msgstr "Waarschuwing: niet-opgeslagen wijzigingen"
-
-#: components/Schedule/shared/ScheduleFormFields.js:43
-msgid "Warning: {selectedValue} is a link to {0} and will be saved as that."
-msgstr "Waarschuwing: {selectedValue} is een link naar {0} en wordt als zodanig opgeslagen."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:119
-msgid "We were unable to locate licenses associated with this account."
-msgstr "We waren niet in staat om de aan deze account gekoppelde licenties te lokaliseren."
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:138
-msgid "We were unable to locate subscriptions associated with this account."
-msgstr "We waren niet in staat om de aan deze account gekoppelde abonnementen te lokaliseren."
-
-#: components/DetailList/LaunchedByDetail.js:24
-#: components/NotificationList/NotificationList.js:202
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:143
-msgid "Webhook"
-msgstr "Webhook"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:177
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:103
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:333
-#: screens/Template/shared/WebhookSubForm.js:199
-msgid "Webhook Credential"
-msgstr "Webhook toegangsgegevens"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:175
-msgid "Webhook Credentials"
-msgstr "Toegangsgegevens Webhook"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:173
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:92
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:326
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:168
-#: screens/Template/shared/WebhookSubForm.js:173
-msgid "Webhook Key"
-msgstr "Webhooksleutel"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:166
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:91
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:311
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:156
-#: screens/Template/shared/WebhookSubForm.js:129
-msgid "Webhook Service"
-msgstr "Webhookservice"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:169
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:95
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:319
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:162
-#: screens/Template/shared/WebhookSubForm.js:161
-#: screens/Template/shared/WebhookSubForm.js:167
-msgid "Webhook URL"
-msgstr "Webhook-URL"
-
-#: screens/Template/shared/JobTemplateForm.js:646
-#: screens/Template/shared/WorkflowJobTemplateForm.js:271
-msgid "Webhook details"
-msgstr "Webhookdetails"
-
-#: screens/Template/shared/JobTemplate.helptext.js:24
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:17
-msgid "Webhook services can launch jobs with this workflow job template by making a POST request to this URL."
-msgstr "Webhookservices kunnen taken met deze sjabloon voor workflowtaken lanceren door het verzenden van een POST-verzoek naar deze URL."
-
-#: screens/Template/shared/JobTemplate.helptext.js:25
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:18
-msgid "Webhook services can use this as a shared secret."
-msgstr "Webhookservices kunnen dit gebruiken als een gedeeld geheim."
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:78
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:41
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:142
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:63
-msgid "Webhooks"
-msgstr "Webhooks"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:26
-msgid "Webhooks: Enable Webhook for this workflow job template."
-msgstr "Webhook inschakelen voor deze workflowtaaksjabloon."
-
-#: screens/Template/shared/JobTemplate.helptext.js:42
-msgid "Webhooks: Enable webhook for this template."
-msgstr "Webhook inschakelen voor deze sjabloon."
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:288
-msgid "Wed"
-msgstr "Wo"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:79
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:185
-#: components/Schedule/shared/FrequencyDetailSubform.js:293
-#: components/Schedule/shared/FrequencyDetailSubform.js:443
-msgid "Wednesday"
-msgstr "Woensdag"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:185
-#: components/Schedule/shared/FrequencyDetailSubform.js:179
-#: components/Schedule/shared/ScheduleFormFields.js:128
-#: components/Schedule/shared/ScheduleFormFields.js:188
-msgid "Week"
-msgstr "Week"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:464
-msgid "Weekday"
-msgstr "Doordeweeks"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:469
-msgid "Weekend day"
-msgstr "Weekenddag"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:59
-msgid ""
-"Welcome to Red Hat Ansible Automation Platform!\n"
-"Please complete the steps below to activate your subscription."
-msgstr "Welkom bij Red Hat Ansible Automation Platform! \n"
-"Volg de onderstaande stappen om uw abonnement te activeren."
-
-#: screens/Login/Login.js:190
-msgid "Welcome to {brandName}!"
-msgstr "Welkom bij {brandName}!"
-
-#: screens/Inventory/shared/Inventory.helptext.js:105
-msgid ""
-"When not checked, a merge will be performed,\n"
-"combining local variables with those found on the\n"
-"external source."
-msgstr "Als dit vakje niet aangevinkt is, worden lokale variabelen samengevoegd met de variabelen die aangetroffen zijn in de externe bron."
-
-#: screens/Inventory/shared/Inventory.helptext.js:93
-msgid ""
-"When not checked, local child\n"
-"hosts and groups not found on the external source will remain\n"
-"untouched by the inventory update process."
-msgstr "Als dit vakje niet aangevinkt is, worden lokale onderliggende hosts en groepen die niet aangetroffen zijn in de externe bron niet behandeld in het synchronisatieproces van de inventaris."
-
-#: components/Workflow/WorkflowLegend.js:96
-msgid "Workflow"
-msgstr "Workflow"
-
-#: components/Workflow/WorkflowNodeHelp.js:75
-msgid "Workflow Approval"
-msgstr "Workflowgoedkeuring"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:52
-msgid "Workflow Approval not found."
-msgstr "Workflowgoedkeuring niet gevonden."
-
-#: routeConfig.js:54
-#: screens/ActivityStream/ActivityStream.js:156
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:118
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:145
-#: screens/WorkflowApproval/WorkflowApprovals.js:13
-#: screens/WorkflowApproval/WorkflowApprovals.js:22
-msgid "Workflow Approvals"
-msgstr "Workflowgoedkeuringen"
-
-#: components/JobList/JobList.js:216
-#: components/JobList/JobListItem.js:47
-#: components/Schedule/ScheduleList/ScheduleListItem.js:40
-#: screens/Job/JobDetail/JobDetail.js:70
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:224
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:164
-msgid "Workflow Job"
-msgstr "Workflowtaak"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:76
-msgid "Workflow Job 1/{0}"
-msgstr "Workflowtaak 1/{0}"
-
-#: components/JobList/JobListItem.js:201
-#: components/Workflow/WorkflowNodeHelp.js:63
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:20
-#: screens/Job/JobDetail/JobDetail.js:244
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:91
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:186
-#: util/getRelatedResourceDeleteDetails.js:105
-msgid "Workflow Job Template"
-msgstr "Workflowtaaksjabloon"
-
-#: util/getRelatedResourceDeleteDetails.js:115
-#: util/getRelatedResourceDeleteDetails.js:157
-#: util/getRelatedResourceDeleteDetails.js:260
-msgid "Workflow Job Template Nodes"
-msgstr "Sjabloonknooppunten workflowtaak"
-
-#: util/getRelatedResourceDeleteDetails.js:140
-msgid "Workflow Job Templates"
-msgstr "Workflowtaaksjablonen"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:23
-msgid "Workflow Link"
-msgstr "Workflowlink"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:100
-msgid "Workflow Nodes"
-msgstr "Werkstroomknooppunten"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:86
-msgid "Workflow Statuses"
-msgstr "Werkstroomstatussen"
-
-#: components/TemplateList/TemplateList.js:218
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:98
-msgid "Workflow Template"
-msgstr "Workflowsjabloon"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:519
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:159
-msgid "Workflow approved message"
-msgstr "Workflow goedgekeurd bericht"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:531
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:168
-msgid "Workflow approved message body"
-msgstr "Workflow goedgekeurde berichtbody"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:543
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:177
-msgid "Workflow denied message"
-msgstr "Workflow geweigerd bericht"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:555
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:186
-msgid "Workflow denied message body"
-msgstr "Workflow geweigerde berichtbody"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:106
-msgid "Workflow documentation"
-msgstr "Workflowdocumentatie"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:220
-msgid "Workflow job details"
-msgstr "Taakdetails weergeven"
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:46
-msgid "Workflow job templates"
-msgstr "Workflowtaaksjablonen"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:24
-msgid "Workflow link modal"
-msgstr "Modus Workflowlink"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:247
-msgid "Workflow node view modal"
-msgstr "Modis Weergave workflowknooppunt"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:567
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:195
-msgid "Workflow pending message"
-msgstr "Bericht Workflow in behandeling"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:579
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:204
-msgid "Workflow pending message body"
-msgstr "Workflow Berichtenbody in behandeling"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:591
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:213
-msgid "Workflow timed out message"
-msgstr "Workflow Time-outbericht"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:603
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:222
-msgid "Workflow timed out message body"
-msgstr "Workflow Berichtbody voor time-out"
-
-#: screens/User/shared/UserTokenForm.js:77
-msgid "Write"
-msgstr "Schrijven"
-
-#: screens/Inventory/shared/Inventory.helptext.js:52
-msgid "YAML:"
-msgstr "YAML:"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:187
-#: components/Schedule/shared/FrequencyDetailSubform.js:183
-#: components/Schedule/shared/ScheduleFormFields.js:130
-#: components/Schedule/shared/ScheduleFormFields.js:190
-msgid "Year"
-msgstr "Jaar"
-
-#: components/Search/Search.js:229
-msgid "Yes"
-msgstr "Ja"
-
-#: components/Lookup/MultiCredentialsLookup.js:155
-msgid "You cannot select multiple vault credentials with the same vault ID. Doing so will automatically deselect the other with the same vault ID."
-msgstr "U kunt niet meerdere kluisreferenties met delfde kluis-ID selecteren. Als u dat wel doet, worden de andere met delfde kluis-ID automatisch gedeselecteerd."
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:96
-msgid "You do not have permission to delete the following Groups: {itemsUnableToDelete}"
-msgstr "U hebt geen machtiging om de volgende groepen te verwijderen: {itemsUnableToDelete}"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:152
-msgid "You do not have permission to delete {pluralizedItemName}: {itemsUnableToDelete}"
-msgstr "U hebt geen machtiging om {pluralizedItemName}: {itemsUnableToDelete} te verwijderen"
-
-#: components/DisassociateButton/DisassociateButton.js:66
-msgid "You do not have permission to disassociate the following: {itemsUnableToDisassociate}"
-msgstr "U hebt geen machtiging om het volgende te ontkoppelen: {itemsUnableToDisassociate}"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:72
-msgid "You do not have permission to remove instances: {itemsUnableToremove}"
-msgstr "U hebt geen machtiging voor gerelateerde bronnen: {itemsUnableToremove}"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:82
-msgid "You have automated against more hosts than your subscription allows."
-msgstr "Je hebt tegen meer hosts geautomatiseerd dan je abonnement toelaat."
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:87
-msgid ""
-"You may apply a number of possible variables in the\n"
-"message. For more information, refer to the"
-msgstr "U kunt een aantal mogelijke variabelen in het\n"
-"bericht toepassen. Voor meer informatie, raadpleeg de"
-
-#: screens/Login/Login.js:198
-msgid "Your session has expired. Please log in to continue where you left off."
-msgstr "Uw sessie is verlopen. Log in om verder te gaan waar u gebleven was."
-
-#: components/AppContainer/AppContainer.js:130
-msgid "Your session is about to expire"
-msgstr "Uw sessie is bijna afgelopen"
-
-#: components/Workflow/WorkflowTools.js:121
-msgid "Zoom In"
-msgstr "Inzoomen"
-
-#: components/Workflow/WorkflowTools.js:100
-msgid "Zoom Out"
-msgstr "Uitzoomen"
-
-#: screens/TopologyView/Header.js:51
-#: screens/TopologyView/Header.js:54
-msgid "Zoom in"
-msgstr "Inzoomen"
-
-#: screens/TopologyView/Header.js:63
-#: screens/TopologyView/Header.js:66
-msgid "Zoom out"
-msgstr "Uitzoomen"
-
-#: screens/Template/shared/JobTemplateForm.js:754
-#: screens/Template/shared/WebhookSubForm.js:150
-msgid "a new webhook key will be generated on save."
-msgstr "Er wordt een nieuwe webhooksleutel gegenereerd bij het opslaan."
-
-#: screens/Template/shared/JobTemplateForm.js:751
-#: screens/Template/shared/WebhookSubForm.js:140
-msgid "a new webhook url will be generated on save."
-msgstr "Er wordt een nieuwe webhook-URL gegenereerd bij het opslaan."
-
-#: screens/Inventory/shared/Inventory.helptext.js:123
-#: screens/Inventory/shared/Inventory.helptext.js:142
-msgid "and click on Update Revision on Launch"
-msgstr "en klik op Herziening updaten bij opstarten"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:505
-msgid "approved"
-msgstr "goedgekeurd"
-
-#: components/AppContainer/AppContainer.js:55
-msgid "brand logo"
-msgstr "merklogo"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:279
-#: screens/Template/Survey/SurveyList.js:72
-msgid "cancel delete"
-msgstr "verwijderen annuleren"
-
-#: screens/Setting/shared/SharedFields.js:341
-msgid "cancel edit login redirect"
-msgstr "omleiden inloggen bewerken annuleren"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:169
-msgid "cancel remove"
-msgstr "Terugzetten annuleren"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:34
-msgid "canceled"
-msgstr "geannuleerd"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:217
-msgid "command"
-msgstr "opdracht"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:267
-#: screens/Template/Survey/SurveyList.js:63
-msgid "confirm delete"
-msgstr "verwijderen bevestigen"
-
-#: components/DisassociateButton/DisassociateButton.js:130
-#: screens/Team/TeamRoles/TeamRolesList.js:219
-msgid "confirm disassociate"
-msgstr "loskoppelen bevestigen"
-
-#: screens/Setting/shared/SharedFields.js:330
-msgid "confirm edit login redirect"
-msgstr "omleiden inloggen bewerken bevestigen"
-
-#: screens/TopologyView/ContentLoading.js:32
-msgid "content-loading-in-progress"
-msgstr "bezig-met-content-laden"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:189
-msgid "day"
-msgstr "Dag"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:151
-msgid "deletion error"
-msgstr "verwijderingsfout"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:513
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:37
-msgid "denied"
-msgstr "geweigerd"
-
-#: screens/Job/JobOutput/EmptyOutput.js:41
-msgid "details."
-msgstr "Meer informatie"
-
-#: components/DisassociateButton/DisassociateButton.js:91
-msgid "disassociate"
-msgstr "loskoppelen"
-
-#: components/Lookup/HostFilterLookup.js:406
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:20
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:39
-#: screens/Template/Survey/SurveyQuestionForm.js:269
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:239
-#: screens/Template/shared/JobTemplate.helptext.js:61
-msgid "documentation"
-msgstr "documentatie"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:105
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:117
-#: screens/Host/HostDetail/HostDetail.js:104
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:97
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:109
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:99
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:289
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:161
-#: screens/Project/ProjectDetail/ProjectDetail.js:309
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:195
-#: screens/User/UserDetail/UserDetail.js:92
-msgid "edit"
-msgstr "bewerken"
-
-#: screens/Template/Survey/SurveyListItem.js:65
-#: screens/Template/Survey/SurveyReorderModal.js:125
-msgid "encrypted"
-msgstr "versleuteld"
-
-#: components/Lookup/HostFilterLookup.js:408
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:241
-msgid "for more info."
-msgstr "voor meer info."
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:21
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:40
-#: screens/Template/Survey/SurveyQuestionForm.js:271
-#: screens/Template/shared/JobTemplate.helptext.js:63
-msgid "for more information."
-msgstr "voor meer informatie."
-
-#: components/AdHocCommands/AdHocDetailsStep.js:150
-msgid "here"
-msgstr "hier"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:118
-#: components/AdHocCommands/AdHocDetailsStep.js:170
-#: screens/Job/Job.helptext.js:39
-msgid "here."
-msgstr "hier."
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:49
-msgid "host-description-{0}"
-msgstr "host-description-{0}"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:44
-msgid "host-name-{0}"
-msgstr "Hostnaam-{0}"
-
-#: components/Lookup/HostFilterLookup.js:418
-msgid "hosts"
-msgstr "hosts"
-
-#: components/Pagination/Pagination.js:24
-msgid "items"
-msgstr "items"
-
-#: screens/User/UserList/UserListItem.js:44
-msgid "ldap user"
-msgstr "ldap-gebruiker"
-
-#: screens/User/UserDetail/UserDetail.js:76
-msgid "login type"
-msgstr "inlogtype"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:203
-msgid "min"
-msgstr "min"
-
-#: screens/Template/Survey/MultipleChoiceField.js:76
-msgid "new choice"
-msgstr "nieuwe keuze"
-
-#: components/Pagination/Pagination.js:36
-#: components/Schedule/shared/FrequencyDetailSubform.js:480
-msgid "of"
-msgstr "van"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:215
-msgid "option to the"
-msgstr "optie aan de"
-
-#: components/Pagination/Pagination.js:25
-msgid "page"
-msgstr "pagina"
-
-#: components/Pagination/Pagination.js:26
-msgid "pages"
-msgstr "pagina's"
-
-#: components/Pagination/Pagination.js:28
-msgid "per page"
-msgstr "per pagina"
-
-#: components/LaunchButton/ReLaunchDropDown.js:77
-#: components/LaunchButton/ReLaunchDropDown.js:100
-msgid "relaunch jobs"
-msgstr "taken opnieuw starten"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:217
-msgid "sec"
-msgstr "sec"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:238
-msgid "seconds"
-msgstr "seconden"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:58
-msgid "select module"
-msgstr "module selecteren"
-
-#: screens/User/UserList/UserListItem.js:49
-msgid "social login"
-msgstr "sociale aanmelding"
-
-#: screens/Template/shared/JobTemplateForm.js:346
-#: screens/Template/shared/WorkflowJobTemplateForm.js:188
-msgid "source control branch"
-msgstr "Broncontrolevertakking"
-
-#: screens/ActivityStream/ActivityStreamListItem.js:30
-msgid "system"
-msgstr "systeem"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:511
-msgid "timed out"
-msgstr "time-out"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:195
-msgid "toggle changes"
-msgstr "wijzigingen wisselen"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:516
-msgid "updated"
-msgstr "bijgewerkt"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:190
-msgid "weekday"
-msgstr "Doordeweeks"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:191
-msgid "weekend day"
-msgstr "Weekenddag"
-
-#: screens/Template/shared/WebhookSubForm.js:181
-msgid "workflow job template webhook key"
-msgstr "webhooksleutel taaksjabloon voor workflows"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:65
-msgid "{0, plural, one {# source with sync failures.} other {# sources with sync failures.}}"
-msgstr "{0, plural, one {# source with sync failures.} other {# sources with sync failures.}}"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:115
-msgid "{0, plural, one {Are you sure you want delete the group below?} other {Are you sure you want delete the groups below?}}"
-msgstr "{0, plural, one {Are you sure you want delete the group below?} other {Are you sure you want delete the groups below?}}"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:86
-msgid "{0, plural, one {Delete Group?} other {Delete Groups?}}"
-msgstr "{0, plural, one {Delete Group?} other {Delete Groups?}}"
-
-#: util/validators.js:138
-msgid "{0, plural, one {Please enter a valid phone number.} other {Please enter valid phone numbers.}}"
-msgstr "{0, plural, one {Please enter a valid phone number.} other {Please enter valid phone numbers.}}"
-
-#: screens/Inventory/InventoryList/InventoryList.js:247
-msgid "{0, plural, one {The inventory will be in a pending status until the final delete is processed.} other {The inventories will be in a pending status until the final delete is processed.}}"
-msgstr "{0, plural, one {The inventory will be in a pending status until the final delete is processed.} other {The inventories will be in a pending status until the final delete is processed.}}"
-
-#: components/JobList/JobList.js:280
-msgid "{0, plural, one {The selected job cannot be deleted due to insufficient permission or a running job status} other {The selected jobs cannot be deleted due to insufficient permissions or a running job status}}"
-msgstr "{0, plural, one {The selected job cannot be deleted due to insufficient permission or a running job status} other {The selected jobs cannot be deleted due to insufficient permissions or a running job status}}"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:151
-msgid "{0, plural, one {This approval cannot be deleted due to insufficient permissions or a pending job status} other {These approvals cannot be deleted due to insufficient permissions or a pending job status}}"
-msgstr "{0, plural, one {This approval cannot be deleted due to insufficient permissions or a pending job status} other {These approvals cannot be deleted due to insufficient permissions or a pending job status}}"
-
-#: screens/Credential/CredentialList/CredentialList.js:198
-msgid "{0, plural, one {This credential is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these credentials could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This credential is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these credentials could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:164
-msgid "{0, plural, one {This credential type is currently being used by some credentials and cannot be deleted.} other {Credential types that are being used by credentials cannot be deleted. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This credential type is currently being used by some credentials and cannot be deleted.} other {Credential types that are being used by credentials cannot be deleted. Are you sure you want to delete anyway?}}"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:194
-msgid "{0, plural, one {This execution environment is currently being used by other resources. Are you sure you want to delete it?} other {These execution environments could be in use by other resources that rely on them. Are you sure you want to delete them anyway?}}"
-msgstr "{0, plural, one {This execution environment is currently being used by other resources. Are you sure you want to delete it?} other {These execution environments could be in use by other resources that rely on them. Are you sure you want to delete them anyway?}}"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:182
-msgid "{0, plural, one {This instance group is currently being by other resources. Are you sure you want to delete it?} other {Deleting these instance groups could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This instance group is currently being by other resources. Are you sure you want to delete it?} other {Deleting these instance groups could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:85
-msgid "{0, plural, one {This intance is currently being used by other resources. Are you sure you want to delete it?} other {Deprovisioning these instances could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This intance is currently being used by other resources. Are you sure you want to delete it?} other {Deprovisioning these instances could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Inventory/InventoryList/InventoryList.js:240
-msgid "{0, plural, one {This inventory is currently being used by some templates. Are you sure you want to delete it?} other {Deleting these inventories could impact some templates that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This inventory is currently being used by some templates. Are you sure you want to delete it?} other {Deleting these inventories could impact some templates that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:196
-msgid "{0, plural, one {This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?} other {Deleting these inventory sources could impact other resources that rely on them. Are you sure you want to delete anyway}}"
-msgstr "{0, plural, one {This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?} other {Deleting these inventory sources could impact other resources that rely on them. Are you sure you want to delete anyway}}"
-
-#: screens/Organization/OrganizationList/OrganizationList.js:166
-msgid "{0, plural, one {This organization is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these organizations could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This organization is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these organizations could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Project/ProjectList/ProjectList.js:252
-msgid "{0, plural, one {This project is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these projects could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This project is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these projects could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:209
-#: components/TemplateList/TemplateList.js:266
-msgid "{0, plural, one {This template is currently being used by some workflow nodes. Are you sure you want to delete it?} other {Deleting these templates could impact some workflow nodes that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This template is currently being used by some workflow nodes. Are you sure you want to delete it?} other {Deleting these templates could impact some workflow nodes that rely on them. Are you sure you want to delete anyway?}}"
-
-#: components/JobList/JobListCancelButton.js:72
-msgid "{0, plural, one {You cannot cancel the following job because it is not running:} other {You cannot cancel the following jobs because they are not running:}}"
-msgstr "{0, plural, one {You cannot cancel the following job because it is not running:} other {You cannot cancel the following jobs because they are not running:}}"
-
-#: components/JobList/JobListCancelButton.js:56
-msgid "{0, plural, one {You do not have permission to cancel the following job:} other {You do not have permission to cancel the following jobs:}}"
-msgstr "{0, plural, one {You do not have permission to cancel the following job:} other {You do not have permission to cancel the following jobs:}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:143
-msgid "{0, selectordinal, one {The first {dayOfWeek}} two {The second {dayOfWeek}} =3 {The third {dayOfWeek}} =4 {The fourth {dayOfWeek}} =5 {The fifth {dayOfWeek}}}"
-msgstr "{0, selectordinal, one {The first {dayOfWeek}} two {The second {dayOfWeek}} =3 {The third {dayOfWeek}} =4 {The fourth {dayOfWeek}} =5 {The fifth {dayOfWeek}}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:202
-msgid "{0, selectordinal, one {The first {weekday} of {month}} two {The second {weekday} of {month}} =3 {The third {weekday} of {month}} =4 {The fourth {weekday} of {month}} =5 {The fifth {weekday} of {month}}}"
-msgstr "{0, selectordinal, one {The first {weekday} van {month}} two {The second {weekday} van {month}} =3 {The third {weekday} van {month}} =4 {The fourth {weekday} van {month}} =5 {The fifth {weekday} van {month}}}"
-
-#: screens/ActivityStream/ActivityStreamListItem.js:28
-msgid "{0} (deleted)"
-msgstr "{0} (verwijderd)"
-
-#: components/ChipGroup/ChipGroup.js:13
-msgid "{0} more"
-msgstr "{0} meer"
-
-#: screens/Job/JobDetail/JobDetail.js:399
-msgid "{0} seconds"
-msgstr "{0} seconden"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:95
-msgid "{automatedInstancesCount} since {automatedInstancesSinceDateTime}"
-msgstr "{automatedInstancesCount} sinds {automatedInstancesSinceDateTime}"
-
-#: components/AppContainer/AppContainer.js:55
-msgid "{brandName} logo"
-msgstr "{brandName} logo"
-
-#: components/DetailList/UserDateDetail.js:23
-msgid "{dateStr} by <0>{username}0>"
-msgstr "{dateStr} door<0>{username}0>"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:231
-#: screens/InstanceGroup/Instances/InstanceListItem.js:148
-#: screens/Instances/InstanceDetail/InstanceDetail.js:274
-#: screens/Instances/InstanceList/InstanceListItem.js:158
-#: screens/TopologyView/Tooltip.js:288
-msgid "{forks, plural, one {# fork} other {# forks}}"
-msgstr "{forks, plural, one {# fork} other {# forks}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:41
-msgid "{interval, plural, one {{interval} day} other {{interval} days}}"
-msgstr "{interval, plural, one {{interval} dag} other {{interval} dagen}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:33
-msgid "{interval, plural, one {{interval} hour} other {{interval} hours}}"
-msgstr "{interval, plural, one {{interval} hour} other {{interval} hours}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:25
-msgid "{interval, plural, one {{interval} minute} other {{interval} minutes}}"
-msgstr "{interval, plural, one {{interval} minuut} other {{interval} minuten}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:57
-msgid "{interval, plural, one {{interval} month} other {{interval} months}}"
-msgstr "{interval, plural, one {{interval} maand} other {{interval} maanden}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:49
-msgid "{interval, plural, one {{interval} week} other {{interval} weeks}}"
-msgstr "{interval, plural, one {{interval} week} other {{interval} weken}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:65
-msgid "{interval, plural, one {{interval} year} other {{interval} years}}"
-msgstr "{interval, plural, one {{interval} jaar} other {{interval} jaar}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:198
-msgid "{intervalValue, plural, one {day} other {days}}"
-msgstr "{intervalValue, plural, one {day} other {days}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:196
-msgid "{intervalValue, plural, one {hour} other {hours}}"
-msgstr "{intervalValue, plural, one {hour} other {hours}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:194
-msgid "{intervalValue, plural, one {minute} other {minutes}}"
-msgstr "{intervalValue, plural, one {minute} other {minutes}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:202
-msgid "{intervalValue, plural, one {month} other {months}}"
-msgstr "{intervalValue, plural, one {month} other {months}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:200
-msgid "{intervalValue, plural, one {week} other {weeks}}"
-msgstr "{intervalValue, plural, one {week} other {weeks}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:204
-msgid "{intervalValue, plural, one {year} other {years}}"
-msgstr "{intervalValue, plural, one {year} other {years}}"
-
-#: components/PromptDetail/PromptDetail.js:44
-msgid "{minutes} min {seconds} sec"
-msgstr "{minutes} min {seconds} sec"
-
-#: components/JobList/JobListCancelButton.js:106
-msgid "{numJobsToCancel, plural, one {Cancel job} other {Cancel jobs}}"
-msgstr "{numJobsToCancel, plural, one {Cancel job} other {Cancel jobs}}"
-
-#: components/JobList/JobListCancelButton.js:168
-msgid "{numJobsToCancel, plural, one {This action will cancel the following job:} other {This action will cancel the following jobs:}}"
-msgstr "{numJobsToCancel, plural, one {This action will cancel the following job:} other {This action will cancel the following jobs:}}"
-
-#: components/JobList/JobListCancelButton.js:91
-msgid "{numJobsToCancel, plural, one {{0}} other {{1}}}"
-msgstr "{numJobsToCancel, plural, one {{0}} other {{1}}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:226
-msgid "{numOccurrences, plural, one {After {numOccurrences} occurrence} other {After {numOccurrences} occurrences}}"
-msgstr "{numOccurrences, plural, one {After {numOccurrences} occurrence} other {After {numOccurrences} occurrences}}"
-
-#: components/PaginatedTable/PaginatedTable.js:79
-msgid "{pluralizedItemName} List"
-msgstr "{pluralizedItemName} Lijst"
-
-#: components/HealthCheckButton/HealthCheckButton.js:13
-msgid "{selectedItemsCount, plural, one {Click to run a health check on the selected instance.} other {Click to run a health check on the selected instances.}}"
-msgstr "{selectedItemsCount, plural, one {Click to run a health check on the selected instance.} other {Click to run a health check on the selected instances.}}"
-
-#: components/AppContainer/AppContainer.js:154
-msgid "{sessionCountdown, plural, one {You will be logged out in # second due to inactivity} other {You will be logged out in # seconds due to inactivity}}"
-msgstr "{sessionCountdown, plural, one {You will be logged out in # second due to inactivity} other {You will be logged out in # seconds due to inactivity}}"
diff --git a/awx/ui/src/locales/zh/messages.po b/awx/ui/src/locales/zh/messages.po
deleted file mode 100644
index c368c702865a..000000000000
--- a/awx/ui/src/locales/zh/messages.po
+++ /dev/null
@@ -1,10698 +0,0 @@
-msgid ""
-msgstr ""
-"POT-Creation-Date: 2018-12-10 10:08-0500\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: @lingui/cli\n"
-"Language: en\n"
-"Project-Id-Version: \n"
-"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: \n"
-"Last-Translator: \n"
-"Language-Team: \n"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:43
-msgid "(Limited to first 10)"
-msgstr "(限制为前 10)"
-
-#: components/TemplateList/TemplateListItem.js:103
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:161
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:90
-msgid "(Prompt on launch)"
-msgstr "(启动时提示)"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:283
-msgid "* This field will be retrieved from an external secret management system using the specified credential."
-msgstr "* 此字段将使用指定的凭证从外部 secret 管理系统检索。"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:228
-msgid "/ (project root)"
-msgstr "/ (project root)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:10
-msgid "0 (Normal)"
-msgstr "0(普通)"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:38
-msgid "0 (Warning)"
-msgstr "0(警告)"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:39
-msgid "1 (Info)"
-msgstr "1(信息)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:11
-msgid "1 (Verbose)"
-msgstr "1(详细)"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:40
-msgid "2 (Debug)"
-msgstr "2(调试)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:12
-msgid "2 (More Verbose)"
-msgstr "2(更多详细内容)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:13
-msgid "3 (Debug)"
-msgstr "3(调试)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:14
-msgid "4 (Connection Debug)"
-msgstr "4(连接调试)"
-
-#: components/VerbositySelectField/VerbositySelectField.js:15
-msgid "5 (WinRM Debug)"
-msgstr "5(WinRM 调试)"
-
-#: screens/Project/shared/Project.helptext.js:76
-msgid ""
-"A refspec to fetch (passed to the Ansible git\n"
-"module). This parameter allows access to references via\n"
-"the branch field not otherwise available."
-msgstr "要获取的 refspec(传递至 Ansible git 模块)。此参数允许通过分支字段访问原本不可用的引用。"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:122
-msgid "A subscription manifest is an export of a Red Hat Subscription. To generate a subscription manifest, go to <0>access.redhat.com0>. For more information, see the <1>User Guide1>."
-msgstr "订阅清单是红帽订阅的一个导出。要生成订阅清单,请访问 <0>access.redhat.com0>。如需更多信息,请参阅<1>用户指南1>。"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputNode.js:143
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:326
-msgid "ALL"
-msgstr "所有"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:284
-msgid "API Service/Integration Key"
-msgstr "API 服务/集成密钥"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:291
-msgid "API Token"
-msgstr "API 令牌"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:306
-msgid "API service/integration key"
-msgstr "API 服务/集成密钥"
-
-#: components/AppContainer/PageHeaderToolbar.js:125
-msgid "About"
-msgstr "关于"
-
-#: routeConfig.js:92
-#: screens/ActivityStream/ActivityStream.js:179
-#: screens/Credential/Credential.js:74
-#: screens/Credential/Credentials.js:29
-#: screens/Inventory/Inventories.js:59
-#: screens/Inventory/Inventory.js:65
-#: screens/Inventory/SmartInventory.js:67
-#: screens/Organization/Organization.js:124
-#: screens/Organization/Organizations.js:31
-#: screens/Project/Project.js:105
-#: screens/Project/Projects.js:27
-#: screens/Team/Team.js:58
-#: screens/Team/Teams.js:31
-#: screens/Template/Template.js:136
-#: screens/Template/Templates.js:45
-#: screens/Template/WorkflowJobTemplate.js:118
-msgid "Access"
-msgstr "访问"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:75
-msgid "Access Token Expiration"
-msgstr "访问令牌过期"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:352
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:421
-msgid "Account SID"
-msgstr "帐户 SID"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:396
-msgid "Account token"
-msgstr "帐户令牌"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:50
-msgid "Action"
-msgstr "操作"
-
-#: components/JobList/JobList.js:249
-#: components/JobList/JobListItem.js:103
-#: components/RelatedTemplateList/RelatedTemplateList.js:189
-#: components/Schedule/ScheduleList/ScheduleList.js:172
-#: components/Schedule/ScheduleList/ScheduleListItem.js:128
-#: components/SelectedList/DraggableSelectedList.js:101
-#: components/TemplateList/TemplateList.js:246
-#: components/TemplateList/TemplateListItem.js:195
-#: screens/ActivityStream/ActivityStream.js:266
-#: screens/ActivityStream/ActivityStreamListItem.js:49
-#: screens/Application/ApplicationsList/ApplicationListItem.js:48
-#: screens/Application/ApplicationsList/ApplicationsList.js:160
-#: screens/Credential/CredentialList/CredentialList.js:166
-#: screens/Credential/CredentialList/CredentialListItem.js:66
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:177
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:38
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:168
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:87
-#: screens/Host/HostGroups/HostGroupItem.js:34
-#: screens/Host/HostGroups/HostGroupsList.js:177
-#: screens/Host/HostList/HostList.js:172
-#: screens/Host/HostList/HostListItem.js:70
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:200
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:75
-#: screens/InstanceGroup/Instances/InstanceList.js:271
-#: screens/InstanceGroup/Instances/InstanceListItem.js:171
-#: screens/Instances/InstanceList/InstanceList.js:206
-#: screens/Instances/InstanceList/InstanceListItem.js:183
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:218
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:53
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:39
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:142
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:41
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:187
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:44
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:140
-#: screens/Inventory/InventoryList/InventoryList.js:222
-#: screens/Inventory/InventoryList/InventoryListItem.js:131
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:233
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:44
-#: screens/Inventory/InventorySources/InventorySourceList.js:214
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:101
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:102
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:73
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:181
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:124
-#: screens/Organization/OrganizationList/OrganizationList.js:146
-#: screens/Organization/OrganizationList/OrganizationListItem.js:69
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:86
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:19
-#: screens/Project/ProjectList/ProjectList.js:225
-#: screens/Project/ProjectList/ProjectListItem.js:222
-#: screens/Team/TeamList/TeamList.js:144
-#: screens/Team/TeamList/TeamListItem.js:47
-#: screens/Template/Survey/SurveyList.js:105
-#: screens/Template/Survey/SurveyList.js:105
-#: screens/Template/Survey/SurveyListItem.js:90
-#: screens/User/UserList/UserList.js:164
-#: screens/User/UserList/UserListItem.js:56
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:167
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:78
-msgid "Actions"
-msgstr "操作"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:98
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:61
-#: components/TemplateList/TemplateListItem.js:277
-#: screens/Host/HostDetail/HostDetail.js:71
-#: screens/Host/HostList/HostListItem.js:95
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:217
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:50
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:76
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:100
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:32
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:115
-msgid "Activity"
-msgstr "活动"
-
-#: routeConfig.js:49
-#: screens/ActivityStream/ActivityStream.js:43
-#: screens/ActivityStream/ActivityStream.js:121
-#: screens/Setting/Settings.js:44
-msgid "Activity Stream"
-msgstr "活动流"
-
-#: screens/ActivityStream/ActivityStream.js:124
-msgid "Activity Stream type selector"
-msgstr "活动流类型选择器"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:169
-msgid "Actor"
-msgstr "操作者"
-
-#: components/AddDropDownButton/AddDropDownButton.js:40
-#: components/PaginatedTable/ToolbarAddButton.js:14
-msgid "Add"
-msgstr "添加"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkAddModal.js:14
-msgid "Add Link"
-msgstr "添加链接"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.js:78
-msgid "Add Node"
-msgstr "添加节点"
-
-#: screens/Template/Templates.js:49
-msgid "Add Question"
-msgstr "添加问题"
-
-#: components/AddRole/AddResourceRole.js:164
-msgid "Add Roles"
-msgstr "添加角色"
-
-#: components/AddRole/AddResourceRole.js:161
-msgid "Add Team Roles"
-msgstr "添加团队角色"
-
-#: components/AddRole/AddResourceRole.js:158
-msgid "Add User Roles"
-msgstr "添加用户角色"
-
-#: components/Workflow/WorkflowStartNode.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:238
-msgid "Add a new node"
-msgstr "添加新令牌"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:49
-msgid "Add a new node between these two nodes"
-msgstr "在这两个节点间添加新节点"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:108
-msgid "Add container group"
-msgstr "添加容器组"
-
-#: components/Schedule/shared/ScheduleFormFields.js:171
-msgid "Add exceptions"
-msgstr "添加例外"
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:140
-msgid "Add existing group"
-msgstr "添加现有组"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:152
-msgid "Add existing host"
-msgstr "添加现有主机"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:109
-msgid "Add instance group"
-msgstr "添加实例组"
-
-#: screens/Inventory/InventoryList/InventoryList.js:136
-msgid "Add inventory"
-msgstr "添加清单"
-
-#: components/TemplateList/TemplateList.js:151
-msgid "Add job template"
-msgstr "添加作业模板"
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:141
-msgid "Add new group"
-msgstr "添加新组"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:153
-msgid "Add new host"
-msgstr "添加新主机"
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:77
-msgid "Add resource type"
-msgstr "添加资源类型"
-
-#: screens/Inventory/InventoryList/InventoryList.js:137
-msgid "Add smart inventory"
-msgstr "添加智能清单"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:202
-msgid "Add team permissions"
-msgstr "添加团队权限"
-
-#: screens/User/UserRoles/UserRolesList.js:198
-msgid "Add user permissions"
-msgstr "添加用户权限"
-
-#: components/TemplateList/TemplateList.js:152
-msgid "Add workflow template"
-msgstr "添加工作流模板"
-
-#: screens/TopologyView/Legend.js:269
-msgid "Adding"
-msgstr "添加"
-
-#: routeConfig.js:113
-#: screens/ActivityStream/ActivityStream.js:190
-msgid "Administration"
-msgstr "管理"
-
-#: components/DataListToolbar/DataListToolbar.js:139
-#: screens/Job/JobOutput/JobOutputSearch.js:136
-msgid "Advanced"
-msgstr "高级"
-
-#: components/Search/AdvancedSearch.js:318
-msgid "Advanced search documentation"
-msgstr "高级搜索文档"
-
-#: components/Search/AdvancedSearch.js:211
-#: components/Search/AdvancedSearch.js:225
-msgid "Advanced search value input"
-msgstr "高级搜索值输入"
-
-#: screens/Inventory/shared/Inventory.helptext.js:131
-msgid ""
-"After every project update where the SCM revision\n"
-"changes, refresh the inventory from the selected source\n"
-"before executing job tasks. This is intended for static content,\n"
-"like the Ansible inventory .ini file format."
-msgstr "因 SCM 修订版本变更进行每次项目更新后,请在执行作业任务前从所选源刷新清单。这是面向静态内容,如 Ansible 清单 .ini 文件格式。"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:524
-msgid "After number of occurrences"
-msgstr "发生次数后"
-
-#: components/AlertModal/AlertModal.js:75
-msgid "Alert modal"
-msgstr "警报模式"
-
-#: components/LaunchButton/ReLaunchDropDown.js:48
-#: components/PromptDetail/PromptDetail.js:132
-#: screens/Metrics/Metrics.js:82
-#: screens/Metrics/Metrics.js:82
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:266
-msgid "All"
-msgstr "所有"
-
-#: screens/Dashboard/DashboardGraph.js:137
-msgid "All job types"
-msgstr "作业作业类型"
-
-#: screens/Dashboard/DashboardGraph.js:162
-msgid "All jobs"
-msgstr "所有作业"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:101
-msgid "Allow Branch Override"
-msgstr "允许分支覆写"
-
-#: components/PromptDetail/PromptProjectDetail.js:66
-#: screens/Project/ProjectDetail/ProjectDetail.js:121
-msgid "Allow branch override"
-msgstr "允许分支覆写"
-
-#: screens/Project/shared/Project.helptext.js:126
-msgid ""
-"Allow changing the Source Control branch or revision in a job\n"
-"template that uses this project."
-msgstr "允许在使用此项目的作业模板中更改 Source Control 分支或修订版本。"
-
-#: screens/Application/shared/Application.helptext.js:6
-msgid "Allowed URIs list, space separated"
-msgstr "允许的 URI 列表,以空格分开"
-
-#: components/Workflow/WorkflowLegend.js:130
-#: components/Workflow/WorkflowLinkHelp.js:24
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:58
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:46
-msgid "Always"
-msgstr "始终"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:98
-msgid "Amazon EC2"
-msgstr "Amazon EC2"
-
-#: components/Lookup/shared/LookupErrorMessage.js:12
-msgid "An error occurred"
-msgstr "发生错误"
-
-#: components/LaunchPrompt/steps/useInventoryStep.js:35
-msgid "An inventory must be selected"
-msgstr "必须选择一个清单"
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:96
-msgid "Ansible Controller Documentation."
-msgstr "Ansible 控制器文档。"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:43
-msgid "Answer type"
-msgstr "回答类型"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:177
-msgid "Answer variable name"
-msgstr "回答变量名称"
-
-#: components/PromptDetail/PromptDetail.js:132
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:263
-msgid "Any"
-msgstr "任何"
-
-#: components/Lookup/ApplicationLookup.js:83
-#: screens/User/UserTokenDetail/UserTokenDetail.js:39
-#: screens/User/shared/UserTokenForm.js:48
-msgid "Application"
-msgstr "应用程序"
-
-#: screens/User/UserTokenList/UserTokenList.js:187
-msgid "Application Name"
-msgstr "应用程序名"
-
-#: screens/Application/Applications.js:67
-#: screens/Application/Applications.js:70
-msgid "Application information"
-msgstr "应用程序信息"
-
-#: screens/User/UserTokenList/UserTokenList.js:123
-#: screens/User/UserTokenList/UserTokenList.js:134
-msgid "Application name"
-msgstr "应用程序名"
-
-#: screens/Application/Application/Application.js:95
-msgid "Application not found."
-msgstr "未找到应用程序。"
-
-#: components/Lookup/ApplicationLookup.js:96
-#: routeConfig.js:142
-#: screens/Application/Applications.js:26
-#: screens/Application/Applications.js:35
-#: screens/Application/ApplicationsList/ApplicationsList.js:113
-#: screens/Application/ApplicationsList/ApplicationsList.js:148
-#: util/getRelatedResourceDeleteDetails.js:209
-msgid "Applications"
-msgstr "应用程序"
-
-#: screens/ActivityStream/ActivityStream.js:211
-msgid "Applications & Tokens"
-msgstr "应用程序和令牌"
-
-#: components/NotificationList/NotificationListItem.js:39
-#: components/NotificationList/NotificationListItem.js:40
-#: components/Workflow/WorkflowLegend.js:114
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:67
-msgid "Approval"
-msgstr "批准"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:82
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:44
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:47
-msgid "Approve"
-msgstr "批准"
-
-#: components/StatusLabel/StatusLabel.js:39
-msgid "Approved"
-msgstr "已批准"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:11
-msgid "Approved - {0}. See the Activity Stream for more information."
-msgstr "已批准 - {0}。详情请参阅活动流。"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:7
-msgid "Approved by {0} - {1}"
-msgstr "由 {0} - {1} 批准"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:162
-#: components/Schedule/shared/FrequencyDetailSubform.js:113
-msgid "April"
-msgstr "4 月"
-
-#: components/JobCancelButton/JobCancelButton.js:104
-msgid "Are you sure you want to cancel this job?"
-msgstr "您确定要取消此作业吗?"
-
-#: components/DeleteButton/DeleteButton.js:127
-msgid "Are you sure you want to delete:"
-msgstr "您确定要删除:"
-
-#: screens/Setting/shared/SharedFields.js:142
-msgid "Are you sure you want to disable local authentication? Doing so could impact users' ability to log in and the system administrator's ability to reverse this change."
-msgstr "您确定要禁用本地身份验证吗?这样做可能会影响用户登录的能力,以及系统管理员撤销此更改的能力。"
-
-#: screens/Setting/shared/SharedFields.js:350
-msgid "Are you sure you want to edit login redirect override URL? Doing so could impact users' ability to log in to the system once local authentication is also disabled."
-msgstr "您确定要编辑登录重定向覆盖 URL? 这样做可能会影响用户在同时禁用本地身份验证后登录系统的能力。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:41
-msgid "Are you sure you want to exit the Workflow Creator without saving your changes?"
-msgstr "您确定要退出 Workflow Creator 而不保存您的更改吗?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:40
-msgid "Are you sure you want to remove all the nodes in this workflow?"
-msgstr "您确定要删除此工作流中的所有节点吗?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:56
-msgid "Are you sure you want to remove the node below:"
-msgstr "您确定要删除以下节点:"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:43
-msgid "Are you sure you want to remove this link?"
-msgstr "您确定要从删除这个链接吗?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:63
-msgid "Are you sure you want to remove this node?"
-msgstr "您确定要从删除这个节点吗?"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:43
-msgid "Are you sure you want to remove {0} access from {1}? Doing so affects all members of the team."
-msgstr "您确定要从 {1} 中删除访问 {0} 吗?这样做会影响团队所有成员。"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:50
-msgid "Are you sure you want to remove {0} access from {username}?"
-msgstr "您确定要从 {username} 中删除 {0} 吗?"
-
-#: screens/Job/JobOutput/JobOutput.js:826
-msgid "Are you sure you want to submit the request to cancel this job?"
-msgstr "您确定要提交取消此任务的请求吗?"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:102
-#: components/AdHocCommands/AdHocDetailsStep.js:104
-msgid "Arguments"
-msgstr "参数"
-
-#: screens/Job/JobDetail/JobDetail.js:559
-msgid "Artifacts"
-msgstr "工件"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:233
-#: screens/User/UserTeams/UserTeamList.js:208
-msgid "Associate"
-msgstr "关联"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:244
-#: screens/User/UserRoles/UserRolesList.js:240
-msgid "Associate role error"
-msgstr "关联角色错误"
-
-#: components/AssociateModal/AssociateModal.js:98
-msgid "Association modal"
-msgstr "关联模态"
-
-#: components/LaunchPrompt/steps/SurveyStep.js:168
-msgid "At least one value must be selected for this field."
-msgstr "此字段至少选择一个值。"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:166
-#: components/Schedule/shared/FrequencyDetailSubform.js:133
-msgid "August"
-msgstr "8 月"
-
-#: screens/Setting/SettingList.js:52
-msgid "Authentication"
-msgstr "身份验证"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:88
-msgid "Authorization Code Expiration"
-msgstr "授权代码过期"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:81
-#: screens/Application/shared/ApplicationForm.js:85
-msgid "Authorization grant type"
-msgstr "授权授予类型"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:215
-#: screens/InstanceGroup/Instances/InstanceListItem.js:204
-#: screens/Instances/InstanceDetail/InstanceDetail.js:209
-#: screens/Instances/InstanceList/InstanceListItem.js:219
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:83
-msgid "Auto"
-msgstr "Auto"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:71
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:72
-msgid "Automation Analytics"
-msgstr "自动化分析"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:111
-msgid "Automation Analytics dashboard"
-msgstr "自动化分析仪表盘"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:175
-msgid "Automation controller version"
-msgstr "Automation Controller 版本"
-
-#: screens/Setting/Settings.js:47
-msgid "Azure AD"
-msgstr "Azure AD"
-
-#: screens/Setting/SettingList.js:57
-msgid "Azure AD settings"
-msgstr "Azure AD 设置"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:49
-#: components/AddRole/AddResourceRole.js:267
-#: components/LaunchPrompt/LaunchPrompt.js:158
-#: components/Schedule/shared/SchedulePromptableFields.js:125
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:90
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:70
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:154
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:157
-msgid "Back"
-msgstr "返回"
-
-#: screens/Credential/Credential.js:65
-msgid "Back to Credentials"
-msgstr "返回到凭证"
-
-#: components/ContentError/ContentError.js:43
-msgid "Back to Dashboard."
-msgstr "返回到仪表盘。"
-
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:49
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:50
-msgid "Back to Groups"
-msgstr "返回到组"
-
-#: screens/Host/Host.js:50
-#: screens/Inventory/InventoryHost/InventoryHost.js:66
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:53
-msgid "Back to Hosts"
-msgstr "返回到主机"
-
-#: screens/InstanceGroup/InstanceGroup.js:61
-msgid "Back to Instance Groups"
-msgstr "返回到实例组"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:173
-#: screens/Instances/Instance.js:22
-msgid "Back to Instances"
-msgstr "返回到实例"
-
-#: screens/Inventory/Inventory.js:57
-#: screens/Inventory/SmartInventory.js:60
-msgid "Back to Inventories"
-msgstr "返回到清单"
-
-#: screens/Job/Job.js:123
-msgid "Back to Jobs"
-msgstr "返回到作业"
-
-#: screens/NotificationTemplate/NotificationTemplate.js:76
-msgid "Back to Notifications"
-msgstr "返回到通知"
-
-#: screens/Organization/Organization.js:116
-msgid "Back to Organizations"
-msgstr "返回到机构"
-
-#: screens/Project/Project.js:97
-msgid "Back to Projects"
-msgstr "返回到项目"
-
-#: components/Schedule/Schedule.js:64
-msgid "Back to Schedules"
-msgstr "返回到调度"
-
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:44
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:78
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:44
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:58
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:95
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:69
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:43
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:90
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:44
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:49
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:45
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:34
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:49
-#: screens/Setting/UI/UIDetail/UIDetail.js:59
-msgid "Back to Settings"
-msgstr "返回到设置"
-
-#: screens/Inventory/InventorySource/InventorySource.js:76
-msgid "Back to Sources"
-msgstr "返回到源"
-
-#: screens/Team/Team.js:50
-msgid "Back to Teams"
-msgstr "返回到团队"
-
-#: screens/Template/Template.js:128
-#: screens/Template/WorkflowJobTemplate.js:110
-msgid "Back to Templates"
-msgstr "返回到模板"
-
-#: screens/User/UserToken/UserToken.js:47
-msgid "Back to Tokens"
-msgstr "返回到令牌"
-
-#: screens/User/User.js:57
-msgid "Back to Users"
-msgstr "返回到用户"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:69
-msgid "Back to Workflow Approvals"
-msgstr "返回到工作流批准"
-
-#: screens/Application/Application/Application.js:72
-msgid "Back to applications"
-msgstr "返回到应用程序"
-
-#: screens/CredentialType/CredentialType.js:55
-msgid "Back to credential types"
-msgstr "返回到凭证类型"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:57
-msgid "Back to execution environments"
-msgstr "返回到执行环境"
-
-#: screens/InstanceGroup/ContainerGroup.js:59
-msgid "Back to instance groups"
-msgstr "返回到实例组"
-
-#: screens/ManagementJob/ManagementJob.js:98
-msgid "Back to management jobs"
-msgstr "返回到管理作业"
-
-#: screens/Project/shared/Project.helptext.js:8
-msgid ""
-"Base path used for locating playbooks. Directories\n"
-"found inside this path will be listed in the playbook directory drop-down.\n"
-"Together the base path and selected playbook directory provide the full\n"
-"path used to locate playbooks."
-msgstr "用于定位 playbook 的基本路径。位于该路径中的目录将列在 playbook 目录下拉列表中。基本路径和所选 playbook 目录一起提供了用于定位 playbook 的完整路径。"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:448
-msgid "Basic auth password"
-msgstr "基本验证密码"
-
-#: screens/Project/shared/Project.helptext.js:104
-msgid ""
-"Branch to checkout. In addition to branches,\n"
-"you can input tags, commit hashes, and arbitrary refs. Some\n"
-"commit hashes and refs may not be available unless you also\n"
-"provide a custom refspec."
-msgstr "要签出的分支。除了分支外,您可以输入标签、提交散列和任意 refs。除非你还提供了自定义 refspec,否则某些提交散列和 refs 可能无法使用。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:27
-msgid "Branch to use in job run. Project default used if blank. Only allowed if project allow_override field is set to true."
-msgstr "要在任务运行中使用的分支。如果为空,则使用项目默认值。只有项目 allow_override 字段设置为 true 时才允许使用。"
-
-#: components/About/About.js:45
-msgid "Brand Image"
-msgstr "品牌图像"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:158
-msgid "Browse"
-msgstr "浏览"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:94
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:115
-msgid "Browse…"
-msgstr "浏览..."
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:36
-msgid "By default, we collect and transmit analytics data on the service usage to Red Hat. There are two categories of data collected by the service. For more information, see <0>this Tower documentation page0>. Uncheck the following boxes to disable this feature."
-msgstr "默认情况下,我们会收集关于服务使用情况的分析数据并将其传送到红帽。服务收集的数据分为两类。如需更多信息,请参阅<0>此 Tower 文档页0>。取消选择以下复选框以禁用此功能。"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:228
-#: screens/InstanceGroup/Instances/InstanceListItem.js:145
-#: screens/Instances/InstanceDetail/InstanceDetail.js:271
-#: screens/Instances/InstanceList/InstanceListItem.js:155
-#: screens/TopologyView/Tooltip.js:285
-msgid "CPU {0}"
-msgstr "CPU {0}"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:102
-#: components/PromptDetail/PromptProjectDetail.js:151
-#: screens/Project/ProjectDetail/ProjectDetail.js:268
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:118
-msgid "Cache Timeout"
-msgstr "缓存超时"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:237
-msgid "Cache timeout"
-msgstr "缓存超时"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:106
-msgid "Cache timeout (seconds)"
-msgstr "缓存超时(秒)"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:50
-#: components/AddRole/AddResourceRole.js:268
-#: components/AssociateModal/AssociateModal.js:114
-#: components/AssociateModal/AssociateModal.js:119
-#: components/DeleteButton/DeleteButton.js:120
-#: components/DeleteButton/DeleteButton.js:123
-#: components/DisassociateButton/DisassociateButton.js:139
-#: components/DisassociateButton/DisassociateButton.js:142
-#: components/FormActionGroup/FormActionGroup.js:23
-#: components/FormActionGroup/FormActionGroup.js:29
-#: components/LaunchPrompt/LaunchPrompt.js:159
-#: components/Lookup/HostFilterLookup.js:388
-#: components/Lookup/Lookup.js:209
-#: components/PaginatedTable/ToolbarDeleteButton.js:282
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:37
-#: components/Schedule/shared/ScheduleForm.js:548
-#: components/Schedule/shared/ScheduleForm.js:553
-#: components/Schedule/shared/SchedulePromptableFields.js:126
-#: components/Schedule/shared/UnsupportedScheduleForm.js:22
-#: components/Schedule/shared/UnsupportedScheduleForm.js:27
-#: screens/Credential/shared/CredentialForm.js:343
-#: screens/Credential/shared/CredentialForm.js:348
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:100
-#: screens/Credential/shared/ExternalTestModal.js:98
-#: screens/Instances/Shared/RemoveInstanceButton.js:174
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:111
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:63
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:66
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:80
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:101
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:107
-#: screens/Setting/shared/RevertAllAlert.js:32
-#: screens/Setting/shared/RevertFormActionGroup.js:31
-#: screens/Setting/shared/RevertFormActionGroup.js:37
-#: screens/Setting/shared/SharedFields.js:133
-#: screens/Setting/shared/SharedFields.js:139
-#: screens/Setting/shared/SharedFields.js:346
-#: screens/Team/TeamRoles/TeamRolesList.js:228
-#: screens/Team/TeamRoles/TeamRolesList.js:231
-#: screens/Template/Survey/SurveyList.js:78
-#: screens/Template/Survey/SurveyReorderModal.js:211
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:31
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:39
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:45
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:50
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:164
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:167
-#: screens/User/UserRoles/UserRolesList.js:224
-#: screens/User/UserRoles/UserRolesList.js:227
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:104
-msgid "Cancel"
-msgstr "取消"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:300
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:112
-msgid "Cancel Inventory Source Sync"
-msgstr "取消清单源同步"
-
-#: components/JobCancelButton/JobCancelButton.js:69
-#: screens/Job/JobOutput/JobOutput.js:802
-#: screens/Job/JobOutput/JobOutput.js:803
-msgid "Cancel Job"
-msgstr "取消作业"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:321
-#: screens/Project/ProjectList/ProjectListItem.js:230
-msgid "Cancel Project Sync"
-msgstr "取消项目同步"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:302
-#: screens/Project/ProjectDetail/ProjectDetail.js:323
-msgid "Cancel Sync"
-msgstr "取消同步"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:322
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:327
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:95
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:101
-msgid "Cancel Workflow"
-msgstr "取消工作流"
-
-#: screens/Job/JobOutput/JobOutput.js:810
-#: screens/Job/JobOutput/JobOutput.js:813
-msgid "Cancel job"
-msgstr "取消作业"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:42
-msgid "Cancel link changes"
-msgstr "取消链路更改"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:34
-msgid "Cancel link removal"
-msgstr "取消链接删除"
-
-#: components/Lookup/Lookup.js:207
-msgid "Cancel lookup"
-msgstr "取消查找"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:28
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:47
-msgid "Cancel node removal"
-msgstr "取消节点删除"
-
-#: screens/Setting/shared/RevertAllAlert.js:29
-msgid "Cancel revert"
-msgstr "取消恢复"
-
-#: components/JobList/JobListCancelButton.js:93
-msgid "Cancel selected job"
-msgstr "取消所选作业"
-
-#: components/JobList/JobListCancelButton.js:94
-msgid "Cancel selected jobs"
-msgstr "取消所选作业"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:77
-msgid "Cancel subscription edit"
-msgstr "取消订阅编辑"
-
-#: components/JobList/JobListItem.js:113
-#: screens/Job/JobDetail/JobDetail.js:600
-#: screens/Job/JobOutput/shared/OutputToolbar.js:137
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:89
-msgid "Cancel {0}"
-msgstr "取消 {0}"
-
-#: components/JobList/JobList.js:234
-#: components/StatusLabel/StatusLabel.js:54
-#: components/Workflow/WorkflowNodeHelp.js:111
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:212
-msgid "Canceled"
-msgstr "已取消"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:129
-msgid ""
-"Cannot enable log aggregator without providing\n"
-"logging aggregator host and logging aggregator type."
-msgstr "在不提供日志记录聚合器主机和日志记录聚合器类型的情况下,无法启用日志聚合器。"
-
-#: screens/Instances/InstanceList/InstanceList.js:199
-#: screens/Instances/InstancePeers/InstancePeerList.js:94
-msgid "Cannot run health check on hop nodes."
-msgstr "无法在跃点节点上运行健康检查。"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:199
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:74
-#: screens/TopologyView/Tooltip.js:312
-msgid "Capacity"
-msgstr "容量"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:225
-#: screens/InstanceGroup/Instances/InstanceList.js:269
-#: screens/InstanceGroup/Instances/InstanceListItem.js:143
-#: screens/Instances/InstanceDetail/InstanceDetail.js:267
-#: screens/Instances/InstanceList/InstanceList.js:204
-#: screens/Instances/InstanceList/InstanceListItem.js:153
-msgid "Capacity Adjustment"
-msgstr "容量调整"
-
-#: components/Search/LookupTypeInput.js:59
-msgid "Case-insensitive version of contains"
-msgstr "包含不区分大小写的版本"
-
-#: components/Search/LookupTypeInput.js:87
-msgid "Case-insensitive version of endswith."
-msgstr "结尾不区分大小写的版本。"
-
-#: components/Search/LookupTypeInput.js:45
-msgid "Case-insensitive version of exact."
-msgstr "完全相同不区分大小写的版本。"
-
-#: components/Search/LookupTypeInput.js:100
-msgid "Case-insensitive version of regex."
-msgstr "regex 不区分大小写的版本。"
-
-#: components/Search/LookupTypeInput.js:73
-msgid "Case-insensitive version of startswith."
-msgstr "开头不区分大小写的版本。"
-
-#: screens/Project/shared/Project.helptext.js:14
-msgid ""
-"Change PROJECTS_ROOT when deploying\n"
-"{brandName} to change this location."
-msgstr "部署 {brandName} 时更改 PROJECTS_ROOT 以更改此位置。"
-
-#: components/StatusLabel/StatusLabel.js:55
-#: screens/Job/JobOutput/shared/HostStatusBar.js:43
-msgid "Changed"
-msgstr "已更改"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:53
-msgid "Changes"
-msgstr "更改"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:258
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:266
-msgid "Channel"
-msgstr "频道"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:138
-#: screens/Template/shared/JobTemplateForm.js:218
-msgid "Check"
-msgstr "检查"
-
-#: components/Search/LookupTypeInput.js:134
-msgid "Check whether the given field or related object is null; expects a boolean value."
-msgstr "检查给定字段或相关对象是否为 null;需要布尔值。"
-
-#: components/Search/LookupTypeInput.js:140
-msgid "Check whether the given field's value is present in the list provided; expects a comma-separated list of items."
-msgstr "检查给定字段的值是否出现在提供的列表中;需要一个以逗号分隔的项目列表。"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:32
-msgid "Choose a .json file"
-msgstr "选择 .json 文件"
-
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:78
-msgid "Choose a Notification Type"
-msgstr "选择通知类型"
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:25
-msgid "Choose a Playbook Directory"
-msgstr "选择 Playbook 目录"
-
-#: screens/Project/shared/ProjectForm.js:268
-msgid "Choose a Source Control Type"
-msgstr "选择源控制类型"
-
-#: screens/Template/shared/WebhookSubForm.js:100
-msgid "Choose a Webhook Service"
-msgstr "选择 Webhook 服务"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:131
-#: screens/Template/shared/JobTemplateForm.js:211
-msgid "Choose a job type"
-msgstr "选择作业类型"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:82
-msgid "Choose a module"
-msgstr "选择模块"
-
-#: screens/Inventory/shared/InventorySourceForm.js:139
-msgid "Choose a source"
-msgstr "选择一个源"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:490
-msgid "Choose an HTTP method"
-msgstr "选择 HTTP 方法"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:46
-msgid ""
-"Choose an answer type or format you want as the prompt for the user.\n"
-"Refer to the Ansible Controller Documentation for more additional\n"
-"information about each option."
-msgstr "选择您想要作为用户提示的回答类型或格式。请参阅 Ansible 控制器文档来了解每个选项的更多其他信息。"
-
-#: components/AddRole/SelectRoleStep.js:20
-msgid "Choose roles to apply to the selected resources. Note that all selected roles will be applied to all selected resources."
-msgstr "选择应用到所选资源的角色。请注意,所有选择的角色将应用到所有选择的资源。"
-
-#: components/AddRole/SelectResourceStep.js:81
-msgid "Choose the resources that will be receiving new roles. You'll be able to select the roles to apply in the next step. Note that the resources chosen here will receive all roles chosen in the next step."
-msgstr "选择将获得新角色的资源。您可以选择下一步中要应用的角色。请注意,此处选择的资源将接收下一步中选择的所有角色。"
-
-#: components/AddRole/AddResourceRole.js:174
-msgid "Choose the type of resource that will be receiving new roles. For example, if you'd like to add new roles to a set of users please choose Users and click Next. You'll be able to select the specific resources in the next step."
-msgstr "选择将获得新角色的资源类型。例如,如果您想为一组用户添加新角色,请选择用户并点击下一步。您可以选择下一步中的具体资源。"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:74
-msgid "Clean"
-msgstr "清理"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:95
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:116
-msgid "Clear"
-msgstr "清除"
-
-#: components/DataListToolbar/DataListToolbar.js:95
-#: screens/Job/JobOutput/JobOutputSearch.js:144
-msgid "Clear all filters"
-msgstr "清除所有过滤器"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:247
-msgid "Clear subscription"
-msgstr "清除订阅"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:252
-msgid "Clear subscription selection"
-msgstr "清除订阅选择"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerGraph.js:245
-msgid "Click an available node to create a new link. Click outside the graph to cancel."
-msgstr "点一个可用的节点来创建新链接。点击图形之外来取消。"
-
-#: screens/TopologyView/Tooltip.js:191
-msgid "Click on a node icon to display the details."
-msgstr "点击节点图标显示详细信息。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:134
-msgid "Click the Edit button below to reconfigure the node."
-msgstr "点击下面的编辑按钮重新配置节点。"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:71
-msgid "Click this button to verify connection to the secret management system using the selected credential and specified inputs."
-msgstr "点击这个按钮使用所选凭证和指定的输入验证到 secret 管理系统的连接。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:179
-msgid "Click to create a new link to this node."
-msgstr "点击以创建到此节点的新链接。"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:251
-msgid "Click to download bundle"
-msgstr "点下载捆绑包"
-
-#: screens/Template/Survey/SurveyToolbar.js:64
-msgid "Click to rearrange the order of the survey questions"
-msgstr "单击以重新安排调查问题的顺序"
-
-#: screens/Template/Survey/MultipleChoiceField.js:117
-msgid "Click to toggle default value"
-msgstr "点击以切换默认值"
-
-#: components/Workflow/WorkflowNodeHelp.js:202
-msgid "Click to view job details"
-msgstr "点击以查看作业详情"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:89
-#: screens/Application/Applications.js:84
-msgid "Client ID"
-msgstr "客户端 ID"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:289
-msgid "Client Identifier"
-msgstr "客户端标识符"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:314
-msgid "Client identifier"
-msgstr "客户端标识符"
-
-#: screens/Application/Applications.js:97
-msgid "Client secret"
-msgstr "客户端 secret"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:100
-#: screens/Application/shared/ApplicationForm.js:127
-msgid "Client type"
-msgstr "客户端类型"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:105
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:169
-msgid "Close"
-msgstr "关闭"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:123
-msgid "Close subscription modal"
-msgstr "关闭订阅模态"
-
-#: components/CredentialChip/CredentialChip.js:11
-msgid "Cloud"
-msgstr "云"
-
-#: components/ExpandCollapse/ExpandCollapse.js:41
-msgid "Collapse"
-msgstr "折叠"
-
-#: screens/Job/JobOutput/PageControls.js:50
-msgid "Collapse all job events"
-msgstr "折叠所有作业事件"
-
-#: screens/Job/JobOutput/shared/JobEventLineToggle.js:39
-msgid "Collapse section"
-msgstr "折叠部分"
-
-#: components/JobList/JobList.js:214
-#: components/JobList/JobListItem.js:45
-#: screens/Job/JobOutput/HostEventModal.js:129
-msgid "Command"
-msgstr "命令"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:66
-msgid "Compliant"
-msgstr "合规"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:68
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:36
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:132
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:58
-#: screens/Template/shared/JobTemplateForm.js:591
-msgid "Concurrent Jobs"
-msgstr "并发作业"
-
-#: screens/Template/shared/JobTemplate.helptext.js:38
-msgid "Concurrent jobs: If enabled, simultaneous runs of this job template will be allowed."
-msgstr "并行作业:如果启用,将允许同时运行此作业模板。"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:25
-msgid "Concurrent jobs: If enabled, simultaneous runs of this workflow job template will be allowed."
-msgstr "并行作业:如果启用,将允许同时运行此工作流作业模板。"
-
-#: screens/Setting/shared/SharedFields.js:121
-#: screens/Setting/shared/SharedFields.js:127
-#: screens/Setting/shared/SharedFields.js:336
-msgid "Confirm"
-msgstr "确认"
-
-#: components/DeleteButton/DeleteButton.js:107
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:95
-msgid "Confirm Delete"
-msgstr "确认删除"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:191
-msgid "Confirm Disable Local Authorization"
-msgstr "确认禁用本地授权"
-
-#: screens/User/shared/UserForm.js:99
-msgid "Confirm Password"
-msgstr "确认密码"
-
-#: components/JobCancelButton/JobCancelButton.js:86
-msgid "Confirm cancel job"
-msgstr "确认取消作业"
-
-#: components/JobCancelButton/JobCancelButton.js:90
-msgid "Confirm cancellation"
-msgstr "确认取消"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:26
-msgid "Confirm delete"
-msgstr "确认删除"
-
-#: screens/User/UserRoles/UserRolesList.js:215
-msgid "Confirm disassociate"
-msgstr "确认解除关联"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:24
-msgid "Confirm link removal"
-msgstr "确认链接删除"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:37
-msgid "Confirm node removal"
-msgstr "确认节点删除"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:18
-msgid "Confirm removal of all nodes"
-msgstr "确认删除所有节点"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:160
-msgid "Confirm remove"
-msgstr "确认删除"
-
-#: screens/Setting/shared/RevertAllAlert.js:20
-msgid "Confirm revert all"
-msgstr "确认全部恢复"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:91
-msgid "Confirm selection"
-msgstr "确认选择"
-
-#: screens/Job/JobDetail/JobDetail.js:366
-msgid "Container Group"
-msgstr "容器组"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:47
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:57
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:68
-msgid "Container group"
-msgstr "容器组"
-
-#: screens/InstanceGroup/ContainerGroup.js:84
-msgid "Container group not found."
-msgstr "未找到容器组。"
-
-#: components/LaunchPrompt/LaunchPrompt.js:153
-#: components/Schedule/shared/SchedulePromptableFields.js:120
-msgid "Content Loading"
-msgstr "内容加载"
-
-#: components/PromptDetail/PromptProjectDetail.js:130
-#: screens/Project/ProjectDetail/ProjectDetail.js:240
-#: screens/Project/shared/ProjectForm.js:290
-msgid "Content Signature Validation Credential"
-msgstr "内容签名验证凭证"
-
-#: components/AppContainer/AppContainer.js:142
-msgid "Continue"
-msgstr "继续"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:207
-#: screens/Instances/InstanceList/InstanceList.js:151
-msgid "Control"
-msgstr "控制"
-
-#: screens/TopologyView/Legend.js:77
-msgid "Control node"
-msgstr "控制节点"
-
-#: screens/Inventory/shared/Inventory.helptext.js:79
-msgid ""
-"Control the level of output Ansible\n"
-"will produce for inventory source update jobs."
-msgstr "控制 Ansible 为清单源更新作业生成的输出级别。"
-
-#: screens/Job/Job.helptext.js:15
-#: screens/Template/shared/JobTemplate.helptext.js:16
-msgid "Control the level of output ansible will produce as the playbook executes."
-msgstr "控制 ansible 在 playbook 执行时生成的输出级别。"
-
-#: screens/Job/JobDetail/JobDetail.js:351
-msgid "Controller Node"
-msgstr "控制器节点"
-
-#: components/PromptDetail/PromptDetail.js:130
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:225
-msgid "Convergence"
-msgstr "趋同"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:256
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:257
-msgid "Convergence select"
-msgstr "趋同选择"
-
-#: components/CopyButton/CopyButton.js:40
-msgid "Copy"
-msgstr "复制"
-
-#: screens/Credential/CredentialList/CredentialListItem.js:80
-msgid "Copy Credential"
-msgstr "复制凭证"
-
-#: components/CopyButton/CopyButton.js:48
-msgid "Copy Error"
-msgstr "复制错误"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:104
-msgid "Copy Execution Environment"
-msgstr "复制执行环境"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:154
-msgid "Copy Inventory"
-msgstr "复制清单"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:152
-msgid "Copy Notification Template"
-msgstr "复制通知模板"
-
-#: screens/Project/ProjectList/ProjectListItem.js:262
-msgid "Copy Project"
-msgstr "复制项目"
-
-#: components/TemplateList/TemplateListItem.js:248
-msgid "Copy Template"
-msgstr "复制模板"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:202
-#: screens/Project/ProjectList/ProjectListItem.js:98
-msgid "Copy full revision to clipboard."
-msgstr "将完整修订复制到剪贴板。"
-
-#: components/About/About.js:35
-msgid "Copyright"
-msgstr "版权"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:231
-#: components/MultiSelect/TagMultiSelect.js:62
-#: screens/Credential/shared/CredentialFormFields/BecomeMethodField.js:66
-#: screens/Inventory/shared/InventoryForm.js:91
-#: screens/Template/shared/JobTemplateForm.js:396
-#: screens/Template/shared/WorkflowJobTemplateForm.js:204
-msgid "Create"
-msgstr "创建"
-
-#: screens/Application/Applications.js:27
-#: screens/Application/Applications.js:36
-msgid "Create New Application"
-msgstr "创建新应用"
-
-#: screens/Credential/Credentials.js:15
-#: screens/Credential/Credentials.js:25
-msgid "Create New Credential"
-msgstr "创建新凭证"
-
-#: screens/Host/Hosts.js:15
-#: screens/Host/Hosts.js:24
-msgid "Create New Host"
-msgstr "创建新主机"
-
-#: screens/Template/Templates.js:18
-msgid "Create New Job Template"
-msgstr "创建新作业模板"
-
-#: screens/NotificationTemplate/NotificationTemplates.js:15
-#: screens/NotificationTemplate/NotificationTemplates.js:22
-msgid "Create New Notification Template"
-msgstr "创建新通知模板"
-
-#: screens/Organization/Organizations.js:17
-#: screens/Organization/Organizations.js:27
-msgid "Create New Organization"
-msgstr "创建新机构"
-
-#: screens/Project/Projects.js:13
-#: screens/Project/Projects.js:23
-msgid "Create New Project"
-msgstr "创建新项目"
-
-#: screens/Inventory/Inventories.js:91
-#: screens/ManagementJob/ManagementJobs.js:24
-#: screens/Project/Projects.js:32
-#: screens/Template/Templates.js:52
-msgid "Create New Schedule"
-msgstr "创建新调度"
-
-#: screens/Team/Teams.js:16
-#: screens/Team/Teams.js:26
-msgid "Create New Team"
-msgstr "创建新团队"
-
-#: screens/User/Users.js:16
-#: screens/User/Users.js:27
-msgid "Create New User"
-msgstr "创建新用户"
-
-#: screens/Template/Templates.js:19
-msgid "Create New Workflow Template"
-msgstr "创建新工作流模板"
-
-#: screens/Host/HostList/SmartInventoryButton.js:26
-msgid "Create a new Smart Inventory with the applied filter"
-msgstr "使用应用的过滤器创建新智能清单"
-
-#: screens/Instances/Instances.js:14
-msgid "Create new Instance"
-msgstr "创建新实例"
-
-#: screens/InstanceGroup/InstanceGroups.js:18
-#: screens/InstanceGroup/InstanceGroups.js:28
-msgid "Create new container group"
-msgstr "创建新容器组"
-
-#: screens/CredentialType/CredentialTypes.js:23
-msgid "Create new credential Type"
-msgstr "创建新凭证类型"
-
-#: screens/CredentialType/CredentialTypes.js:14
-msgid "Create new credential type"
-msgstr "创建新凭证类型"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:14
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:23
-msgid "Create new execution environment"
-msgstr "创建新执行环境"
-
-#: screens/Inventory/Inventories.js:75
-#: screens/Inventory/Inventories.js:82
-msgid "Create new group"
-msgstr "创建新组"
-
-#: screens/Inventory/Inventories.js:66
-#: screens/Inventory/Inventories.js:80
-msgid "Create new host"
-msgstr "创建新主机"
-
-#: screens/InstanceGroup/InstanceGroups.js:17
-#: screens/InstanceGroup/InstanceGroups.js:27
-msgid "Create new instance group"
-msgstr "创建新实例组"
-
-#: screens/Inventory/Inventories.js:18
-msgid "Create new inventory"
-msgstr "创建新清单"
-
-#: screens/Inventory/Inventories.js:19
-msgid "Create new smart inventory"
-msgstr "创建新智能清单"
-
-#: screens/Inventory/Inventories.js:85
-msgid "Create new source"
-msgstr "创建新源"
-
-#: screens/User/Users.js:35
-msgid "Create user token"
-msgstr "创建用户令牌"
-
-#: components/Lookup/ApplicationLookup.js:115
-#: components/PromptDetail/PromptDetail.js:154
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:406
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:105
-#: screens/Credential/CredentialDetail/CredentialDetail.js:257
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:90
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:103
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:151
-#: screens/Host/HostDetail/HostDetail.js:86
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:67
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:93
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:173
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:43
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:81
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:277
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:149
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:46
-#: screens/Job/JobDetail/JobDetail.js:534
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:393
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:115
-#: screens/Project/ProjectDetail/ProjectDetail.js:292
-#: screens/Team/TeamDetail/TeamDetail.js:47
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:353
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:187
-#: screens/User/UserDetail/UserDetail.js:82
-#: screens/User/UserTokenDetail/UserTokenDetail.js:61
-#: screens/User/UserTokenList/UserTokenList.js:150
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:199
-msgid "Created"
-msgstr "创建"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:122
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:112
-#: components/AddRole/AddResourceRole.js:57
-#: components/AssociateModal/AssociateModal.js:144
-#: components/LaunchPrompt/steps/CredentialsStep.js:173
-#: components/LaunchPrompt/steps/InventoryStep.js:89
-#: components/Lookup/CredentialLookup.js:194
-#: components/Lookup/InventoryLookup.js:152
-#: components/Lookup/InventoryLookup.js:207
-#: components/Lookup/MultiCredentialsLookup.js:194
-#: components/Lookup/OrganizationLookup.js:134
-#: components/Lookup/ProjectLookup.js:151
-#: components/NotificationList/NotificationList.js:206
-#: components/RelatedTemplateList/RelatedTemplateList.js:166
-#: components/Schedule/ScheduleList/ScheduleList.js:198
-#: components/TemplateList/TemplateList.js:226
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:27
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:58
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:104
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:127
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:173
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:196
-#: screens/Credential/CredentialList/CredentialList.js:150
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:96
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:132
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:102
-#: screens/Host/HostGroups/HostGroupsList.js:164
-#: screens/Host/HostList/HostList.js:157
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:199
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:129
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:174
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:128
-#: screens/Inventory/InventoryList/InventoryList.js:199
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:185
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:94
-#: screens/Organization/OrganizationList/OrganizationList.js:131
-#: screens/Project/ProjectList/ProjectList.js:213
-#: screens/Team/TeamList/TeamList.js:130
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:161
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:112
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:108
-msgid "Created By (Username)"
-msgstr "创建者(用户名)"
-
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:81
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:147
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:73
-msgid "Created by (username)"
-msgstr "创建者(用户名)"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:54
-#: components/AdHocCommands/useAdHocCredentialStep.js:24
-#: components/PromptDetail/PromptInventorySourceDetail.js:107
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:40
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:52
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:50
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:258
-#: screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.js:38
-#: screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/GCESubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.js:38
-#: screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:84
-#: screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.js:36
-#: screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.js:38
-#: screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.js:38
-#: util/getRelatedResourceDeleteDetails.js:167
-msgid "Credential"
-msgstr "凭证"
-
-#: util/getRelatedResourceDeleteDetails.js:74
-msgid "Credential Input Sources"
-msgstr "凭证输入源"
-
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:83
-#: components/Lookup/InstanceGroupsLookup.js:108
-msgid "Credential Name"
-msgstr "凭证名称"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:241
-#: screens/Credential/CredentialList/CredentialList.js:158
-#: screens/Credential/shared/CredentialForm.js:128
-#: screens/Credential/shared/CredentialForm.js:196
-msgid "Credential Type"
-msgstr "凭证类型"
-
-#: routeConfig.js:117
-#: screens/ActivityStream/ActivityStream.js:192
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:118
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:161
-#: screens/CredentialType/CredentialTypes.js:13
-#: screens/CredentialType/CredentialTypes.js:22
-msgid "Credential Types"
-msgstr "凭证类型"
-
-#: screens/Credential/CredentialList/CredentialList.js:113
-msgid "Credential copied successfully"
-msgstr "成功复制的凭证"
-
-#: screens/Credential/Credential.js:98
-msgid "Credential not found."
-msgstr "未找到凭证。"
-
-#: components/AdHocCommands/useAdHocCredentialPasswordStep.js:23
-#: components/LaunchPrompt/steps/useCredentialPasswordsStep.js:28
-msgid "Credential passwords"
-msgstr "凭证密码"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:53
-msgid "Credential to authenticate with Kubernetes or OpenShift"
-msgstr "使用 Kubernetes 或 OpenShift 进行身份验证的凭证"
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:57
-msgid "Credential to authenticate with Kubernetes or OpenShift. Must be of type \"Kubernetes/OpenShift API Bearer Token\". If left blank, the underlying Pod's service account will be used."
-msgstr "与 Kubernetes 或 OpenShift 进行身份验证的凭证。必须为“Kubernetes/OpenShift API Bearer Token”类型。如果留空,底层 Pod 的服务帐户会被使用。"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:21
-msgid "Credential to authenticate with a protected container registry."
-msgstr "使用受保护的容器注册表进行身份验证的凭证。"
-
-#: screens/CredentialType/CredentialType.js:76
-msgid "Credential type not found."
-msgstr "未找到凭证类型。"
-
-#: components/JobList/JobListItem.js:260
-#: components/LaunchPrompt/steps/CredentialsStep.js:190
-#: components/LaunchPrompt/steps/useCredentialsStep.js:62
-#: components/Lookup/MultiCredentialsLookup.js:138
-#: components/Lookup/MultiCredentialsLookup.js:211
-#: components/PromptDetail/PromptDetail.js:192
-#: components/PromptDetail/PromptJobTemplateDetail.js:191
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:528
-#: components/TemplateList/TemplateListItem.js:323
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:77
-#: routeConfig.js:70
-#: screens/ActivityStream/ActivityStream.js:167
-#: screens/Credential/CredentialList/CredentialList.js:195
-#: screens/Credential/Credentials.js:14
-#: screens/Credential/Credentials.js:24
-#: screens/Job/JobDetail/JobDetail.js:429
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:374
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:49
-#: screens/Template/shared/JobTemplateForm.js:372
-#: util/getRelatedResourceDeleteDetails.js:91
-msgid "Credentials"
-msgstr "凭证"
-
-#: components/LaunchPrompt/steps/credentialsValidator.js:52
-msgid "Credentials that require passwords on launch are not permitted. Please remove or replace the following credentials with a credential of the same type in order to proceed: {0}"
-msgstr "不允许在启动时需要密码的凭证。请删除或替换为同一类型的凭证以便继续: {0}"
-
-#: components/Pagination/Pagination.js:34
-msgid "Current page"
-msgstr "当前页"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:85
-msgid "Custom Kubernetes or OpenShift Pod specification."
-msgstr "自定义 Kubernetes 或 OpenShift Pod 的规格。"
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:79
-msgid "Custom pod spec"
-msgstr "自定义 pod 规格"
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:79
-#: screens/Organization/OrganizationList/OrganizationListItem.js:55
-#: screens/Project/ProjectList/ProjectListItem.js:188
-msgid "Custom virtual environment {0} must be replaced by an execution environment."
-msgstr "自定义虚拟环境 {0} 必须替换为一个执行环境。"
-
-#: components/TemplateList/TemplateListItem.js:163
-msgid "Custom virtual environment {0} must be replaced by an execution environment. For more information about migrating to execution environments see <0>the documentation.0>"
-msgstr "自定义虚拟环境 {0} 必须替换为执行环境。有关迁移到执行环境的更多信息,请参阅<0>文档0>。"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:73
-msgid "Custom virtual environment {virtualEnvironment} must be replaced by an execution environment. For more information about migrating to execution environments see <0>the documentation.0>"
-msgstr "自定义虚拟环境 {virtualEnvironment} 必须替换为执行环境。有关迁移到执行环境的更多信息,请参阅<0>文档0>。"
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:61
-msgid "Customize messages…"
-msgstr "自定义消息…"
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:65
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:66
-msgid "Customize pod specification"
-msgstr "自定义 Pod 规格"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputNode.js:109
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:212
-msgid "DELETED"
-msgstr "已删除"
-
-#: routeConfig.js:34
-#: screens/Dashboard/Dashboard.js:74
-msgid "Dashboard"
-msgstr "仪表盘"
-
-#: screens/ActivityStream/ActivityStream.js:147
-msgid "Dashboard (all activity)"
-msgstr "仪表盘(所有活动)"
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:75
-msgid "Data retention period"
-msgstr "数据保留的周期"
-
-#: screens/Dashboard/shared/LineChart.js:168
-msgid "Date"
-msgstr "日期"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:184
-#: components/Schedule/shared/FrequencyDetailSubform.js:177
-#: components/Schedule/shared/FrequencyDetailSubform.js:356
-#: components/Schedule/shared/FrequencyDetailSubform.js:460
-#: components/Schedule/shared/ScheduleFormFields.js:127
-#: components/Schedule/shared/ScheduleFormFields.js:187
-msgid "Day"
-msgstr "天"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:130
-msgid "Day {0}"
-msgstr "天 {0}"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:399
-#: components/Schedule/shared/ScheduleFormFields.js:136
-msgid "Days of Data to Keep"
-msgstr "保留数据的天数"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/DaysToKeepStep.js:28
-msgid "Days of data to be retained"
-msgstr "数据被保留的天数"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:167
-msgid "Days remaining"
-msgstr "剩余的天数"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useDaysToKeepStep.js:35
-msgid "Days to keep"
-msgstr "保存的天数"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:102
-msgid "Debug"
-msgstr "调试"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:170
-#: components/Schedule/shared/FrequencyDetailSubform.js:153
-msgid "December"
-msgstr "12 月"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:102
-#: screens/Template/Survey/SurveyList.js:104
-#: screens/Template/Survey/SurveyList.js:104
-#: screens/Template/Survey/SurveyListItem.js:63
-msgid "Default"
-msgstr "默认"
-
-#: screens/Template/Survey/SurveyReorderModal.js:219
-#: screens/Template/Survey/SurveyReorderModal.js:219
-#: screens/Template/Survey/SurveyReorderModal.js:241
-msgid "Default Answer(s)"
-msgstr "默认回答"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:40
-msgid "Default Execution Environment"
-msgstr "默认执行环境"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:238
-#: screens/Template/Survey/SurveyQuestionForm.js:246
-#: screens/Template/Survey/SurveyQuestionForm.js:253
-msgid "Default answer"
-msgstr "默认回答"
-
-#: screens/Setting/SettingList.js:103
-msgid "Define system-level features and functions"
-msgstr "定义系统级的特性和功能"
-
-#: components/DeleteButton/DeleteButton.js:75
-#: components/DeleteButton/DeleteButton.js:80
-#: components/DeleteButton/DeleteButton.js:90
-#: components/DeleteButton/DeleteButton.js:94
-#: components/DeleteButton/DeleteButton.js:114
-#: components/PaginatedTable/ToolbarDeleteButton.js:158
-#: components/PaginatedTable/ToolbarDeleteButton.js:235
-#: components/PaginatedTable/ToolbarDeleteButton.js:246
-#: components/PaginatedTable/ToolbarDeleteButton.js:250
-#: components/PaginatedTable/ToolbarDeleteButton.js:273
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:29
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:646
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:128
-#: screens/Credential/CredentialDetail/CredentialDetail.js:306
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:124
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:134
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:115
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:127
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:201
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:101
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:316
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:174
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:64
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:68
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:73
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:78
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:102
-#: screens/Job/JobDetail/JobDetail.js:612
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:436
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:199
-#: screens/Project/ProjectDetail/ProjectDetail.js:340
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:80
-#: screens/Team/TeamDetail/TeamDetail.js:70
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:545
-#: screens/Template/Survey/SurveyList.js:66
-#: screens/Template/Survey/SurveyToolbar.js:93
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:269
-#: screens/User/UserDetail/UserDetail.js:107
-#: screens/User/UserTokenDetail/UserTokenDetail.js:78
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:340
-msgid "Delete"
-msgstr "删除"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:130
-msgid "Delete All Groups and Hosts"
-msgstr "删除所有组和主机"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:300
-msgid "Delete Credential"
-msgstr "删除凭证"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:127
-msgid "Delete Execution Environment"
-msgstr "删除执行环境"
-
-#: screens/Host/HostDetail/HostDetail.js:114
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:109
-msgid "Delete Host"
-msgstr "删除主机"
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:196
-msgid "Delete Inventory"
-msgstr "删除清单"
-
-#: screens/Job/JobDetail/JobDetail.js:608
-#: screens/Job/JobOutput/shared/OutputToolbar.js:195
-#: screens/Job/JobOutput/shared/OutputToolbar.js:199
-msgid "Delete Job"
-msgstr "删除作业"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:539
-msgid "Delete Job Template"
-msgstr "删除作业模板"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:432
-msgid "Delete Notification"
-msgstr "删除通知"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:193
-msgid "Delete Organization"
-msgstr "删除机构"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:334
-msgid "Delete Project"
-msgstr "删除项目"
-
-#: screens/Template/Survey/SurveyList.js:52
-msgid "Delete Questions"
-msgstr "删除问题"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:642
-msgid "Delete Schedule"
-msgstr "删除调度"
-
-#: screens/Template/Survey/SurveyList.js:52
-msgid "Delete Survey"
-msgstr "删除问卷调查"
-
-#: screens/Team/TeamDetail/TeamDetail.js:66
-msgid "Delete Team"
-msgstr "删除团队"
-
-#: screens/User/UserDetail/UserDetail.js:103
-msgid "Delete User"
-msgstr "删除用户"
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:74
-msgid "Delete User Token"
-msgstr "删除用户令牌"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:336
-msgid "Delete Workflow Approval"
-msgstr "删除工作流批准"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:263
-msgid "Delete Workflow Job Template"
-msgstr "删除工作流作业模板"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:138
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:141
-msgid "Delete all nodes"
-msgstr "删除所有节点"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:124
-msgid "Delete application"
-msgstr "创建应用"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:116
-msgid "Delete credential type"
-msgstr "删除凭证类型"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:249
-msgid "Delete error"
-msgstr "删除错误"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:109
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:121
-msgid "Delete instance group"
-msgstr "删除实例组"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:310
-msgid "Delete inventory source"
-msgstr "删除清单源"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:170
-msgid "Delete smart inventory"
-msgstr "删除智能清单"
-
-#: screens/Template/Survey/SurveyToolbar.js:83
-msgid "Delete survey question"
-msgstr "删除问卷调查问题"
-
-#: screens/Project/shared/Project.helptext.js:114
-msgid ""
-"Delete the local repository in its entirety prior to\n"
-"performing an update. Depending on the size of the\n"
-"repository this may significantly increase the amount\n"
-"of time required to complete an update."
-msgstr "在进行更新前删除整个本地存储库。根据存储库的大小,这可能会显著增加完成更新所需的时间。"
-
-#: components/PromptDetail/PromptProjectDetail.js:51
-#: screens/Project/ProjectDetail/ProjectDetail.js:100
-msgid "Delete the project before syncing"
-msgstr "在同步前删除项目"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:83
-msgid "Delete this link"
-msgstr "删除此链接"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:274
-msgid "Delete this node"
-msgstr "删除此节点"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:163
-msgid "Delete {pluralizedItemName}?"
-msgstr "删除 {pluralizedItemName}?"
-
-#: components/DetailList/DeletedDetail.js:19
-#: components/Workflow/WorkflowNodeHelp.js:157
-#: components/Workflow/WorkflowNodeHelp.js:193
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:231
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:49
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:60
-msgid "Deleted"
-msgstr "已删除"
-
-#: components/TemplateList/TemplateList.js:296
-#: screens/Credential/CredentialList/CredentialList.js:211
-#: screens/Inventory/InventoryList/InventoryList.js:284
-#: screens/Project/ProjectList/ProjectList.js:290
-msgid "Deletion Error"
-msgstr "删除错误"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:202
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:227
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:219
-msgid "Deletion error"
-msgstr "删除错误"
-
-#: components/StatusLabel/StatusLabel.js:40
-msgid "Denied"
-msgstr "已拒绝"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:21
-msgid "Denied - {0}. See the Activity Stream for more information."
-msgstr "拒绝 - {0}。详情请查看活动流。"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:17
-msgid "Denied by {0} - {1}"
-msgstr "已拒绝 {0} - {1}"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:89
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:35
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:42
-msgid "Deny"
-msgstr "拒绝"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:103
-msgid "Deprecated"
-msgstr "已弃用"
-
-#: components/StatusLabel/StatusLabel.js:60
-#: screens/TopologyView/Legend.js:164
-msgid "Deprovisioning"
-msgstr "取消置备"
-
-#: components/StatusLabel/StatusLabel.js:63
-msgid "Deprovisioning fail"
-msgstr "取消置备失败"
-
-#: components/HostForm/HostForm.js:104
-#: components/Lookup/ApplicationLookup.js:105
-#: components/Lookup/ApplicationLookup.js:123
-#: components/Lookup/HostFilterLookup.js:423
-#: components/Lookup/HostListItem.js:9
-#: components/NotificationList/NotificationList.js:186
-#: components/PromptDetail/PromptDetail.js:119
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:327
-#: components/Schedule/ScheduleList/ScheduleList.js:194
-#: components/Schedule/shared/ScheduleFormFields.js:80
-#: components/TemplateList/TemplateList.js:210
-#: components/TemplateList/TemplateListItem.js:271
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:65
-#: screens/Application/ApplicationsList/ApplicationsList.js:123
-#: screens/Application/shared/ApplicationForm.js:62
-#: screens/Credential/CredentialDetail/CredentialDetail.js:223
-#: screens/Credential/CredentialList/CredentialList.js:146
-#: screens/Credential/shared/CredentialForm.js:169
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:72
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:128
-#: screens/CredentialType/shared/CredentialTypeForm.js:29
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:60
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:159
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:127
-#: screens/Host/HostDetail/HostDetail.js:75
-#: screens/Host/HostList/HostList.js:153
-#: screens/Host/HostList/HostList.js:170
-#: screens/Host/HostList/HostListItem.js:57
-#: screens/Instances/Shared/InstanceForm.js:26
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:93
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:35
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:216
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:80
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:40
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:124
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:139
-#: screens/Inventory/InventoryList/InventoryList.js:195
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:198
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:104
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:36
-#: screens/Inventory/shared/InventoryForm.js:58
-#: screens/Inventory/shared/InventoryGroupForm.js:40
-#: screens/Inventory/shared/InventorySourceForm.js:108
-#: screens/Inventory/shared/SmartInventoryForm.js:55
-#: screens/Job/JobOutput/HostEventModal.js:112
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:101
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:72
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:109
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:127
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:49
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:95
-#: screens/Organization/OrganizationList/OrganizationList.js:127
-#: screens/Organization/shared/OrganizationForm.js:64
-#: screens/Project/ProjectDetail/ProjectDetail.js:177
-#: screens/Project/ProjectList/ProjectList.js:190
-#: screens/Project/ProjectList/ProjectListItem.js:281
-#: screens/Project/shared/ProjectForm.js:222
-#: screens/Team/TeamDetail/TeamDetail.js:38
-#: screens/Team/TeamList/TeamList.js:122
-#: screens/Team/shared/TeamForm.js:37
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:187
-#: screens/Template/Survey/SurveyQuestionForm.js:171
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:112
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:183
-#: screens/Template/shared/JobTemplateForm.js:251
-#: screens/Template/shared/WorkflowJobTemplateForm.js:117
-#: screens/User/UserOrganizations/UserOrganizationList.js:80
-#: screens/User/UserOrganizations/UserOrganizationListItem.js:18
-#: screens/User/UserTeams/UserTeamList.js:182
-#: screens/User/UserTeams/UserTeamListItem.js:32
-#: screens/User/UserTokenDetail/UserTokenDetail.js:45
-#: screens/User/UserTokenList/UserTokenList.js:128
-#: screens/User/UserTokenList/UserTokenList.js:138
-#: screens/User/UserTokenList/UserTokenList.js:188
-#: screens/User/UserTokenList/UserTokenListItem.js:29
-#: screens/User/shared/UserTokenForm.js:59
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:145
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:128
-msgid "Description"
-msgstr "描述"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:325
-msgid "Destination Channels"
-msgstr "目标频道"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:233
-msgid "Destination Channels or Users"
-msgstr "目标频道或用户"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:346
-msgid "Destination SMS Number(s)"
-msgstr "目标 SMS 号码"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:412
-msgid "Destination SMS number(s)"
-msgstr "目标 SMS 号码"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:364
-msgid "Destination channels"
-msgstr "目标频道"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:231
-msgid "Destination channels or users"
-msgstr "目标频道或用户"
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:35
-#: components/ErrorDetail/ErrorDetail.js:88
-#: components/Schedule/Schedule.js:71
-#: screens/Application/Application/Application.js:79
-#: screens/Application/Applications.js:39
-#: screens/Credential/Credential.js:72
-#: screens/Credential/Credentials.js:28
-#: screens/CredentialType/CredentialType.js:63
-#: screens/CredentialType/CredentialTypes.js:26
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:65
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:26
-#: screens/Host/Host.js:58
-#: screens/Host/Hosts.js:27
-#: screens/InstanceGroup/ContainerGroup.js:66
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:180
-#: screens/InstanceGroup/InstanceGroup.js:69
-#: screens/InstanceGroup/InstanceGroups.js:30
-#: screens/InstanceGroup/InstanceGroups.js:38
-#: screens/Instances/Instance.js:29
-#: screens/Instances/Instances.js:24
-#: screens/Inventory/Inventories.js:61
-#: screens/Inventory/Inventories.js:87
-#: screens/Inventory/Inventory.js:64
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:57
-#: screens/Inventory/InventoryHost/InventoryHost.js:73
-#: screens/Inventory/InventorySource/InventorySource.js:83
-#: screens/Inventory/SmartInventory.js:66
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:60
-#: screens/Job/Job.js:130
-#: screens/Job/JobOutput/HostEventModal.js:103
-#: screens/Job/Jobs.js:35
-#: screens/ManagementJob/ManagementJobs.js:26
-#: screens/NotificationTemplate/NotificationTemplate.js:84
-#: screens/NotificationTemplate/NotificationTemplates.js:25
-#: screens/Organization/Organization.js:123
-#: screens/Organization/Organizations.js:30
-#: screens/Project/Project.js:104
-#: screens/Project/Projects.js:26
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:51
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:51
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:65
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:76
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:50
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:97
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:51
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:56
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:52
-#: screens/Setting/Settings.js:45
-#: screens/Setting/Settings.js:48
-#: screens/Setting/Settings.js:52
-#: screens/Setting/Settings.js:55
-#: screens/Setting/Settings.js:58
-#: screens/Setting/Settings.js:61
-#: screens/Setting/Settings.js:64
-#: screens/Setting/Settings.js:67
-#: screens/Setting/Settings.js:70
-#: screens/Setting/Settings.js:73
-#: screens/Setting/Settings.js:76
-#: screens/Setting/Settings.js:85
-#: screens/Setting/Settings.js:86
-#: screens/Setting/Settings.js:87
-#: screens/Setting/Settings.js:88
-#: screens/Setting/Settings.js:89
-#: screens/Setting/Settings.js:90
-#: screens/Setting/Settings.js:98
-#: screens/Setting/Settings.js:101
-#: screens/Setting/Settings.js:104
-#: screens/Setting/Settings.js:107
-#: screens/Setting/Settings.js:110
-#: screens/Setting/Settings.js:113
-#: screens/Setting/Settings.js:116
-#: screens/Setting/Settings.js:119
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:56
-#: screens/Setting/UI/UIDetail/UIDetail.js:66
-#: screens/Team/Team.js:57
-#: screens/Team/Teams.js:29
-#: screens/Template/Template.js:135
-#: screens/Template/Templates.js:43
-#: screens/Template/WorkflowJobTemplate.js:117
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:138
-#: screens/TopologyView/Tooltip.js:187
-#: screens/TopologyView/Tooltip.js:213
-#: screens/User/User.js:64
-#: screens/User/UserToken/UserToken.js:54
-#: screens/User/Users.js:30
-#: screens/User/Users.js:36
-#: screens/WorkflowApproval/WorkflowApproval.js:77
-#: screens/WorkflowApproval/WorkflowApprovals.js:24
-msgid "Details"
-msgstr "详情"
-
-#: screens/Job/JobOutput/HostEventModal.js:100
-msgid "Details tab"
-msgstr "详情标签页"
-
-#: components/Search/AdvancedSearch.js:271
-msgid "Direct Keys"
-msgstr "直接密钥"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:209
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:268
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:313
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:371
-msgid "Disable SSL Verification"
-msgstr "禁用 SSL 验证"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:187
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:240
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:279
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:350
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:461
-msgid "Disable SSL verification"
-msgstr "禁用 SSL 验证"
-
-#: components/InstanceToggle/InstanceToggle.js:56
-#: components/StatusLabel/StatusLabel.js:53
-#: screens/TopologyView/Legend.js:233
-msgid "Disabled"
-msgstr "禁用"
-
-#: components/DisassociateButton/DisassociateButton.js:73
-#: components/DisassociateButton/DisassociateButton.js:97
-#: components/DisassociateButton/DisassociateButton.js:109
-#: components/DisassociateButton/DisassociateButton.js:113
-#: components/DisassociateButton/DisassociateButton.js:133
-#: screens/Team/TeamRoles/TeamRolesList.js:222
-#: screens/User/UserRoles/UserRolesList.js:218
-msgid "Disassociate"
-msgstr "解除关联"
-
-#: screens/Host/HostGroups/HostGroupsList.js:211
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:229
-msgid "Disassociate group from host?"
-msgstr "从主机中解除关联组?"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:247
-msgid "Disassociate host from group?"
-msgstr "从组中解除关联主机?"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:296
-#: screens/InstanceGroup/Instances/InstanceList.js:245
-msgid "Disassociate instance from instance group?"
-msgstr "从实例组中解除关联实例?"
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:225
-msgid "Disassociate related group(s)?"
-msgstr "解除关联相关的组?"
-
-#: screens/User/UserTeams/UserTeamList.js:216
-msgid "Disassociate related team(s)?"
-msgstr "解除关联相关的团队?"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:209
-#: screens/User/UserRoles/UserRolesList.js:205
-msgid "Disassociate role"
-msgstr "解除关联角色"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:212
-#: screens/User/UserRoles/UserRolesList.js:208
-msgid "Disassociate role!"
-msgstr "解除关联角色!"
-
-#: components/DisassociateButton/DisassociateButton.js:18
-msgid "Disassociate?"
-msgstr "解除关联?"
-
-#: components/PromptDetail/PromptProjectDetail.js:46
-#: screens/Project/ProjectDetail/ProjectDetail.js:94
-msgid "Discard local changes before syncing"
-msgstr "在同步前丢弃本地更改"
-
-#: screens/Job/Job.helptext.js:16
-#: screens/Template/shared/JobTemplate.helptext.js:17
-msgid "Divide the work done by this job template into the specified number of job slices, each running the same tasks against a portion of the inventory."
-msgstr "将此任务模板完成的工作分成指定任务分片数,每一分片都针对清单的一部分运行相同的任务。"
-
-#: screens/Project/shared/Project.helptext.js:100
-msgid "Documentation."
-msgstr "文档。"
-
-#: components/CodeEditor/VariablesDetail.js:117
-#: components/CodeEditor/VariablesDetail.js:123
-#: components/CodeEditor/VariablesField.js:139
-#: components/CodeEditor/VariablesField.js:145
-msgid "Done"
-msgstr "完成"
-
-#: screens/TopologyView/Tooltip.js:251
-msgid "Download Bundle"
-msgstr "下载捆绑包"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:179
-#: screens/Job/JobOutput/shared/OutputToolbar.js:184
-msgid "Download Output"
-msgstr "下载输出"
-
-#: screens/TopologyView/Tooltip.js:247
-msgid "Download bundle"
-msgstr "下载捆绑包"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:93
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:114
-msgid "Drag a file here or browse to upload"
-msgstr "把文件拖放在这里或浏览以上传"
-
-#: components/SelectedList/DraggableSelectedList.js:68
-msgid "Draggable list to reorder and remove selected items."
-msgstr "可拖动列表以重新排序和删除选定的项目。"
-
-#: components/SelectedList/DraggableSelectedList.js:43
-msgid "Dragging cancelled. List is unchanged."
-msgstr "拖放已取消。列表保持不变。"
-
-#: components/SelectedList/DraggableSelectedList.js:38
-msgid "Dragging item {id}. Item with index {oldIndex} in now {newIndex}."
-msgstr "拖动项目 {id}。带有索引 {oldIndex} 的项现在 {newIndex} 。"
-
-#: components/SelectedList/DraggableSelectedList.js:32
-msgid "Dragging started for item id: {newId}."
-msgstr "拖放项目 ID: {newId} 已开始。"
-
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:81
-msgid "E-mail"
-msgstr "电子邮件"
-
-#: screens/Inventory/shared/Inventory.helptext.js:113
-msgid ""
-"Each time a job runs using this inventory,\n"
-"refresh the inventory from the selected source before\n"
-"executing job tasks."
-msgstr "每次使用此清单运行作业时,请在执行作业前从所选源中刷新清单。"
-
-#: screens/Project/shared/Project.helptext.js:124
-msgid ""
-"Each time a job runs using this project, update the\n"
-"revision of the project prior to starting the job."
-msgstr "每次使用此项目运行作业时,请在启动该作业前更新项目的修订。"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:632
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:636
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:115
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:117
-#: screens/Credential/CredentialDetail/CredentialDetail.js:293
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:109
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:121
-#: screens/Host/HostDetail/HostDetail.js:108
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:101
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:113
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:190
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:55
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:62
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:103
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:292
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:127
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:164
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:418
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:420
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:138
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:182
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:186
-#: screens/Project/ProjectDetail/ProjectDetail.js:313
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:85
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:89
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:148
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:152
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:85
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:89
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:96
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:100
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:164
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:168
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:107
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:111
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:84
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:88
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:152
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:156
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:85
-#: screens/Setting/OIDC/OIDCDetail/OIDCDetail.js:89
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:99
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:103
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:86
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:90
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:199
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:103
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:108
-#: screens/Setting/UI/UIDetail/UIDetail.js:105
-#: screens/Setting/UI/UIDetail/UIDetail.js:110
-#: screens/Team/TeamDetail/TeamDetail.js:55
-#: screens/Team/TeamDetail/TeamDetail.js:59
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:514
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:516
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:239
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:241
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:260
-#: screens/User/UserDetail/UserDetail.js:96
-msgid "Edit"
-msgstr "编辑"
-
-#: screens/Credential/CredentialList/CredentialListItem.js:67
-#: screens/Credential/CredentialList/CredentialListItem.js:71
-msgid "Edit Credential"
-msgstr "编辑凭证"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:37
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:42
-msgid "Edit Credential Plugin Configuration"
-msgstr "编辑凭证插件配置"
-
-#: screens/Application/Applications.js:38
-#: screens/Credential/Credentials.js:27
-#: screens/Host/Hosts.js:26
-#: screens/ManagementJob/ManagementJobs.js:27
-#: screens/NotificationTemplate/NotificationTemplates.js:24
-#: screens/Organization/Organizations.js:29
-#: screens/Project/Projects.js:25
-#: screens/Project/Projects.js:35
-#: screens/Setting/Settings.js:46
-#: screens/Setting/Settings.js:49
-#: screens/Setting/Settings.js:53
-#: screens/Setting/Settings.js:56
-#: screens/Setting/Settings.js:59
-#: screens/Setting/Settings.js:62
-#: screens/Setting/Settings.js:65
-#: screens/Setting/Settings.js:68
-#: screens/Setting/Settings.js:71
-#: screens/Setting/Settings.js:74
-#: screens/Setting/Settings.js:77
-#: screens/Setting/Settings.js:91
-#: screens/Setting/Settings.js:92
-#: screens/Setting/Settings.js:93
-#: screens/Setting/Settings.js:94
-#: screens/Setting/Settings.js:95
-#: screens/Setting/Settings.js:96
-#: screens/Setting/Settings.js:99
-#: screens/Setting/Settings.js:102
-#: screens/Setting/Settings.js:105
-#: screens/Setting/Settings.js:108
-#: screens/Setting/Settings.js:111
-#: screens/Setting/Settings.js:114
-#: screens/Setting/Settings.js:117
-#: screens/Setting/Settings.js:120
-#: screens/Team/Teams.js:28
-#: screens/Template/Templates.js:44
-#: screens/User/Users.js:29
-msgid "Edit Details"
-msgstr "类型详情"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:90
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:94
-msgid "Edit Execution Environment"
-msgstr "编辑执行环境"
-
-#: screens/Host/HostGroups/HostGroupItem.js:37
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:46
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:46
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:51
-msgid "Edit Group"
-msgstr "编辑组"
-
-#: screens/Host/HostList/HostListItem.js:74
-#: screens/Host/HostList/HostListItem.js:78
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:61
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:64
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:67
-msgid "Edit Host"
-msgstr "编辑主机"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:134
-#: screens/Inventory/InventoryList/InventoryListItem.js:139
-msgid "Edit Inventory"
-msgstr "编辑清单"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkEditModal.js:14
-msgid "Edit Link"
-msgstr "编辑链接"
-
-#: screens/Setting/shared/SharedFields.js:290
-msgid "Edit Login redirect override URL"
-msgstr "编辑登录重定向覆写 URL"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeEditModal.js:64
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:257
-msgid "Edit Node"
-msgstr "编辑节点"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:142
-msgid "Edit Notification Template"
-msgstr "编辑通知模板"
-
-#: screens/Template/Survey/SurveyToolbar.js:73
-msgid "Edit Order"
-msgstr "编辑顺序"
-
-#: screens/Organization/OrganizationList/OrganizationListItem.js:72
-#: screens/Organization/OrganizationList/OrganizationListItem.js:76
-msgid "Edit Organization"
-msgstr "编辑机构"
-
-#: screens/Project/ProjectList/ProjectListItem.js:248
-#: screens/Project/ProjectList/ProjectListItem.js:253
-msgid "Edit Project"
-msgstr "编辑项目"
-
-#: screens/Template/Templates.js:50
-msgid "Edit Question"
-msgstr "编辑问题"
-
-#: components/Schedule/ScheduleList/ScheduleListItem.js:132
-#: components/Schedule/ScheduleList/ScheduleListItem.js:136
-#: screens/Template/Templates.js:55
-msgid "Edit Schedule"
-msgstr "编辑调度"
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:131
-msgid "Edit Source"
-msgstr "编辑源"
-
-#: screens/Template/Survey/SurveyListItem.js:92
-msgid "Edit Survey"
-msgstr "编辑问卷调查"
-
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:22
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:26
-#: screens/Team/TeamList/TeamListItem.js:50
-#: screens/Team/TeamList/TeamListItem.js:54
-msgid "Edit Team"
-msgstr "编辑团队"
-
-#: components/TemplateList/TemplateListItem.js:233
-#: components/TemplateList/TemplateListItem.js:239
-msgid "Edit Template"
-msgstr "编辑模板"
-
-#: screens/User/UserList/UserListItem.js:59
-#: screens/User/UserList/UserListItem.js:63
-msgid "Edit User"
-msgstr "编辑用户"
-
-#: screens/Application/ApplicationsList/ApplicationListItem.js:51
-#: screens/Application/ApplicationsList/ApplicationListItem.js:55
-msgid "Edit application"
-msgstr "编辑应用"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:41
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:45
-msgid "Edit credential type"
-msgstr "编辑凭证类型"
-
-#: screens/CredentialType/CredentialTypes.js:25
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:25
-#: screens/InstanceGroup/InstanceGroups.js:35
-#: screens/InstanceGroup/InstanceGroups.js:40
-#: screens/Inventory/Inventories.js:63
-#: screens/Inventory/Inventories.js:68
-#: screens/Inventory/Inventories.js:77
-#: screens/Inventory/Inventories.js:88
-msgid "Edit details"
-msgstr "编辑详情"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:42
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:44
-msgid "Edit group"
-msgstr "编辑组"
-
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:48
-msgid "Edit host"
-msgstr "编辑主机"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:78
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:82
-msgid "Edit instance group"
-msgstr "编辑实例组"
-
-#: screens/Setting/shared/SharedFields.js:320
-#: screens/Setting/shared/SharedFields.js:322
-msgid "Edit login redirect override URL"
-msgstr "编辑登录重定向覆写 URL"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:70
-msgid "Edit this link"
-msgstr "编辑这个链接"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:248
-msgid "Edit this node"
-msgstr "编辑此节点"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:97
-msgid "Edit workflow"
-msgstr "编辑工作流"
-
-#: components/Workflow/WorkflowNodeHelp.js:170
-#: screens/Job/JobOutput/shared/OutputToolbar.js:125
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:216
-msgid "Elapsed"
-msgstr "已经过"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:124
-msgid "Elapsed Time"
-msgstr "过期的时间"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:126
-msgid "Elapsed time that the job ran"
-msgstr "作业运行所经过的时间"
-
-#: components/NotificationList/NotificationList.js:193
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:134
-#: screens/User/UserDetail/UserDetail.js:66
-#: screens/User/UserList/UserList.js:115
-#: screens/User/shared/UserForm.js:73
-msgid "Email"
-msgstr "电子邮件"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:177
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:125
-msgid "Email Options"
-msgstr "电子邮件选项"
-
-#: screens/Template/shared/WorkflowJobTemplateForm.js:263
-msgid "Enable Concurrent Jobs"
-msgstr "启用并发作业"
-
-#: screens/Template/shared/JobTemplateForm.js:597
-msgid "Enable Fact Storage"
-msgstr "启用事实缓存"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:192
-msgid "Enable HTTPS certificate verification"
-msgstr "启用 HTTPS 证书验证"
-
-#: screens/Instances/Shared/InstanceForm.js:58
-msgid "Enable Instance"
-msgstr "启用实例"
-
-#: screens/Template/shared/JobTemplateForm.js:573
-#: screens/Template/shared/JobTemplateForm.js:576
-#: screens/Template/shared/WorkflowJobTemplateForm.js:244
-#: screens/Template/shared/WorkflowJobTemplateForm.js:247
-msgid "Enable Webhook"
-msgstr "启用 Webhook"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:15
-msgid "Enable Webhook for this workflow job template."
-msgstr "为此工作流作业模板启用 Webhook。"
-
-#: screens/Project/shared/Project.helptext.js:108
-msgid ""
-"Enable content signing to verify that the content \n"
-"has remained secure when a project is synced. \n"
-"If the content has been tampered with, the \n"
-"job will not run."
-msgstr "启用内容签名以验证内容在项目同步时仍然保持安全。如果内容已被篡改,任务将不会运行。"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:136
-msgid "Enable external logging"
-msgstr "启用外部日志记录"
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:168
-msgid "Enable log system tracking facts individually"
-msgstr "单独启用日志系统跟踪事实"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:201
-#: components/AdHocCommands/AdHocDetailsStep.js:204
-msgid "Enable privilege escalation"
-msgstr "启用权限升级"
-
-#: screens/Setting/SettingList.js:53
-msgid "Enable simplified login for your {brandName} applications"
-msgstr "为您的 {brandName} 应用启用简化的登录"
-
-#: screens/Template/shared/JobTemplate.helptext.js:31
-msgid "Enable webhook for this template."
-msgstr "为此模板启用 Webhook。"
-
-#: components/InstanceToggle/InstanceToggle.js:55
-#: components/Lookup/HostFilterLookup.js:110
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:45
-#: screens/TopologyView/Legend.js:205
-msgid "Enabled"
-msgstr "启用"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:171
-#: components/PromptDetail/PromptJobTemplateDetail.js:187
-#: components/PromptDetail/PromptProjectDetail.js:145
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:99
-#: screens/Credential/CredentialDetail/CredentialDetail.js:268
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:138
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:265
-#: screens/Project/ProjectDetail/ProjectDetail.js:302
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:365
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:199
-msgid "Enabled Options"
-msgstr "启用的选项"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:252
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:132
-msgid "Enabled Value"
-msgstr "启用的值"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:247
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:119
-msgid "Enabled Variable"
-msgstr "启用的变量"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:209
-msgid ""
-"Enables creation of a provisioning\n"
-"callback URL. Using the URL a host can contact {brandName}\n"
-"and request a configuration update using this job\n"
-"template"
-msgstr "允许创建部署回调 URL。使用此 URL,主机可访问 {brandName} 并使用此任务模板请求配置更新"
-
-#: screens/Template/shared/JobTemplate.helptext.js:29
-msgid "Enables creation of a provisioning callback URL. Using the URL a host can contact {brandName} and request a configuration update using this job template."
-msgstr "允许创建部署回调 URL。使用此 URL,主机可访问 {brandName} 并使用此任务模板请求配置更新。"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:160
-#: screens/Setting/shared/SettingDetail.js:87
-msgid "Encrypted"
-msgstr "已加密"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:109
-#: components/Schedule/shared/FrequencyDetailSubform.js:507
-msgid "End"
-msgstr "结束"
-
-#: screens/Setting/Subscription/SubscriptionEdit/EulaStep.js:14
-msgid "End User License Agreement"
-msgstr "最终用户许可证协议"
-
-#: components/Schedule/shared/DateTimePicker.js:51
-msgid "End date"
-msgstr "结束日期"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:561
-msgid "End date/time"
-msgstr "结束日期/时间"
-
-#: components/Schedule/shared/buildRuleObj.js:110
-msgid "End did not match an expected value ({0})"
-msgstr "结束与预期值不匹配({0})"
-
-#: components/Schedule/shared/DateTimePicker.js:61
-msgid "End time"
-msgstr "结束时间"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:209
-msgid "End user license agreement"
-msgstr "最终用户许可证协议"
-
-#: screens/Host/HostList/SmartInventoryButton.js:23
-msgid "Enter at least one search filter to create a new Smart Inventory"
-msgstr "请至少输入一个搜索过滤来创建一个新的智能清单"
-
-#: screens/CredentialType/shared/CredentialTypeForm.js:43
-msgid "Enter injectors using either JSON or YAML syntax. Refer to the Ansible Controller documentation for example syntax."
-msgstr "使用 JSON 或 YAML 语法输入注入程序。示例语法请参阅 Ansible 控制器文档。"
-
-#: screens/CredentialType/shared/CredentialTypeForm.js:35
-msgid "Enter inputs using either JSON or YAML syntax. Refer to the Ansible Controller documentation for example syntax."
-msgstr "使用 JSON 或 YAML 语法输入。示例语法请参阅 Ansible 控制器文档。"
-
-#: screens/Inventory/shared/SmartInventoryForm.js:94
-msgid ""
-"Enter inventory variables using either JSON or YAML syntax.\n"
-"Use the radio button to toggle between the two. Refer to the\n"
-"Ansible Controller documentation for example syntax."
-msgstr "使用 JSON 或 YAML 语法输入清单变量。使用单选按钮在两者之间切换。示例语法请参阅 Ansible 控制器文档。"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:87
-msgid "Environment variables or extra variables that specify the values a credential type can inject."
-msgstr "用于指定凭证类型可注入值的环境变量或额外变量。"
-
-#: components/JobList/JobList.js:233
-#: components/StatusLabel/StatusLabel.js:46
-#: components/Workflow/WorkflowNodeHelp.js:108
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:133
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:205
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:143
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:230
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:123
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:135
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:222
-#: screens/Job/JobOutput/JobOutputSearch.js:104
-#: screens/TopologyView/Legend.js:178
-msgid "Error"
-msgstr "错误"
-
-#: screens/Project/ProjectList/ProjectList.js:302
-msgid "Error fetching updated project"
-msgstr "获取更新的项目时出错"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:501
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:141
-msgid "Error message"
-msgstr "错误消息"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:510
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:150
-msgid "Error message body"
-msgstr "错误消息正文"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:709
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:711
-msgid "Error saving the workflow!"
-msgstr "保存工作流时出错!"
-
-#: components/AdHocCommands/AdHocCommands.js:104
-#: components/CopyButton/CopyButton.js:51
-#: components/DeleteButton/DeleteButton.js:56
-#: components/HostToggle/HostToggle.js:76
-#: components/InstanceToggle/InstanceToggle.js:67
-#: components/JobList/JobList.js:315
-#: components/JobList/JobList.js:326
-#: components/LaunchButton/LaunchButton.js:185
-#: components/LaunchPrompt/LaunchPrompt.js:96
-#: components/NotificationList/NotificationList.js:246
-#: components/PaginatedTable/ToolbarDeleteButton.js:205
-#: components/RelatedTemplateList/RelatedTemplateList.js:241
-#: components/ResourceAccessList/ResourceAccessList.js:277
-#: components/ResourceAccessList/ResourceAccessList.js:289
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:654
-#: components/Schedule/ScheduleList/ScheduleList.js:239
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:73
-#: components/Schedule/shared/SchedulePromptableFields.js:63
-#: components/TemplateList/TemplateList.js:299
-#: contexts/Config.js:94
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:136
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:155
-#: screens/Application/ApplicationsList/ApplicationsList.js:185
-#: screens/Credential/CredentialDetail/CredentialDetail.js:314
-#: screens/Credential/CredentialList/CredentialList.js:214
-#: screens/Host/HostDetail/HostDetail.js:56
-#: screens/Host/HostDetail/HostDetail.js:123
-#: screens/Host/HostGroups/HostGroupsList.js:244
-#: screens/Host/HostList/HostList.js:233
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:310
-#: screens/InstanceGroup/Instances/InstanceList.js:308
-#: screens/InstanceGroup/Instances/InstanceListItem.js:218
-#: screens/Instances/InstanceDetail/InstanceDetail.js:360
-#: screens/Instances/InstanceDetail/InstanceDetail.js:375
-#: screens/Instances/InstanceList/InstanceList.js:231
-#: screens/Instances/InstanceList/InstanceList.js:243
-#: screens/Instances/InstanceList/InstanceListItem.js:234
-#: screens/Instances/Shared/RemoveInstanceButton.js:104
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:210
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:78
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:285
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:296
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:56
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:118
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:261
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:201
-#: screens/Inventory/InventoryList/InventoryList.js:285
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:264
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:323
-#: screens/Inventory/InventorySources/InventorySourceList.js:239
-#: screens/Inventory/InventorySources/InventorySourceList.js:252
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:183
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:152
-#: screens/Inventory/shared/InventorySourceSyncButton.js:49
-#: screens/Login/Login.js:239
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:125
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:444
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:233
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:169
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:208
-#: screens/Organization/OrganizationList/OrganizationList.js:195
-#: screens/Project/ProjectDetail/ProjectDetail.js:348
-#: screens/Project/ProjectList/ProjectList.js:291
-#: screens/Project/ProjectList/ProjectList.js:303
-#: screens/Project/shared/ProjectSyncButton.js:60
-#: screens/Team/TeamDetail/TeamDetail.js:78
-#: screens/Team/TeamList/TeamList.js:192
-#: screens/Team/TeamRoles/TeamRolesList.js:247
-#: screens/Team/TeamRoles/TeamRolesList.js:258
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:554
-#: screens/Template/TemplateSurvey.js:130
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:277
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:180
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:195
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:337
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:373
-#: screens/TopologyView/MeshGraph.js:405
-#: screens/TopologyView/Tooltip.js:199
-#: screens/User/UserDetail/UserDetail.js:115
-#: screens/User/UserList/UserList.js:189
-#: screens/User/UserRoles/UserRolesList.js:243
-#: screens/User/UserRoles/UserRolesList.js:254
-#: screens/User/UserTeams/UserTeamList.js:259
-#: screens/User/UserTokenDetail/UserTokenDetail.js:85
-#: screens/User/UserTokenList/UserTokenList.js:214
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:348
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:189
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:53
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:48
-msgid "Error!"
-msgstr "错误!"
-
-#: components/CodeEditor/VariablesDetail.js:105
-msgid "Error:"
-msgstr "错误:"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:267
-#: screens/Instances/InstanceDetail/InstanceDetail.js:315
-msgid "Errors"
-msgstr "错误"
-
-#: screens/TopologyView/Legend.js:253
-msgid "Established"
-msgstr "已建立"
-
-#: screens/ActivityStream/ActivityStream.js:265
-#: screens/ActivityStream/ActivityStreamListItem.js:46
-#: screens/Job/JobOutput/JobOutputSearch.js:99
-msgid "Event"
-msgstr "事件"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:35
-msgid "Event detail"
-msgstr "查看详情"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:36
-msgid "Event detail modal"
-msgstr "事件详情模式"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:555
-msgid "Event summary not available"
-msgstr "事件摘要不可用"
-
-#: screens/ActivityStream/ActivityStream.js:234
-msgid "Events"
-msgstr "事件"
-
-#: screens/Job/JobOutput/JobOutput.js:713
-msgid "Events processing complete."
-msgstr "事件处理完成。"
-
-#: components/Search/LookupTypeInput.js:39
-msgid "Exact match (default lookup if not specified)."
-msgstr "完全匹配(如果没有指定,则默认查找)。"
-
-#: components/Search/RelatedLookupTypeInput.js:38
-msgid "Exact search on id field."
-msgstr "对 id 字段进行精确搜索。"
-
-#: screens/Project/shared/Project.helptext.js:23
-msgid "Example URLs for GIT Source Control include:"
-msgstr "GIT 源控制的 URL 示例包括:"
-
-#: screens/Project/shared/Project.helptext.js:62
-msgid "Example URLs for Remote Archive Source Control include:"
-msgstr "远程归档源控制的 URL 示例包括:"
-
-#: screens/Project/shared/Project.helptext.js:45
-msgid "Example URLs for Subversion Source Control include:"
-msgstr "Subversion SCM 源控制 URL 示例包括:"
-
-#: screens/Project/shared/Project.helptext.js:84
-msgid "Examples include:"
-msgstr "示例包括::"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:10
-msgid "Examples:"
-msgstr "示例:"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:354
-msgid "Exception Frequency"
-msgstr "例外频率"
-
-#: components/Schedule/shared/ScheduleFormFields.js:160
-msgid "Exceptions"
-msgstr "例外"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:47
-msgid "Execute regardless of the parent node's final state."
-msgstr "无论父节点的最后状态如何都执行。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:40
-msgid "Execute when the parent node results in a failure state."
-msgstr "当父节点出现故障状态时执行。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:33
-msgid "Execute when the parent node results in a successful state."
-msgstr "当父节点具有成功状态时执行。"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:208
-#: screens/Instances/InstanceList/InstanceList.js:152
-msgid "Execution"
-msgstr "执行"
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:90
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:91
-#: components/AdHocCommands/AdHocPreviewStep.js:58
-#: components/AdHocCommands/useAdHocExecutionEnvironmentStep.js:15
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:41
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:105
-#: components/LaunchPrompt/steps/useExecutionEnvironmentStep.js:29
-#: components/Lookup/ExecutionEnvironmentLookup.js:159
-#: components/Lookup/ExecutionEnvironmentLookup.js:191
-#: components/Lookup/ExecutionEnvironmentLookup.js:208
-#: components/PromptDetail/PromptDetail.js:220
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:451
-msgid "Execution Environment"
-msgstr "执行环境"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:70
-#: components/TemplateList/TemplateListItem.js:160
-msgid "Execution Environment Missing"
-msgstr "缺少执行环境"
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:103
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:107
-#: routeConfig.js:147
-#: screens/ActivityStream/ActivityStream.js:217
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:129
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:191
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:13
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:22
-#: screens/Organization/Organization.js:127
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:78
-#: screens/Organization/Organizations.js:34
-#: util/getRelatedResourceDeleteDetails.js:81
-#: util/getRelatedResourceDeleteDetails.js:188
-msgid "Execution Environments"
-msgstr "执行环境"
-
-#: screens/Job/JobDetail/JobDetail.js:345
-msgid "Execution Node"
-msgstr "执行节点"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:103
-msgid "Execution environment copied successfully"
-msgstr "执行环境复制成功"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:112
-msgid "Execution environment is missing or deleted."
-msgstr "执行环境缺失或删除。"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:83
-msgid "Execution environment not found."
-msgstr "未找到执行环境。"
-
-#: screens/TopologyView/Legend.js:86
-msgid "Execution node"
-msgstr "执行节点"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:23
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:26
-msgid "Exit Without Saving"
-msgstr "不保存退出"
-
-#: components/ExpandCollapse/ExpandCollapse.js:52
-msgid "Expand"
-msgstr "展开"
-
-#: components/DataListToolbar/DataListToolbar.js:105
-msgid "Expand all rows"
-msgstr "扩展所有行"
-
-#: components/CodeEditor/VariablesDetail.js:212
-#: components/CodeEditor/VariablesField.js:248
-msgid "Expand input"
-msgstr "展开输入"
-
-#: screens/Job/JobOutput/PageControls.js:50
-msgid "Expand job events"
-msgstr "扩展作业事件"
-
-#: screens/Job/JobOutput/shared/JobEventLineToggle.js:37
-msgid "Expand section"
-msgstr "展开部分"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:46
-msgid "Expected at least one of client_email, project_id or private_key to be present in the file."
-msgstr "预期该文件中至少有一个 client_email、project_id 或 private_key 之一。"
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:137
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:34
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:148
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:172
-#: screens/User/UserTokenDetail/UserTokenDetail.js:56
-#: screens/User/UserTokenList/UserTokenList.js:146
-#: screens/User/UserTokenList/UserTokenList.js:190
-#: screens/User/UserTokenList/UserTokenListItem.js:35
-#: screens/User/UserTokens/UserTokens.js:89
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:151
-msgid "Expires"
-msgstr "过期"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:146
-msgid "Expires on"
-msgstr "过期于"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:156
-msgid "Expires on UTC"
-msgstr "在 UTC 过期"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:50
-msgid "Expires on {0}"
-msgstr "在 {0} 过期"
-
-#: components/JobList/JobListItem.js:307
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:181
-msgid "Explanation"
-msgstr "解释"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:113
-msgid "External Secret Management System"
-msgstr "外部 Secret 管理系统"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:272
-#: components/AdHocCommands/AdHocDetailsStep.js:273
-msgid "Extra variables"
-msgstr "额外变量"
-
-#: components/Sparkline/Sparkline.js:35
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:164
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:43
-#: screens/Project/ProjectDetail/ProjectDetail.js:139
-#: screens/Project/ProjectList/ProjectListItem.js:77
-msgid "FINISHED:"
-msgstr "完成:"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:73
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:137
-msgid "Fact Storage"
-msgstr "事实存储"
-
-#: screens/Template/shared/JobTemplate.helptext.js:39
-msgid "Fact storage: If enabled, this will store gathered facts so they can be viewed at the host level. Facts are persisted and injected into the fact cache at runtime.."
-msgstr "事实存储:如果启用,这将存储收集的事实,以便在主机一级查看它们。事实在运行时会被持久化并注入事实缓存。"
-
-#: screens/Host/Host.js:63
-#: screens/Host/HostFacts/HostFacts.js:45
-#: screens/Host/Hosts.js:28
-#: screens/Inventory/Inventories.js:71
-#: screens/Inventory/InventoryHost/InventoryHost.js:78
-#: screens/Inventory/InventoryHostFacts/InventoryHostFacts.js:39
-msgid "Facts"
-msgstr "事实"
-
-#: components/JobList/JobList.js:232
-#: components/StatusLabel/StatusLabel.js:45
-#: components/Workflow/WorkflowNodeHelp.js:105
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:90
-#: screens/Dashboard/shared/ChartTooltip.js:66
-#: screens/Job/JobOutput/shared/HostStatusBar.js:47
-#: screens/Job/JobOutput/shared/OutputToolbar.js:113
-msgid "Failed"
-msgstr "失败"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:112
-msgid "Failed Host Count"
-msgstr "失败的主机计数"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:114
-msgid "Failed Hosts"
-msgstr "失败的主机"
-
-#: components/LaunchButton/ReLaunchDropDown.js:61
-#: screens/Dashboard/Dashboard.js:87
-msgid "Failed hosts"
-msgstr "失败的主机"
-
-#: screens/Dashboard/DashboardGraph.js:170
-msgid "Failed jobs"
-msgstr "失败的作业"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:56
-msgid "Failed to approve {0}."
-msgstr "批准 {0} 失败。"
-
-#: components/ResourceAccessList/ResourceAccessList.js:281
-msgid "Failed to assign roles properly"
-msgstr "正确分配角色失败"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:250
-#: screens/User/UserRoles/UserRolesList.js:246
-msgid "Failed to associate role"
-msgstr "关联角色失败"
-
-#: screens/Host/HostGroups/HostGroupsList.js:248
-#: screens/InstanceGroup/Instances/InstanceList.js:311
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:288
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:265
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:268
-#: screens/User/UserTeams/UserTeamList.js:263
-msgid "Failed to associate."
-msgstr "关联失败。"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:301
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:111
-msgid "Failed to cancel Inventory Source Sync"
-msgstr "取消清单源同步失败"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:322
-#: screens/Project/ProjectList/ProjectListItem.js:232
-msgid "Failed to cancel Project Sync"
-msgstr "取消项目同步失败"
-
-#: components/JobList/JobList.js:329
-msgid "Failed to cancel one or more jobs."
-msgstr "取消一个或多个作业失败。"
-
-#: components/JobList/JobListItem.js:114
-#: screens/Job/JobDetail/JobDetail.js:601
-#: screens/Job/JobOutput/shared/OutputToolbar.js:138
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:90
-msgid "Failed to cancel {0}"
-msgstr "取消 {0} 失败"
-
-#: screens/Credential/CredentialList/CredentialListItem.js:88
-msgid "Failed to copy credential."
-msgstr "复制凭证失败。"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:112
-msgid "Failed to copy execution environment"
-msgstr "复制执行环境失败"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:162
-msgid "Failed to copy inventory."
-msgstr "复制清单失败。"
-
-#: screens/Project/ProjectList/ProjectListItem.js:270
-msgid "Failed to copy project."
-msgstr "复制项目失败。"
-
-#: components/TemplateList/TemplateListItem.js:253
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:160
-msgid "Failed to copy template."
-msgstr "复制模板失败。"
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:139
-msgid "Failed to delete application."
-msgstr "删除应用程序失败。"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:317
-msgid "Failed to delete credential."
-msgstr "删除凭证失败。"
-
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:82
-msgid "Failed to delete group {0}."
-msgstr "删除组 {0} 失败。"
-
-#: screens/Host/HostDetail/HostDetail.js:126
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:121
-msgid "Failed to delete host."
-msgstr "删除主机失败。"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:327
-msgid "Failed to delete inventory source {name}."
-msgstr "删除清单源 {name} 失败。"
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:213
-msgid "Failed to delete inventory."
-msgstr "删除清单失败。"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:557
-msgid "Failed to delete job template."
-msgstr "删除作业模板失败。"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:448
-msgid "Failed to delete notification."
-msgstr "删除通知失败。"
-
-#: screens/Application/ApplicationsList/ApplicationsList.js:188
-msgid "Failed to delete one or more applications."
-msgstr "删除一个或多个应用程序失败。"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:208
-msgid "Failed to delete one or more credential types."
-msgstr "删除一个或多个凭证类型失败。"
-
-#: screens/Credential/CredentialList/CredentialList.js:217
-msgid "Failed to delete one or more credentials."
-msgstr "删除一个或多个凭证失败。"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:233
-msgid "Failed to delete one or more execution environments"
-msgstr "删除一个或多个执行环境失败"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:155
-msgid "Failed to delete one or more groups."
-msgstr "删除一个或多个组失败。"
-
-#: screens/Host/HostList/HostList.js:236
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:204
-msgid "Failed to delete one or more hosts."
-msgstr "删除一个或多个主机失败。"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:225
-msgid "Failed to delete one or more instance groups."
-msgstr "删除一个或多个实例组失败。"
-
-#: screens/Inventory/InventoryList/InventoryList.js:288
-msgid "Failed to delete one or more inventories."
-msgstr "删除一个或多个清单失败。"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:255
-msgid "Failed to delete one or more inventory sources."
-msgstr "删除一个或多个清单源失败。"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:244
-msgid "Failed to delete one or more job templates."
-msgstr "删除一个或多个作业模板失败。"
-
-#: components/JobList/JobList.js:318
-msgid "Failed to delete one or more jobs."
-msgstr "删除一个或多个作业失败。"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:236
-msgid "Failed to delete one or more notification template."
-msgstr "删除一个或多个通知模板失败。"
-
-#: screens/Organization/OrganizationList/OrganizationList.js:198
-msgid "Failed to delete one or more organizations."
-msgstr "删除一个或多个机构失败。"
-
-#: screens/Project/ProjectList/ProjectList.js:294
-msgid "Failed to delete one or more projects."
-msgstr "删除一个或多个项目失败。"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:242
-msgid "Failed to delete one or more schedules."
-msgstr "删除一个或多个调度失败。"
-
-#: screens/Team/TeamList/TeamList.js:195
-msgid "Failed to delete one or more teams."
-msgstr "删除一个或多个团队失败。"
-
-#: components/TemplateList/TemplateList.js:302
-msgid "Failed to delete one or more templates."
-msgstr "删除一个或多个模板失败。"
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:158
-msgid "Failed to delete one or more tokens."
-msgstr "删除一个或多个令牌失败。"
-
-#: screens/User/UserTokenList/UserTokenList.js:217
-msgid "Failed to delete one or more user tokens."
-msgstr "删除一个或多个用户令牌失败。"
-
-#: screens/User/UserList/UserList.js:192
-msgid "Failed to delete one or more users."
-msgstr "删除一个或多个用户失败。"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:192
-msgid "Failed to delete one or more workflow approval."
-msgstr "无法删除一个或多个工作流批准。"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:211
-msgid "Failed to delete organization."
-msgstr "删除机构失败。"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:351
-msgid "Failed to delete project."
-msgstr "删除项目失败。"
-
-#: components/ResourceAccessList/ResourceAccessList.js:292
-msgid "Failed to delete role"
-msgstr "删除角色失败"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:261
-#: screens/User/UserRoles/UserRolesList.js:257
-msgid "Failed to delete role."
-msgstr "删除角色失败。"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:657
-msgid "Failed to delete schedule."
-msgstr "删除调度失败。"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:186
-msgid "Failed to delete smart inventory."
-msgstr "删除智能清单失败。"
-
-#: screens/Team/TeamDetail/TeamDetail.js:81
-msgid "Failed to delete team."
-msgstr "删除团队失败。"
-
-#: screens/User/UserDetail/UserDetail.js:118
-msgid "Failed to delete user."
-msgstr "删除用户失败。"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:351
-msgid "Failed to delete workflow approval."
-msgstr "删除工作流批准失败。"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:280
-msgid "Failed to delete workflow job template."
-msgstr "删除工作流任务模板失败。"
-
-#: screens/Host/HostDetail/HostDetail.js:59
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:59
-msgid "Failed to delete {name}."
-msgstr "删除 {name} 失败。"
-
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:51
-msgid "Failed to deny {0}."
-msgstr "拒绝 {0} 失败。"
-
-#: screens/Host/HostGroups/HostGroupsList.js:249
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:266
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:269
-msgid "Failed to disassociate one or more groups."
-msgstr "解除关联一个或多个组关联。"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:299
-msgid "Failed to disassociate one or more hosts."
-msgstr "解除关联一个或多个主机失败。"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:315
-#: screens/InstanceGroup/Instances/InstanceList.js:313
-#: screens/Instances/InstanceDetail/InstanceDetail.js:365
-msgid "Failed to disassociate one or more instances."
-msgstr "解除关联一个或多个实例失败。"
-
-#: screens/User/UserTeams/UserTeamList.js:264
-msgid "Failed to disassociate one or more teams."
-msgstr "解除关联一个或多个团队失败。"
-
-#: screens/Login/Login.js:243
-msgid "Failed to fetch custom login configuration settings. System defaults will be shown instead."
-msgstr "获取自定义登录配置设置失败。系统默认设置会被显示。"
-
-#: screens/Project/ProjectList/ProjectList.js:306
-msgid "Failed to fetch the updated project data."
-msgstr "获取更新的项目数据失败。"
-
-#: screens/TopologyView/MeshGraph.js:409
-msgid "Failed to get instance."
-msgstr "获取实例失败。"
-
-#: components/AdHocCommands/AdHocCommands.js:112
-#: components/LaunchButton/LaunchButton.js:188
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:128
-msgid "Failed to launch job."
-msgstr "启动作业失败。"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:378
-#: screens/Instances/InstanceList/InstanceList.js:246
-msgid "Failed to remove one or more instances."
-msgstr "删除一个或多个实例失败。"
-
-#: contexts/Config.js:98
-msgid "Failed to retrieve configuration."
-msgstr "获取配置失败。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:376
-msgid "Failed to retrieve full node resource object."
-msgstr "获取完整节点资源对象失败。"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:315
-#: screens/Instances/InstanceList/InstanceList.js:234
-msgid "Failed to run a health check on one or more instances."
-msgstr "在一个或多个实例上运行健康检查失败。"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:172
-msgid "Failed to send test notification."
-msgstr "发送测试通知失败。"
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:52
-msgid "Failed to sync inventory source."
-msgstr "同步清单源失败。"
-
-#: screens/Project/shared/ProjectSyncButton.js:63
-msgid "Failed to sync project."
-msgstr "同步项目失败。"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:242
-msgid "Failed to sync some or all inventory sources."
-msgstr "同步部分或所有清单源失败。"
-
-#: components/HostToggle/HostToggle.js:80
-msgid "Failed to toggle host."
-msgstr "切换主机失败。"
-
-#: components/InstanceToggle/InstanceToggle.js:71
-msgid "Failed to toggle instance."
-msgstr "切换实例失败。"
-
-#: components/NotificationList/NotificationList.js:250
-msgid "Failed to toggle notification."
-msgstr "切换通知失败。"
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:77
-msgid "Failed to toggle schedule."
-msgstr "切换调度失败。"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:314
-#: screens/InstanceGroup/Instances/InstanceListItem.js:222
-#: screens/Instances/InstanceDetail/InstanceDetail.js:364
-#: screens/Instances/InstanceList/InstanceListItem.js:238
-msgid "Failed to update capacity adjustment."
-msgstr "更新容量调整失败。"
-
-#: screens/TopologyView/Tooltip.js:204
-msgid "Failed to update instance."
-msgstr "更新实例失败。"
-
-#: screens/Template/TemplateSurvey.js:133
-msgid "Failed to update survey."
-msgstr "更新问卷调查失败。"
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:88
-msgid "Failed to user token."
-msgstr "用户令牌失败。"
-
-#: components/NotificationList/NotificationListItem.js:85
-#: components/NotificationList/NotificationListItem.js:86
-msgid "Failure"
-msgstr "失败"
-
-#: screens/Job/JobOutput/EmptyOutput.js:45
-msgid "Failure Explanation:"
-msgstr "解释失败:"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:66
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:210
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:240
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:270
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:315
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:373
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:142
-msgid "False"
-msgstr "false"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:160
-#: components/Schedule/shared/FrequencyDetailSubform.js:103
-msgid "February"
-msgstr "2 月"
-
-#: components/Search/LookupTypeInput.js:52
-msgid "Field contains value."
-msgstr "字段包含值。"
-
-#: components/Search/LookupTypeInput.js:80
-msgid "Field ends with value."
-msgstr "字段以值结尾。"
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:76
-msgid "Field for passing a custom Kubernetes or OpenShift Pod specification."
-msgstr "用于传递自定义 Kubernetes 或 OpenShift Pod 规格的字段。"
-
-#: components/Search/LookupTypeInput.js:94
-msgid "Field matches the given regular expression."
-msgstr "字段与给出的正则表达式匹配。"
-
-#: components/Search/LookupTypeInput.js:66
-msgid "Field starts with value."
-msgstr "字段以值开头。"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:416
-msgid "Fifth"
-msgstr "第五"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:105
-msgid "File Difference"
-msgstr "文件差异"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:72
-msgid "File upload rejected. Please select a single .json file."
-msgstr "上传文件被拒绝。请选择单个 .json 文件。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:96
-msgid "File, directory or script"
-msgstr "文件、目录或脚本"
-
-#: components/Search/Search.js:198
-#: components/Search/Search.js:222
-msgid "Filter By {name}"
-msgstr "按 {name} 过滤"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:88
-msgid "Filter by failed jobs"
-msgstr "根据失败的作业过滤"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:94
-msgid "Filter by successful jobs"
-msgstr "根据成功的作业过滤"
-
-#: components/JobList/JobList.js:248
-#: components/JobList/JobListItem.js:100
-msgid "Finish Time"
-msgstr "完成时间"
-
-#: screens/Job/JobDetail/JobDetail.js:226
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:208
-msgid "Finished"
-msgstr "完成"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:404
-msgid "First"
-msgstr "第一"
-
-#: components/AddRole/AddResourceRole.js:28
-#: components/AddRole/AddResourceRole.js:42
-#: components/ResourceAccessList/ResourceAccessList.js:178
-#: screens/User/UserDetail/UserDetail.js:64
-#: screens/User/UserList/UserList.js:124
-#: screens/User/UserList/UserList.js:161
-#: screens/User/UserList/UserListItem.js:53
-#: screens/User/shared/UserForm.js:63
-msgid "First Name"
-msgstr "名"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:332
-msgid "First Run"
-msgstr "首次运行"
-
-#: components/ResourceAccessList/ResourceAccessList.js:227
-#: components/ResourceAccessList/ResourceAccessListItem.js:67
-msgid "First name"
-msgstr "名字"
-
-#: components/Search/AdvancedSearch.js:213
-#: components/Search/AdvancedSearch.js:227
-msgid "First, select a key"
-msgstr "首先,选择一个密钥"
-
-#: components/Workflow/WorkflowTools.js:88
-msgid "Fit the graph to the available screen size"
-msgstr "使图像与可用屏幕大小匹配"
-
-#: screens/TopologyView/Header.js:75
-#: screens/TopologyView/Header.js:78
-msgid "Fit to screen"
-msgstr "根据屏幕调整"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:94
-msgid "Float"
-msgstr "浮点值"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:183
-msgid "Follow"
-msgstr "关注"
-
-#: screens/Job/Job.helptext.js:5
-#: screens/Template/shared/JobTemplate.helptext.js:6
-msgid "For job templates, select run to execute the playbook. Select check to only check playbook syntax, test environment setup, and report problems without executing the playbook."
-msgstr "对于任务模板,选择“运行”来执行 playbook。选择“检查”将只检查 playbook 语法、测试环境设置和报告问题,而不执行 playbook。"
-
-#: screens/Project/shared/Project.helptext.js:98
-msgid "For more information, refer to the"
-msgstr "有关详情请参阅"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:160
-#: components/AdHocCommands/AdHocDetailsStep.js:161
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:55
-#: components/PromptDetail/PromptDetail.js:345
-#: components/PromptDetail/PromptJobTemplateDetail.js:150
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:475
-#: screens/Job/JobDetail/JobDetail.js:389
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:259
-#: screens/Template/shared/JobTemplateForm.js:409
-#: screens/TopologyView/Tooltip.js:282
-msgid "Forks"
-msgstr "Forks"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:414
-msgid "Fourth"
-msgstr "第四"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:363
-#: components/Schedule/shared/ScheduleFormFields.js:146
-msgid "Frequency Details"
-msgstr "频率详情"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:381
-msgid "Frequency Exception Details"
-msgstr "频率例外详情"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:72
-#: components/Schedule/shared/FrequencyDetailSubform.js:185
-#: components/Schedule/shared/FrequencyDetailSubform.js:206
-#: components/Schedule/shared/buildRuleObj.js:91
-msgid "Frequency did not match an expected value"
-msgstr "频率与预期值不匹配"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:310
-msgid "Fri"
-msgstr "周五"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:81
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:187
-#: components/Schedule/shared/FrequencyDetailSubform.js:315
-#: components/Schedule/shared/FrequencyDetailSubform.js:453
-msgid "Friday"
-msgstr "周五"
-
-#: components/Search/RelatedLookupTypeInput.js:45
-msgid "Fuzzy search on id, name or description fields."
-msgstr "模糊搜索 id、name 或 description 字段。"
-
-#: components/Search/RelatedLookupTypeInput.js:32
-msgid "Fuzzy search on name field."
-msgstr "模糊搜索名称字段。"
-
-#: components/CredentialChip/CredentialChip.js:13
-msgid "GPG Public Key"
-msgstr "GPG 公钥"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:153
-#: screens/Organization/shared/OrganizationForm.js:101
-msgid "Galaxy Credentials"
-msgstr "Galaxy 凭证"
-
-#: screens/Credential/shared/CredentialForm.js:185
-msgid "Galaxy credentials must be owned by an Organization."
-msgstr "Galaxy 凭证必须属于机构。"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:106
-msgid "Gathering Facts"
-msgstr "收集事实"
-
-#: screens/Setting/Settings.js:72
-msgid "Generic OIDC"
-msgstr "通用 OIDC"
-
-#: screens/Setting/SettingList.js:85
-msgid "Generic OIDC settings"
-msgstr "通用 OIDC 设置"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:222
-msgid "Get subscription"
-msgstr "获取订阅"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:216
-msgid "Get subscriptions"
-msgstr "获取订阅"
-
-#: components/Lookup/ProjectLookup.js:136
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:89
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:158
-#: screens/Job/JobDetail/JobDetail.js:75
-#: screens/Project/ProjectList/ProjectList.js:198
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:97
-msgid "Git"
-msgstr "Git"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:312
-#: screens/Template/shared/WebhookSubForm.js:106
-msgid "GitHub"
-msgstr "GitHub"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:85
-#: screens/Setting/Settings.js:51
-msgid "GitHub Default"
-msgstr "GitHub Default"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:100
-#: screens/Setting/Settings.js:60
-msgid "GitHub Enterprise"
-msgstr "GitHub Enterprise"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:105
-#: screens/Setting/Settings.js:63
-msgid "GitHub Enterprise Organization"
-msgstr "GitHub Enterprise Organization"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:110
-#: screens/Setting/Settings.js:66
-msgid "GitHub Enterprise Team"
-msgstr "GitHub Enterprise Team"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:90
-#: screens/Setting/Settings.js:54
-msgid "GitHub Organization"
-msgstr "GitHub Organization"
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:95
-#: screens/Setting/Settings.js:57
-msgid "GitHub Team"
-msgstr "GitHub Team"
-
-#: screens/Setting/SettingList.js:61
-msgid "GitHub settings"
-msgstr "GitHub 设置"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:312
-#: screens/Template/shared/WebhookSubForm.js:112
-msgid "GitLab"
-msgstr "GitLab"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:79
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:84
-msgid "Globally Available"
-msgstr "全局可用"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:134
-msgid "Globally available execution environment can not be reassigned to a specific Organization"
-msgstr "全局可用的执行环境无法重新分配给特定机构"
-
-#: components/Pagination/Pagination.js:29
-msgid "Go to first page"
-msgstr "前往第一页"
-
-#: components/Pagination/Pagination.js:31
-msgid "Go to last page"
-msgstr "进入最后页"
-
-#: components/Pagination/Pagination.js:32
-msgid "Go to next page"
-msgstr "进入下一页"
-
-#: components/Pagination/Pagination.js:30
-msgid "Go to previous page"
-msgstr "进入上一页"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:99
-msgid "Google Compute Engine"
-msgstr "Google Compute Engine"
-
-#: screens/Setting/SettingList.js:65
-msgid "Google OAuth 2 settings"
-msgstr "Google OAuth2 设置"
-
-#: screens/Setting/Settings.js:69
-msgid "Google OAuth2"
-msgstr "Google OAuth2"
-
-#: components/NotificationList/NotificationList.js:194
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:135
-msgid "Grafana"
-msgstr "Grafana"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:160
-msgid "Grafana API key"
-msgstr "Grafana API 密钥"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:187
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:151
-msgid "Grafana URL"
-msgstr "Grafana URL"
-
-#: components/Search/LookupTypeInput.js:106
-msgid "Greater than comparison."
-msgstr "大于比较。"
-
-#: components/Search/LookupTypeInput.js:113
-msgid "Greater than or equal to comparison."
-msgstr "大于或等于比较。"
-
-#: components/Lookup/HostFilterLookup.js:102
-msgid "Group"
-msgstr "组"
-
-#: screens/Inventory/Inventories.js:78
-msgid "Group details"
-msgstr "组详情"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:124
-msgid "Group type"
-msgstr "组类型"
-
-#: screens/Host/Host.js:68
-#: screens/Host/HostGroups/HostGroupsList.js:231
-#: screens/Host/Hosts.js:29
-#: screens/Inventory/Inventories.js:72
-#: screens/Inventory/Inventories.js:74
-#: screens/Inventory/Inventory.js:66
-#: screens/Inventory/InventoryHost/InventoryHost.js:83
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:248
-#: screens/Inventory/InventoryList/InventoryListItem.js:127
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:251
-#: util/getRelatedResourceDeleteDetails.js:119
-msgid "Groups"
-msgstr "组"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:383
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:468
-msgid "HTTP Headers"
-msgstr "HTTP 标头"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:378
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:481
-msgid "HTTP Method"
-msgstr "HTTP 方法"
-
-#: components/HealthCheckAlert/HealthCheckAlert.js:22
-msgid "Health check request(s) submitted. Please wait and reload the page."
-msgstr "提交健康检查请求。请等待并重新载入页面。"
-
-#: components/StatusLabel/StatusLabel.js:42
-msgid "Healthy"
-msgstr "健康"
-
-#: components/AppContainer/PageHeaderToolbar.js:116
-msgid "Help"
-msgstr "帮助"
-
-#: components/FormField/PasswordInput.js:35
-msgid "Hide"
-msgstr "隐藏"
-
-#: components/LaunchPrompt/LaunchPrompt.js:135
-#: components/Schedule/shared/SchedulePromptableFields.js:102
-msgid "Hide description"
-msgstr "隐藏描述"
-
-#: components/NotificationList/NotificationList.js:195
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:136
-msgid "Hipchat"
-msgstr "HipChat"
-
-#: screens/Instances/InstanceList/InstanceList.js:154
-msgid "Hop"
-msgstr "Hop(跃点)"
-
-#: screens/TopologyView/Legend.js:103
-msgid "Hop node"
-msgstr "Hop(跃点)节点"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:211
-#: screens/Job/JobOutput/HostEventModal.js:109
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:149
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:78
-msgid "Host"
-msgstr "主机"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:107
-msgid "Host Async Failure"
-msgstr "主机同步故障"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:108
-msgid "Host Async OK"
-msgstr "主机异步正常"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:159
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:297
-#: screens/Template/shared/JobTemplateForm.js:633
-msgid "Host Config Key"
-msgstr "主机配置键"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:96
-msgid "Host Count"
-msgstr "主机计数"
-
-#: screens/Job/JobOutput/HostEventModal.js:88
-msgid "Host Details"
-msgstr "类型详情"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:109
-msgid "Host Failed"
-msgstr "主机故障"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:110
-msgid "Host Failure"
-msgstr "主机故障"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:242
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:145
-msgid "Host Filter"
-msgstr "主机过滤器"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:201
-#: screens/Instances/InstanceDetail/InstanceDetail.js:191
-#: screens/Instances/Shared/InstanceForm.js:18
-msgid "Host Name"
-msgstr "主机名"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:111
-msgid "Host OK"
-msgstr "主机正常"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:112
-msgid "Host Polling"
-msgstr "主机轮询"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:113
-msgid "Host Retry"
-msgstr "主机重试"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:114
-msgid "Host Skipped"
-msgstr "主机已跳过"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:115
-msgid "Host Started"
-msgstr "主机已启动"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:116
-msgid "Host Unreachable"
-msgstr "主机无法访问"
-
-#: screens/Inventory/Inventories.js:69
-msgid "Host details"
-msgstr "主机详情"
-
-#: screens/Job/JobOutput/HostEventModal.js:89
-msgid "Host details modal"
-msgstr "主机详情模式"
-
-#: screens/Host/Host.js:96
-#: screens/Inventory/InventoryHost/InventoryHost.js:100
-msgid "Host not found."
-msgstr "未找到主机。"
-
-#: screens/Job/JobOutput/shared/HostStatusBar.js:76
-msgid "Host status information for this job is unavailable."
-msgstr "此作业的主机状态信息不可用。"
-
-#: routeConfig.js:85
-#: screens/ActivityStream/ActivityStream.js:176
-#: screens/Dashboard/Dashboard.js:81
-#: screens/Host/HostList/HostList.js:143
-#: screens/Host/HostList/HostList.js:191
-#: screens/Host/Hosts.js:14
-#: screens/Host/Hosts.js:23
-#: screens/Inventory/Inventories.js:65
-#: screens/Inventory/Inventories.js:79
-#: screens/Inventory/Inventory.js:67
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:67
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:189
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:272
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:112
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:172
-#: screens/Inventory/SmartInventory.js:68
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:71
-#: screens/Job/JobOutput/shared/OutputToolbar.js:97
-#: util/getRelatedResourceDeleteDetails.js:123
-msgid "Hosts"
-msgstr "主机"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:92
-msgid "Hosts automated"
-msgstr "自动的主机"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:118
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:125
-msgid "Hosts available"
-msgstr "可用主机"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:107
-msgid "Hosts imported"
-msgstr "导入的主机"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:112
-msgid "Hosts remaining"
-msgstr "剩余主机"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:183
-#: components/Schedule/shared/FrequencyDetailSubform.js:175
-#: components/Schedule/shared/ScheduleFormFields.js:126
-#: components/Schedule/shared/ScheduleFormFields.js:186
-msgid "Hour"
-msgstr "小时"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:209
-#: screens/Instances/InstanceList/InstanceList.js:153
-msgid "Hybrid"
-msgstr "混合"
-
-#: screens/TopologyView/Legend.js:95
-msgid "Hybrid node"
-msgstr "混合节点"
-
-#: components/JobList/JobList.js:200
-#: components/Lookup/HostFilterLookup.js:98
-#: screens/Team/TeamRoles/TeamRolesList.js:155
-msgid "ID"
-msgstr "ID"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:193
-msgid "ID of the Dashboard"
-msgstr "仪表盘 ID"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:198
-msgid "ID of the Panel"
-msgstr "面板 ID"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:167
-msgid "ID of the dashboard (optional)"
-msgstr "仪表盘 ID(可选)"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:173
-msgid "ID of the panel (optional)"
-msgstr "面板 ID(可选)"
-
-#: screens/TopologyView/Tooltip.js:265
-msgid "IP address"
-msgstr "IP 地址"
-
-#: components/NotificationList/NotificationList.js:196
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:137
-msgid "IRC"
-msgstr "IRC"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:228
-msgid "IRC Nick"
-msgstr "IRC Nick"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:223
-msgid "IRC Server Address"
-msgstr "IRC 服务器地址"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:218
-msgid "IRC Server Port"
-msgstr "IRC 服务器端口"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:223
-msgid "IRC nick"
-msgstr "IRC Nick"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:215
-msgid "IRC server address"
-msgstr "IRC 服务器地址"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:201
-msgid "IRC server password"
-msgstr "IRC 服务器密码"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:206
-msgid "IRC server port"
-msgstr "IRC 服务器端口"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:263
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:308
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:272
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:343
-msgid "Icon URL"
-msgstr "图标 URL"
-
-#: screens/Inventory/shared/Inventory.helptext.js:100
-msgid ""
-"If checked, all variables for child groups\n"
-"and hosts will be removed and replaced by those found\n"
-"on the external source."
-msgstr "如果选中,子组和主机的所有变量都将被删除,并替换为外部源上的变量。"
-
-#: screens/Inventory/shared/Inventory.helptext.js:84
-msgid ""
-"If checked, any hosts and groups that were\n"
-"previously present on the external source but are now removed\n"
-"will be removed from the inventory. Hosts and groups\n"
-"that were not managed by the inventory source will be promoted\n"
-"to the next manually created group or if there is no manually\n"
-"created group to promote them into, they will be left in the \"all\"\n"
-"default group for the inventory."
-msgstr "如果选中,以前存在于外部源上的但现已被删除的任何主机和组都将从清单中删除。不由清单源管理的主机和组将提升到下一个手动创建的组,如果没有手动创建组来提升它们,则它们将保留在清单的“all”默认组中。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:30
-msgid "If enabled, run this playbook as an administrator."
-msgstr "如果启用,则以管理员身份运行此 playbook。"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:184
-msgid ""
-"If enabled, show the changes made\n"
-"by Ansible tasks, where supported. This is equivalent to Ansible’s\n"
-"--diff mode."
-msgstr "如果启用,显示 Ansible 任务所做的更改(在支持的情况下)。这等同于 Ansible 的 --diff mode。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:19
-msgid "If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible's --diff mode."
-msgstr "如果启用,显示 Ansible 任务所做的更改(在支持的情况下)。这等同于 Ansible 的 --diff 模式。"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:181
-msgid "If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible’s --diff mode."
-msgstr "如果启用,显示 Ansible 任务所做的更改(在支持的情况下)。这等同于 Ansible 的 --diff 模式。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:32
-msgid "If enabled, simultaneous runs of this job template will be allowed."
-msgstr "如果启用,将允许同时运行此任务模板。"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:16
-msgid "If enabled, simultaneous runs of this workflow job template will be allowed."
-msgstr "如果启用,将允许同时运行此工作流任务模板。"
-
-#: screens/Inventory/shared/Inventory.helptext.js:194
-msgid ""
-"If enabled, the inventory will prevent adding any organization instance groups to the list of preferred instances groups to run associated job templates on.\n"
-"Note: If this setting is enabled and you provided an empty list, the global instance groups will be applied."
-msgstr "如果启用,清单将阻止将任何机构实例组添加到运行关联作业模板的首选实例组列表中。 \n"
-" 注: 如果启用此设置,并且提供了空列表,则会应用全局实例组。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:33
-msgid ""
-"If enabled, the job template will prevent adding any inventory or organization instance groups to the list of preferred instances groups to run on.\n"
-"Note: If this setting is enabled and you provided an empty list, the global instance groups will be applied."
-msgstr "如果启用,作业模板将阻止将任何清单或机构实例组添加到要运行的首选实例组列表中。 \n"
-" 注: 如果启用此设置,并且提供了空列表,则会应用全局实例组。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:35
-msgid "If enabled, this will store gathered facts so they can be viewed at the host level. Facts are persisted and injected into the fact cache at runtime."
-msgstr "如果启用,这将存储收集的事实,以便在主机一级查看它们。事实在运行时会被持久化并注入事实缓存。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:275
-msgid "If specified, this field will be shown on the node instead of the resource name when viewing the workflow"
-msgstr "如果指定,则在查看工作流时此字段将显示在节点上,而不是资源名称"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:180
-msgid "If you are ready to upgrade or renew, please <0>contact us.0>"
-msgstr "如果您准备进行升级或续订,请<0>联系我们。0>"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:63
-msgid ""
-"If you do not have a subscription, you can visit\n"
-"Red Hat to obtain a trial subscription."
-msgstr "如果您还没有订阅,请联系红帽来获得一个试用订阅。"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:46
-msgid "If you only want to remove access for this particular user, please remove them from the team."
-msgstr "如果您只想删除这个特定用户的访问,请将其从团队中删除。"
-
-#: screens/Inventory/shared/Inventory.helptext.js:120
-#: screens/Inventory/shared/Inventory.helptext.js:139
-msgid ""
-"If you want the Inventory Source to update on\n"
-"launch and on project update, click on Update on launch, and also go to"
-msgstr "如果您希望清单源在启动和项目更新时更新,请点启动时更新,并进入"
-
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:80
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:91
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:101
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:54
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:141
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:147
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:166
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:75
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:98
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:89
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:108
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.js:21
-msgid "Image"
-msgstr "镜像"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:117
-msgid "Including File"
-msgstr "包含文件"
-
-#: components/HostToggle/HostToggle.js:16
-msgid ""
-"Indicates if a host is available and should be included in running\n"
-"jobs. For hosts that are part of an external inventory, this may be\n"
-"reset by the inventory sync process."
-msgstr "指明主机是否可用且应该包含在正在运行的作业中。对于作为外部清单一部分的主机,可能会被清单同步过程重置。"
-
-#: components/AppContainer/PageHeaderToolbar.js:103
-msgid "Info"
-msgstr "Info"
-
-#: screens/ActivityStream/ActivityStreamListItem.js:45
-msgid "Initiated By"
-msgstr "启动者"
-
-#: screens/ActivityStream/ActivityStream.js:253
-#: screens/ActivityStream/ActivityStream.js:263
-#: screens/ActivityStream/ActivityStreamDetailButton.js:44
-msgid "Initiated by"
-msgstr "启动者"
-
-#: screens/ActivityStream/ActivityStream.js:243
-msgid "Initiated by (username)"
-msgstr "启动者(用户名)"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:82
-#: screens/CredentialType/shared/CredentialTypeForm.js:46
-msgid "Injector configuration"
-msgstr "注入程序配置"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:74
-#: screens/CredentialType/shared/CredentialTypeForm.js:38
-msgid "Input configuration"
-msgstr "输入配置"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:79
-msgid "Input schema which defines a set of ordered fields for that type."
-msgstr "输入架构,为该类型定义一组排序字段。"
-
-#: screens/Project/shared/ProjectSubForms/InsightsSubForm.js:31
-msgid "Insights Credential"
-msgstr "Insights 凭证"
-
-#: components/Lookup/HostFilterLookup.js:123
-msgid "Insights system ID"
-msgstr "Insights 系统 ID"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:249
-msgid "Install Bundle"
-msgstr "安装捆绑包"
-
-#: components/StatusLabel/StatusLabel.js:58
-#: screens/TopologyView/Legend.js:136
-msgid "Installed"
-msgstr "已安装"
-
-#: screens/Metrics/Metrics.js:187
-msgid "Instance"
-msgstr "实例"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:135
-msgid "Instance Filters"
-msgstr "实例过滤器"
-
-#: screens/Job/JobDetail/JobDetail.js:358
-msgid "Instance Group"
-msgstr "实例组"
-
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:96
-#: components/LaunchPrompt/steps/useInstanceGroupsStep.js:31
-#: components/Lookup/InstanceGroupsLookup.js:74
-#: components/Lookup/InstanceGroupsLookup.js:121
-#: components/Lookup/InstanceGroupsLookup.js:141
-#: components/Lookup/InstanceGroupsLookup.js:151
-#: components/PromptDetail/PromptDetail.js:227
-#: components/PromptDetail/PromptJobTemplateDetail.js:228
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:499
-#: routeConfig.js:132
-#: screens/ActivityStream/ActivityStream.js:205
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:106
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:179
-#: screens/InstanceGroup/InstanceGroups.js:16
-#: screens/InstanceGroup/InstanceGroups.js:26
-#: screens/Instances/InstanceDetail/InstanceDetail.js:217
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:107
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:127
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:422
-#: util/getRelatedResourceDeleteDetails.js:282
-msgid "Instance Groups"
-msgstr "实例组"
-
-#: components/Lookup/HostFilterLookup.js:115
-msgid "Instance ID"
-msgstr "实例 ID"
-
-#: screens/Instances/Shared/InstanceForm.js:32
-msgid "Instance State"
-msgstr "实例状态"
-
-#: screens/Instances/Shared/InstanceForm.js:48
-msgid "Instance Type"
-msgstr "实例类型"
-
-#: screens/InstanceGroup/InstanceGroups.js:33
-msgid "Instance details"
-msgstr "实例详情"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:58
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:69
-msgid "Instance group"
-msgstr "实例组"
-
-#: screens/InstanceGroup/InstanceGroup.js:92
-msgid "Instance group not found."
-msgstr "没有找到实例组。"
-
-#: screens/InstanceGroup/Instances/InstanceListItem.js:165
-#: screens/Instances/InstanceList/InstanceListItem.js:176
-msgid "Instance group used capacity"
-msgstr "实例组使用的容量"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:122
-#: screens/TopologyView/Tooltip.js:273
-msgid "Instance groups"
-msgstr "实例组"
-
-#: screens/TopologyView/Tooltip.js:234
-msgid "Instance status"
-msgstr "实例状态"
-
-#: screens/TopologyView/Tooltip.js:240
-msgid "Instance type"
-msgstr "实例类型"
-
-#: routeConfig.js:137
-#: screens/ActivityStream/ActivityStream.js:203
-#: screens/InstanceGroup/InstanceGroup.js:74
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:198
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:73
-#: screens/InstanceGroup/InstanceGroups.js:31
-#: screens/InstanceGroup/Instances/InstanceList.js:192
-#: screens/InstanceGroup/Instances/InstanceList.js:290
-#: screens/Instances/InstanceList/InstanceList.js:136
-#: screens/Instances/Instances.js:13
-#: screens/Instances/Instances.js:22
-msgid "Instances"
-msgstr "实例"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:93
-msgid "Integer"
-msgstr "整数"
-
-#: util/validators.js:94
-msgid "Invalid email address"
-msgstr "无效的电子邮件地址"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:116
-msgid "Invalid file format. Please upload a valid Red Hat Subscription Manifest."
-msgstr "无效的文件格式。请上传有效的红帽订阅清单。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:178
-msgid "Invalid link target. Unable to link to children or ancestor nodes. Graph cycles are not supported."
-msgstr "无效的链路目标。无法连接到子节点或祖先节点。不支持图形周期。"
-
-#: util/validators.js:33
-msgid "Invalid time format"
-msgstr "无效的时间格式"
-
-#: screens/Login/Login.js:153
-msgid "Invalid username or password. Please try again."
-msgstr "无效的用户名或密码。请重试。"
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:119
-#: routeConfig.js:80
-#: screens/ActivityStream/ActivityStream.js:173
-#: screens/Dashboard/Dashboard.js:92
-#: screens/Inventory/Inventories.js:17
-#: screens/Inventory/InventoryList/InventoryList.js:174
-#: screens/Inventory/InventoryList/InventoryList.js:237
-#: util/getRelatedResourceDeleteDetails.js:202
-#: util/getRelatedResourceDeleteDetails.js:270
-msgid "Inventories"
-msgstr "清单"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:153
-msgid "Inventories with sources cannot be copied"
-msgstr "无法复制含有源的清单"
-
-#: components/HostForm/HostForm.js:48
-#: components/JobList/JobListItem.js:223
-#: components/LaunchPrompt/steps/InventoryStep.js:105
-#: components/LaunchPrompt/steps/useInventoryStep.js:48
-#: components/Lookup/HostFilterLookup.js:424
-#: components/Lookup/HostListItem.js:10
-#: components/Lookup/InventoryLookup.js:119
-#: components/Lookup/InventoryLookup.js:128
-#: components/Lookup/InventoryLookup.js:169
-#: components/Lookup/InventoryLookup.js:184
-#: components/Lookup/InventoryLookup.js:224
-#: components/PromptDetail/PromptDetail.js:214
-#: components/PromptDetail/PromptInventorySourceDetail.js:76
-#: components/PromptDetail/PromptJobTemplateDetail.js:119
-#: components/PromptDetail/PromptJobTemplateDetail.js:129
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:79
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:430
-#: components/TemplateList/TemplateListItem.js:285
-#: components/TemplateList/TemplateListItem.js:295
-#: screens/Host/HostDetail/HostDetail.js:77
-#: screens/Host/HostList/HostList.js:171
-#: screens/Host/HostList/HostListItem.js:61
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:94
-#: screens/Inventory/InventoryList/InventoryList.js:186
-#: screens/Inventory/InventoryList/InventoryListItem.js:117
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:38
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:113
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:41
-#: screens/Job/JobDetail/JobDetail.js:107
-#: screens/Job/JobDetail/JobDetail.js:122
-#: screens/Job/JobDetail/JobDetail.js:129
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:214
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:224
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:141
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:33
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:252
-msgid "Inventory"
-msgstr "清单"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:104
-msgid "Inventory (Name)"
-msgstr "清单(名称)"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:99
-msgid "Inventory File"
-msgstr "清单文件"
-
-#: components/Lookup/HostFilterLookup.js:106
-msgid "Inventory ID"
-msgstr "清单 ID"
-
-#: screens/Job/JobDetail/JobDetail.js:282
-msgid "Inventory Source"
-msgstr "清单源"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:73
-msgid "Inventory Source Sync"
-msgstr "清单源同步"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:299
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:110
-msgid "Inventory Source Sync Error"
-msgstr "清单源同步错误"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:176
-#: screens/Inventory/InventorySources/InventorySourceList.js:193
-#: util/getRelatedResourceDeleteDetails.js:67
-#: util/getRelatedResourceDeleteDetails.js:147
-msgid "Inventory Sources"
-msgstr "清单源"
-
-#: components/JobList/JobList.js:212
-#: components/JobList/JobListItem.js:43
-#: components/Schedule/ScheduleList/ScheduleListItem.js:36
-#: components/Workflow/WorkflowLegend.js:100
-#: screens/Job/JobDetail/JobDetail.js:66
-msgid "Inventory Sync"
-msgstr "清单同步"
-
-#: screens/Inventory/InventoryList/InventoryList.js:183
-msgid "Inventory Type"
-msgstr "清单类型"
-
-#: components/Workflow/WorkflowNodeHelp.js:71
-msgid "Inventory Update"
-msgstr "清单更新"
-
-#: screens/Inventory/InventoryList/InventoryList.js:121
-msgid "Inventory copied successfully"
-msgstr "成功复制清单"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:226
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:109
-msgid "Inventory file"
-msgstr "清单文件"
-
-#: screens/Inventory/Inventory.js:94
-msgid "Inventory not found."
-msgstr "未找到清单。"
-
-#: screens/Dashboard/DashboardGraph.js:140
-msgid "Inventory sync"
-msgstr "清单同步"
-
-#: screens/Dashboard/Dashboard.js:98
-msgid "Inventory sync failures"
-msgstr "清单同步失败"
-
-#: components/DataListToolbar/DataListToolbar.js:110
-msgid "Is expanded"
-msgstr "已展开"
-
-#: components/DataListToolbar/DataListToolbar.js:112
-msgid "Is not expanded"
-msgstr "未扩展"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:118
-msgid "Item Failed"
-msgstr "项故障"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:119
-msgid "Item OK"
-msgstr "项正常"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:120
-msgid "Item Skipped"
-msgstr "项已跳过"
-
-#: components/AssociateModal/AssociateModal.js:20
-#: components/PaginatedTable/PaginatedTable.js:42
-msgid "Items"
-msgstr "项"
-
-#: components/Pagination/Pagination.js:27
-msgid "Items per page"
-msgstr "每页的项"
-
-#: components/Sparkline/Sparkline.js:28
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:157
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:36
-#: screens/Project/ProjectDetail/ProjectDetail.js:132
-#: screens/Project/ProjectList/ProjectListItem.js:70
-msgid "JOB ID:"
-msgstr "作业 ID:"
-
-#: screens/Job/JobOutput/HostEventModal.js:136
-msgid "JSON"
-msgstr "JSON"
-
-#: screens/Job/JobOutput/HostEventModal.js:137
-msgid "JSON tab"
-msgstr "JSON 标签页"
-
-#: screens/Inventory/shared/Inventory.helptext.js:49
-msgid "JSON:"
-msgstr "JSON:"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:159
-#: components/Schedule/shared/FrequencyDetailSubform.js:98
-msgid "January"
-msgstr "1 月"
-
-#: components/JobList/JobListItem.js:112
-#: screens/Job/JobDetail/JobDetail.js:599
-#: screens/Job/JobOutput/JobOutput.js:845
-#: screens/Job/JobOutput/JobOutput.js:846
-#: screens/Job/JobOutput/shared/OutputToolbar.js:136
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:88
-msgid "Job Cancel Error"
-msgstr "作业取消错误"
-
-#: screens/Job/JobDetail/JobDetail.js:621
-#: screens/Job/JobOutput/JobOutput.js:834
-#: screens/Job/JobOutput/JobOutput.js:835
-msgid "Job Delete Error"
-msgstr "作业删除错误"
-
-#: screens/Job/JobDetail/JobDetail.js:206
-msgid "Job ID"
-msgstr "作业 ID"
-
-#: screens/Dashboard/shared/LineChart.js:128
-msgid "Job Runs"
-msgstr "作业运行"
-
-#: components/JobList/JobListItem.js:314
-#: screens/Job/JobDetail/JobDetail.js:374
-msgid "Job Slice"
-msgstr "作业分片"
-
-#: components/JobList/JobListItem.js:319
-#: screens/Job/JobDetail/JobDetail.js:382
-msgid "Job Slice Parent"
-msgstr "任务分片父级"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:76
-#: components/PromptDetail/PromptDetail.js:349
-#: components/PromptDetail/PromptJobTemplateDetail.js:158
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:494
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:289
-#: screens/Template/shared/JobTemplateForm.js:453
-msgid "Job Slicing"
-msgstr "作业分片"
-
-#: components/Workflow/WorkflowNodeHelp.js:164
-msgid "Job Status"
-msgstr "作业状态"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:97
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:98
-#: components/PromptDetail/PromptDetail.js:267
-#: components/PromptDetail/PromptJobTemplateDetail.js:247
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:571
-#: screens/Job/JobDetail/JobDetail.js:474
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:449
-#: screens/Template/shared/JobTemplateForm.js:521
-#: screens/Template/shared/WorkflowJobTemplateForm.js:218
-msgid "Job Tags"
-msgstr "作业标签"
-
-#: components/JobList/JobListItem.js:191
-#: components/TemplateList/TemplateList.js:217
-#: components/Workflow/WorkflowLegend.js:92
-#: components/Workflow/WorkflowNodeHelp.js:59
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:97
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:19
-#: screens/Job/JobDetail/JobDetail.js:233
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:79
-msgid "Job Template"
-msgstr "任务模板"
-
-#: components/LaunchPrompt/steps/credentialsValidator.js:38
-msgid "Job Template default credentials must be replaced with one of the same type. Please select a credential for the following types in order to proceed: {0}"
-msgstr "作业模板默认凭证必须替换为相同类型之一。请为以下类型选择一个凭证才能继续: {0}"
-
-#: screens/Credential/Credential.js:79
-#: screens/Credential/Credentials.js:30
-#: screens/Inventory/Inventories.js:62
-#: screens/Inventory/Inventory.js:74
-#: screens/Inventory/SmartInventory.js:74
-#: screens/Project/Project.js:107
-#: screens/Project/Projects.js:29
-#: util/getRelatedResourceDeleteDetails.js:56
-#: util/getRelatedResourceDeleteDetails.js:101
-#: util/getRelatedResourceDeleteDetails.js:133
-msgid "Job Templates"
-msgstr "作业模板"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.js:25
-msgid "Job Templates with a missing inventory or project cannot be selected when creating or editing nodes. Select another template or fix the missing fields to proceed."
-msgstr "在创建或编辑节点时无法选择缺失的清单或项目的作业模板。选择另一个模板或修复缺少的字段以继续。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useWorkflowNodeSteps.js:357
-msgid "Job Templates with credentials that prompt for passwords cannot be selected when creating or editing nodes"
-msgstr "在创建或编辑节点时无法选择具有提示密码凭证的作业模板"
-
-#: components/JobList/JobList.js:208
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:146
-#: components/PromptDetail/PromptDetail.js:185
-#: components/PromptDetail/PromptJobTemplateDetail.js:102
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:423
-#: screens/Job/JobDetail/JobDetail.js:267
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:192
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:138
-#: screens/Template/shared/JobTemplateForm.js:256
-msgid "Job Type"
-msgstr "作业类型"
-
-#: screens/Dashboard/Dashboard.js:125
-msgid "Job status"
-msgstr "作业状态"
-
-#: screens/Dashboard/Dashboard.js:123
-msgid "Job status graph tab"
-msgstr "作业状态图标签页"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:156
-#: components/RelatedTemplateList/RelatedTemplateList.js:206
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:15
-msgid "Job templates"
-msgstr "作业模板"
-
-#: components/JobList/JobList.js:191
-#: components/JobList/JobList.js:274
-#: routeConfig.js:39
-#: screens/ActivityStream/ActivityStream.js:150
-#: screens/Dashboard/shared/LineChart.js:64
-#: screens/Host/Host.js:73
-#: screens/Host/Hosts.js:30
-#: screens/InstanceGroup/ContainerGroup.js:71
-#: screens/InstanceGroup/InstanceGroup.js:79
-#: screens/InstanceGroup/InstanceGroups.js:34
-#: screens/InstanceGroup/InstanceGroups.js:39
-#: screens/Inventory/Inventories.js:60
-#: screens/Inventory/Inventories.js:70
-#: screens/Inventory/Inventory.js:70
-#: screens/Inventory/InventoryHost/InventoryHost.js:88
-#: screens/Inventory/SmartInventory.js:70
-#: screens/Job/Jobs.js:22
-#: screens/Job/Jobs.js:32
-#: screens/Setting/SettingList.js:91
-#: screens/Setting/Settings.js:75
-#: screens/Template/Template.js:155
-#: screens/Template/Templates.js:47
-#: screens/Template/WorkflowJobTemplate.js:141
-msgid "Jobs"
-msgstr "作业"
-
-#: screens/Setting/SettingList.js:96
-msgid "Jobs settings"
-msgstr "作业设置"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:165
-#: components/Schedule/shared/FrequencyDetailSubform.js:128
-msgid "July"
-msgstr "7 月"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:164
-#: components/Schedule/shared/FrequencyDetailSubform.js:123
-msgid "June"
-msgstr "6 月"
-
-#: components/Search/AdvancedSearch.js:262
-msgid "Key"
-msgstr "密钥"
-
-#: components/Search/AdvancedSearch.js:253
-msgid "Key select"
-msgstr "键选择"
-
-#: components/Search/AdvancedSearch.js:256
-msgid "Key typeahead"
-msgstr "键 typeahead"
-
-#: screens/ActivityStream/ActivityStream.js:238
-msgid "Keyword"
-msgstr "关键字"
-
-#: screens/User/UserDetail/UserDetail.js:56
-#: screens/User/UserList/UserListItem.js:44
-msgid "LDAP"
-msgstr "LDAP"
-
-#: screens/Setting/Settings.js:80
-msgid "LDAP 1"
-msgstr "LDAP 1"
-
-#: screens/Setting/Settings.js:81
-msgid "LDAP 2"
-msgstr "LDAP 2"
-
-#: screens/Setting/Settings.js:82
-msgid "LDAP 3"
-msgstr "LDAP 3"
-
-#: screens/Setting/Settings.js:83
-msgid "LDAP 4"
-msgstr "LDAP 4"
-
-#: screens/Setting/Settings.js:84
-msgid "LDAP 5"
-msgstr "LDAP 5"
-
-#: screens/Setting/Settings.js:79
-msgid "LDAP Default"
-msgstr "LDAP 默认"
-
-#: screens/Setting/SettingList.js:69
-msgid "LDAP settings"
-msgstr "LDAP 设置"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:107
-msgid "LDAP1"
-msgstr "LDAP1"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:112
-msgid "LDAP2"
-msgstr "LDAP2"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:117
-msgid "LDAP3"
-msgstr "LDAP3"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:122
-msgid "LDAP4"
-msgstr "LDAP4"
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:127
-msgid "LDAP5"
-msgstr "LDAP5"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:178
-#: components/TemplateList/TemplateList.js:234
-msgid "Label"
-msgstr "标志"
-
-#: components/JobList/JobList.js:204
-msgid "Label Name"
-msgstr "标签名称"
-
-#: components/JobList/JobListItem.js:284
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:223
-#: components/PromptDetail/PromptDetail.js:323
-#: components/PromptDetail/PromptJobTemplateDetail.js:209
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:116
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:551
-#: components/TemplateList/TemplateListItem.js:347
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:148
-#: screens/Inventory/shared/InventoryForm.js:83
-#: screens/Job/JobDetail/JobDetail.js:453
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:401
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:206
-#: screens/Template/shared/JobTemplateForm.js:387
-#: screens/Template/shared/WorkflowJobTemplateForm.js:195
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:273
-msgid "Labels"
-msgstr "标签"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:417
-msgid "Last"
-msgstr "最后"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:220
-#: screens/InstanceGroup/Instances/InstanceListItem.js:133
-#: screens/InstanceGroup/Instances/InstanceListItem.js:208
-#: screens/Instances/InstanceDetail/InstanceDetail.js:243
-#: screens/Instances/InstanceList/InstanceListItem.js:138
-#: screens/Instances/InstanceList/InstanceListItem.js:223
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:47
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:87
-msgid "Last Health Check"
-msgstr "最后的健康检查"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:183
-#: screens/Project/ProjectDetail/ProjectDetail.js:161
-msgid "Last Job Status"
-msgstr "最后的作业状态"
-
-#: screens/User/UserDetail/UserDetail.js:80
-msgid "Last Login"
-msgstr "最近登陆"
-
-#: components/PromptDetail/PromptDetail.js:161
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:411
-#: components/TemplateList/TemplateListItem.js:316
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:106
-#: screens/Application/ApplicationsList/ApplicationListItem.js:45
-#: screens/Application/ApplicationsList/ApplicationsList.js:159
-#: screens/Credential/CredentialDetail/CredentialDetail.js:263
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:95
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:108
-#: screens/Host/HostDetail/HostDetail.js:89
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:72
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:98
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:178
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:45
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:84
-#: screens/Job/JobDetail/JobDetail.js:538
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:398
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:120
-#: screens/Project/ProjectDetail/ProjectDetail.js:297
-#: screens/Team/TeamDetail/TeamDetail.js:48
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:358
-#: screens/User/UserDetail/UserDetail.js:84
-#: screens/User/UserTokenDetail/UserTokenDetail.js:66
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:204
-msgid "Last Modified"
-msgstr "最后修改"
-
-#: components/AddRole/AddResourceRole.js:32
-#: components/AddRole/AddResourceRole.js:46
-#: components/ResourceAccessList/ResourceAccessList.js:182
-#: screens/User/UserDetail/UserDetail.js:65
-#: screens/User/UserList/UserList.js:128
-#: screens/User/UserList/UserList.js:162
-#: screens/User/UserList/UserListItem.js:54
-#: screens/User/shared/UserForm.js:69
-msgid "Last Name"
-msgstr "姓氏"
-
-#: components/TemplateList/TemplateList.js:245
-#: components/TemplateList/TemplateListItem.js:194
-msgid "Last Ran"
-msgstr "最后运行"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:341
-msgid "Last Run"
-msgstr "最后运行"
-
-#: components/Lookup/HostFilterLookup.js:119
-msgid "Last job"
-msgstr "最后作业"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:280
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:151
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:49
-#: screens/Project/ProjectList/ProjectListItem.js:308
-#: screens/TopologyView/Tooltip.js:331
-msgid "Last modified"
-msgstr "最后修改"
-
-#: components/ResourceAccessList/ResourceAccessList.js:228
-#: components/ResourceAccessList/ResourceAccessListItem.js:68
-msgid "Last name"
-msgstr "姓"
-
-#: screens/TopologyView/Tooltip.js:337
-msgid "Last seen"
-msgstr "最后看到"
-
-#: screens/Project/ProjectList/ProjectListItem.js:313
-msgid "Last used"
-msgstr "最后使用"
-
-#: components/AdHocCommands/useAdHocPreviewStep.js:22
-#: components/LaunchPrompt/steps/usePreviewStep.js:35
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:54
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:57
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:520
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:529
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:245
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:254
-msgid "Launch"
-msgstr "启动"
-
-#: components/TemplateList/TemplateListItem.js:214
-msgid "Launch Template"
-msgstr "启动模板"
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:32
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:34
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:46
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:47
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:87
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:90
-msgid "Launch management job"
-msgstr "启动管理作业"
-
-#: components/TemplateList/TemplateListItem.js:222
-msgid "Launch template"
-msgstr "启动模板"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:119
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:120
-msgid "Launch workflow"
-msgstr "启动工作流"
-
-#: components/LaunchPrompt/LaunchPrompt.js:130
-msgid "Launch | {0}"
-msgstr "启动 | {0}"
-
-#: components/DetailList/LaunchedByDetail.js:54
-msgid "Launched By"
-msgstr "启动者"
-
-#: components/JobList/JobList.js:220
-msgid "Launched By (Username)"
-msgstr "启动者(用户名)"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:120
-msgid "Learn more about Automation Analytics"
-msgstr "了解更多有关 Automation Analytics 的信息"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:69
-msgid "Leave this field blank to make the execution environment globally available."
-msgstr "将此字段留空以使执行环境全局可用。"
-
-#: components/Workflow/WorkflowLegend.js:86
-#: screens/Metrics/LineChart.js:120
-#: screens/TopologyView/Header.js:102
-#: screens/TopologyView/Legend.js:67
-msgid "Legend"
-msgstr "图例"
-
-#: components/Search/LookupTypeInput.js:120
-msgid "Less than comparison."
-msgstr "小于比较。"
-
-#: components/Search/LookupTypeInput.js:127
-msgid "Less than or equal to comparison."
-msgstr "小于或等于比较。"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:140
-#: components/AdHocCommands/AdHocDetailsStep.js:141
-#: components/JobList/JobList.js:238
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:65
-#: components/PromptDetail/PromptDetail.js:255
-#: components/PromptDetail/PromptJobTemplateDetail.js:153
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:90
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:473
-#: screens/Job/JobDetail/JobDetail.js:325
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:265
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:151
-#: screens/Template/shared/JobTemplateForm.js:429
-#: screens/Template/shared/WorkflowJobTemplateForm.js:159
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:238
-msgid "Limit"
-msgstr "限制"
-
-#: screens/TopologyView/Legend.js:237
-msgid "Link state types"
-msgstr "链接状态类型"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:261
-msgid "Link to an available node"
-msgstr "链接到可用节点"
-
-#: screens/Instances/Shared/InstanceForm.js:40
-msgid "Listener Port"
-msgstr "侦听器端口"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:353
-msgid "Loading"
-msgstr "正在加载"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:49
-msgid "Local"
-msgstr "本地"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:343
-msgid "Local Time Zone"
-msgstr "本地时区"
-
-#: components/Schedule/shared/ScheduleFormFields.js:95
-msgid "Local time zone"
-msgstr "本地时区"
-
-#: screens/Login/Login.js:217
-msgid "Log In"
-msgstr "登录"
-
-#: screens/Setting/Settings.js:97
-msgid "Logging"
-msgstr "日志记录"
-
-#: screens/Setting/SettingList.js:115
-msgid "Logging settings"
-msgstr "日志设置"
-
-#: components/AppContainer/AppContainer.js:81
-#: components/AppContainer/AppContainer.js:150
-#: components/AppContainer/PageHeaderToolbar.js:169
-msgid "Logout"
-msgstr "退出"
-
-#: components/Lookup/HostFilterLookup.js:367
-#: components/Lookup/Lookup.js:187
-msgid "Lookup modal"
-msgstr "查找模式"
-
-#: components/Search/LookupTypeInput.js:22
-msgid "Lookup select"
-msgstr "查找选择"
-
-#: components/Search/LookupTypeInput.js:31
-msgid "Lookup type"
-msgstr "查找类型"
-
-#: components/Search/LookupTypeInput.js:25
-msgid "Lookup typeahead"
-msgstr "查找 typeahead"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:155
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:34
-#: screens/Project/ProjectDetail/ProjectDetail.js:130
-#: screens/Project/ProjectList/ProjectListItem.js:68
-msgid "MOST RECENT SYNC"
-msgstr "最新同步"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:97
-#: components/AdHocCommands/AdHocCredentialStep.js:98
-#: components/AdHocCommands/AdHocCredentialStep.js:112
-#: screens/Job/JobDetail/JobDetail.js:407
-msgid "Machine Credential"
-msgstr "机器凭证"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:65
-msgid "Managed"
-msgstr "受管"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:147
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:169
-msgid "Managed nodes"
-msgstr "受管的节点"
-
-#: components/JobList/JobList.js:215
-#: components/JobList/JobListItem.js:46
-#: components/Schedule/ScheduleList/ScheduleListItem.js:39
-#: components/Workflow/WorkflowLegend.js:108
-#: components/Workflow/WorkflowNodeHelp.js:79
-#: screens/Job/JobDetail/JobDetail.js:69
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:102
-msgid "Management Job"
-msgstr "管理作业"
-
-#: routeConfig.js:127
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:84
-msgid "Management Jobs"
-msgstr "管理作业"
-
-#: screens/ManagementJob/ManagementJobs.js:20
-msgid "Management job"
-msgstr "管理作业"
-
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:109
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:110
-msgid "Management job launch error"
-msgstr "管理作业启动错误"
-
-#: screens/ManagementJob/ManagementJob.js:133
-msgid "Management job not found."
-msgstr "未找到管理作业。"
-
-#: screens/ManagementJob/ManagementJobs.js:13
-msgid "Management jobs"
-msgstr "管理作业"
-
-#: components/Lookup/ProjectLookup.js:135
-#: components/PromptDetail/PromptProjectDetail.js:98
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:88
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:157
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:215
-#: screens/InstanceGroup/Instances/InstanceListItem.js:204
-#: screens/Instances/InstanceDetail/InstanceDetail.js:209
-#: screens/Instances/InstanceList/InstanceListItem.js:219
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:83
-#: screens/Job/JobDetail/JobDetail.js:74
-#: screens/Project/ProjectDetail/ProjectDetail.js:192
-#: screens/Project/ProjectList/ProjectList.js:197
-#: screens/Project/ProjectList/ProjectListItem.js:219
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:96
-msgid "Manual"
-msgstr "手动"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:161
-#: components/Schedule/shared/FrequencyDetailSubform.js:108
-msgid "March"
-msgstr "3 月"
-
-#: components/NotificationList/NotificationList.js:197
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:138
-msgid "Mattermost"
-msgstr "Mattermost"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:98
-#: screens/Organization/shared/OrganizationForm.js:71
-msgid "Max Hosts"
-msgstr "最大主机数"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:220
-msgid "Maximum"
-msgstr "最大值"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:204
-msgid "Maximum length"
-msgstr "最大长度"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:163
-#: components/Schedule/shared/FrequencyDetailSubform.js:118
-msgid "May"
-msgstr "5 月"
-
-#: screens/Organization/OrganizationList/OrganizationList.js:144
-#: screens/Organization/OrganizationList/OrganizationListItem.js:63
-msgid "Members"
-msgstr "成员"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:47
-msgid "Metadata"
-msgstr "元数据"
-
-#: screens/Metrics/Metrics.js:207
-msgid "Metric"
-msgstr "指标"
-
-#: screens/Metrics/Metrics.js:179
-msgid "Metrics"
-msgstr "指标"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:100
-msgid "Microsoft Azure Resource Manager"
-msgstr "Microsoft Azure Resource Manager"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:214
-msgid "Minimum"
-msgstr "最小值"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:198
-msgid "Minimum length"
-msgstr "最小长度"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:65
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:31
-msgid ""
-"Minimum number of instances that will be automatically\n"
-"assigned to this group when new instances come online."
-msgstr "新实例上线时自动分配给此组的最小实例数量。"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:71
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:41
-msgid ""
-"Minimum percentage of all instances that will be automatically\n"
-"assigned to this group when new instances come online."
-msgstr "新实例上线时将自动分配给此组的所有实例的最小百分比。"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:182
-#: components/Schedule/shared/FrequencyDetailSubform.js:173
-#: components/Schedule/shared/ScheduleFormFields.js:125
-#: components/Schedule/shared/ScheduleFormFields.js:185
-msgid "Minute"
-msgstr "分钟"
-
-#: screens/Setting/Settings.js:100
-msgid "Miscellaneous Authentication"
-msgstr "其它身份验证"
-
-#: screens/Setting/SettingList.js:111
-msgid "Miscellaneous Authentication settings"
-msgstr "其它身份验证设置"
-
-#: screens/Setting/Settings.js:103
-msgid "Miscellaneous System"
-msgstr "杂项系统"
-
-#: screens/Setting/SettingList.js:107
-msgid "Miscellaneous System settings"
-msgstr "杂项系统设置"
-
-#: components/Workflow/WorkflowNodeHelp.js:120
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:87
-msgid "Missing"
-msgstr "缺少"
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:66
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:109
-msgid "Missing resource"
-msgstr "缺少资源"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:192
-#: screens/User/UserTokenList/UserTokenList.js:154
-msgid "Modified"
-msgstr "修改"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:126
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:116
-#: components/AddRole/AddResourceRole.js:61
-#: components/AssociateModal/AssociateModal.js:148
-#: components/LaunchPrompt/steps/CredentialsStep.js:177
-#: components/LaunchPrompt/steps/InventoryStep.js:93
-#: components/Lookup/CredentialLookup.js:198
-#: components/Lookup/InventoryLookup.js:156
-#: components/Lookup/InventoryLookup.js:211
-#: components/Lookup/MultiCredentialsLookup.js:198
-#: components/Lookup/OrganizationLookup.js:138
-#: components/Lookup/ProjectLookup.js:147
-#: components/NotificationList/NotificationList.js:210
-#: components/RelatedTemplateList/RelatedTemplateList.js:170
-#: components/Schedule/ScheduleList/ScheduleList.js:202
-#: components/TemplateList/TemplateList.js:230
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:31
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:62
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:100
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:131
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:169
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:200
-#: screens/Credential/CredentialList/CredentialList.js:154
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:100
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:136
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:106
-#: screens/Host/HostGroups/HostGroupsList.js:168
-#: screens/Host/HostList/HostList.js:161
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:203
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:133
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:178
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:132
-#: screens/Inventory/InventoryList/InventoryList.js:203
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:189
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:98
-#: screens/Organization/OrganizationList/OrganizationList.js:135
-#: screens/Project/ProjectList/ProjectList.js:209
-#: screens/Team/TeamList/TeamList.js:134
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:165
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:108
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:112
-msgid "Modified By (Username)"
-msgstr "修改者(用户名)"
-
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:85
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:151
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:77
-msgid "Modified by (username)"
-msgstr "修改者(用户名)"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:59
-#: screens/Job/JobOutput/HostEventModal.js:125
-msgid "Module"
-msgstr "模块"
-
-#: screens/Job/JobDetail/JobDetail.js:530
-msgid "Module Arguments"
-msgstr "模块参数"
-
-#: screens/Job/JobDetail/JobDetail.js:524
-msgid "Module Name"
-msgstr "模块名称"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:266
-msgid "Mon"
-msgstr "周一"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:77
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:183
-#: components/Schedule/shared/FrequencyDetailSubform.js:271
-#: components/Schedule/shared/FrequencyDetailSubform.js:433
-msgid "Monday"
-msgstr "周一"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:186
-#: components/Schedule/shared/FrequencyDetailSubform.js:181
-#: components/Schedule/shared/ScheduleFormFields.js:129
-#: components/Schedule/shared/ScheduleFormFields.js:189
-msgid "Month"
-msgstr "月"
-
-#: components/Popover/Popover.js:32
-msgid "More information"
-msgstr "更多信息"
-
-#: screens/Setting/shared/SharedFields.js:73
-msgid "More information for"
-msgstr "更多信息"
-
-#: screens/Template/Survey/SurveyReorderModal.js:162
-#: screens/Template/Survey/SurveyReorderModal.js:163
-msgid "Multi-Select"
-msgstr "多选"
-
-#: screens/Template/Survey/SurveyReorderModal.js:146
-#: screens/Template/Survey/SurveyReorderModal.js:147
-msgid "Multiple Choice"
-msgstr "多选"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:91
-msgid "Multiple Choice (multiple select)"
-msgstr "多项选择(多选)"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:86
-msgid "Multiple Choice (single select)"
-msgstr "多项选择(单选)"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:258
-msgid "Multiple Choice Options"
-msgstr "多项选择选项"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:117
-#: components/AdHocCommands/AdHocCredentialStep.js:132
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:107
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:122
-#: components/AddRole/AddResourceRole.js:52
-#: components/AddRole/AddResourceRole.js:68
-#: components/AssociateModal/AssociateModal.js:139
-#: components/AssociateModal/AssociateModal.js:154
-#: components/HostForm/HostForm.js:96
-#: components/JobList/JobList.js:195
-#: components/JobList/JobList.js:244
-#: components/JobList/JobListItem.js:86
-#: components/LaunchPrompt/steps/CredentialsStep.js:168
-#: components/LaunchPrompt/steps/CredentialsStep.js:183
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:76
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:86
-#: components/LaunchPrompt/steps/ExecutionEnvironmentStep.js:97
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:78
-#: components/LaunchPrompt/steps/InstanceGroupsStep.js:89
-#: components/LaunchPrompt/steps/InventoryStep.js:84
-#: components/LaunchPrompt/steps/InventoryStep.js:99
-#: components/Lookup/ApplicationLookup.js:100
-#: components/Lookup/ApplicationLookup.js:111
-#: components/Lookup/CredentialLookup.js:189
-#: components/Lookup/CredentialLookup.js:204
-#: components/Lookup/ExecutionEnvironmentLookup.js:177
-#: components/Lookup/ExecutionEnvironmentLookup.js:184
-#: components/Lookup/HostFilterLookup.js:93
-#: components/Lookup/HostFilterLookup.js:422
-#: components/Lookup/HostListItem.js:8
-#: components/Lookup/InstanceGroupsLookup.js:103
-#: components/Lookup/InstanceGroupsLookup.js:114
-#: components/Lookup/InventoryLookup.js:147
-#: components/Lookup/InventoryLookup.js:162
-#: components/Lookup/InventoryLookup.js:202
-#: components/Lookup/InventoryLookup.js:217
-#: components/Lookup/MultiCredentialsLookup.js:189
-#: components/Lookup/MultiCredentialsLookup.js:204
-#: components/Lookup/OrganizationLookup.js:129
-#: components/Lookup/OrganizationLookup.js:144
-#: components/Lookup/ProjectLookup.js:127
-#: components/Lookup/ProjectLookup.js:157
-#: components/NotificationList/NotificationList.js:181
-#: components/NotificationList/NotificationList.js:218
-#: components/NotificationList/NotificationListItem.js:28
-#: components/OptionsList/OptionsList.js:57
-#: components/PaginatedTable/PaginatedTable.js:72
-#: components/PromptDetail/PromptDetail.js:114
-#: components/RelatedTemplateList/RelatedTemplateList.js:161
-#: components/RelatedTemplateList/RelatedTemplateList.js:186
-#: components/ResourceAccessList/ResourceAccessListItem.js:58
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:325
-#: components/Schedule/ScheduleList/ScheduleList.js:168
-#: components/Schedule/ScheduleList/ScheduleList.js:189
-#: components/Schedule/ScheduleList/ScheduleListItem.js:86
-#: components/Schedule/shared/ScheduleFormFields.js:72
-#: components/TemplateList/TemplateList.js:205
-#: components/TemplateList/TemplateList.js:242
-#: components/TemplateList/TemplateListItem.js:142
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:18
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:37
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:49
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:68
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:80
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:110
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:122
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:137
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:149
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:179
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:191
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:206
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:60
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:109
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:135
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:28
-#: screens/Application/Applications.js:81
-#: screens/Application/ApplicationsList/ApplicationListItem.js:33
-#: screens/Application/ApplicationsList/ApplicationsList.js:118
-#: screens/Application/ApplicationsList/ApplicationsList.js:155
-#: screens/Application/shared/ApplicationForm.js:54
-#: screens/Credential/CredentialDetail/CredentialDetail.js:217
-#: screens/Credential/CredentialList/CredentialList.js:141
-#: screens/Credential/CredentialList/CredentialList.js:164
-#: screens/Credential/CredentialList/CredentialListItem.js:58
-#: screens/Credential/shared/CredentialForm.js:161
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:71
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:91
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:68
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:123
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:176
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:33
-#: screens/CredentialType/shared/CredentialTypeForm.js:21
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:49
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:136
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:165
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:69
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:89
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:115
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:12
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:89
-#: screens/Host/HostDetail/HostDetail.js:69
-#: screens/Host/HostGroups/HostGroupItem.js:28
-#: screens/Host/HostGroups/HostGroupsList.js:159
-#: screens/Host/HostGroups/HostGroupsList.js:176
-#: screens/Host/HostList/HostList.js:148
-#: screens/Host/HostList/HostList.js:169
-#: screens/Host/HostList/HostListItem.js:50
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:41
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:49
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:161
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:194
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:61
-#: screens/InstanceGroup/Instances/InstanceList.js:199
-#: screens/InstanceGroup/Instances/InstanceList.js:215
-#: screens/InstanceGroup/Instances/InstanceList.js:266
-#: screens/InstanceGroup/Instances/InstanceList.js:299
-#: screens/InstanceGroup/Instances/InstanceListItem.js:124
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:44
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:19
-#: screens/Instances/InstanceList/InstanceList.js:143
-#: screens/Instances/InstanceList/InstanceList.js:160
-#: screens/Instances/InstanceList/InstanceList.js:201
-#: screens/Instances/InstanceList/InstanceListItem.js:128
-#: screens/Instances/InstancePeers/InstancePeerList.js:80
-#: screens/Instances/InstancePeers/InstancePeerList.js:87
-#: screens/Instances/InstancePeers/InstancePeerList.js:96
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:37
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:89
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:31
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:194
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:209
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:215
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:34
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:119
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:141
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:74
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:36
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:169
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:186
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:33
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:119
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:138
-#: screens/Inventory/InventoryList/InventoryList.js:178
-#: screens/Inventory/InventoryList/InventoryList.js:209
-#: screens/Inventory/InventoryList/InventoryList.js:218
-#: screens/Inventory/InventoryList/InventoryListItem.js:92
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:180
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:195
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:232
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:181
-#: screens/Inventory/InventorySources/InventorySourceList.js:211
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:71
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:98
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:30
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:76
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:111
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:33
-#: screens/Inventory/shared/InventoryForm.js:50
-#: screens/Inventory/shared/InventoryGroupForm.js:32
-#: screens/Inventory/shared/InventorySourceForm.js:100
-#: screens/Inventory/shared/SmartInventoryForm.js:47
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:90
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:100
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:67
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:107
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:122
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:178
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:112
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:41
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:91
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:84
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:107
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.js:16
-#: screens/Organization/OrganizationList/OrganizationList.js:122
-#: screens/Organization/OrganizationList/OrganizationList.js:143
-#: screens/Organization/OrganizationList/OrganizationListItem.js:45
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:68
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:85
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:14
-#: screens/Organization/shared/OrganizationForm.js:56
-#: screens/Project/ProjectDetail/ProjectDetail.js:176
-#: screens/Project/ProjectList/ProjectList.js:185
-#: screens/Project/ProjectList/ProjectList.js:221
-#: screens/Project/ProjectList/ProjectListItem.js:179
-#: screens/Project/shared/ProjectForm.js:214
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:146
-#: screens/Team/TeamDetail/TeamDetail.js:37
-#: screens/Team/TeamList/TeamList.js:117
-#: screens/Team/TeamList/TeamList.js:142
-#: screens/Team/TeamList/TeamListItem.js:33
-#: screens/Team/shared/TeamForm.js:29
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:185
-#: screens/Template/Survey/SurveyList.js:102
-#: screens/Template/Survey/SurveyList.js:102
-#: screens/Template/Survey/SurveyListItem.js:39
-#: screens/Template/Survey/SurveyReorderModal.js:218
-#: screens/Template/Survey/SurveyReorderModal.js:218
-#: screens/Template/Survey/SurveyReorderModal.js:238
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:111
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:69
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:88
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:120
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:152
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:178
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:88
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.js:74
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.js:94
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:75
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:95
-#: screens/Template/shared/JobTemplateForm.js:243
-#: screens/Template/shared/WorkflowJobTemplateForm.js:109
-#: screens/User/UserOrganizations/UserOrganizationList.js:75
-#: screens/User/UserOrganizations/UserOrganizationList.js:79
-#: screens/User/UserOrganizations/UserOrganizationListItem.js:13
-#: screens/User/UserRoles/UserRolesList.js:155
-#: screens/User/UserRoles/UserRolesListItem.js:12
-#: screens/User/UserTeams/UserTeamList.js:180
-#: screens/User/UserTeams/UserTeamList.js:232
-#: screens/User/UserTeams/UserTeamListItem.js:18
-#: screens/User/UserTokenList/UserTokenListItem.js:22
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:140
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:123
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:163
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:43
-msgid "Name"
-msgstr "名称"
-
-#: components/AppContainer/AppContainer.js:95
-msgid "Navigation"
-msgstr "导航"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:221
-#: components/Schedule/shared/FrequencyDetailSubform.js:512
-#: screens/Dashboard/shared/ChartTooltip.js:106
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:57
-msgid "Never"
-msgstr "永不"
-
-#: components/Workflow/WorkflowNodeHelp.js:114
-msgid "Never Updated"
-msgstr "永不更新"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:47
-msgid "Never expires"
-msgstr "永不过期"
-
-#: components/JobList/JobList.js:227
-#: components/Workflow/WorkflowNodeHelp.js:90
-msgid "New"
-msgstr "新"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:51
-#: components/AdHocCommands/useAdHocCredentialStep.js:29
-#: components/AdHocCommands/useAdHocDetailsStep.js:40
-#: components/AdHocCommands/useAdHocExecutionEnvironmentStep.js:22
-#: components/AddRole/AddResourceRole.js:196
-#: components/AddRole/AddResourceRole.js:231
-#: components/LaunchPrompt/LaunchPrompt.js:160
-#: components/Schedule/shared/SchedulePromptableFields.js:127
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:66
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:59
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:132
-msgid "Next"
-msgstr "下一"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:337
-#: components/Schedule/ScheduleList/ScheduleList.js:171
-#: components/Schedule/ScheduleList/ScheduleListItem.js:118
-#: components/Schedule/ScheduleList/ScheduleListItem.js:122
-msgid "Next Run"
-msgstr "下次运行"
-
-#: components/Search/Search.js:232
-msgid "No"
-msgstr "否"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:121
-msgid "No Hosts Matched"
-msgstr "未匹配主机"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:122
-#: screens/Job/JobOutput/JobOutputSearch.js:123
-msgid "No Hosts Remaining"
-msgstr "没有剩余主机"
-
-#: screens/Job/JobOutput/HostEventModal.js:150
-msgid "No JSON Available"
-msgstr "没有可用的 JSON"
-
-#: screens/Dashboard/shared/ChartTooltip.js:82
-msgid "No Jobs"
-msgstr "没有作业"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:72
-msgid "No inventory sync failures."
-msgstr "没有清单同步失败。"
-
-#: components/ContentEmpty/ContentEmpty.js:20
-msgid "No items found."
-msgstr "没有找到项。"
-
-#: screens/Host/HostList/HostListItem.js:100
-msgid "No job data available"
-msgstr "没有可用作业数据"
-
-#: screens/Job/JobOutput/EmptyOutput.js:52
-msgid "No output found for this job."
-msgstr "没有为该作业找到输出。"
-
-#: screens/Job/JobOutput/HostEventModal.js:126
-msgid "No result found"
-msgstr "未找到结果"
-
-#: components/LabelSelect/LabelSelect.js:130
-#: components/LaunchPrompt/steps/SurveyStep.js:136
-#: components/LaunchPrompt/steps/SurveyStep.js:195
-#: components/MultiSelect/TagMultiSelect.js:60
-#: components/Search/AdvancedSearch.js:151
-#: components/Search/AdvancedSearch.js:266
-#: components/Search/LookupTypeInput.js:33
-#: components/Search/RelatedLookupTypeInput.js:26
-#: components/Search/Search.js:153
-#: components/Search/Search.js:202
-#: components/Search/Search.js:226
-#: screens/ActivityStream/ActivityStream.js:142
-#: screens/Credential/shared/CredentialForm.js:143
-#: screens/Credential/shared/CredentialFormFields/BecomeMethodField.js:65
-#: screens/Dashboard/DashboardGraph.js:106
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:137
-#: screens/Template/Survey/SurveyReorderModal.js:166
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:260
-#: screens/Template/shared/PlaybookSelect.js:72
-msgid "No results found"
-msgstr "没有找到结果"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:116
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:137
-msgid "No subscriptions found"
-msgstr "未找到订阅"
-
-#: screens/Template/Survey/SurveyList.js:147
-msgid "No survey questions found."
-msgstr "没有找到问卷调查问题。"
-
-#: screens/Job/JobDetail/JobDetail.js:399
-msgid "No timeout specified"
-msgstr "未指定超时"
-
-#: components/PaginatedTable/PaginatedTable.js:80
-msgid "No {pluralizedItemName} Found"
-msgstr "未找到 {pluralizedItemName}"
-
-#: components/Workflow/WorkflowNodeHelp.js:148
-#: components/Workflow/WorkflowNodeHelp.js:184
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:273
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:274
-msgid "Node Alias"
-msgstr "节点别名"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:223
-#: screens/InstanceGroup/Instances/InstanceList.js:204
-#: screens/InstanceGroup/Instances/InstanceList.js:268
-#: screens/InstanceGroup/Instances/InstanceList.js:300
-#: screens/InstanceGroup/Instances/InstanceListItem.js:142
-#: screens/Instances/InstanceDetail/InstanceDetail.js:204
-#: screens/Instances/InstanceList/InstanceList.js:148
-#: screens/Instances/InstanceList/InstanceList.js:203
-#: screens/Instances/InstanceList/InstanceListItem.js:150
-#: screens/Instances/InstancePeers/InstancePeerList.js:98
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:59
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:118
-msgid "Node Type"
-msgstr "节点类型"
-
-#: screens/TopologyView/Legend.js:107
-msgid "Node state types"
-msgstr "节点状态类型"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.js:75
-msgid "Node type"
-msgstr "节点类型"
-
-#: screens/TopologyView/Legend.js:70
-msgid "Node types"
-msgstr "节点类型"
-
-#: components/Schedule/shared/ScheduleFormFields.js:180
-#: components/Schedule/shared/ScheduleFormFields.js:184
-#: components/Workflow/WorkflowNodeHelp.js:123
-msgid "None"
-msgstr "无"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:193
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:196
-msgid "None (Run Once)"
-msgstr "无(运行一次)"
-
-#: components/Schedule/shared/ScheduleFormFields.js:120
-#: components/Schedule/shared/ScheduleFormFields.js:124
-msgid "None (run once)"
-msgstr "无(运行一次)"
-
-#: screens/User/UserDetail/UserDetail.js:51
-#: screens/User/UserList/UserListItem.js:23
-#: screens/User/shared/UserForm.js:29
-msgid "Normal User"
-msgstr "普通用户"
-
-#: components/ContentError/ContentError.js:37
-msgid "Not Found"
-msgstr "未找到"
-
-#: screens/Setting/shared/SettingDetail.js:71
-#: screens/Setting/shared/SettingDetail.js:112
-msgid "Not configured"
-msgstr "没有配置"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:75
-msgid "Not configured for inventory sync."
-msgstr "没有为清单同步配置。"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:248
-msgid ""
-"Note that only hosts directly in this group can\n"
-"be disassociated. Hosts in sub-groups must be disassociated\n"
-"directly from the sub-group level that they belong."
-msgstr "请注意,只有直接属于此组的主机才能解除关联。子组中的主机必须与其所属的子组级别直接解除关联。"
-
-#: screens/Host/HostGroups/HostGroupsList.js:212
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:230
-msgid ""
-"Note that you may still see the group in the list after\n"
-"disassociating if the host is also a member of that group’s\n"
-"children. This list shows all groups the host is associated\n"
-"with directly and indirectly."
-msgstr "请注意,如果主机也是组子对象的成员,在解除关联后,您可能仍然可以在列表中看到组。这个列表显示了主机与其直接及间接关联的所有组。"
-
-#: components/Lookup/InstanceGroupsLookup.js:90
-msgid "Note: The order in which these are selected sets the execution precedence. Select more than one to enable drag."
-msgstr "注:选择它们的顺序设定执行优先级。选择多个来启用拖放。"
-
-#: screens/Organization/shared/OrganizationForm.js:116
-msgid "Note: The order of these credentials sets precedence for the sync and lookup of the content. Select more than one to enable drag."
-msgstr "注意:这些凭据的顺序设置内容同步和查找的优先级。选择多个来启用拖放。"
-
-#: screens/Project/shared/Project.helptext.js:81
-msgid "Note: This field assumes the remote name is \"origin\"."
-msgstr "注意:该字段假设远程名称为“origin”。"
-
-#: screens/Project/shared/Project.helptext.js:35
-msgid ""
-"Note: When using SSH protocol for GitHub or\n"
-"Bitbucket, enter an SSH key only, do not enter a username\n"
-"(other than git). Additionally, GitHub and Bitbucket do\n"
-"not support password authentication when using SSH. GIT\n"
-"read only protocol (git://) does not use username or\n"
-"password information."
-msgstr "备注:在将 SSH 协议用于 GitHub 或 Bitbucket 时,只需输入 SSH 密钥,而不要输入用户名(除 git 外)。另外,GitHub 和 Bitbucket 在使用 SSH 时不支持密码验证。GIT 只读协议 (git://) 不使用用户名或密码信息。"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:331
-msgid "Notification Color"
-msgstr "通知颜色"
-
-#: screens/NotificationTemplate/NotificationTemplate.js:58
-#: screens/NotificationTemplate/NotificationTemplateAdd.js:50
-msgid "Notification Template not found."
-msgstr "没有找到通知模板。"
-
-#: screens/ActivityStream/ActivityStream.js:198
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:117
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:171
-#: screens/NotificationTemplate/NotificationTemplates.js:14
-#: screens/NotificationTemplate/NotificationTemplates.js:21
-#: util/getRelatedResourceDeleteDetails.js:181
-msgid "Notification Templates"
-msgstr "通知模板"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:134
-msgid "Notification Type"
-msgstr "通知类型"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:380
-msgid "Notification color"
-msgstr "通知颜色"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:193
-msgid "Notification sent successfully"
-msgstr "发送通知成功"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:449
-msgid "Notification test failed."
-msgstr "通知测试失败。"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:197
-msgid "Notification timed out"
-msgstr "通知超时"
-
-#: components/NotificationList/NotificationList.js:190
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:131
-msgid "Notification type"
-msgstr "通知类型"
-
-#: components/NotificationList/NotificationList.js:177
-#: routeConfig.js:122
-#: screens/Inventory/Inventories.js:93
-#: screens/Inventory/InventorySource/InventorySource.js:99
-#: screens/ManagementJob/ManagementJob.js:116
-#: screens/ManagementJob/ManagementJobs.js:22
-#: screens/Organization/Organization.js:135
-#: screens/Organization/Organizations.js:33
-#: screens/Project/Project.js:114
-#: screens/Project/Projects.js:28
-#: screens/Template/Template.js:141
-#: screens/Template/Templates.js:46
-#: screens/Template/WorkflowJobTemplate.js:123
-msgid "Notifications"
-msgstr "通知"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:169
-#: components/Schedule/shared/FrequencyDetailSubform.js:148
-msgid "November"
-msgstr "11 月"
-
-#: components/StatusLabel/StatusLabel.js:44
-#: components/Workflow/WorkflowNodeHelp.js:117
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:66
-#: screens/Job/JobOutput/shared/HostStatusBar.js:35
-msgid "OK"
-msgstr "确定"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:42
-#: components/Schedule/shared/FrequencyDetailSubform.js:549
-msgid "Occurrences"
-msgstr "发生次数"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:168
-#: components/Schedule/shared/FrequencyDetailSubform.js:143
-msgid "October"
-msgstr "10 月"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:189
-#: components/HostToggle/HostToggle.js:61
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:192
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:195
-#: components/PromptDetail/PromptDetail.js:362
-#: components/PromptDetail/PromptJobTemplateDetail.js:156
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:489
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:58
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:45
-#: screens/Setting/shared/SettingDetail.js:98
-#: screens/Setting/shared/SharedFields.js:150
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:284
-#: screens/Template/shared/JobTemplateForm.js:504
-msgid "Off"
-msgstr "关"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:188
-#: components/HostToggle/HostToggle.js:60
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:192
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:194
-#: components/PromptDetail/PromptDetail.js:362
-#: components/PromptDetail/PromptJobTemplateDetail.js:156
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:489
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:57
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:45
-#: screens/Setting/shared/SettingDetail.js:98
-#: screens/Setting/shared/SharedFields.js:149
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:284
-#: screens/Template/shared/JobTemplateForm.js:504
-msgid "On"
-msgstr "开"
-
-#: components/Workflow/WorkflowLegend.js:126
-#: components/Workflow/WorkflowLinkHelp.js:30
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:39
-msgid "On Failure"
-msgstr "失败时"
-
-#: components/Workflow/WorkflowLegend.js:122
-#: components/Workflow/WorkflowLinkHelp.js:27
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:63
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:32
-msgid "On Success"
-msgstr "成功时"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:536
-msgid "On date"
-msgstr "于日期"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:99
-#: components/Schedule/shared/FrequencyDetailSubform.js:251
-msgid "On days"
-msgstr "于日"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:18
-msgid ""
-"One Slack channel per line. The pound symbol (#)\n"
-"is required for channels. To respond to or start a thread to a specific message add the parent message Id to the channel where the parent message Id is 16 digits. A dot (.) must be manually inserted after the 10th digit. ie:#destination-channel, 1231257890.006423. See Slack"
-msgstr "每行一个 Slack 频道。频道需要一个井号(#)。要响应一个特点信息或启动一个特定消息,将父信息 Id 添加到频道中,父信息 Id 为 16 位。在第 10 位数字后需要手动插入一个点(.)。例如:#destination-channel, 1231257890.006423。请参阅 Slack"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:154
-msgid "Only Group By"
-msgstr "唯一分组标准"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:103
-msgid "OpenStack"
-msgstr "OpenStack"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:111
-msgid "Option Details"
-msgstr "选项详情"
-
-#: screens/Inventory/shared/Inventory.helptext.js:25
-msgid ""
-"Optional labels that describe this inventory,\n"
-"such as 'dev' or 'test'. Labels can be used to group and filter\n"
-"inventories and completed jobs."
-msgstr "描述此清单的可选标签,如 'dev' 或 'test'。标签可用于对清单和完成的作业进行分组和过滤。"
-
-#: screens/Job/Job.helptext.js:12
-#: screens/Template/shared/JobTemplate.helptext.js:13
-msgid "Optional labels that describe this job template, such as 'dev' or 'test'. Labels can be used to group and filter job templates and completed jobs."
-msgstr "描述此任务模板的可选标签,如 'dev' 或 'test'。标签可用于对任务模板和完成的任务进行分组和过滤。"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:11
-msgid ""
-"Optional labels that describe this workflow job template,\n"
-"such as 'dev' or 'test'. Labels can be used to group and filter\n"
-"workflow job templates and completed jobs."
-msgstr "描述此工作流作业模板的可选标签,如 'dev' 或 'test'。标签可用于对工作流作业模板和完成的作业进行分组和过滤。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:26
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:19
-msgid "Optionally select the credential to use to send status updates back to the webhook service."
-msgstr "(可选)选择要用来向 Webhook 服务发回状态更新的凭证。"
-
-#: components/NotificationList/NotificationList.js:220
-#: components/NotificationList/NotificationListItem.js:34
-#: screens/Credential/shared/TypeInputsSubForm.js:47
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:61
-#: screens/Instances/Shared/InstanceForm.js:54
-#: screens/Inventory/shared/InventoryForm.js:94
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:69
-#: screens/Template/shared/JobTemplateForm.js:545
-#: screens/Template/shared/WorkflowJobTemplateForm.js:241
-msgid "Options"
-msgstr "选项"
-
-#: screens/Template/Survey/SurveyReorderModal.js:217
-#: screens/Template/Survey/SurveyReorderModal.js:217
-#: screens/Template/Survey/SurveyReorderModal.js:233
-msgid "Order"
-msgstr "顺序"
-
-#: components/Lookup/ApplicationLookup.js:119
-#: components/Lookup/OrganizationLookup.js:101
-#: components/Lookup/OrganizationLookup.js:107
-#: components/Lookup/OrganizationLookup.js:124
-#: components/PromptDetail/PromptInventorySourceDetail.js:62
-#: components/PromptDetail/PromptInventorySourceDetail.js:72
-#: components/PromptDetail/PromptJobTemplateDetail.js:105
-#: components/PromptDetail/PromptJobTemplateDetail.js:115
-#: components/PromptDetail/PromptProjectDetail.js:77
-#: components/PromptDetail/PromptProjectDetail.js:88
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:67
-#: components/TemplateList/TemplateList.js:244
-#: components/TemplateList/TemplateListItem.js:185
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:70
-#: screens/Application/ApplicationsList/ApplicationListItem.js:38
-#: screens/Application/ApplicationsList/ApplicationsList.js:157
-#: screens/Credential/CredentialDetail/CredentialDetail.js:230
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:70
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:155
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:167
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:76
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:96
-#: screens/Inventory/InventoryList/InventoryList.js:191
-#: screens/Inventory/InventoryList/InventoryList.js:221
-#: screens/Inventory/InventoryList/InventoryListItem.js:119
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:202
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:107
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:121
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:131
-#: screens/Project/ProjectDetail/ProjectDetail.js:180
-#: screens/Project/ProjectList/ProjectListItem.js:287
-#: screens/Project/ProjectList/ProjectListItem.js:298
-#: screens/Team/TeamDetail/TeamDetail.js:40
-#: screens/Team/TeamList/TeamList.js:143
-#: screens/Team/TeamList/TeamListItem.js:38
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:199
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:210
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:120
-#: screens/User/UserTeams/UserTeamList.js:181
-#: screens/User/UserTeams/UserTeamList.js:237
-#: screens/User/UserTeams/UserTeamListItem.js:23
-msgid "Organization"
-msgstr "机构"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:100
-msgid "Organization (Name)"
-msgstr "机构(名称)"
-
-#: screens/Team/TeamList/TeamList.js:126
-msgid "Organization Name"
-msgstr "机构名称"
-
-#: screens/Organization/Organization.js:154
-msgid "Organization not found."
-msgstr "未找到机构。"
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:188
-#: routeConfig.js:96
-#: screens/ActivityStream/ActivityStream.js:181
-#: screens/Organization/OrganizationList/OrganizationList.js:117
-#: screens/Organization/OrganizationList/OrganizationList.js:163
-#: screens/Organization/Organizations.js:16
-#: screens/Organization/Organizations.js:26
-#: screens/User/User.js:66
-#: screens/User/UserOrganizations/UserOrganizationList.js:72
-#: screens/User/Users.js:33
-#: util/getRelatedResourceDeleteDetails.js:232
-#: util/getRelatedResourceDeleteDetails.js:266
-msgid "Organizations"
-msgstr "机构"
-
-#: components/LaunchPrompt/steps/useOtherPromptsStep.js:90
-msgid "Other prompts"
-msgstr "其他提示"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:79
-msgid "Out of compliance"
-msgstr "不合规"
-
-#: screens/Job/Job.js:131
-#: screens/Job/JobOutput/HostEventModal.js:156
-#: screens/Job/Jobs.js:34
-msgid "Output"
-msgstr "输出"
-
-#: screens/Job/JobOutput/HostEventModal.js:157
-msgid "Output tab"
-msgstr "输出标签页"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:80
-msgid "Overwrite"
-msgstr "覆盖"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:41
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:121
-msgid "Overwrite local groups and hosts from remote inventory source"
-msgstr "从远程清单源覆盖本地组和主机"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:46
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:127
-msgid "Overwrite local variables from remote inventory source"
-msgstr "从远程清单源覆盖本地变量"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:86
-msgid "Overwrite variables"
-msgstr "覆盖变量"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:493
-msgid "POST"
-msgstr "POST"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:494
-msgid "PUT"
-msgstr "PUT"
-
-#: components/NotificationList/NotificationList.js:198
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:139
-msgid "Pagerduty"
-msgstr "Pagerduty"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:279
-msgid "Pagerduty Subdomain"
-msgstr "Pagerduty 子域"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:298
-msgid "Pagerduty subdomain"
-msgstr "Pagerduty 子域"
-
-#: components/Pagination/Pagination.js:35
-msgid "Pagination"
-msgstr "分页"
-
-#: components/Workflow/WorkflowTools.js:165
-msgid "Pan Down"
-msgstr "Pan Down"
-
-#: components/Workflow/WorkflowTools.js:132
-msgid "Pan Left"
-msgstr "Pan Left"
-
-#: components/Workflow/WorkflowTools.js:176
-msgid "Pan Right"
-msgstr "Pan Right"
-
-#: components/Workflow/WorkflowTools.js:143
-msgid "Pan Up"
-msgstr "Pan Up"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:243
-msgid "Pass extra command line changes. There are two ansible command line parameters:"
-msgstr "传递额外的命令行更改。有两个 ansible 命令行参数:"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:14
-msgid "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. Refer to the Ansible Controller documentation for example syntax."
-msgstr "向 playbook 传递额外的命令行变量。这是 ansible-playbook 的 -e 或 --extra-vars 命令行参数。使用 YAML 或 JSON 提供键/值对。示例语法请参阅 Ansible 控制器文档。"
-
-#: screens/Job/Job.helptext.js:13
-#: screens/Template/shared/JobTemplate.helptext.js:14
-msgid "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. Refer to the documentation for example syntax."
-msgstr "向 playbook 传递额外的命令行变量。这是 ansible-playbook 的 -e 或 --extra-vars 命令行参数。使用 YAML \n"
-"或 JSON 提供键/值对。示例语法请参阅相关文档。"
-
-#: screens/Login/Login.js:227
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:73
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:101
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:212
-#: screens/Template/Survey/SurveyQuestionForm.js:82
-#: screens/User/shared/UserForm.js:88
-msgid "Password"
-msgstr "密码"
-
-#: screens/Dashboard/DashboardGraph.js:119
-msgid "Past 24 hours"
-msgstr "过去 24 小时"
-
-#: screens/Dashboard/DashboardGraph.js:110
-msgid "Past month"
-msgstr "过去一个月"
-
-#: screens/Dashboard/DashboardGraph.js:113
-msgid "Past two weeks"
-msgstr "过去两周"
-
-#: screens/Dashboard/DashboardGraph.js:116
-msgid "Past week"
-msgstr "过去一周"
-
-#: screens/Instances/Instance.js:51
-#: screens/Instances/InstancePeers/InstancePeerList.js:74
-msgid "Peers"
-msgstr "对等"
-
-#: components/JobList/JobList.js:228
-#: components/StatusLabel/StatusLabel.js:49
-#: components/Workflow/WorkflowNodeHelp.js:93
-msgid "Pending"
-msgstr "待处理"
-
-#: components/AppContainer/PageHeaderToolbar.js:76
-msgid "Pending Workflow Approvals"
-msgstr "等待工作流批准"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:128
-msgid "Pending delete"
-msgstr "等待删除"
-
-#: components/Lookup/HostFilterLookup.js:370
-msgid "Perform a search to define a host filter"
-msgstr "执行搜索以定义主机过滤器"
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:73
-#: screens/User/UserTokenList/UserTokenList.js:105
-msgid "Personal Access Token"
-msgstr "个人访问令牌"
-
-#: screens/User/UserTokenList/UserTokenListItem.js:26
-msgid "Personal access token"
-msgstr "个人访问令牌"
-
-#: screens/Job/JobOutput/HostEventModal.js:122
-msgid "Play"
-msgstr "Play"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:84
-msgid "Play Count"
-msgstr "play 数量"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:124
-msgid "Play Started"
-msgstr "Play 已启动"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:148
-#: screens/Job/JobDetail/JobDetail.js:319
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:253
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:43
-#: screens/Template/shared/JobTemplateForm.js:357
-msgid "Playbook"
-msgstr "Playbook"
-
-#: components/JobList/JobListItem.js:44
-#: screens/Job/JobDetail/JobDetail.js:67
-msgid "Playbook Check"
-msgstr "Playbook 检查"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:125
-msgid "Playbook Complete"
-msgstr "Playbook 完成"
-
-#: components/PromptDetail/PromptProjectDetail.js:165
-#: screens/Project/ProjectDetail/ProjectDetail.js:288
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:72
-msgid "Playbook Directory"
-msgstr "Playbook 目录"
-
-#: components/JobList/JobList.js:213
-#: components/JobList/JobListItem.js:44
-#: components/Schedule/ScheduleList/ScheduleListItem.js:37
-#: screens/Job/JobDetail/JobDetail.js:67
-msgid "Playbook Run"
-msgstr "Playbook 运行"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:126
-msgid "Playbook Started"
-msgstr "Playbook 已启动"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:174
-#: components/TemplateList/TemplateList.js:222
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:23
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:54
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:157
-msgid "Playbook name"
-msgstr "Playbook 名称"
-
-#: screens/Dashboard/DashboardGraph.js:146
-msgid "Playbook run"
-msgstr "Playbook 运行"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:85
-msgid "Plays"
-msgstr "Play"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:149
-msgid "Please add a Schedule to populate this list."
-msgstr "请添加一个调度来填充此列表。"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:152
-msgid "Please add a Schedule to populate this list. Schedules can be added to a Template, Project, or Inventory Source."
-msgstr "请添加一个调度来填充此列表。调度可以添加到模板、项目或清单源中。"
-
-#: screens/Template/Survey/SurveyList.js:146
-msgid "Please add survey questions."
-msgstr "请添加问卷调查问题。"
-
-#: components/PaginatedTable/PaginatedTable.js:93
-msgid "Please add {pluralizedItemName} to populate this list"
-msgstr "请添加 {pluralizedItemName} 来填充此列表"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:43
-msgid "Please click the Start button to begin."
-msgstr "请点开始按钮开始。"
-
-#: components/Schedule/shared/ScheduleForm.js:421
-msgid "Please enter a number of occurrences."
-msgstr "请输入事件发生的值。"
-
-#: util/validators.js:160
-msgid "Please enter a valid URL"
-msgstr "请输入有效的 URL"
-
-#: screens/User/shared/UserTokenForm.js:20
-msgid "Please enter a value."
-msgstr "请输入一个值。"
-
-#: screens/Login/Login.js:191
-msgid "Please log in"
-msgstr "请登录"
-
-#: components/JobList/JobList.js:190
-msgid "Please run a job to populate this list."
-msgstr "请运行一个作业来填充此列表。"
-
-#: components/Schedule/shared/ScheduleForm.js:417
-msgid "Please select a day number between 1 and 31."
-msgstr "选择的日数字应介于 1 到 31 之间。"
-
-#: screens/Template/shared/JobTemplateForm.js:174
-msgid "Please select an Inventory or check the Prompt on Launch option"
-msgstr "请选择一个清单或者选中“启动时提示”选项"
-
-#: components/Schedule/shared/ScheduleForm.js:429
-msgid "Please select an end date/time that comes after the start date/time."
-msgstr "请选择一个比开始日期/时间晚的结束日期/时间。"
-
-#: components/Lookup/HostFilterLookup.js:359
-msgid "Please select an organization before editing the host filter"
-msgstr "请在编辑主机过滤器前选择机构"
-
-#: screens/Job/JobOutput/EmptyOutput.js:32
-msgid "Please try another search using the filter above"
-msgstr "请使用上面的过滤器尝试另一个搜索"
-
-#: screens/TopologyView/ContentLoading.js:40
-msgid "Please wait until the topology view is populated..."
-msgstr "请等到拓扑视图被填充..."
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:78
-msgid "Pod spec override"
-msgstr "Pod 规格覆写"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:214
-#: screens/InstanceGroup/Instances/InstanceListItem.js:203
-#: screens/Instances/InstanceDetail/InstanceDetail.js:208
-#: screens/Instances/InstanceList/InstanceListItem.js:218
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:82
-msgid "Policy Type"
-msgstr "策略类型"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:63
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:26
-msgid "Policy instance minimum"
-msgstr "策略实例最小值"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:70
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:36
-msgid "Policy instance percentage"
-msgstr "策略实例百分比"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginField.js:64
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginField.js:70
-msgid "Populate field from an external secret management system"
-msgstr "从外部 secret 管理系统填充字段"
-
-#: components/Lookup/HostFilterLookup.js:349
-msgid ""
-"Populate the hosts for this inventory by using a search\n"
-"filter. Example: ansible_facts__ansible_distribution:\"RedHat\".\n"
-"Refer to the documentation for further syntax and\n"
-"examples. Refer to the Ansible Controller documentation for further syntax and\n"
-"examples."
-msgstr "使用搜索过滤器填充此清单的主机。例如:ansible_facts__ansible_distribution:\"RedHat\"。如需语法和实例的更多信息,请参阅相关文档。请参阅 Ansible 控制器文档来获得更多信息。"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:165
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:104
-msgid "Port"
-msgstr "端口"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:231
-msgid "Preconditions for running this node when there are multiple parents. Refer to the"
-msgstr "在有多个父对象时运行此节点的先决条件。请参阅"
-
-#: screens/Template/Survey/MultipleChoiceField.js:59
-msgid ""
-"Press 'Enter' to add more answer choices. One answer\n"
-"choice per line."
-msgstr "按 'Enter' 添加更多回答选择。每行一个回答选择。"
-
-#: components/CodeEditor/CodeEditor.js:181
-msgid "Press Enter to edit. Press ESC to stop editing."
-msgstr "按 Enter 进行编辑。按 ESC 停止编辑。"
-
-#: components/SelectedList/DraggableSelectedList.js:85
-msgid ""
-"Press space or enter to begin dragging,\n"
-"and use the arrow keys to navigate up or down.\n"
-"Press enter to confirm the drag, or any other key to\n"
-"cancel the drag operation."
-msgstr "按空格或输入开始拖动,并使用箭头键导航或关闭。按 Enter 键确认拖动或任何其他键以取消拖动操作。"
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:71
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:130
-#: screens/Inventory/shared/InventoryForm.js:99
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:147
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:347
-#: screens/Template/shared/JobTemplateForm.js:603
-msgid "Prevent Instance Group Fallback"
-msgstr "防止实例组 Fallback"
-
-#: screens/Inventory/shared/Inventory.helptext.js:197
-msgid "Prevent Instance Group Fallback: If enabled, the inventory will prevent adding any organization instance groups to the list of preferred instances groups to run associated job templates on."
-msgstr "防止实例组 Fallback:如果启用,则该清单将阻止将任何机构实例组添加到运行相关作业模板的首选实例组列表中。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:43
-msgid "Prevent Instance Group Fallback: If enabled, the job template will prevent adding any inventory or organization instance groups to the list of preferred instances groups to run on."
-msgstr "防止实例组 Fallback:如果启用,则作业模板将阻止将任何清单或机构实例组添加到要运行的首选实例组列表中。"
-
-#: components/AdHocCommands/useAdHocPreviewStep.js:17
-#: components/LaunchPrompt/steps/usePreviewStep.js:23
-msgid "Preview"
-msgstr "预览"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:103
-msgid "Private key passphrase"
-msgstr "私钥密码"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:58
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:122
-#: screens/Template/shared/JobTemplateForm.js:551
-msgid "Privilege Escalation"
-msgstr "权限升级"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:111
-msgid "Privilege escalation password"
-msgstr "权限升级密码"
-
-#: screens/Template/shared/JobTemplate.helptext.js:40
-msgid "Privilege escalation: If enabled, run this playbook as an administrator."
-msgstr "权利升级:如果启用,则以管理员身份运行此 playbook。"
-
-#: components/JobList/JobListItem.js:239
-#: components/Lookup/ProjectLookup.js:104
-#: components/Lookup/ProjectLookup.js:109
-#: components/Lookup/ProjectLookup.js:166
-#: components/PromptDetail/PromptInventorySourceDetail.js:87
-#: components/PromptDetail/PromptJobTemplateDetail.js:133
-#: components/PromptDetail/PromptJobTemplateDetail.js:141
-#: components/TemplateList/TemplateListItem.js:300
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:216
-#: screens/Job/JobDetail/JobDetail.js:172
-#: screens/Job/JobDetail/JobDetail.js:198
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:229
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:239
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:38
-msgid "Project"
-msgstr "项目"
-
-#: components/PromptDetail/PromptProjectDetail.js:158
-#: screens/Project/ProjectDetail/ProjectDetail.js:281
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:61
-msgid "Project Base Path"
-msgstr "项目基本路径"
-
-#: components/Workflow/WorkflowLegend.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:85
-msgid "Project Sync"
-msgstr "项目同步"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:320
-#: screens/Project/ProjectList/ProjectListItem.js:229
-msgid "Project Sync Error"
-msgstr "项目同步错误"
-
-#: components/Workflow/WorkflowNodeHelp.js:67
-msgid "Project Update"
-msgstr "项目更新"
-
-#: screens/Job/JobDetail/JobDetail.js:180
-msgid "Project Update Status"
-msgstr "项目更新状态"
-
-#: screens/Job/Job.helptext.js:22
-msgid "Project checkout results"
-msgstr "项目签出结果"
-
-#: screens/Project/ProjectList/ProjectList.js:132
-msgid "Project copied successfully"
-msgstr "成功复制的项目"
-
-#: screens/Project/Project.js:136
-msgid "Project not found."
-msgstr "未找到项目。"
-
-#: screens/Dashboard/Dashboard.js:109
-msgid "Project sync failures"
-msgstr "项目同步失败"
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:146
-#: routeConfig.js:75
-#: screens/ActivityStream/ActivityStream.js:170
-#: screens/Dashboard/Dashboard.js:103
-#: screens/Project/ProjectList/ProjectList.js:180
-#: screens/Project/ProjectList/ProjectList.js:249
-#: screens/Project/Projects.js:12
-#: screens/Project/Projects.js:22
-#: util/getRelatedResourceDeleteDetails.js:60
-#: util/getRelatedResourceDeleteDetails.js:195
-#: util/getRelatedResourceDeleteDetails.js:225
-msgid "Projects"
-msgstr "项目"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:139
-msgid "Promote Child Groups and Hosts"
-msgstr "提升子组和主机"
-
-#: components/Schedule/shared/ScheduleForm.js:540
-#: components/Schedule/shared/ScheduleForm.js:543
-msgid "Prompt"
-msgstr "提示"
-
-#: components/PromptDetail/PromptDetail.js:182
-msgid "Prompt Overrides"
-msgstr "提示覆盖"
-
-#: components/CodeEditor/VariablesField.js:241
-#: components/FieldWithPrompt/FieldWithPrompt.js:46
-#: screens/Credential/CredentialDetail/CredentialDetail.js:175
-msgid "Prompt on launch"
-msgstr "启动时提示"
-
-#: components/Schedule/shared/SchedulePromptableFields.js:97
-msgid "Prompt | {0}"
-msgstr "提示 | {0}"
-
-#: components/PromptDetail/PromptDetail.js:180
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:418
-msgid "Prompted Values"
-msgstr "提示的值"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:6
-msgid ""
-"Provide a host pattern to further constrain\n"
-"the list of hosts that will be managed or affected by the\n"
-"playbook. Multiple patterns are allowed. Refer to Ansible\n"
-"documentation for more information and examples on patterns."
-msgstr "提供主机模式以进一步限制受 playbook 管理或影响的主机列表。允许使用多种模式。请参阅 Ansible 文档,以了解更多有关模式的信息和示例。"
-
-#: screens/Job/Job.helptext.js:14
-#: screens/Template/shared/JobTemplate.helptext.js:15
-msgid "Provide a host pattern to further constrain the list of hosts that will be managed or affected by the playbook. Multiple patterns are allowed. Refer to Ansible documentation for more information and examples on patterns."
-msgstr "提供主机模式以进一步限制受 playbook 管理或影响的主机列表。允许使用多种模式。请参阅 Ansible 文档,以了解更多有关模式的信息和示例。"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:179
-msgid "Provide a value for this field or select the Prompt on launch option."
-msgstr "为这个字段输入值或者选择「启动时提示」选项。"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:247
-msgid ""
-"Provide key/value pairs using either\n"
-"YAML or JSON."
-msgstr "使用 YAML 或 JSON 提供\n"
-"键/值对。"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:191
-msgid ""
-"Provide your Red Hat or Red Hat Satellite credentials\n"
-"below and you can choose from a list of your available subscriptions.\n"
-"The credentials you use will be stored for future use in\n"
-"retrieving renewal or expanded subscriptions."
-msgstr "提供您的 Red Hat 或 Red Hat Satellite 凭证,可从可用许可证列表中进行选择。您使用的凭证会被保存,以供将来用于续订或扩展订阅。"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:83
-msgid "Provide your Red Hat or Red Hat Satellite credentials to enable Automation Analytics."
-msgstr "提供您的 Red Hat 或 Red Hat Satellite 凭证以启用 Automation Analytics。"
-
-#: components/StatusLabel/StatusLabel.js:59
-#: screens/TopologyView/Legend.js:150
-msgid "Provisioning"
-msgstr "置备"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:162
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:302
-#: screens/Template/shared/JobTemplateForm.js:620
-msgid "Provisioning Callback URL"
-msgstr "部署回调 URL"
-
-#: screens/Template/shared/JobTemplateForm.js:615
-msgid "Provisioning Callback details"
-msgstr "置备回调详情"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:63
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:127
-#: screens/Template/shared/JobTemplateForm.js:555
-#: screens/Template/shared/JobTemplateForm.js:558
-msgid "Provisioning Callbacks"
-msgstr "置备回调"
-
-#: screens/Template/shared/JobTemplate.helptext.js:41
-msgid "Provisioning callbacks: Enables creation of a provisioning callback URL. Using the URL a host can contact Ansible AWX and request a configuration update using this job template."
-msgstr "置备回调:允许创建部署回调 URL。使用此 URL,主机可访问 Ansible AWX 并使用此作业模板请求配置更新。"
-
-#: components/StatusLabel/StatusLabel.js:62
-msgid "Provisioning fail"
-msgstr "置备失败"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:86
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:114
-msgid "Pull"
-msgstr "Pull"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:163
-msgid "Question"
-msgstr "问题"
-
-#: screens/Setting/Settings.js:106
-msgid "RADIUS"
-msgstr "RADIUS"
-
-#: screens/Setting/SettingList.js:73
-msgid "RADIUS settings"
-msgstr "RADIUS 设置"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:244
-#: screens/InstanceGroup/Instances/InstanceListItem.js:161
-#: screens/Instances/InstanceDetail/InstanceDetail.js:287
-#: screens/Instances/InstanceList/InstanceListItem.js:171
-#: screens/TopologyView/Tooltip.js:307
-msgid "RAM {0}"
-msgstr "RAM {0}"
-
-#: screens/User/shared/UserTokenForm.js:76
-msgid "Read"
-msgstr "读取"
-
-#: components/StatusLabel/StatusLabel.js:57
-#: screens/TopologyView/Legend.js:122
-msgid "Ready"
-msgstr "就绪"
-
-#: screens/Dashboard/Dashboard.js:133
-msgid "Recent Jobs"
-msgstr "最近的作业"
-
-#: screens/Dashboard/Dashboard.js:131
-msgid "Recent Jobs list tab"
-msgstr "最近的任务列表标签页"
-
-#: screens/Dashboard/Dashboard.js:145
-msgid "Recent Templates"
-msgstr "最近模板"
-
-#: screens/Dashboard/Dashboard.js:143
-msgid "Recent Templates list tab"
-msgstr "最近模板列表标签页"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:188
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:112
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:38
-msgid "Recent jobs"
-msgstr "最近的作业"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:154
-msgid "Recipient List"
-msgstr "接收者列表"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:86
-msgid "Recipient list"
-msgstr "接收者列表"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:105
-msgid "Red Hat Ansible Automation Platform"
-msgstr "Red Hat Ansible Automation Platform"
-
-#: components/Lookup/ProjectLookup.js:139
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:92
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:161
-#: screens/Job/JobDetail/JobDetail.js:77
-#: screens/Project/ProjectList/ProjectList.js:201
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:100
-msgid "Red Hat Insights"
-msgstr "Red Hat Insights"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:102
-msgid "Red Hat Satellite 6"
-msgstr "Red Hat Satellite 6"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:104
-msgid "Red Hat Virtualization"
-msgstr "Red Hat Virtualization"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:117
-msgid "Red Hat subscription manifest"
-msgstr "Red Hat 订阅清单"
-
-#: components/About/About.js:36
-msgid "Red Hat, Inc."
-msgstr "Red Hat, Inc."
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:94
-#: screens/Application/shared/ApplicationForm.js:107
-msgid "Redirect URIs"
-msgstr "重定向 URI"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:259
-msgid "Redirecting to dashboard"
-msgstr "重定向到仪表盘"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:263
-msgid "Redirecting to subscription detail"
-msgstr "重定向到订阅详情"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:261
-#: screens/Template/shared/JobTemplate.helptext.js:55
-msgid "Refer to the"
-msgstr "请参阅"
-
-#: screens/Job/Job.helptext.js:27
-#: screens/Template/shared/JobTemplate.helptext.js:50
-msgid "Refer to the Ansible documentation for details about the configuration file."
-msgstr "有关配置文件的详情请参阅 Ansible 文档。"
-
-#: screens/User/UserTokens/UserTokens.js:77
-msgid "Refresh Token"
-msgstr "刷新令牌"
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:81
-msgid "Refresh Token Expiration"
-msgstr "刷新令牌过期"
-
-#: screens/Project/ProjectList/ProjectListItem.js:132
-msgid "Refresh for revision"
-msgstr "重新刷新修订版本"
-
-#: screens/Project/ProjectList/ProjectListItem.js:134
-msgid "Refresh project revision"
-msgstr "重新刷新项目修订版本"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:116
-msgid "Regions"
-msgstr "区域"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:92
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:143
-msgid "Registry credential"
-msgstr "注册表凭证"
-
-#: screens/Inventory/shared/Inventory.helptext.js:156
-msgid "Regular expression where only matching host names will be imported. The filter is applied as a post-processing step after any inventory plugin filters are applied."
-msgstr "仅导入主机名与这个正则表达式匹配的主机。该过滤器在应用任何清单插件过滤器后作为后步骤使用。"
-
-#: screens/Inventory/Inventories.js:81
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:62
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:175
-msgid "Related Groups"
-msgstr "相关组"
-
-#: components/Search/AdvancedSearch.js:287
-msgid "Related Keys"
-msgstr "相关密钥"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:169
-#: components/Schedule/ScheduleList/ScheduleListItem.js:105
-msgid "Related resource"
-msgstr "相关资源"
-
-#: components/Search/RelatedLookupTypeInput.js:16
-#: components/Search/RelatedLookupTypeInput.js:24
-msgid "Related search type"
-msgstr "相关的搜索类型"
-
-#: components/Search/RelatedLookupTypeInput.js:19
-msgid "Related search type typeahead"
-msgstr "相关的搜索类型 typeahead"
-
-#: components/JobList/JobListItem.js:146
-#: components/LaunchButton/ReLaunchDropDown.js:82
-#: screens/Job/JobDetail/JobDetail.js:580
-#: screens/Job/JobDetail/JobDetail.js:588
-#: screens/Job/JobOutput/shared/OutputToolbar.js:167
-msgid "Relaunch"
-msgstr "重新启动"
-
-#: components/JobList/JobListItem.js:126
-#: screens/Job/JobOutput/shared/OutputToolbar.js:147
-msgid "Relaunch Job"
-msgstr "重新启动作业"
-
-#: components/LaunchButton/ReLaunchDropDown.js:41
-msgid "Relaunch all hosts"
-msgstr "重新启动所有主机"
-
-#: components/LaunchButton/ReLaunchDropDown.js:54
-msgid "Relaunch failed hosts"
-msgstr "重新启动失败的主机"
-
-#: components/LaunchButton/ReLaunchDropDown.js:30
-#: components/LaunchButton/ReLaunchDropDown.js:35
-msgid "Relaunch on"
-msgstr "重新启动于"
-
-#: components/JobList/JobListItem.js:125
-#: screens/Job/JobOutput/shared/OutputToolbar.js:146
-msgid "Relaunch using host parameters"
-msgstr "使用主机参数重新启动"
-
-#: components/HealthCheckAlert/HealthCheckAlert.js:27
-msgid "Reload"
-msgstr "重新加载"
-
-#: screens/Job/JobOutput/JobOutput.js:723
-msgid "Reload output"
-msgstr "重新加载输出"
-
-#: components/Lookup/ProjectLookup.js:138
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:91
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:160
-#: screens/Job/JobDetail/JobDetail.js:78
-#: screens/Project/ProjectList/ProjectList.js:200
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:99
-msgid "Remote Archive"
-msgstr "远程归档"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:374
-#: screens/Instances/InstanceList/InstanceList.js:242
-msgid "Removal Error"
-msgstr "删除错误"
-
-#: components/SelectedList/DraggableSelectedList.js:105
-#: screens/Instances/Shared/RemoveInstanceButton.js:75
-#: screens/Instances/Shared/RemoveInstanceButton.js:129
-#: screens/Instances/Shared/RemoveInstanceButton.js:143
-#: screens/Instances/Shared/RemoveInstanceButton.js:163
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:21
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:29
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:40
-msgid "Remove"
-msgstr "删除"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:36
-msgid "Remove All Nodes"
-msgstr "删除所有节点"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:152
-msgid "Remove Instances"
-msgstr "删除实例"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:17
-msgid "Remove Link"
-msgstr "删除链接"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:28
-msgid "Remove Node {nodeName}"
-msgstr "删除节点 {nodeName}"
-
-#: screens/Project/shared/Project.helptext.js:113
-msgid "Remove any local modifications prior to performing an update."
-msgstr "在进行更新前删除任何本地修改。"
-
-#: components/Search/AdvancedSearch.js:206
-msgid "Remove the current search related to ansible facts to enable another search using this key."
-msgstr "删除与 ansible 事实相关的当前搜索,以启用使用此键的另一个搜索。"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:14
-msgid "Remove {0} Access"
-msgstr "删除 {0} 访问"
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:45
-msgid "Remove {0} chip"
-msgstr "删除 {0} 芯片"
-
-#: screens/TopologyView/Legend.js:285
-msgid "Removing"
-msgstr "删除"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:48
-msgid "Removing this link will orphan the rest of the branch and cause it to be executed immediately on launch."
-msgstr "删除此链接将会孤立分支的剩余部分,并导致它在启动时立即执行。"
-
-#: components/SelectedList/DraggableSelectedList.js:83
-msgid "Reorder"
-msgstr "重新排序"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:349
-msgid "Repeat Frequency"
-msgstr "重复频率"
-
-#: components/Schedule/shared/ScheduleFormFields.js:113
-msgid "Repeat frequency"
-msgstr "重复频率"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:52
-msgid "Replace"
-msgstr "替换"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:60
-msgid "Replace field with new value"
-msgstr "使用新值替换项"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:67
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:74
-msgid "Request subscription"
-msgstr "请求订阅"
-
-#: screens/Template/Survey/SurveyListItem.js:51
-#: screens/Template/Survey/SurveyQuestionForm.js:188
-msgid "Required"
-msgstr "必需"
-
-#: screens/TopologyView/Header.js:87
-#: screens/TopologyView/Header.js:90
-msgid "Reset zoom"
-msgstr "重新设置缩放"
-
-#: components/Workflow/WorkflowNodeHelp.js:154
-#: components/Workflow/WorkflowNodeHelp.js:190
-#: screens/Team/TeamRoles/TeamRoleListItem.js:12
-#: screens/Team/TeamRoles/TeamRolesList.js:180
-msgid "Resource Name"
-msgstr "资源名称"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:246
-msgid "Resource deleted"
-msgstr "资源已删除"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:170
-#: components/Schedule/ScheduleList/ScheduleListItem.js:111
-msgid "Resource type"
-msgstr "资源类型"
-
-#: routeConfig.js:61
-#: screens/ActivityStream/ActivityStream.js:159
-msgid "Resources"
-msgstr "资源"
-
-#: components/TemplateList/TemplateListItem.js:149
-msgid "Resources are missing from this template."
-msgstr "此模板中缺少资源。"
-
-#: screens/Setting/shared/RevertButton.js:43
-msgid "Restore initial value."
-msgstr "恢复初始值。"
-
-#: screens/Inventory/shared/Inventory.helptext.js:153
-msgid ""
-"Retrieve the enabled state from the given dict of host variables.\n"
-"The enabled variable may be specified using dot notation, e.g: 'foo.bar'"
-msgstr "从给定的主机变量字典中检索启用的状态。启用的变量可以使用点符号来指定,如 'foo.bar'"
-
-#: components/JobCancelButton/JobCancelButton.js:96
-#: components/JobCancelButton/JobCancelButton.js:100
-#: components/JobList/JobListCancelButton.js:160
-#: components/JobList/JobListCancelButton.js:163
-#: screens/Job/JobOutput/JobOutput.js:819
-#: screens/Job/JobOutput/JobOutput.js:822
-msgid "Return"
-msgstr "返回"
-
-#: screens/Job/JobOutput/EmptyOutput.js:40
-msgid "Return to"
-msgstr "返回到"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:129
-msgid "Return to subscription management."
-msgstr "返回到订阅管理。"
-
-#: components/Search/AdvancedSearch.js:171
-msgid "Returns results that have values other than this one as well as other filters."
-msgstr "返回具有这个以外的值和其他过滤器的结果。"
-
-#: components/Search/AdvancedSearch.js:158
-msgid "Returns results that satisfy this one as well as other filters. This is the default set type if nothing is selected."
-msgstr "返回满足这个及其他过滤器的结果。如果没有进行选择,这是默认的集合类型。"
-
-#: components/Search/AdvancedSearch.js:164
-msgid "Returns results that satisfy this one or any other filters."
-msgstr "返回满足这个或任何其他过滤器的结果。"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:52
-#: screens/Setting/shared/RevertButton.js:53
-#: screens/Setting/shared/RevertButton.js:62
-msgid "Revert"
-msgstr "恢复"
-
-#: screens/Setting/shared/RevertAllAlert.js:23
-msgid "Revert all"
-msgstr "恢复所有"
-
-#: screens/Setting/shared/RevertFormActionGroup.js:21
-#: screens/Setting/shared/RevertFormActionGroup.js:27
-msgid "Revert all to default"
-msgstr "全部恢复为默认值"
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:59
-msgid "Revert field to previously saved value"
-msgstr "将字段恢复到之前保存的值"
-
-#: screens/Setting/shared/RevertAllAlert.js:11
-msgid "Revert settings"
-msgstr "恢复设置"
-
-#: screens/Setting/shared/RevertButton.js:42
-msgid "Revert to factory default."
-msgstr "恢复到工厂默认值。"
-
-#: screens/Job/JobDetail/JobDetail.js:314
-#: screens/Project/ProjectList/ProjectList.js:224
-#: screens/Project/ProjectList/ProjectListItem.js:221
-msgid "Revision"
-msgstr "修订"
-
-#: screens/Project/shared/ProjectSubForms/SvnSubForm.js:22
-msgid "Revision #"
-msgstr "修订号 #"
-
-#: components/NotificationList/NotificationList.js:199
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:140
-msgid "Rocket.Chat"
-msgstr "Rocket.Chat"
-
-#: screens/Team/TeamRoles/TeamRoleListItem.js:20
-#: screens/Team/TeamRoles/TeamRolesList.js:148
-#: screens/Team/TeamRoles/TeamRolesList.js:182
-#: screens/User/UserList/UserList.js:163
-#: screens/User/UserList/UserListItem.js:55
-#: screens/User/UserRoles/UserRolesList.js:146
-#: screens/User/UserRoles/UserRolesList.js:157
-#: screens/User/UserRoles/UserRolesListItem.js:26
-msgid "Role"
-msgstr "角色"
-
-#: components/ResourceAccessList/ResourceAccessList.js:189
-#: components/ResourceAccessList/ResourceAccessList.js:202
-#: components/ResourceAccessList/ResourceAccessList.js:229
-#: components/ResourceAccessList/ResourceAccessListItem.js:69
-#: screens/Team/Team.js:59
-#: screens/Team/Teams.js:32
-#: screens/User/User.js:71
-#: screens/User/Users.js:31
-msgid "Roles"
-msgstr "角色"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:134
-#: components/Workflow/WorkflowLinkHelp.js:39
-#: screens/Credential/shared/ExternalTestModal.js:89
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:49
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:23
-#: screens/Template/shared/JobTemplateForm.js:214
-msgid "Run"
-msgstr "运行"
-
-#: components/AdHocCommands/AdHocCommands.js:131
-#: components/AdHocCommands/AdHocCommands.js:135
-#: components/AdHocCommands/AdHocCommands.js:141
-#: components/AdHocCommands/AdHocCommands.js:145
-#: screens/Job/JobDetail/JobDetail.js:68
-msgid "Run Command"
-msgstr "运行命令"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:277
-#: screens/Instances/InstanceDetail/InstanceDetail.js:335
-msgid "Run a health check on the instance"
-msgstr "对实例运行健康检查"
-
-#: components/AdHocCommands/AdHocCommands.js:125
-msgid "Run ad hoc command"
-msgstr "运行临时命令"
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:48
-msgid "Run command"
-msgstr "运行命令"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:93
-#: components/Schedule/shared/FrequencyDetailSubform.js:223
-msgid "Run every"
-msgstr "运行每"
-
-#: components/HealthCheckButton/HealthCheckButton.js:32
-#: components/HealthCheckButton/HealthCheckButton.js:45
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:286
-#: screens/Instances/InstanceDetail/InstanceDetail.js:344
-msgid "Run health check"
-msgstr "运行健康检查"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:129
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:138
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:175
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:197
-#: components/Schedule/shared/FrequencyDetailSubform.js:344
-msgid "Run on"
-msgstr "运行于"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useRunTypeStep.js:32
-msgid "Run type"
-msgstr "运行类型"
-
-#: components/JobList/JobList.js:230
-#: components/StatusLabel/StatusLabel.js:48
-#: components/TemplateList/TemplateListItem.js:118
-#: components/Workflow/WorkflowNodeHelp.js:99
-msgid "Running"
-msgstr "运行中"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:127
-msgid "Running Handlers"
-msgstr "正在运行的处理程序"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:217
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:196
-#: screens/InstanceGroup/Instances/InstanceListItem.js:194
-#: screens/Instances/InstanceDetail/InstanceDetail.js:212
-#: screens/Instances/InstanceList/InstanceListItem.js:209
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:73
-msgid "Running Jobs"
-msgstr "运行任务"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:284
-#: screens/Instances/InstanceDetail/InstanceDetail.js:342
-msgid "Running health check"
-msgstr "运行健康检查"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:71
-msgid "Running jobs"
-msgstr "运行作业"
-
-#: screens/Setting/Settings.js:109
-msgid "SAML"
-msgstr "SAML"
-
-#: screens/Setting/SettingList.js:77
-msgid "SAML settings"
-msgstr "SAML 设置"
-
-#: screens/Dashboard/DashboardGraph.js:143
-msgid "SCM update"
-msgstr "SCM 更新"
-
-#: screens/User/UserDetail/UserDetail.js:58
-#: screens/User/UserList/UserListItem.js:49
-msgid "SOCIAL"
-msgstr "社交"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:95
-msgid "SSH password"
-msgstr "SSH 密码"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:239
-msgid "SSL Connection"
-msgstr "SSL 连接"
-
-#: components/Workflow/WorkflowStartNode.js:60
-#: components/Workflow/workflowReducer.js:419
-msgid "START"
-msgstr "开始"
-
-#: components/Sparkline/Sparkline.js:31
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:160
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:39
-#: screens/Project/ProjectDetail/ProjectDetail.js:135
-#: screens/Project/ProjectList/ProjectListItem.js:73
-msgid "STATUS:"
-msgstr "状态:"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:321
-msgid "Sat"
-msgstr "周六"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:82
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:188
-#: components/Schedule/shared/FrequencyDetailSubform.js:326
-#: components/Schedule/shared/FrequencyDetailSubform.js:458
-msgid "Saturday"
-msgstr "周六"
-
-#: components/AddRole/AddResourceRole.js:247
-#: components/AssociateModal/AssociateModal.js:104
-#: components/AssociateModal/AssociateModal.js:110
-#: components/FormActionGroup/FormActionGroup.js:13
-#: components/FormActionGroup/FormActionGroup.js:19
-#: components/Schedule/shared/ScheduleForm.js:526
-#: components/Schedule/shared/ScheduleForm.js:532
-#: components/Schedule/shared/useSchedulePromptSteps.js:49
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:130
-#: screens/Credential/shared/CredentialForm.js:318
-#: screens/Credential/shared/CredentialForm.js:323
-#: screens/Setting/shared/RevertFormActionGroup.js:12
-#: screens/Setting/shared/RevertFormActionGroup.js:18
-#: screens/Template/Survey/SurveyReorderModal.js:205
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:35
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:131
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:158
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:162
-msgid "Save"
-msgstr "保存"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:33
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:36
-msgid "Save & Exit"
-msgstr "保存并退出"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:32
-msgid "Save link changes"
-msgstr "保存链路更改"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:254
-msgid "Save successful!"
-msgstr "保存成功!"
-
-#: components/JobList/JobListItem.js:181
-#: components/JobList/JobListItem.js:187
-msgid "Schedule"
-msgstr "调度"
-
-#: screens/Project/Projects.js:34
-#: screens/Template/Templates.js:54
-msgid "Schedule Details"
-msgstr "调度详情"
-
-#: components/Schedule/shared/UnsupportedScheduleForm.js:15
-msgid "Schedule Rules"
-msgstr "调度规则"
-
-#: screens/Inventory/Inventories.js:92
-msgid "Schedule details"
-msgstr "调度详情"
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:49
-msgid "Schedule is active"
-msgstr "调度处于活跃状态"
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:49
-msgid "Schedule is inactive"
-msgstr "调度处于非活跃状态"
-
-#: components/Schedule/shared/ScheduleForm.js:394
-msgid "Schedule is missing rrule"
-msgstr "调度缺少规则"
-
-#: components/Schedule/Schedule.js:82
-msgid "Schedule not found."
-msgstr "未找到调度。"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:163
-#: components/Schedule/ScheduleList/ScheduleList.js:229
-#: routeConfig.js:44
-#: screens/ActivityStream/ActivityStream.js:153
-#: screens/Inventory/Inventories.js:89
-#: screens/Inventory/InventorySource/InventorySource.js:88
-#: screens/ManagementJob/ManagementJob.js:108
-#: screens/ManagementJob/ManagementJobs.js:23
-#: screens/Project/Project.js:120
-#: screens/Project/Projects.js:31
-#: screens/Schedule/AllSchedules.js:21
-#: screens/Template/Template.js:148
-#: screens/Template/Templates.js:51
-#: screens/Template/WorkflowJobTemplate.js:130
-msgid "Schedules"
-msgstr "调度"
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:136
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:33
-#: screens/User/UserTokenDetail/UserTokenDetail.js:50
-#: screens/User/UserTokenList/UserTokenList.js:142
-#: screens/User/UserTokenList/UserTokenList.js:189
-#: screens/User/UserTokenList/UserTokenListItem.js:32
-#: screens/User/shared/UserTokenForm.js:68
-msgid "Scope"
-msgstr "范围"
-
-#: screens/User/shared/User.helptext.js:5
-msgid "Scope for the token's access"
-msgstr "令牌访问的范围"
-
-#: screens/Job/JobOutput/PageControls.js:79
-msgid "Scroll first"
-msgstr "滚动到第一"
-
-#: screens/Job/JobOutput/PageControls.js:87
-msgid "Scroll last"
-msgstr "滚动到最后"
-
-#: screens/Job/JobOutput/PageControls.js:71
-msgid "Scroll next"
-msgstr "滚动到下一个"
-
-#: screens/Job/JobOutput/PageControls.js:63
-msgid "Scroll previous"
-msgstr "滚动到前一个"
-
-#: components/Lookup/HostFilterLookup.js:290
-#: components/Lookup/Lookup.js:143
-msgid "Search"
-msgstr "搜索"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:151
-msgid "Search is disabled while the job is running"
-msgstr "作业运行时会禁用搜索"
-
-#: components/Search/AdvancedSearch.js:311
-#: components/Search/Search.js:259
-msgid "Search submit button"
-msgstr "搜索提交按钮"
-
-#: components/Search/Search.js:248
-msgid "Search text input"
-msgstr "搜索文本输入"
-
-#: components/Lookup/HostFilterLookup.js:398
-msgid "Searching by ansible_facts requires special syntax. Refer to the"
-msgstr "根据 ansible_facts 搜索需要特殊的语法。请参阅"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:408
-msgid "Second"
-msgstr "秒"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:103
-#: components/PromptDetail/PromptProjectDetail.js:153
-#: screens/Project/ProjectDetail/ProjectDetail.js:269
-msgid "Seconds"
-msgstr "秒"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:34
-msgid "See Django"
-msgstr "请参阅 Django"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:35
-#: components/LaunchPrompt/steps/PreviewStep.js:61
-msgid "See errors on the left"
-msgstr "在左侧查看错误"
-
-#: components/JobList/JobListItem.js:84
-#: components/Lookup/HostFilterLookup.js:380
-#: components/Lookup/Lookup.js:200
-#: components/Pagination/Pagination.js:33
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:98
-msgid "Select"
-msgstr "选择"
-
-#: screens/Credential/shared/CredentialForm.js:129
-msgid "Select Credential Type"
-msgstr "编辑凭证类型"
-
-#: screens/Host/HostGroups/HostGroupsList.js:237
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:254
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:257
-msgid "Select Groups"
-msgstr "选择组"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:278
-msgid "Select Hosts"
-msgstr "选择主机"
-
-#: components/AnsibleSelect/AnsibleSelect.js:38
-msgid "Select Input"
-msgstr "选择输入"
-
-#: screens/InstanceGroup/Instances/InstanceList.js:295
-msgid "Select Instances"
-msgstr "选择实例"
-
-#: components/AssociateModal/AssociateModal.js:22
-msgid "Select Items"
-msgstr "选择项"
-
-#: components/AddRole/AddResourceRole.js:201
-msgid "Select Items from List"
-msgstr "从列表中选择项"
-
-#: components/LabelSelect/LabelSelect.js:127
-msgid "Select Labels"
-msgstr "选择标签"
-
-#: components/AddRole/AddResourceRole.js:236
-msgid "Select Roles to Apply"
-msgstr "选择要应用的角色"
-
-#: screens/User/UserTeams/UserTeamList.js:251
-msgid "Select Teams"
-msgstr "选择团队"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:25
-msgid "Select a JSON formatted service account key to autopopulate the following fields."
-msgstr "选择一个 JSON 格式的服务帐户密钥来自动填充以下字段。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:122
-msgid "Select a Node Type"
-msgstr "选择节点类型"
-
-#: components/AddRole/AddResourceRole.js:170
-msgid "Select a Resource Type"
-msgstr "选择资源类型"
-
-#: screens/Job/Job.helptext.js:21
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:10
-msgid "Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch."
-msgstr "为工作流选择分支。此分支应用于提示分支的所有任务模板节点。"
-
-#: screens/Credential/shared/CredentialForm.js:139
-msgid "Select a credential Type"
-msgstr "选择一个凭证类型"
-
-#: components/JobList/JobListCancelButton.js:98
-msgid "Select a job to cancel"
-msgstr "选择要取消的作业"
-
-#: screens/Metrics/Metrics.js:211
-msgid "Select a metric"
-msgstr "选择一个指标"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:75
-msgid "Select a module"
-msgstr "选择一个模块"
-
-#: screens/Template/shared/PlaybookSelect.js:60
-#: screens/Template/shared/PlaybookSelect.js:61
-msgid "Select a playbook"
-msgstr "选择一个 playbook"
-
-#: screens/Template/shared/JobTemplateForm.js:323
-msgid "Select a project before editing the execution environment."
-msgstr "在编辑执行环境前选择一个项目。"
-
-#: screens/Template/Survey/SurveyToolbar.js:82
-msgid "Select a question to delete"
-msgstr "选择要删除的问题"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:160
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:103
-msgid "Select a row to delete"
-msgstr "选择要删除的行"
-
-#: components/DisassociateButton/DisassociateButton.js:75
-msgid "Select a row to disassociate"
-msgstr "选择要解除关联的行"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:77
-msgid "Select a row to remove"
-msgstr "选择要删除的行"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:87
-msgid "Select a subscription"
-msgstr "导入一个订阅"
-
-#: components/HostForm/HostForm.js:39
-#: components/Schedule/shared/FrequencyDetailSubform.js:71
-#: components/Schedule/shared/FrequencyDetailSubform.js:78
-#: components/Schedule/shared/FrequencyDetailSubform.js:88
-#: components/Schedule/shared/ScheduleFormFields.js:33
-#: components/Schedule/shared/ScheduleFormFields.js:37
-#: components/Schedule/shared/ScheduleFormFields.js:61
-#: screens/Credential/shared/CredentialForm.js:44
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:79
-#: screens/Inventory/shared/InventoryForm.js:72
-#: screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.js:46
-#: screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/GCESubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.js:46
-#: screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:97
-#: screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.js:44
-#: screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.js:46
-#: screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.js:46
-#: screens/Inventory/shared/SmartInventoryForm.js:67
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:24
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:61
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:436
-#: screens/Project/shared/ProjectForm.js:234
-#: screens/Project/shared/ProjectSubForms/InsightsSubForm.js:39
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:37
-#: screens/Team/shared/TeamForm.js:49
-#: screens/Template/Survey/SurveyQuestionForm.js:30
-#: screens/Template/shared/WorkflowJobTemplateForm.js:130
-#: screens/User/shared/UserForm.js:139
-#: util/validators.js:201
-msgid "Select a value for this field"
-msgstr "为这个字段选择一个值"
-
-#: screens/Template/shared/JobTemplate.helptext.js:23
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:20
-msgid "Select a webhook service."
-msgstr "选择 Webhook 服务。"
-
-#: components/DataListToolbar/DataListToolbar.js:121
-#: components/DataListToolbar/DataListToolbar.js:125
-#: screens/Template/Survey/SurveyToolbar.js:49
-msgid "Select all"
-msgstr "选择所有"
-
-#: screens/ActivityStream/ActivityStream.js:129
-msgid "Select an activity type"
-msgstr "选择一个活动类型"
-
-#: screens/Metrics/Metrics.js:200
-msgid "Select an instance"
-msgstr "选择一个实例"
-
-#: screens/Metrics/Metrics.js:242
-msgid "Select an instance and a metric to show chart"
-msgstr "选择一个实例和一个指标来显示图表"
-
-#: components/HealthCheckButton/HealthCheckButton.js:19
-msgid "Select an instance to run a health check."
-msgstr "选择一个要运行健康检查的实例。"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:5
-msgid "Select an inventory for the workflow. This inventory is applied to all workflow nodes that prompt for an inventory."
-msgstr "为工作流选择清单。此清单应用于提示清单的所有工作流节点。"
-
-#: components/LaunchPrompt/steps/SurveyStep.js:131
-msgid "Select an option"
-msgstr "选择一个选项"
-
-#: screens/Project/shared/ProjectForm.js:245
-msgid "Select an organization before editing the default execution environment."
-msgstr "在编辑默认执行环境前选择一个机构。"
-
-#: screens/Job/Job.helptext.js:11
-#: screens/Template/shared/JobTemplate.helptext.js:12
-msgid "Select credentials for accessing the nodes this job will be ran against. You can only select one credential of each type. For machine credentials (SSH), checking \"Prompt on launch\" without selecting credentials will require you to select a machine credential at run time. If you select credentials and check \"Prompt on launch\", the selected credential(s) become the defaults that can be updated at run time."
-msgstr "选择允许访问将运行此作业的节点的凭证。每种类型您只能选择一个凭证。对于机器凭证 (SSH),如果选中了“启动时提示”但未选择凭证,您需要在运行时选择机器凭证。如果选择了凭证并选中了“启动时提示”,则所选凭证将变为默认设置,可以在运行时更新。"
-
-#: components/Schedule/shared/ScheduleFormFields.js:120
-#: components/Schedule/shared/ScheduleFormFields.js:179
-msgid "Select frequency"
-msgstr "选择频率"
-
-#: screens/Project/shared/Project.helptext.js:18
-msgid ""
-"Select from the list of directories found in\n"
-"the Project Base Path. Together the base path and the playbook\n"
-"directory provide the full path used to locate playbooks."
-msgstr "从位于项目基本路径的目录列表中进行选择。基本路径和 playbook 目录一起提供了用于定位 playbook 的完整路径。"
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:98
-msgid "Select items from list"
-msgstr "从列表中选择项"
-
-#: screens/Dashboard/DashboardGraph.js:124
-#: screens/Dashboard/DashboardGraph.js:125
-msgid "Select job type"
-msgstr "选择作业类型"
-
-#: components/LaunchPrompt/steps/SurveyStep.js:179
-msgid "Select option(s)"
-msgstr "选择选项"
-
-#: screens/Dashboard/DashboardGraph.js:95
-#: screens/Dashboard/DashboardGraph.js:96
-#: screens/Dashboard/DashboardGraph.js:97
-msgid "Select period"
-msgstr "选择周期"
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:117
-msgid "Select roles to apply"
-msgstr "选择要应用的角色"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:128
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:129
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:130
-msgid "Select source path"
-msgstr "选择源路径"
-
-#: screens/Dashboard/DashboardGraph.js:151
-#: screens/Dashboard/DashboardGraph.js:152
-msgid "Select status"
-msgstr "选择状态"
-
-#: components/MultiSelect/TagMultiSelect.js:59
-msgid "Select tags"
-msgstr "选择标签"
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:94
-msgid "Select the Execution Environment you want this command to run inside."
-msgstr "选择您希望这个命令在内运行的执行环境。"
-
-#: screens/Inventory/shared/SmartInventoryForm.js:87
-msgid "Select the Instance Groups for this Inventory to run on."
-msgstr "选择要运行此清单的实例组。"
-
-#: screens/Job/Job.helptext.js:18
-#: screens/Template/shared/JobTemplate.helptext.js:20
-msgid "Select the Instance Groups for this Job Template to run on."
-msgstr "选择要运行此任务模板的实例组。"
-
-#: screens/Organization/shared/OrganizationForm.js:83
-msgid "Select the Instance Groups for this Organization to run on."
-msgstr "选择要运行此机构的实例组。"
-
-#: components/AdHocCommands/AdHocCredentialStep.js:104
-msgid "Select the credential you want to use when accessing the remote hosts to run the command. Choose the credential containing the username and SSH key or password that Ansible will need to log into the remote hosts."
-msgstr "选择要在访问远程主机时用来运行命令的凭证。选择包含 Ansbile 登录远程主机所需的用户名和 SSH 密钥或密码的凭证。"
-
-#: components/Lookup/InventoryLookup.js:123
-msgid ""
-"Select the inventory containing the hosts\n"
-"you want this job to manage."
-msgstr "选择包含此作业要管理的主机的清单。"
-
-#: screens/Job/Job.helptext.js:6
-#: screens/Template/shared/JobTemplate.helptext.js:7
-msgid "Select the inventory containing the hosts you want this job to manage."
-msgstr "选择包含此任务要管理的主机的清单。"
-
-#: components/HostForm/HostForm.js:32
-#: components/HostForm/HostForm.js:51
-msgid "Select the inventory that this host will belong to."
-msgstr "选择此主机要属于的清单。"
-
-#: screens/Job/Job.helptext.js:10
-#: screens/Template/shared/JobTemplate.helptext.js:11
-msgid "Select the playbook to be executed by this job."
-msgstr "选择要由此作业执行的 playbook。"
-
-#: screens/Instances/Shared/InstanceForm.js:43
-msgid "Select the port that Receptor will listen on for incoming connections. Default is 27199."
-msgstr "选择 Receptor 将侦听传入连接的端口。默认为 27199。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:8
-msgid "Select the project containing the playbook you want this job to execute."
-msgstr "选择包含此任务要执行的 playbook 的项目。"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:79
-msgid "Select your Ansible Automation Platform subscription to use."
-msgstr "选择要使用的 Ansible Automation Platform 订阅。"
-
-#: components/Lookup/Lookup.js:186
-msgid "Select {0}"
-msgstr "选择 {0}"
-
-#: components/AddRole/AddResourceRole.js:212
-#: components/AddRole/AddResourceRole.js:224
-#: components/AddRole/AddResourceRole.js:242
-#: components/AddRole/SelectRoleStep.js:27
-#: components/CheckboxListItem/CheckboxListItem.js:44
-#: components/Lookup/InstanceGroupsLookup.js:87
-#: components/OptionsList/OptionsList.js:74
-#: components/Schedule/ScheduleList/ScheduleListItem.js:84
-#: components/TemplateList/TemplateListItem.js:140
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:107
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:125
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:26
-#: screens/Application/ApplicationsList/ApplicationListItem.js:31
-#: screens/Credential/CredentialList/CredentialListItem.js:56
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:31
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:65
-#: screens/Host/HostGroups/HostGroupItem.js:26
-#: screens/Host/HostList/HostListItem.js:48
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:59
-#: screens/InstanceGroup/Instances/InstanceListItem.js:122
-#: screens/Instances/InstanceList/InstanceListItem.js:126
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:42
-#: screens/Inventory/InventoryList/InventoryListItem.js:90
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:37
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:110
-#: screens/Organization/OrganizationList/OrganizationListItem.js:43
-#: screens/Organization/shared/OrganizationForm.js:113
-#: screens/Project/ProjectList/ProjectListItem.js:177
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:242
-#: screens/Team/TeamList/TeamListItem.js:31
-#: screens/Template/Survey/SurveyListItem.js:34
-#: screens/User/UserTokenList/UserTokenListItem.js:19
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:41
-msgid "Selected"
-msgstr "已选择"
-
-#: components/LaunchPrompt/steps/CredentialsStep.js:142
-#: components/LaunchPrompt/steps/CredentialsStep.js:147
-#: components/Lookup/MultiCredentialsLookup.js:162
-#: components/Lookup/MultiCredentialsLookup.js:167
-msgid "Selected Category"
-msgstr "选择的类别"
-
-#: components/Schedule/shared/ScheduleForm.js:446
-#: components/Schedule/shared/ScheduleForm.js:447
-msgid "Selected date range must have at least 1 schedule occurrence."
-msgstr "选定日期范围必须至少有 1 个计划发生。"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:160
-msgid "Sender Email"
-msgstr "发件人电子邮件"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:96
-msgid "Sender e-mail"
-msgstr "发件人电子邮件"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:167
-#: components/Schedule/shared/FrequencyDetailSubform.js:138
-msgid "September"
-msgstr "9 月"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:24
-msgid "Service account JSON file"
-msgstr "服务账户 JSON 文件"
-
-#: screens/Inventory/shared/InventorySourceForm.js:46
-#: screens/Project/shared/ProjectForm.js:112
-msgid "Set a value for this field"
-msgstr "为这个字段设置值"
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:70
-msgid "Set how many days of data should be retained."
-msgstr "设置数据应保留的天数。"
-
-#: screens/Setting/SettingList.js:122
-msgid "Set preferences for data collection, logos, and logins"
-msgstr "为数据收集、日志和登录设置偏好"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:131
-msgid "Set source path to"
-msgstr "设置源路径为"
-
-#: components/InstanceToggle/InstanceToggle.js:48
-#: screens/Instances/Shared/InstanceForm.js:59
-msgid "Set the instance enabled or disabled. If disabled, jobs will not be assigned to this instance."
-msgstr "设置实例被启用或禁用。如果禁用,则不会将作业分配给此实例。"
-
-#: screens/Application/shared/Application.helptext.js:5
-msgid "Set to Public or Confidential depending on how secure the client device is."
-msgstr "根据客户端设备的安全情况,设置为公共或机密。"
-
-#: components/Search/AdvancedSearch.js:149
-msgid "Set type"
-msgstr "设置类型"
-
-#: components/Search/AdvancedSearch.js:239
-msgid "Set type disabled for related search field fuzzy searches"
-msgstr "为相关搜索字段模糊搜索设置类型禁用"
-
-#: components/Search/AdvancedSearch.js:140
-msgid "Set type select"
-msgstr "设置类型选项"
-
-#: components/Search/AdvancedSearch.js:143
-msgid "Set type typeahead"
-msgstr "设置类型 typeahead"
-
-#: components/Workflow/WorkflowTools.js:154
-msgid "Set zoom to 100% and center graph"
-msgstr "将缩放设置为 100% 和中心图"
-
-#: screens/Instances/Shared/InstanceForm.js:35
-msgid "Sets the current life cycle stage of this instance. Default is \"installed.\""
-msgstr "设置此实例的当前生命周期阶段。默认为\"installed\"。"
-
-#: screens/Instances/Shared/InstanceForm.js:51
-msgid "Sets the role that this instance will play within mesh topology. Default is \"execution.\""
-msgstr "设置此实例在网格拓扑中扮演的角色。默认为 \"execution\"。"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:46
-msgid "Setting category"
-msgstr "设置类别"
-
-#: screens/Setting/shared/RevertButton.js:46
-msgid "Setting matches factory default."
-msgstr "设置与工厂默认匹配。"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:49
-msgid "Setting name"
-msgstr "设置名称"
-
-#: routeConfig.js:159
-#: routeConfig.js:163
-#: screens/ActivityStream/ActivityStream.js:220
-#: screens/ActivityStream/ActivityStream.js:222
-#: screens/Setting/Settings.js:43
-msgid "Settings"
-msgstr "设置"
-
-#: components/FormField/PasswordInput.js:35
-msgid "Show"
-msgstr "显示"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:182
-#: components/PromptDetail/PromptDetail.js:361
-#: components/PromptDetail/PromptJobTemplateDetail.js:156
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:488
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:283
-#: screens/Template/shared/JobTemplateForm.js:497
-msgid "Show Changes"
-msgstr "显示更改"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:177
-#: components/AdHocCommands/AdHocDetailsStep.js:178
-msgid "Show changes"
-msgstr "显示更改"
-
-#: components/LaunchPrompt/LaunchPrompt.js:135
-#: components/Schedule/shared/SchedulePromptableFields.js:102
-msgid "Show description"
-msgstr "显示描述"
-
-#: components/ChipGroup/ChipGroup.js:12
-msgid "Show less"
-msgstr "显示更少"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:126
-msgid "Show only root groups"
-msgstr "只显示 root 组"
-
-#: screens/Login/Login.js:262
-msgid "Sign in with Azure AD"
-msgstr "使用 Azure AD 登陆"
-
-#: screens/Login/Login.js:276
-msgid "Sign in with GitHub"
-msgstr "使用 GitHub 登陆"
-
-#: screens/Login/Login.js:318
-msgid "Sign in with GitHub Enterprise"
-msgstr "使用 GitHub Enterprise 登录"
-
-#: screens/Login/Login.js:333
-msgid "Sign in with GitHub Enterprise Organizations"
-msgstr "使用 GitHub Enterprise Organizations 登录"
-
-#: screens/Login/Login.js:349
-msgid "Sign in with GitHub Enterprise Teams"
-msgstr "使用 GitHub Enterprise Teams 登录"
-
-#: screens/Login/Login.js:290
-msgid "Sign in with GitHub Organizations"
-msgstr "使用 GitHub Organizations 登录"
-
-#: screens/Login/Login.js:304
-msgid "Sign in with GitHub Teams"
-msgstr "使用 GitHub Teams 登录"
-
-#: screens/Login/Login.js:364
-msgid "Sign in with Google"
-msgstr "使用 Google 登录"
-
-#: screens/Login/Login.js:378
-msgid "Sign in with OIDC"
-msgstr "使用 OIDC 登陆"
-
-#: screens/Login/Login.js:397
-msgid "Sign in with SAML"
-msgstr "使用 SAML 登陆"
-
-#: screens/Login/Login.js:396
-msgid "Sign in with SAML {samlIDP}"
-msgstr "使用 SAML {samlIDP} 登陆"
-
-#: components/Search/Search.js:145
-#: components/Search/Search.js:146
-msgid "Simple key select"
-msgstr "简单键选择"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:106
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:107
-#: components/PromptDetail/PromptDetail.js:295
-#: components/PromptDetail/PromptJobTemplateDetail.js:267
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:595
-#: screens/Job/JobDetail/JobDetail.js:500
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:475
-#: screens/Template/shared/JobTemplateForm.js:533
-#: screens/Template/shared/WorkflowJobTemplateForm.js:230
-msgid "Skip Tags"
-msgstr "跳过标签"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:93
-#: components/Schedule/shared/FrequencyDetailSubform.js:223
-msgid "Skip every"
-msgstr "跳过每个"
-
-#: screens/Job/Job.helptext.js:20
-#: screens/Template/shared/JobTemplate.helptext.js:22
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:22
-msgid "Skip tags are useful when you have a large playbook, and you want to skip specific parts of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags."
-msgstr "如果有大型一个 playbook,而您想要跳过某个 play 或任务的特定部分,则跳过标签很有用。使用逗号分隔多个标签。请参阅相关文档了解使用标签的详情。"
-
-#: screens/Job/JobOutput/shared/HostStatusBar.js:39
-msgid "Skipped"
-msgstr "跳过"
-
-#: components/StatusLabel/StatusLabel.js:50
-msgid "Skipped'"
-msgstr "跳过'"
-
-#: components/NotificationList/NotificationList.js:200
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:141
-msgid "Slack"
-msgstr "Slack"
-
-#: screens/Host/HostList/SmartInventoryButton.js:39
-#: screens/Host/HostList/SmartInventoryButton.js:48
-#: screens/Host/HostList/SmartInventoryButton.js:52
-#: screens/Inventory/InventoryList/InventoryList.js:187
-#: screens/Inventory/InventoryList/InventoryListItem.js:117
-msgid "Smart Inventory"
-msgstr "智能清单"
-
-#: screens/Inventory/SmartInventory.js:94
-msgid "Smart Inventory not found."
-msgstr "未找到智能清单。"
-
-#: components/Lookup/HostFilterLookup.js:345
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:116
-msgid "Smart host filter"
-msgstr "智能主机过滤器"
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:105
-msgid "Smart inventory"
-msgstr "智能清单"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:32
-#: components/LaunchPrompt/steps/PreviewStep.js:58
-msgid "Some of the previous step(s) have errors"
-msgstr "前面的一些步骤有错误"
-
-#: screens/Host/HostList/SmartInventoryButton.js:17
-msgid "Some search modifiers like not__ and __search are not supported in Smart Inventory host filters. Remove these to create a new Smart Inventory with this filter."
-msgstr "智能清单主机过滤器中不支持 not__ 和 __search 等一些搜索修饰符。删除这些修改以使用此过滤器创建新的智能清单。"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.js:39
-msgid "Something went wrong with the request to test this credential and metadata."
-msgstr "请求测试此凭证和元数据时出错。"
-
-#: components/ContentError/ContentError.js:37
-msgid "Something went wrong..."
-msgstr "出现错误..."
-
-#: components/Sort/Sort.js:139
-msgid "Sort"
-msgstr "排序"
-
-#: components/JobList/JobListItem.js:169
-#: components/PromptDetail/PromptInventorySourceDetail.js:84
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:199
-#: screens/Inventory/shared/InventorySourceForm.js:130
-#: screens/Job/JobDetail/JobDetail.js:172
-#: screens/Job/JobDetail/JobDetail.js:294
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:93
-msgid "Source"
-msgstr "源"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:44
-#: components/PromptDetail/PromptDetail.js:250
-#: components/PromptDetail/PromptJobTemplateDetail.js:147
-#: components/PromptDetail/PromptProjectDetail.js:106
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:89
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:467
-#: screens/Job/JobDetail/JobDetail.js:307
-#: screens/Project/ProjectDetail/ProjectDetail.js:230
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:248
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:133
-#: screens/Template/shared/JobTemplateForm.js:335
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:245
-msgid "Source Control Branch"
-msgstr "源控制分支"
-
-#: screens/Project/shared/ProjectSubForms/GitSubForm.js:29
-msgid "Source Control Branch/Tag/Commit"
-msgstr "源控制分支/标签/提交"
-
-#: components/PromptDetail/PromptProjectDetail.js:117
-#: screens/Project/ProjectDetail/ProjectDetail.js:256
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:56
-msgid "Source Control Credential"
-msgstr "源控制凭证"
-
-#: components/PromptDetail/PromptProjectDetail.js:111
-#: screens/Project/ProjectDetail/ProjectDetail.js:235
-#: screens/Project/shared/ProjectSubForms/GitSubForm.js:32
-msgid "Source Control Refspec"
-msgstr "源控制 Refspec"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:195
-msgid "Source Control Revision"
-msgstr "源控制修订"
-
-#: components/PromptDetail/PromptProjectDetail.js:96
-#: screens/Job/JobDetail/JobDetail.js:273
-#: screens/Project/ProjectDetail/ProjectDetail.js:191
-#: screens/Project/shared/ProjectForm.js:259
-msgid "Source Control Type"
-msgstr "源控制类型"
-
-#: components/Lookup/ProjectLookup.js:143
-#: components/PromptDetail/PromptProjectDetail.js:101
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:96
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:165
-#: screens/Project/ProjectDetail/ProjectDetail.js:225
-#: screens/Project/ProjectList/ProjectList.js:205
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:16
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:104
-msgid "Source Control URL"
-msgstr "源控制 URL"
-
-#: components/JobList/JobList.js:211
-#: components/JobList/JobListItem.js:42
-#: components/Schedule/ScheduleList/ScheduleListItem.js:38
-#: screens/Job/JobDetail/JobDetail.js:65
-msgid "Source Control Update"
-msgstr "源控制更新"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:340
-msgid "Source Phone Number"
-msgstr "源电话号码"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:176
-msgid "Source Variables"
-msgstr "源变量"
-
-#: components/JobList/JobListItem.js:213
-#: screens/Job/JobDetail/JobDetail.js:257
-msgid "Source Workflow Job"
-msgstr "源工作流作业"
-
-#: screens/Template/shared/WorkflowJobTemplateForm.js:177
-msgid "Source control branch"
-msgstr "源控制分支"
-
-#: screens/Inventory/shared/InventorySourceForm.js:152
-msgid "Source details"
-msgstr "源详情"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:403
-msgid "Source phone number"
-msgstr "源电话号码"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:269
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:21
-msgid "Source variables"
-msgstr "源变量"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:97
-msgid "Sourced from a project"
-msgstr "来自项目的源"
-
-#: screens/Inventory/Inventories.js:84
-#: screens/Inventory/Inventory.js:68
-msgid "Sources"
-msgstr "源"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:30
-msgid ""
-"Specify HTTP Headers in JSON format. Refer to\n"
-"the Ansible Controller documentation for example syntax."
-msgstr "以 JSON 格式指定 HTTP 标头。示例语法请参阅 Ansible 控制器文档。"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:24
-msgid ""
-"Specify a notification color. Acceptable colors are hex\n"
-"color code (example: #3af or #789abc)."
-msgstr "指定通知颜色。可接受的颜色为十六进制颜色代码(示例:#3af 或者 #789abc)。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:26
-msgid "Specify the conditions under which this node should be executed"
-msgstr "指定应该执行此节点的条件"
-
-#: screens/Job/JobOutput/HostEventModal.js:173
-msgid "Standard Error"
-msgstr "标准错误"
-
-#: screens/Job/JobOutput/HostEventModal.js:174
-msgid "Standard error tab"
-msgstr "标准错误标签页"
-
-#: components/NotificationList/NotificationListItem.js:57
-#: components/NotificationList/NotificationListItem.js:58
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:47
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:53
-msgid "Start"
-msgstr "开始"
-
-#: components/JobList/JobList.js:247
-#: components/JobList/JobListItem.js:99
-msgid "Start Time"
-msgstr "开始时间"
-
-#: components/Schedule/shared/DateTimePicker.js:51
-msgid "Start date"
-msgstr "开始日期"
-
-#: components/Schedule/shared/ScheduleFormFields.js:87
-msgid "Start date/time"
-msgstr "开始日期/时间"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:465
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:105
-msgid "Start message"
-msgstr "开始消息"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:474
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:114
-msgid "Start message body"
-msgstr "开始消息正文"
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:33
-msgid "Start sync process"
-msgstr "启动同步进程"
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:37
-msgid "Start sync source"
-msgstr "启动同步源"
-
-#: components/Schedule/shared/DateTimePicker.js:61
-msgid "Start time"
-msgstr "开始时间"
-
-#: screens/Job/JobDetail/JobDetail.js:220
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:165
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:63
-msgid "Started"
-msgstr "已开始"
-
-#: components/JobList/JobList.js:224
-#: components/JobList/JobList.js:245
-#: components/JobList/JobListItem.js:95
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:206
-#: screens/InstanceGroup/Instances/InstanceList.js:267
-#: screens/InstanceGroup/Instances/InstanceListItem.js:129
-#: screens/Instances/InstanceDetail/InstanceDetail.js:196
-#: screens/Instances/InstanceList/InstanceList.js:202
-#: screens/Instances/InstanceList/InstanceListItem.js:134
-#: screens/Instances/InstancePeers/InstancePeerList.js:97
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:43
-#: screens/Inventory/InventoryList/InventoryListItem.js:101
-#: screens/Inventory/InventorySources/InventorySourceList.js:212
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:87
-#: screens/Job/JobDetail/JobDetail.js:210
-#: screens/Job/JobOutput/HostEventModal.js:118
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:115
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:179
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:117
-#: screens/Project/ProjectList/ProjectList.js:222
-#: screens/Project/ProjectList/ProjectListItem.js:197
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:61
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:162
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:166
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:66
-msgid "Status"
-msgstr "状态"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:91
-msgid "Stdout"
-msgstr "Stdout"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:37
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:49
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:212
-msgid "Submit"
-msgstr "提交"
-
-#: screens/Project/shared/Project.helptext.js:118
-msgid ""
-"Submodules will track the latest commit on\n"
-"their master branch (or other branch specified in\n"
-".gitmodules). If no, submodules will be kept at\n"
-"the revision specified by the main project.\n"
-"This is equivalent to specifying the --remote\n"
-"flag to git submodule update."
-msgstr "子模块将跟踪其 master 分支(或在 .gitmodules 中指定的其他分支)的最新提交。如果没有,子模块将会保留在主项目指定的修订版本中。这等同于在 git submodule update 命令中指定 --remote 标志。"
-
-#: screens/Setting/SettingList.js:132
-#: screens/Setting/Settings.js:112
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:136
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:195
-msgid "Subscription"
-msgstr "订阅"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:41
-msgid "Subscription Details"
-msgstr "订阅详情"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:194
-msgid "Subscription Management"
-msgstr "订阅管理"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:82
-msgid "Subscription manifest"
-msgstr "订阅清单"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:84
-msgid "Subscription selection modal"
-msgstr "订阅选择模态"
-
-#: screens/Setting/SettingList.js:137
-msgid "Subscription settings"
-msgstr "订阅设置"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:131
-msgid "Subscription type"
-msgstr "订阅类型"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:142
-msgid "Subscriptions table"
-msgstr "订阅表"
-
-#: components/Lookup/ProjectLookup.js:137
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:90
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:159
-#: screens/Job/JobDetail/JobDetail.js:76
-#: screens/Project/ProjectList/ProjectList.js:199
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:98
-msgid "Subversion"
-msgstr "Subversion"
-
-#: components/NotificationList/NotificationListItem.js:71
-#: components/NotificationList/NotificationListItem.js:72
-#: components/StatusLabel/StatusLabel.js:41
-msgid "Success"
-msgstr "成功"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:483
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:123
-msgid "Success message"
-msgstr "成功信息"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:492
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:132
-msgid "Success message body"
-msgstr "成功消息正文"
-
-#: components/JobList/JobList.js:231
-#: components/StatusLabel/StatusLabel.js:43
-#: components/Workflow/WorkflowNodeHelp.js:102
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:96
-#: screens/Dashboard/shared/ChartTooltip.js:59
-msgid "Successful"
-msgstr "成功"
-
-#: screens/Dashboard/DashboardGraph.js:166
-msgid "Successful jobs"
-msgstr "成功的作业"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:28
-msgid "Successfully Approved"
-msgstr "成功批准"
-
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:25
-msgid "Successfully Denied"
-msgstr "成功拒绝"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:201
-#: screens/Project/ProjectList/ProjectListItem.js:97
-msgid "Successfully copied to clipboard!"
-msgstr "成功复制至剪贴板!"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:255
-msgid "Sun"
-msgstr "周日"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:83
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:182
-#: components/Schedule/shared/FrequencyDetailSubform.js:260
-#: components/Schedule/shared/FrequencyDetailSubform.js:428
-msgid "Sunday"
-msgstr "周日"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:26
-#: screens/Template/Template.js:159
-#: screens/Template/Templates.js:48
-#: screens/Template/WorkflowJobTemplate.js:145
-msgid "Survey"
-msgstr "问卷调查"
-
-#: screens/Template/Survey/SurveyToolbar.js:105
-msgid "Survey Disabled"
-msgstr "禁用问卷调查"
-
-#: screens/Template/Survey/SurveyToolbar.js:104
-msgid "Survey Enabled"
-msgstr "启用问卷调查"
-
-#: screens/Template/Survey/SurveyReorderModal.js:191
-msgid "Survey Question Order"
-msgstr "问卷调查问题顺序"
-
-#: screens/Template/Survey/SurveyToolbar.js:102
-msgid "Survey Toggle"
-msgstr "问卷调查切换"
-
-#: screens/Template/Survey/SurveyReorderModal.js:192
-msgid "Survey preview modal"
-msgstr "问卷调查预览模态"
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:120
-#: screens/Inventory/shared/InventorySourceSyncButton.js:41
-#: screens/Project/shared/ProjectSyncButton.js:41
-#: screens/Project/shared/ProjectSyncButton.js:53
-msgid "Sync"
-msgstr "同步"
-
-#: screens/Project/ProjectList/ProjectListItem.js:238
-#: screens/Project/shared/ProjectSyncButton.js:37
-#: screens/Project/shared/ProjectSyncButton.js:48
-msgid "Sync Project"
-msgstr "同步项目"
-
-#: screens/Inventory/InventoryList/InventoryList.js:219
-msgid "Sync Status"
-msgstr "同步状态"
-
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:19
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:29
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:32
-msgid "Sync all"
-msgstr "全部同步"
-
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:25
-msgid "Sync all sources"
-msgstr "同步所有源"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:236
-msgid "Sync error"
-msgstr "同步错误"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:213
-#: screens/Project/ProjectList/ProjectListItem.js:109
-msgid "Sync for revision"
-msgstr "修订版本同步"
-
-#: screens/Project/ProjectList/ProjectListItem.js:122
-msgid "Syncing"
-msgstr "同步"
-
-#: screens/Setting/SettingList.js:102
-#: screens/User/UserRoles/UserRolesListItem.js:18
-msgid "System"
-msgstr "系统"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:128
-#: screens/User/UserDetail/UserDetail.js:47
-#: screens/User/UserList/UserListItem.js:19
-#: screens/User/UserRoles/UserRolesList.js:127
-#: screens/User/shared/UserForm.js:41
-msgid "System Administrator"
-msgstr "系统管理员"
-
-#: screens/User/UserDetail/UserDetail.js:49
-#: screens/User/UserList/UserListItem.js:21
-#: screens/User/shared/UserForm.js:35
-msgid "System Auditor"
-msgstr "系统审核员"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:128
-msgid "System Warning"
-msgstr "系统警告"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:131
-#: screens/User/UserRoles/UserRolesList.js:130
-msgid "System administrators have unrestricted access to all resources."
-msgstr "系统管理员对所有资源的访问权限是不受限制的。"
-
-#: screens/Setting/Settings.js:115
-msgid "TACACS+"
-msgstr "TACACS+"
-
-#: screens/Setting/SettingList.js:81
-msgid "TACACS+ settings"
-msgstr "TACACS+ 设置"
-
-#: screens/Dashboard/Dashboard.js:117
-#: screens/Job/JobOutput/HostEventModal.js:94
-msgid "Tabs"
-msgstr "制表符"
-
-#: screens/Job/Job.helptext.js:19
-#: screens/Template/shared/JobTemplate.helptext.js:21
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:21
-msgid "Tags are useful when you have a large playbook, and you want to run a specific part of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags."
-msgstr "如果有一个大的 playbook,而您想要运行某个 play 或任务的特定部分,则标签会很有用。使用逗号分隔多个标签。请参阅 Ansible Tower 文档了解使用标签的详情。"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:203
-msgid "Tags for the Annotation"
-msgstr "注解的标签"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:179
-msgid "Tags for the annotation (optional)"
-msgstr "注解的标签(可选)"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:248
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:298
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:366
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:252
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:329
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:453
-msgid "Target URL"
-msgstr "目标 URL"
-
-#: screens/Job/JobOutput/HostEventModal.js:123
-msgid "Task"
-msgstr "任务"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:90
-msgid "Task Count"
-msgstr "任务计数"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:129
-msgid "Task Started"
-msgstr "任务已启动"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:91
-msgid "Tasks"
-msgstr "任务"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:13
-msgid "Team"
-msgstr "团队"
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:85
-#: screens/Team/TeamRoles/TeamRolesList.js:144
-msgid "Team Roles"
-msgstr "团队角色"
-
-#: screens/Team/Team.js:75
-msgid "Team not found."
-msgstr "未找到团队。"
-
-#: components/AddRole/AddResourceRole.js:188
-#: components/AddRole/AddResourceRole.js:189
-#: routeConfig.js:106
-#: screens/ActivityStream/ActivityStream.js:187
-#: screens/Organization/Organization.js:125
-#: screens/Organization/OrganizationList/OrganizationList.js:145
-#: screens/Organization/OrganizationList/OrganizationListItem.js:66
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:64
-#: screens/Organization/Organizations.js:32
-#: screens/Team/TeamList/TeamList.js:112
-#: screens/Team/TeamList/TeamList.js:166
-#: screens/Team/Teams.js:15
-#: screens/Team/Teams.js:25
-#: screens/User/User.js:70
-#: screens/User/UserTeams/UserTeamList.js:175
-#: screens/User/UserTeams/UserTeamList.js:246
-#: screens/User/Users.js:32
-#: util/getRelatedResourceDeleteDetails.js:174
-msgid "Teams"
-msgstr "团队"
-
-#: screens/Setting/Jobs/JobsEdit/JobsEdit.js:130
-msgid "Template"
-msgstr "模板"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:115
-#: components/TemplateList/TemplateList.js:133
-msgid "Template copied successfully"
-msgstr "成功复制的模板"
-
-#: screens/Template/Template.js:175
-#: screens/Template/WorkflowJobTemplate.js:175
-msgid "Template not found."
-msgstr "未找到模板。"
-
-#: components/TemplateList/TemplateList.js:200
-#: components/TemplateList/TemplateList.js:263
-#: routeConfig.js:65
-#: screens/ActivityStream/ActivityStream.js:164
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:70
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:83
-#: screens/Template/Templates.js:17
-#: util/getRelatedResourceDeleteDetails.js:218
-#: util/getRelatedResourceDeleteDetails.js:275
-msgid "Templates"
-msgstr "模板"
-
-#: screens/Credential/shared/CredentialForm.js:331
-#: screens/Credential/shared/CredentialForm.js:337
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:80
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:426
-msgid "Test"
-msgstr "测试"
-
-#: screens/Credential/shared/ExternalTestModal.js:77
-msgid "Test External Credential"
-msgstr "测试外部凭证"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:128
-msgid "Test Notification"
-msgstr "测试通知"
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:125
-msgid "Test notification"
-msgstr "测试通知"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.js:44
-msgid "Test passed"
-msgstr "测试通过"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:80
-#: screens/Template/Survey/SurveyReorderModal.js:181
-msgid "Text"
-msgstr "文本"
-
-#: screens/Template/Survey/SurveyReorderModal.js:135
-msgid "Text Area"
-msgstr "文本区"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:81
-msgid "Textarea"
-msgstr "文本区"
-
-#: components/Lookup/Lookup.js:63
-msgid "That value was not found. Please enter or select a valid value."
-msgstr "未找到该值。请输入或选择一个有效值。"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:398
-msgid "The"
-msgstr "The"
-
-#: screens/Application/shared/Application.helptext.js:4
-msgid "The Grant type the user must use to acquire tokens for this application"
-msgstr "用户必须用来获取此应用令牌的授予类型"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:128
-msgid "The Instance Groups for this Organization to run on."
-msgstr "要运行此机构的实例组。"
-
-#: screens/Instances/InstanceDetail/InstanceDetail.js:219
-msgid "The Instance Groups to which this instance belongs."
-msgstr "此实例所属的实例组。"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:6
-msgid ""
-"The amount of time (in seconds) before the email\n"
-"notification stops trying to reach the host and times out. Ranges\n"
-"from 1 to 120 seconds."
-msgstr "电子邮件通知停止尝试到达主机并超时之前所经过的时间(以秒为单位)。范围为 1 秒到 120 秒。"
-
-#: screens/Job/Job.helptext.js:17
-#: screens/Template/shared/JobTemplate.helptext.js:18
-msgid "The amount of time (in seconds) to run before the job is canceled. Defaults to 0 for no job timeout."
-msgstr "取消作业前运行的时间(以秒为单位)。默认为 0,即没有作业超时。"
-
-#: screens/User/shared/User.helptext.js:4
-msgid "The application that this token belongs to, or leave this field empty to create a Personal Access Token."
-msgstr "此令牌所属的应用,或将此字段留空以创建个人访问令牌。"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:9
-msgid ""
-"The base URL of the Grafana server - the\n"
-"/api/annotations endpoint will be added automatically to the base\n"
-"Grafana URL."
-msgstr "Grafana 服务器的基本 URL - /api/annotations 端点将自动添加到基本 Grafana URL。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:9
-msgid "The container image to be used for execution."
-msgstr "用于执行的容器镜像。"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:109
-msgid ""
-"The execution environment that will be used for jobs\n"
-"inside of this organization. This will be used a fallback when\n"
-"an execution environment has not been explicitly assigned at the\n"
-"project, job template or workflow level."
-msgstr "用于本机构内作业的执行环境。当项目、作业模板或工作流没有显式分配执行环境时,则会使用它。"
-
-#: screens/Organization/shared/OrganizationForm.js:93
-msgid "The execution environment that will be used for jobs inside of this organization. This will be used a fallback when an execution environment has not been explicitly assigned at the project, job template or workflow level."
-msgstr "用于本机构内作业的执行环境。当项目、作业模板或工作流没有显式分配执行环境时,则会使用它。"
-
-#: screens/Project/shared/Project.helptext.js:5
-msgid "The execution environment that will be used for jobs that use this project. This will be used as fallback when an execution environment has not been explicitly assigned at the job template or workflow level."
-msgstr "用于使用此项目的作业的执行环境。当作业模板或工作流没有在作业模板或工作流一级显式分配执行环境时,则会使用它。"
-
-#: screens/Job/Job.helptext.js:9
-#: screens/Template/shared/JobTemplate.helptext.js:10
-msgid "The execution environment that will be used when launching this job template. The resolved execution environment can be overridden by explicitly assigning a different one to this job template."
-msgstr "启动此作业模板时要使用的执行环境。解析的执行环境可以通过为此作业模板明确分配不同的执行环境来覆盖。"
-
-#: screens/Project/shared/Project.helptext.js:93
-msgid ""
-"The first fetches all references. The second\n"
-"fetches the Github pull request number 62, in this example\n"
-"the branch needs to be \"pull/62/head\"."
-msgstr "第一次获取所有引用。第二次获取 Github 拉取请求号 62,在本示例中,分支需要为 \"pull/62/head\"。"
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:7
-msgid "The full image location, including the container registry, image name, and version tag."
-msgstr "完整镜像位置,包括容器注册表、镜像名称和版本标签。"
-
-#: screens/Inventory/shared/Inventory.helptext.js:191
-msgid ""
-"The inventory file\n"
-"to be synced by this source. You can select from\n"
-"the dropdown or enter a file within the input."
-msgstr "要由此源同步的清单文件。您可以从下拉列表中选择,或者在输入中输入一个文件。"
-
-#: screens/Host/HostDetail/HostDetail.js:79
-msgid "The inventory that this host belongs to."
-msgstr "此主机要属于的清单。"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:141
-msgid "The last {dayOfWeek}"
-msgstr "最后 {dayOfWeek}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:200
-msgid "The last {weekday} of {month}"
-msgstr "最后 {weekday}({month})"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:100
-msgid ""
-"The maximum number of hosts allowed to be managed by\n"
-"this organization. Value defaults to 0 which means no limit.\n"
-"Refer to the Ansible documentation for more details."
-msgstr "允许由此机构管理的最大主机数。默认值为 0,表示无限制。请参阅 Ansible 文档以了解更多详情。"
-
-#: screens/Organization/shared/OrganizationForm.js:72
-msgid ""
-"The maximum number of hosts allowed to be managed by this organization.\n"
-"Value defaults to 0 which means no limit. Refer to the Ansible\n"
-"documentation for more details."
-msgstr "允许由此机构管理的最大主机数。默认值为 0,表示无限制。请参阅 Ansible 文档以了解更多详情。"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:26
-msgid ""
-"The number associated with the \"Messaging\n"
-"Service\" in Twilio with the format +18005550199."
-msgstr "在 Twilio 中与“信息服务”关联的号码,格式为 +18005550199。"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:69
-msgid "The number of hosts you have automated against is below your subscription count."
-msgstr "您已自动针对的主机数量低于您的订阅数。"
-
-#: screens/Job/Job.helptext.js:25
-#: screens/Template/shared/JobTemplate.helptext.js:48
-msgid "The number of parallel or simultaneous processes to use while executing the playbook. An empty value, or a value less than 1 will use the Ansible default which is usually 5. The default number of forks can be overwritten with a change to"
-msgstr "执行 playbook 时使用的并行或同步进程数量。空值或小于 1 的值将使用 Ansible 默认值,通常为 5。要覆盖默认分叉数,可更改"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:164
-msgid "The number of parallel or simultaneous processes to use while executing the playbook. Inputting no value will use the default value from the ansible configuration file. You can find more information"
-msgstr "执行 playbook 时使用的并行或同步进程数量。如果不输入值,则将使用 ansible 配置文件中的默认值。您可以找到更多信息"
-
-#: components/ContentError/ContentError.js:41
-#: screens/Job/Job.js:161
-msgid "The page you requested could not be found."
-msgstr "您请求的页面无法找到。"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:144
-msgid "The pattern used to target hosts in the inventory. Leaving the field blank, all, and * will all target all hosts in the inventory. You can find more information about Ansible's host patterns"
-msgstr "用于将字段保留为清单中的目标主机的模式。留空、所有和 * 将针对清单中的所有主机。您可以找到有关 Ansible 主机模式的更多信息"
-
-#: screens/Job/Job.helptext.js:7
-msgid "The project containing the playbook this job will execute."
-msgstr "包含此作业要执行的 playbook 的项目。"
-
-#: screens/Job/Job.helptext.js:8
-msgid "The project from which this inventory update is sourced."
-msgstr "此清单更新源自的项目。"
-
-#: screens/Project/ProjectList/ProjectListItem.js:120
-msgid "The project is currently syncing and the revision will be available after the sync is complete."
-msgstr "该项目目前正在同步,且修订将在同步完成后可用。"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:211
-#: screens/Project/ProjectList/ProjectListItem.js:107
-msgid "The project must be synced before a revision is available."
-msgstr "项目必须在修订可用前同步。"
-
-#: screens/Project/ProjectList/ProjectListItem.js:130
-msgid "The project revision is currently out of date. Please refresh to fetch the most recent revision."
-msgstr "项目修订当前已过期。请刷新以获取最新的修订版本。"
-
-#: components/Workflow/WorkflowNodeHelp.js:138
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:131
-msgid "The resource associated with this node has been deleted."
-msgstr "已删除与该节点关联的资源。"
-
-#: screens/Job/JobOutput/EmptyOutput.js:31
-msgid "The search filter did not produce any results…"
-msgstr "搜索过滤器没有产生任何结果…"
-
-#: screens/Template/Survey/SurveyQuestionForm.js:180
-msgid ""
-"The suggested format for variable names is lowercase and\n"
-"underscore-separated (for example, foo_bar, user_id, host_name,\n"
-"etc.). Variable names with spaces are not allowed."
-msgstr "变量名称的建议格式为小写字母并用下划线分隔(例如:foo_bar、user_id、host_name 等等)。不允许使用带空格的变量名称。"
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:50
-msgid ""
-"There are no available playbook directories in {project_base_dir}.\n"
-"Either that directory is empty, or all of the contents are already\n"
-"assigned to other projects. Create a new directory there and make\n"
-"sure the playbook files can be read by the \"awx\" system user,\n"
-"or have {brandName} directly retrieve your playbooks from\n"
-"source control using the Source Control Type option above."
-msgstr "{project_base_dir} 中没有可用的 playbook 目录。该目录可能是空目录,或所有内容都已被分配给其他项目。创建一个新目录并确保 playbook 文件可以被 \"awx\" 系统用户读取,或者使用上述的 Source Control Type 选项从源控制控制选项直接获取 {brandName}。"
-
-#: screens/Template/Survey/MultipleChoiceField.js:34
-msgid "There must be a value in at least one input"
-msgstr "至少在一个输入中必须有一个值"
-
-#: screens/Login/Login.js:155
-msgid "There was a problem logging in. Please try again."
-msgstr "登录时有问题。请重试。"
-
-#: components/ContentError/ContentError.js:42
-msgid "There was an error loading this content. Please reload the page."
-msgstr "加载此内容时出错。请重新加载页面。"
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:56
-msgid "There was an error parsing the file. Please check the file formatting and try again."
-msgstr "解析该文件时出错。请检查文件格式然后重试。"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:713
-msgid "There was an error saving the workflow."
-msgstr "保存工作流时出错。"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:69
-msgid "These are the modules that {brandName} supports running commands against."
-msgstr "这些是 {brandName} 支持运行命令的模块。"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:129
-msgid "These are the verbosity levels for standard out of the command run that are supported."
-msgstr "这些是支持的标准运行命令运行的详细程度。"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:122
-#: screens/Job/Job.helptext.js:43
-msgid "These arguments are used with the specified module."
-msgstr "这些参数与指定的模块一起使用。"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:111
-msgid "These arguments are used with the specified module. You can find information about {0} by clicking"
-msgstr "这些参数与指定的模块一起使用。点击可以找到有关 {0} 的信息"
-
-#: screens/Job/Job.helptext.js:33
-msgid "These arguments are used with the specified module. You can find information about {moduleName} by clicking"
-msgstr "这些参数与指定的模块一起使用。点击可以找到有关 {moduleName} 的信息"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:410
-msgid "Third"
-msgstr "第三"
-
-#: screens/Template/shared/JobTemplateForm.js:157
-msgid "This Project needs to be updated"
-msgstr "此项目需要被更新"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:286
-#: screens/Template/Survey/SurveyList.js:82
-msgid "This action will delete the following:"
-msgstr "此操作将删除以下内容:"
-
-#: screens/User/UserTeams/UserTeamList.js:217
-msgid "This action will disassociate all roles for this user from the selected teams."
-msgstr "此操作将从所选团队中解除该用户的所有角色。"
-
-#: screens/Team/TeamRoles/TeamRolesList.js:236
-#: screens/User/UserRoles/UserRolesList.js:232
-msgid "This action will disassociate the following role from {0}:"
-msgstr "此操作将从 {0} 中解除以下角色关联:"
-
-#: components/DisassociateButton/DisassociateButton.js:148
-msgid "This action will disassociate the following:"
-msgstr "此操作将解除以下关联:"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:178
-msgid "This action will remove the following instances:"
-msgstr "此操作将删除以下实例:"
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:113
-msgid "This container group is currently being by other resources. Are you sure you want to delete it?"
-msgstr "其他资源目前正在此容器组中。确定要删除它吗?"
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:304
-msgid "This credential is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "其他资源目前正在使用此凭证。确定要删除它吗?"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:121
-msgid "This credential type is currently being used by some credentials and cannot be deleted"
-msgstr "一些凭证目前正在使用此凭证类型,无法删除"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:74
-msgid ""
-"This data is used to enhance\n"
-"future releases of the Software and to provide\n"
-"Automation Analytics."
-msgstr "这些用户用于增强\n"
-"以后发行的软件并提供\n"
-"Automation Analytics。"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:62
-msgid ""
-"This data is used to enhance\n"
-"future releases of the Tower Software and help\n"
-"streamline customer experience and success."
-msgstr "这些数据用于增强未来的 Tower 软件发行版本,并帮助简化客户体验和成功。"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:132
-msgid "This execution environment is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "其他资源目前正在使用此执行环境。确定要删除它吗?"
-
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:74
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:79
-msgid "This feature is deprecated and will be removed in a future release."
-msgstr "这个功能已被弃用并将在以后的发行版本中被删除。"
-
-#: screens/Inventory/shared/Inventory.helptext.js:155
-msgid "This field is ignored unless an Enabled Variable is set. If the enabled variable matches this value, the host will be enabled on import."
-msgstr "除非设置了启用的变量,否则此字段会被忽略。如果启用的变量与这个值匹配,则会在导入时启用主机。"
-
-#: components/AdHocCommands/useAdHocCredentialPasswordStep.js:44
-#: components/LaunchPrompt/steps/useCredentialPasswordsStep.js:50
-msgid "This field may not be blank"
-msgstr "此字段不得为空白"
-
-#: util/validators.js:127
-msgid "This field must be a number"
-msgstr "此字段必须是数字"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:107
-msgid "This field must be a number and have a value between {0} and {1}"
-msgstr "此字段必须是数字,且值介于 {0} 和 {1}"
-
-#: util/validators.js:67
-msgid "This field must be a number and have a value between {min} and {max}"
-msgstr "此字段必须是数字,且值介于 {min} 和 {max}"
-
-#: util/validators.js:64
-msgid "This field must be a number and have a value greater than {min}"
-msgstr "此字段必须是一个数字,且值需要大于 {min}"
-
-#: util/validators.js:61
-msgid "This field must be a number and have a value less than {max}"
-msgstr "此字段必须是一个数字,且值需要小于 {max}"
-
-#: util/validators.js:184
-msgid "This field must be a regular expression"
-msgstr "此字段必须是正则表达式"
-
-#: util/validators.js:111
-#: util/validators.js:194
-msgid "This field must be an integer"
-msgstr "此字段必须是整数"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:99
-msgid "This field must be at least {0} characters"
-msgstr "此字段必须至少为 {0} 个字符"
-
-#: util/validators.js:52
-msgid "This field must be at least {min} characters"
-msgstr "此字段必须至少为 {min} 个字符"
-
-#: util/validators.js:197
-msgid "This field must be greater than 0"
-msgstr "此字段必须大于 0"
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:52
-#: components/LaunchPrompt/steps/useSurveyStep.js:111
-#: screens/Template/shared/JobTemplateForm.js:154
-#: screens/User/shared/UserForm.js:92
-#: screens/User/shared/UserForm.js:103
-#: util/validators.js:5
-#: util/validators.js:76
-msgid "This field must not be blank"
-msgstr "此字段不能为空"
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:46
-msgid "This field must not be blank."
-msgstr "此字段不能为空。"
-
-#: util/validators.js:101
-msgid "This field must not contain spaces"
-msgstr "此字段不得包含空格"
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:102
-msgid "This field must not exceed {0} characters"
-msgstr "此字段不能超过 {0} 个字符"
-
-#: util/validators.js:43
-msgid "This field must not exceed {max} characters"
-msgstr "此字段不能超过 {max} 个字符"
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:51
-msgid "This field will be retrieved from an external secret management system using the specified credential."
-msgstr "此字段将使用指定的凭证从外部 secret 管理系统检索。"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:82
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:89
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:104
-msgid "This has already been acted on"
-msgstr "此已操作"
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:125
-msgid "This instance group is currently being by other resources. Are you sure you want to delete it?"
-msgstr "其他资源目前正在此实例组中。确定要删除它吗?"
-
-#: components/LaunchPrompt/steps/useInventoryStep.js:59
-msgid "This inventory is applied to all workflow nodes within this workflow ({0}) that prompt for an inventory."
-msgstr "此清单会应用到在这个工作流 ({0}) 中的所有作业模板,它会提示输入一个清单。"
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:199
-msgid "This inventory is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "其他资源目前正在使用此清单。确定要删除它吗?"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:313
-msgid "This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?"
-msgstr "依赖该清单源的其他资源目前正在使用此清单源。确定要删除它吗?"
-
-#: screens/Application/Applications.js:77
-msgid "This is the only time the client secret will be shown."
-msgstr "这是唯一显示客户端 secret 的时间。"
-
-#: screens/User/UserTokens/UserTokens.js:59
-msgid "This is the only time the token value and associated refresh token value will be shown."
-msgstr "这是唯一显示令牌值和关联刷新令牌值的时间。"
-
-#: screens/Job/JobOutput/EmptyOutput.js:37
-msgid "This job failed and has no output."
-msgstr "此作业失败,且没有输出。"
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:543
-msgid "This job template is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "其他资源目前正在使用此任务模板。确定要删除它吗?"
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:197
-msgid "This organization is currently being by other resources. Are you sure you want to delete it?"
-msgstr "这个机构目前由其他资源使用。您确定要删除它吗?"
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:338
-msgid "This project is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "其他资源目前正在使用这个项目。确定要删除它吗?"
-
-#: screens/Project/shared/Project.helptext.js:59
-msgid "This project is currently on sync and cannot be clicked until sync process completed"
-msgstr "此项目当前处于同步状态,在同步过程完成前无法点击"
-
-#: components/Schedule/shared/ScheduleForm.js:460
-msgid "This schedule has no occurrences due to the selected exceptions."
-msgstr "由于所选的例外,此计划没有发生。"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:122
-msgid "This schedule is missing an Inventory"
-msgstr "此调度缺少清单"
-
-#: components/Schedule/ScheduleList/ScheduleList.js:147
-msgid "This schedule is missing required survey values"
-msgstr "此调度缺少所需的调查值"
-
-#: components/Schedule/shared/UnsupportedScheduleForm.js:12
-msgid ""
-"This schedule uses complex rules that are not supported in the\n"
-"UI. Please use the API to manage this schedule."
-msgstr "UI 尚不支持复杂的规则,请使用 API 管理此调度。"
-
-#: components/LaunchPrompt/steps/StepName.js:26
-msgid "This step contains errors"
-msgstr "这一步包含错误"
-
-#: screens/User/shared/UserForm.js:150
-msgid "This value does not match the password you entered previously. Please confirm that password."
-msgstr "此值与之前输入的密码不匹配。请确认该密码。"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:106
-msgid "This will cancel all subsequent nodes in this workflow"
-msgstr "这将取消此工作流中的所有后续节点"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:328
-msgid "This will cancel all subsequent nodes in this workflow."
-msgstr "这将取消此工作流中的所有后续节点。"
-
-#: screens/Setting/shared/RevertAllAlert.js:36
-msgid ""
-"This will revert all configuration values on this page to\n"
-"their factory defaults. Are you sure you want to proceed?"
-msgstr "这会将此页中的所有配置值重置为其工厂默认值。确定要继续?"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:40
-msgid "This workflow does not have any nodes configured."
-msgstr "此工作流没有配置任何节点。"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:43
-#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:35
-msgid "This workflow has already been acted on"
-msgstr "此工作流已进行"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:267
-msgid "This workflow job template is currently being used by other resources. Are you sure you want to delete it?"
-msgstr "其他资源目前正在使用此工作流作业模板。确定要删除它吗?"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:299
-msgid "Thu"
-msgstr "周四"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:80
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:186
-#: components/Schedule/shared/FrequencyDetailSubform.js:304
-#: components/Schedule/shared/FrequencyDetailSubform.js:448
-msgid "Thursday"
-msgstr "周四"
-
-#: screens/ActivityStream/ActivityStream.js:249
-#: screens/ActivityStream/ActivityStream.js:261
-#: screens/ActivityStream/ActivityStreamDetailButton.js:41
-#: screens/ActivityStream/ActivityStreamListItem.js:42
-msgid "Time"
-msgstr "时间"
-
-#: screens/Project/shared/Project.helptext.js:128
-msgid ""
-"Time in seconds to consider a project\n"
-"to be current. During job runs and callbacks the task\n"
-"system will evaluate the timestamp of the latest project\n"
-"update. If it is older than Cache Timeout, it is not\n"
-"considered current, and a new project update will be\n"
-"performed."
-msgstr "将项目视为最新的时间(以秒为单位)。在作业运行和回调期间,任务系统将评估最新项目更新的时间戳。如果它比缓存超时旧,则不被视为最新,并且会执行新的项目更新。"
-
-#: screens/Inventory/shared/Inventory.helptext.js:147
-msgid ""
-"Time in seconds to consider an inventory sync\n"
-"to be current. During job runs and callbacks the task system will\n"
-"evaluate the timestamp of the latest sync. If it is older than\n"
-"Cache Timeout, it is not considered current, and a new\n"
-"inventory sync will be performed."
-msgstr "将清单同步视为最新的时间(以秒为单位)。在作业运行和回调期间,任务系统将评估最新同步的时间戳。如果它比缓存超时旧,则不被视为最新,并且会执行新的清单同步。"
-
-#: components/StatusLabel/StatusLabel.js:51
-msgid "Timed out"
-msgstr "超时"
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:86
-#: components/PromptDetail/PromptDetail.js:136
-#: components/PromptDetail/PromptDetail.js:355
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:484
-#: screens/Job/JobDetail/JobDetail.js:397
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:170
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:114
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:277
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:186
-#: screens/Template/shared/JobTemplateForm.js:475
-msgid "Timeout"
-msgstr "超时"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:193
-msgid "Timeout minutes"
-msgstr "超时分钟"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:207
-msgid "Timeout seconds"
-msgstr "超时秒"
-
-#: screens/Host/HostList/SmartInventoryButton.js:20
-msgid "To create a smart inventory using ansible facts, go to the smart inventory screen."
-msgstr "要使用 ansible 事实创建智能清单,请转至智能清单屏幕。"
-
-#: screens/Template/Survey/SurveyReorderModal.js:194
-msgid "To reorder the survey questions drag and drop them in the desired location."
-msgstr "要重新调整调查问题的顺序,将问题拖放到所需的位置。"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:106
-msgid "Toggle Legend"
-msgstr "切换图例"
-
-#: components/FormField/PasswordInput.js:39
-msgid "Toggle Password"
-msgstr "切换密码"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:116
-msgid "Toggle Tools"
-msgstr "切换工具"
-
-#: components/HostToggle/HostToggle.js:70
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:56
-msgid "Toggle host"
-msgstr "切换主机"
-
-#: components/InstanceToggle/InstanceToggle.js:61
-msgid "Toggle instance"
-msgstr "切换实例"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:80
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:82
-#: screens/TopologyView/Header.js:99
-msgid "Toggle legend"
-msgstr "切换图例"
-
-#: components/NotificationList/NotificationListItem.js:50
-msgid "Toggle notification approvals"
-msgstr "切换通知批准"
-
-#: components/NotificationList/NotificationListItem.js:92
-msgid "Toggle notification failure"
-msgstr "切换通知失败"
-
-#: components/NotificationList/NotificationListItem.js:64
-msgid "Toggle notification start"
-msgstr "切换通知开始"
-
-#: components/NotificationList/NotificationListItem.js:78
-msgid "Toggle notification success"
-msgstr "切换通知成功"
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:66
-msgid "Toggle schedule"
-msgstr "删除调度"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:92
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:94
-msgid "Toggle tools"
-msgstr "切换工具"
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:373
-#: screens/User/UserTokens/UserTokens.js:64
-msgid "Token"
-msgstr "令牌"
-
-#: screens/User/UserTokens/UserTokens.js:50
-#: screens/User/UserTokens/UserTokens.js:53
-msgid "Token information"
-msgstr "令牌信息"
-
-#: screens/User/UserToken/UserToken.js:73
-msgid "Token not found."
-msgstr "未找到令牌"
-
-#: screens/Application/Application/Application.js:80
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:105
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:128
-#: screens/Application/Applications.js:40
-#: screens/User/User.js:76
-#: screens/User/UserTokenList/UserTokenList.js:118
-#: screens/User/Users.js:34
-msgid "Tokens"
-msgstr "令牌"
-
-#: components/Workflow/WorkflowTools.js:83
-msgid "Tools"
-msgstr "工具"
-
-#: routeConfig.js:152
-#: screens/TopologyView/TopologyView.js:40
-msgid "Topology View"
-msgstr "拓扑视图"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:218
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:197
-#: screens/InstanceGroup/Instances/InstanceListItem.js:199
-#: screens/Instances/InstanceDetail/InstanceDetail.js:213
-#: screens/Instances/InstanceList/InstanceListItem.js:214
-#: screens/Instances/InstancePeers/InstancePeerListItem.js:78
-msgid "Total Jobs"
-msgstr "作业总数"
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:76
-msgid "Total Nodes"
-msgstr "节点总数"
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:103
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:119
-msgid "Total hosts"
-msgstr "主机总数"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:72
-msgid "Total jobs"
-msgstr "作业总数"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:87
-msgid "Track submodules"
-msgstr "跟踪子模块"
-
-#: components/PromptDetail/PromptProjectDetail.js:56
-#: screens/Project/ProjectDetail/ProjectDetail.js:109
-msgid "Track submodules latest commit on branch"
-msgstr "跟踪分支中的最新提交"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:141
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:168
-msgid "Trial"
-msgstr "试用"
-
-#: components/JobList/JobListItem.js:319
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:66
-#: screens/Job/JobDetail/JobDetail.js:383
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:210
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:240
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:270
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:315
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:373
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:142
-msgid "True"
-msgstr "True"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:277
-msgid "Tue"
-msgstr "周二"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:78
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:184
-#: components/Schedule/shared/FrequencyDetailSubform.js:282
-#: components/Schedule/shared/FrequencyDetailSubform.js:438
-msgid "Tuesday"
-msgstr "周二"
-
-#: components/NotificationList/NotificationList.js:201
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:142
-msgid "Twilio"
-msgstr "Twilio"
-
-#: components/JobList/JobList.js:246
-#: components/JobList/JobListItem.js:98
-#: components/Lookup/ProjectLookup.js:132
-#: components/NotificationList/NotificationList.js:219
-#: components/NotificationList/NotificationListItem.js:33
-#: components/PromptDetail/PromptDetail.js:124
-#: components/RelatedTemplateList/RelatedTemplateList.js:187
-#: components/TemplateList/TemplateList.js:214
-#: components/TemplateList/TemplateList.js:243
-#: components/TemplateList/TemplateListItem.js:184
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:85
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:154
-#: components/Workflow/WorkflowNodeHelp.js:160
-#: components/Workflow/WorkflowNodeHelp.js:196
-#: screens/Credential/CredentialList/CredentialList.js:165
-#: screens/Credential/CredentialList/CredentialListItem.js:63
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:94
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:116
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:17
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:46
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:54
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:195
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:66
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:94
-#: screens/Inventory/InventoryList/InventoryList.js:220
-#: screens/Inventory/InventoryList/InventoryListItem.js:116
-#: screens/Inventory/InventorySources/InventorySourceList.js:213
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:100
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:105
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:180
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:120
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:68
-#: screens/Project/ProjectList/ProjectList.js:194
-#: screens/Project/ProjectList/ProjectList.js:223
-#: screens/Project/ProjectList/ProjectListItem.js:218
-#: screens/Team/TeamRoles/TeamRoleListItem.js:17
-#: screens/Team/TeamRoles/TeamRolesList.js:181
-#: screens/Template/Survey/SurveyList.js:103
-#: screens/Template/Survey/SurveyList.js:103
-#: screens/Template/Survey/SurveyListItem.js:60
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:93
-#: screens/User/UserDetail/UserDetail.js:75
-#: screens/User/UserRoles/UserRolesList.js:156
-#: screens/User/UserRoles/UserRolesListItem.js:21
-msgid "Type"
-msgstr "类型"
-
-#: screens/Credential/shared/TypeInputsSubForm.js:25
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:47
-#: screens/Project/shared/ProjectForm.js:298
-msgid "Type Details"
-msgstr "类型详情"
-
-#: screens/Template/Survey/MultipleChoiceField.js:56
-msgid ""
-"Type answer then click checkbox on right to select answer as\n"
-"default."
-msgstr "键入回答,然后点右侧选择回答作为默认选项。"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:50
-msgid "UTC"
-msgstr "UTC"
-
-#: components/HostForm/HostForm.js:62
-msgid "Unable to change inventory on a host"
-msgstr "无法更改主机上的清单"
-
-#: screens/Project/ProjectList/ProjectListItem.js:211
-msgid "Unable to load last job update"
-msgstr "无法加载最后的作业更新"
-
-#: components/StatusLabel/StatusLabel.js:61
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:260
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:87
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:46
-#: screens/InstanceGroup/Instances/InstanceListItem.js:78
-#: screens/Instances/InstanceDetail/InstanceDetail.js:306
-#: screens/Instances/InstanceList/InstanceListItem.js:77
-#: screens/TopologyView/Tooltip.js:121
-msgid "Unavailable"
-msgstr "不可用"
-
-#: screens/Setting/shared/RevertButton.js:53
-#: screens/Setting/shared/RevertButton.js:62
-msgid "Undo"
-msgstr "撤消"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:183
-msgid "Unfollow"
-msgstr "未追随"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:126
-msgid "Unlimited"
-msgstr "无限"
-
-#: components/StatusLabel/StatusLabel.js:47
-#: screens/Job/JobOutput/shared/HostStatusBar.js:51
-#: screens/Job/JobOutput/shared/OutputToolbar.js:103
-msgid "Unreachable"
-msgstr "无法访问"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:102
-msgid "Unreachable Host Count"
-msgstr "无法访问的主机数"
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:104
-msgid "Unreachable Hosts"
-msgstr "无法访问的主机"
-
-#: util/dates.js:74
-msgid "Unrecognized day string"
-msgstr "未识别的日字符串"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:15
-msgid "Unsaved changes modal"
-msgstr "未保存的修改 modal"
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:94
-msgid "Update Revision on Launch"
-msgstr "启动时更新修订"
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:51
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:133
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:92
-msgid "Update on launch"
-msgstr "启动时更新"
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:75
-msgid "Update options"
-msgstr "更新选项"
-
-#: components/PromptDetail/PromptProjectDetail.js:61
-#: screens/Project/ProjectDetail/ProjectDetail.js:115
-msgid "Update revision on job launch"
-msgstr "启动作业时更新修订"
-
-#: screens/Setting/SettingList.js:92
-msgid "Update settings pertaining to Jobs within {brandName}"
-msgstr "更新 {brandName} 中与作业相关的设置"
-
-#: screens/Template/shared/WebhookSubForm.js:188
-msgid "Update webhook key"
-msgstr "轮转 Webhook 密钥"
-
-#: components/Workflow/WorkflowNodeHelp.js:126
-msgid "Updating"
-msgstr "更新"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:118
-msgid "Upload a .zip file"
-msgstr "上传一个 .zip 文件"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:97
-msgid "Upload a Red Hat Subscription Manifest containing your subscription. To generate your subscription manifest, go to <0>subscription allocations0> on the Red Hat Customer Portal."
-msgstr "上传一个包含了您的订阅的 Red Hat Subscription Manifest。要生成订阅清单,请访问红帽用户门户网站中的 <0>subscription allocations0>。"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:53
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:132
-msgid "Use SSL"
-msgstr "使用 SSL"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:58
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:137
-msgid "Use TLS"
-msgstr "使用 TLS"
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:72
-msgid ""
-"Use custom messages to change the content of\n"
-"notifications sent when a job starts, succeeds, or fails. Use\n"
-"curly braces to access information about the job:"
-msgstr "当一个作业开始、成功或失败时使用的自定义消息来更改通知的内容。使用大括号来访问该作业的信息:"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:12
-msgid "Use one Annotation Tag per line, without commas."
-msgstr "每行使用一个注解标签,不带逗号。"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:13
-msgid ""
-"Use one IRC channel or username per line. The pound\n"
-"symbol (#) for channels, and the at (@) symbol for users, are not\n"
-"required."
-msgstr "每行使用一个 IRC 频道或用户名。频道不需要输入 # 号,用户不需要输入 @ 符号。"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:5
-msgid "Use one email address per line to create a recipient list for this type of notification."
-msgstr "每行一个电子邮件地址,为这类通知创建一个接收者列表。"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:28
-msgid ""
-"Use one phone number per line to specify where to\n"
-"route SMS messages. Phone numbers should be formatted +11231231234. For more information see Twilio documentation"
-msgstr "每行一个电话号码以指定路由 SMS 消息的位置。电话号的格式化为 +11231231234。如需更多信息,请参阅 Twilio 文档"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:249
-#: screens/InstanceGroup/Instances/InstanceList.js:270
-#: screens/Instances/InstanceDetail/InstanceDetail.js:292
-#: screens/Instances/InstanceList/InstanceList.js:205
-msgid "Used Capacity"
-msgstr "已使用容量"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:253
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:257
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:78
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:86
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:42
-#: screens/InstanceGroup/Instances/InstanceListItem.js:74
-#: screens/Instances/InstanceDetail/InstanceDetail.js:297
-#: screens/Instances/InstanceDetail/InstanceDetail.js:303
-#: screens/Instances/InstanceList/InstanceListItem.js:73
-#: screens/TopologyView/Tooltip.js:117
-msgid "Used capacity"
-msgstr "使用的容量"
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:13
-msgid "User"
-msgstr "用户"
-
-#: components/AppContainer/PageHeaderToolbar.js:160
-msgid "User Details"
-msgstr "用户详情"
-
-#: screens/Setting/SettingList.js:121
-#: screens/Setting/Settings.js:118
-msgid "User Interface"
-msgstr "用户界面"
-
-#: screens/Setting/SettingList.js:126
-msgid "User Interface settings"
-msgstr "用户界面设置"
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:72
-#: screens/User/UserRoles/UserRolesList.js:142
-msgid "User Roles"
-msgstr "用户角色"
-
-#: screens/User/UserDetail/UserDetail.js:72
-#: screens/User/shared/UserForm.js:119
-msgid "User Type"
-msgstr "用户类型"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:59
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:60
-msgid "User analytics"
-msgstr "用户分析"
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:34
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:202
-msgid "User and Automation Analytics"
-msgstr "用户和 Automation Analytics"
-
-#: components/AppContainer/PageHeaderToolbar.js:154
-msgid "User details"
-msgstr "用户详情"
-
-#: screens/User/User.js:96
-msgid "User not found."
-msgstr "未找到用户。"
-
-#: screens/User/UserTokenList/UserTokenList.js:180
-msgid "User tokens"
-msgstr "用户令牌"
-
-#: components/AddRole/AddResourceRole.js:23
-#: components/AddRole/AddResourceRole.js:38
-#: components/ResourceAccessList/ResourceAccessList.js:173
-#: components/ResourceAccessList/ResourceAccessList.js:226
-#: screens/Login/Login.js:230
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:144
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:253
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:303
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:361
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:67
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:260
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:337
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:442
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:92
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:204
-#: screens/User/UserDetail/UserDetail.js:68
-#: screens/User/UserList/UserList.js:120
-#: screens/User/UserList/UserList.js:160
-#: screens/User/UserList/UserListItem.js:38
-#: screens/User/shared/UserForm.js:76
-msgid "Username"
-msgstr "用户名"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:88
-msgid "Username / password"
-msgstr "用户名/密码"
-
-#: components/AddRole/AddResourceRole.js:178
-#: components/AddRole/AddResourceRole.js:179
-#: routeConfig.js:101
-#: screens/ActivityStream/ActivityStream.js:184
-#: screens/Team/Teams.js:30
-#: screens/User/UserList/UserList.js:110
-#: screens/User/UserList/UserList.js:153
-#: screens/User/Users.js:15
-#: screens/User/Users.js:26
-msgid "Users"
-msgstr "用户"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:101
-msgid "VMware vCenter"
-msgstr "VMware vCenter"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:69
-#: components/HostForm/HostForm.js:113
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:115
-#: components/PromptDetail/PromptDetail.js:168
-#: components/PromptDetail/PromptDetail.js:369
-#: components/PromptDetail/PromptJobTemplateDetail.js:286
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:135
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:620
-#: screens/Host/HostDetail/HostDetail.js:93
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:165
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:37
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:88
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:143
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:53
-#: screens/Inventory/shared/InventoryForm.js:108
-#: screens/Inventory/shared/InventoryGroupForm.js:46
-#: screens/Inventory/shared/SmartInventoryForm.js:93
-#: screens/Job/JobDetail/JobDetail.js:546
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:501
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:226
-#: screens/Template/shared/JobTemplateForm.js:402
-#: screens/Template/shared/WorkflowJobTemplateForm.js:212
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:293
-msgid "Variables"
-msgstr "变量"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:130
-msgid "Variables Prompted"
-msgstr "提示变量"
-
-#: screens/Inventory/shared/Inventory.helptext.js:43
-msgid "Variables must be in JSON or YAML syntax. Use the radio button to toggle between the two."
-msgstr "变量需要是 JSON 或 YAML 语法格式。使用单选按钮在两者之间切换。"
-
-#: screens/Inventory/shared/Inventory.helptext.js:166
-msgid "Variables used to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>{sourceType}1> plugin configuration guide."
-msgstr "用来配置清单源的变量。有关如何配置此插件的详细描述,请参阅文档中的<0>清单插件0>部分,以及 <1>{sourceType}1> 插件配置指南 。"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:121
-msgid "Vault password"
-msgstr "Vault 密码"
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:121
-msgid "Vault password | {credId}"
-msgstr "Vault 密码 | {credId}"
-
-#: screens/Job/JobOutput/JobOutputSearch.js:131
-msgid "Verbose"
-msgstr "详细"
-
-#: components/AdHocCommands/AdHocPreviewStep.js:63
-#: components/PromptDetail/PromptDetail.js:260
-#: components/PromptDetail/PromptInventorySourceDetail.js:100
-#: components/PromptDetail/PromptJobTemplateDetail.js:154
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:478
-#: components/VerbositySelectField/VerbositySelectField.js:34
-#: components/VerbositySelectField/VerbositySelectField.js:45
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:232
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:47
-#: screens/Job/JobDetail/JobDetail.js:331
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:271
-msgid "Verbosity"
-msgstr "详细程度"
-
-#: screens/Setting/AzureAD/AzureAD.js:25
-msgid "View Azure AD settings"
-msgstr "查看 Azure AD 设置"
-
-#: screens/Credential/Credential.js:143
-#: screens/Credential/Credential.js:155
-msgid "View Credential Details"
-msgstr "查看凭证详情"
-
-#: components/Schedule/Schedule.js:151
-msgid "View Details"
-msgstr "查看详情"
-
-#: screens/Setting/GitHub/GitHub.js:58
-msgid "View GitHub Settings"
-msgstr "查看 GitHub 设置"
-
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2.js:26
-msgid "View Google OAuth 2.0 settings"
-msgstr "查看 Google OAuth 2.0 设置"
-
-#: screens/Host/Host.js:137
-msgid "View Host Details"
-msgstr "查看主机详情"
-
-#: screens/Instances/Instance.js:78
-msgid "View Instance Details"
-msgstr "查看实例详情"
-
-#: screens/Inventory/Inventory.js:192
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:142
-#: screens/Inventory/SmartInventory.js:175
-msgid "View Inventory Details"
-msgstr "查看清单脚本"
-
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:92
-msgid "View Inventory Groups"
-msgstr "查看清单组"
-
-#: screens/Inventory/InventoryHost/InventoryHost.js:160
-msgid "View Inventory Host Details"
-msgstr "查看清单主机详情"
-
-#: screens/Inventory/shared/Inventory.helptext.js:55
-msgid "View JSON examples at <0>www.json.org0>"
-msgstr "在 <0>www.json.org0> 查看 JSON 示例"
-
-#: screens/Job/Job.js:212
-msgid "View Job Details"
-msgstr "查看作业详情"
-
-#: screens/Setting/Jobs/Jobs.js:25
-msgid "View Jobs settings"
-msgstr "查看作业设置"
-
-#: screens/Setting/LDAP/LDAP.js:38
-msgid "View LDAP Settings"
-msgstr "查看 LDAP 设置"
-
-#: screens/Setting/Logging/Logging.js:32
-msgid "View Logging settings"
-msgstr "查看日志记录设置"
-
-#: screens/Setting/MiscAuthentication/MiscAuthentication.js:32
-msgid "View Miscellaneous Authentication settings"
-msgstr "查看其他身份验证设置"
-
-#: screens/Setting/MiscSystem/MiscSystem.js:32
-msgid "View Miscellaneous System settings"
-msgstr "查看杂项系统设置"
-
-#: screens/Setting/OIDC/OIDC.js:25
-msgid "View OIDC settings"
-msgstr "查看 OIDC 设置"
-
-#: screens/Organization/Organization.js:225
-msgid "View Organization Details"
-msgstr "查看机构详情"
-
-#: screens/Project/Project.js:200
-msgid "View Project Details"
-msgstr "查看项目详情"
-
-#: screens/Setting/RADIUS/RADIUS.js:25
-msgid "View RADIUS settings"
-msgstr "查看 RADIUS 设置"
-
-#: screens/Setting/SAML/SAML.js:25
-msgid "View SAML settings"
-msgstr "查看 SAML 设置"
-
-#: components/Schedule/Schedule.js:83
-#: components/Schedule/Schedule.js:101
-msgid "View Schedules"
-msgstr "查看调度"
-
-#: screens/Setting/Subscription/Subscription.js:30
-msgid "View Settings"
-msgstr "查看设置"
-
-#: screens/Template/Template.js:159
-#: screens/Template/WorkflowJobTemplate.js:145
-msgid "View Survey"
-msgstr "查看问卷调查"
-
-#: screens/Setting/TACACS/TACACS.js:25
-msgid "View TACACS+ settings"
-msgstr "查看 TACACS+ 设置"
-
-#: screens/Team/Team.js:118
-msgid "View Team Details"
-msgstr "查看团队详情"
-
-#: screens/Template/Template.js:260
-#: screens/Template/WorkflowJobTemplate.js:275
-msgid "View Template Details"
-msgstr "查看模板详情"
-
-#: screens/User/UserToken/UserToken.js:100
-msgid "View Tokens"
-msgstr "查看令牌"
-
-#: screens/User/User.js:141
-msgid "View User Details"
-msgstr "查看用户详情"
-
-#: screens/Setting/UI/UI.js:26
-msgid "View User Interface settings"
-msgstr "查看用户界面设置"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:105
-msgid "View Workflow Approval Details"
-msgstr "查看工作流批准详情"
-
-#: screens/Inventory/shared/Inventory.helptext.js:66
-msgid "View YAML examples at <0>docs.ansible.com0>"
-msgstr "在 <0>docs.ansible.com0> 查看 YAML 示例"
-
-#: components/ScreenHeader/ScreenHeader.js:65
-#: components/ScreenHeader/ScreenHeader.js:68
-msgid "View activity stream"
-msgstr "查看活动流"
-
-#: screens/Credential/Credential.js:99
-msgid "View all Credentials."
-msgstr "查看所有凭证。"
-
-#: screens/Host/Host.js:97
-msgid "View all Hosts."
-msgstr "查看所有主机。"
-
-#: screens/Inventory/Inventory.js:95
-#: screens/Inventory/SmartInventory.js:95
-msgid "View all Inventories."
-msgstr "查看所有清单。"
-
-#: screens/Inventory/InventoryHost/InventoryHost.js:101
-msgid "View all Inventory Hosts."
-msgstr "查看所有清单主机。"
-
-#: screens/Job/JobTypeRedirect.js:40
-msgid "View all Jobs"
-msgstr "查看所有作业"
-
-#: screens/Job/Job.js:162
-msgid "View all Jobs."
-msgstr "查看所有作业"
-
-#: screens/NotificationTemplate/NotificationTemplate.js:60
-#: screens/NotificationTemplate/NotificationTemplateAdd.js:52
-msgid "View all Notification Templates."
-msgstr "查看所有通知模板。"
-
-#: screens/Organization/Organization.js:155
-msgid "View all Organizations."
-msgstr "查看所有机构。"
-
-#: screens/Project/Project.js:137
-msgid "View all Projects."
-msgstr "查看所有项目。"
-
-#: screens/Team/Team.js:76
-msgid "View all Teams."
-msgstr "查看所有团队。"
-
-#: screens/Template/Template.js:176
-#: screens/Template/WorkflowJobTemplate.js:176
-msgid "View all Templates."
-msgstr "查看所有模板。"
-
-#: screens/User/User.js:97
-msgid "View all Users."
-msgstr "查看所有用户。"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:54
-msgid "View all Workflow Approvals."
-msgstr "查看所有工作流批准。"
-
-#: screens/Application/Application/Application.js:96
-msgid "View all applications."
-msgstr "查看所有应用程序。"
-
-#: screens/CredentialType/CredentialType.js:78
-msgid "View all credential types"
-msgstr "查看所有凭证类型"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:85
-msgid "View all execution environments"
-msgstr "查看所有执行环境"
-
-#: screens/InstanceGroup/ContainerGroup.js:86
-#: screens/InstanceGroup/InstanceGroup.js:94
-msgid "View all instance groups"
-msgstr "查看所有实例组"
-
-#: screens/ManagementJob/ManagementJob.js:135
-msgid "View all management jobs"
-msgstr "查看所有管理作业"
-
-#: screens/Setting/Settings.js:204
-msgid "View all settings"
-msgstr "查看所有设置"
-
-#: screens/User/UserToken/UserToken.js:74
-msgid "View all tokens."
-msgstr "查看所有令牌。"
-
-#: screens/Setting/SettingList.js:133
-msgid "View and edit your subscription information"
-msgstr "查看并编辑您的订阅信息"
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:25
-#: screens/ActivityStream/ActivityStreamListItem.js:50
-msgid "View event details"
-msgstr "查看事件详情"
-
-#: screens/Inventory/InventorySource/InventorySource.js:167
-msgid "View inventory source details"
-msgstr "查看清单源详情"
-
-#: components/Sparkline/Sparkline.js:44
-msgid "View job {0}"
-msgstr "查看作业 {0}"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:220
-msgid "View node details"
-msgstr "查看节点详情"
-
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:85
-msgid "View smart inventory host details"
-msgstr "查看智能清单主机详情"
-
-#: routeConfig.js:30
-#: screens/ActivityStream/ActivityStream.js:145
-msgid "Views"
-msgstr "视图"
-
-#: components/TemplateList/TemplateListItem.js:198
-#: components/TemplateList/TemplateListItem.js:204
-#: screens/Template/WorkflowJobTemplate.js:137
-msgid "Visualizer"
-msgstr "可视化工具"
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:44
-msgid "WARNING:"
-msgstr "警告:"
-
-#: components/JobList/JobList.js:229
-#: components/StatusLabel/StatusLabel.js:52
-#: components/Workflow/WorkflowNodeHelp.js:96
-msgid "Waiting"
-msgstr "等待"
-
-#: screens/Job/JobOutput/EmptyOutput.js:35
-msgid "Waiting for job output…"
-msgstr "等待作业输出…"
-
-#: components/Workflow/WorkflowLegend.js:118
-#: screens/Job/JobOutput/JobOutputSearch.js:132
-msgid "Warning"
-msgstr "警告"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:14
-msgid "Warning: Unsaved Changes"
-msgstr "警告:未保存的更改"
-
-#: components/Schedule/shared/ScheduleFormFields.js:43
-msgid "Warning: {selectedValue} is a link to {0} and will be saved as that."
-msgstr "警告: {selectedValue} 是到 {0} 的链接,并将保存为到其中。"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:119
-msgid "We were unable to locate licenses associated with this account."
-msgstr "我们无法找到与这个帐户关联的许可证。"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:138
-msgid "We were unable to locate subscriptions associated with this account."
-msgstr "我们无法找到与这个帐户关联的许可证。"
-
-#: components/DetailList/LaunchedByDetail.js:24
-#: components/NotificationList/NotificationList.js:202
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:143
-msgid "Webhook"
-msgstr "Webhook"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:177
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:103
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:333
-#: screens/Template/shared/WebhookSubForm.js:199
-msgid "Webhook Credential"
-msgstr "Webhook 凭证"
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:175
-msgid "Webhook Credentials"
-msgstr "Webhook 凭证"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:173
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:92
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:326
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:168
-#: screens/Template/shared/WebhookSubForm.js:173
-msgid "Webhook Key"
-msgstr "Webhook 密钥"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:166
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:91
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:311
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:156
-#: screens/Template/shared/WebhookSubForm.js:129
-msgid "Webhook Service"
-msgstr "Webhook 服务"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:169
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:95
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:319
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:162
-#: screens/Template/shared/WebhookSubForm.js:161
-#: screens/Template/shared/WebhookSubForm.js:167
-msgid "Webhook URL"
-msgstr "Webhook URL"
-
-#: screens/Template/shared/JobTemplateForm.js:646
-#: screens/Template/shared/WorkflowJobTemplateForm.js:271
-msgid "Webhook details"
-msgstr "Webhook 详情"
-
-#: screens/Template/shared/JobTemplate.helptext.js:24
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:17
-msgid "Webhook services can launch jobs with this workflow job template by making a POST request to this URL."
-msgstr "Webhook 服务可通过向此 URL 发出 POST 请求来使用此工作流作业模板启动作业。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:25
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:18
-msgid "Webhook services can use this as a shared secret."
-msgstr "Webhook 服务可以将此用作共享机密。"
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:78
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:41
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:142
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:63
-msgid "Webhooks"
-msgstr "Webhook"
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:26
-msgid "Webhooks: Enable Webhook for this workflow job template."
-msgstr "Webhook:为此工作流任务模板启用 Webhook。"
-
-#: screens/Template/shared/JobTemplate.helptext.js:42
-msgid "Webhooks: Enable webhook for this template."
-msgstr "Webhook:为此模板启用 Webhook。"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:288
-msgid "Wed"
-msgstr "周三"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:79
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:185
-#: components/Schedule/shared/FrequencyDetailSubform.js:293
-#: components/Schedule/shared/FrequencyDetailSubform.js:443
-msgid "Wednesday"
-msgstr "周三"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:185
-#: components/Schedule/shared/FrequencyDetailSubform.js:179
-#: components/Schedule/shared/ScheduleFormFields.js:128
-#: components/Schedule/shared/ScheduleFormFields.js:188
-msgid "Week"
-msgstr "周"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:464
-msgid "Weekday"
-msgstr "周中日"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:469
-msgid "Weekend day"
-msgstr "周末日"
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:59
-msgid ""
-"Welcome to Red Hat Ansible Automation Platform!\n"
-"Please complete the steps below to activate your subscription."
-msgstr "欢迎使用 Red Hat Ansible Automation Platform!请完成以下步骤以激活订阅。"
-
-#: screens/Login/Login.js:190
-msgid "Welcome to {brandName}!"
-msgstr "欢迎使用 {brandName}!"
-
-#: screens/Inventory/shared/Inventory.helptext.js:105
-msgid ""
-"When not checked, a merge will be performed,\n"
-"combining local variables with those found on the\n"
-"external source."
-msgstr "如果没有选中,就会执行合并,将本地变量与外部源上的变量合并。"
-
-#: screens/Inventory/shared/Inventory.helptext.js:93
-msgid ""
-"When not checked, local child\n"
-"hosts and groups not found on the external source will remain\n"
-"untouched by the inventory update process."
-msgstr "如果没有选中,外部源上没有的本地子主机和组将在清单更新过程中保持不变。"
-
-#: components/Workflow/WorkflowLegend.js:96
-msgid "Workflow"
-msgstr "工作流"
-
-#: components/Workflow/WorkflowNodeHelp.js:75
-msgid "Workflow Approval"
-msgstr "工作流已批准"
-
-#: screens/WorkflowApproval/WorkflowApproval.js:52
-msgid "Workflow Approval not found."
-msgstr "未找到工作流批准。"
-
-#: routeConfig.js:54
-#: screens/ActivityStream/ActivityStream.js:156
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:118
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:145
-#: screens/WorkflowApproval/WorkflowApprovals.js:13
-#: screens/WorkflowApproval/WorkflowApprovals.js:22
-msgid "Workflow Approvals"
-msgstr "工作流批准"
-
-#: components/JobList/JobList.js:216
-#: components/JobList/JobListItem.js:47
-#: components/Schedule/ScheduleList/ScheduleListItem.js:40
-#: screens/Job/JobDetail/JobDetail.js:70
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:224
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:164
-msgid "Workflow Job"
-msgstr "工作流任务"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:76
-msgid "Workflow Job 1/{0}"
-msgstr "工作流作业 1/{0}"
-
-#: components/JobList/JobListItem.js:201
-#: components/Workflow/WorkflowNodeHelp.js:63
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:20
-#: screens/Job/JobDetail/JobDetail.js:244
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:91
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:186
-#: util/getRelatedResourceDeleteDetails.js:105
-msgid "Workflow Job Template"
-msgstr "工作流作业模板"
-
-#: util/getRelatedResourceDeleteDetails.js:115
-#: util/getRelatedResourceDeleteDetails.js:157
-#: util/getRelatedResourceDeleteDetails.js:260
-msgid "Workflow Job Template Nodes"
-msgstr "工作流作业模板节点"
-
-#: util/getRelatedResourceDeleteDetails.js:140
-msgid "Workflow Job Templates"
-msgstr "工作流作业模板"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:23
-msgid "Workflow Link"
-msgstr "工作流链接"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:100
-msgid "Workflow Nodes"
-msgstr "工作流节点"
-
-#: components/WorkflowOutputNavigation/WorkflowOutputNavigation.js:86
-msgid "Workflow Statuses"
-msgstr "工作流状态"
-
-#: components/TemplateList/TemplateList.js:218
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:98
-msgid "Workflow Template"
-msgstr "工作流模板"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:519
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:159
-msgid "Workflow approved message"
-msgstr "工作流批准的消息"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:531
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:168
-msgid "Workflow approved message body"
-msgstr "工作流批准的消息正文"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:543
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:177
-msgid "Workflow denied message"
-msgstr "工作流拒绝的消息"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:555
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:186
-msgid "Workflow denied message body"
-msgstr "工作流拒绝的消息正文"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:106
-msgid "Workflow documentation"
-msgstr "工作流文档"
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:220
-msgid "Workflow job details"
-msgstr "工作流作业详情"
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:46
-msgid "Workflow job templates"
-msgstr "工作流作业模板"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:24
-msgid "Workflow link modal"
-msgstr "工作流链接模式"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:247
-msgid "Workflow node view modal"
-msgstr "工作流节点查看模式"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:567
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:195
-msgid "Workflow pending message"
-msgstr "工作流待处理信息"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:579
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:204
-msgid "Workflow pending message body"
-msgstr "工作流待处理信息正文"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:591
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:213
-msgid "Workflow timed out message"
-msgstr "工作流超时信息"
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:603
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:222
-msgid "Workflow timed out message body"
-msgstr "工作流超时信息正文"
-
-#: screens/User/shared/UserTokenForm.js:77
-msgid "Write"
-msgstr "写入"
-
-#: screens/Inventory/shared/Inventory.helptext.js:52
-msgid "YAML:"
-msgstr "YAML:"
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:187
-#: components/Schedule/shared/FrequencyDetailSubform.js:183
-#: components/Schedule/shared/ScheduleFormFields.js:130
-#: components/Schedule/shared/ScheduleFormFields.js:190
-msgid "Year"
-msgstr "年"
-
-#: components/Search/Search.js:229
-msgid "Yes"
-msgstr "是"
-
-#: components/Lookup/MultiCredentialsLookup.js:155
-msgid "You cannot select multiple vault credentials with the same vault ID. Doing so will automatically deselect the other with the same vault ID."
-msgstr "您不能选择具有相同 vault ID 的多个 vault 凭证。这样做会自动取消选择具有相同的 vault ID 的另一个凭证。"
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:96
-msgid "You do not have permission to delete the following Groups: {itemsUnableToDelete}"
-msgstr "您没有权限删除以下组: {itemsUnableToDelete}"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:152
-msgid "You do not have permission to delete {pluralizedItemName}: {itemsUnableToDelete}"
-msgstr "您没有权限删除 {pluralizedItemName}:{itemsUnableToDelete}"
-
-#: components/DisassociateButton/DisassociateButton.js:66
-msgid "You do not have permission to disassociate the following: {itemsUnableToDisassociate}"
-msgstr "您没有权限取消关联: {itemsUnableToDisassociate}"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:72
-msgid "You do not have permission to remove instances: {itemsUnableToremove}"
-msgstr "您没有删除实例的权限:{itemsUnableToremove}"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:82
-msgid "You have automated against more hosts than your subscription allows."
-msgstr "您已自动针对的主机数量大于订阅所允许的数量。"
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:87
-msgid ""
-"You may apply a number of possible variables in the\n"
-"message. For more information, refer to the"
-msgstr "您可以在消息中应用多个可能的变量。如需更多信息,请参阅"
-
-#: screens/Login/Login.js:198
-msgid "Your session has expired. Please log in to continue where you left off."
-msgstr "您的会话已过期。请登录以继续使用会话过期前所在的位置。"
-
-#: components/AppContainer/AppContainer.js:130
-msgid "Your session is about to expire"
-msgstr "您的会话即将到期"
-
-#: components/Workflow/WorkflowTools.js:121
-msgid "Zoom In"
-msgstr "放大"
-
-#: components/Workflow/WorkflowTools.js:100
-msgid "Zoom Out"
-msgstr "缩小"
-
-#: screens/TopologyView/Header.js:51
-#: screens/TopologyView/Header.js:54
-msgid "Zoom in"
-msgstr "放大"
-
-#: screens/TopologyView/Header.js:63
-#: screens/TopologyView/Header.js:66
-msgid "Zoom out"
-msgstr "缩小"
-
-#: screens/Template/shared/JobTemplateForm.js:754
-#: screens/Template/shared/WebhookSubForm.js:150
-msgid "a new webhook key will be generated on save."
-msgstr "在保存时会生成一个新的 WEBHOOK 密钥。"
-
-#: screens/Template/shared/JobTemplateForm.js:751
-#: screens/Template/shared/WebhookSubForm.js:140
-msgid "a new webhook url will be generated on save."
-msgstr "在保存时会生成一个新的 WEBHOOK url。"
-
-#: screens/Inventory/shared/Inventory.helptext.js:123
-#: screens/Inventory/shared/Inventory.helptext.js:142
-msgid "and click on Update Revision on Launch"
-msgstr "点 Update Revision on Launch"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:505
-msgid "approved"
-msgstr "批准"
-
-#: components/AppContainer/AppContainer.js:55
-msgid "brand logo"
-msgstr "品牌徽标"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:279
-#: screens/Template/Survey/SurveyList.js:72
-msgid "cancel delete"
-msgstr "取消删除"
-
-#: screens/Setting/shared/SharedFields.js:341
-msgid "cancel edit login redirect"
-msgstr "取消编辑登录重定向"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:169
-msgid "cancel remove"
-msgstr "取消删除"
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:34
-msgid "canceled"
-msgstr "取消"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:217
-msgid "command"
-msgstr "命令"
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:267
-#: screens/Template/Survey/SurveyList.js:63
-msgid "confirm delete"
-msgstr "确认删除"
-
-#: components/DisassociateButton/DisassociateButton.js:130
-#: screens/Team/TeamRoles/TeamRolesList.js:219
-msgid "confirm disassociate"
-msgstr "确认解除关联"
-
-#: screens/Setting/shared/SharedFields.js:330
-msgid "confirm edit login redirect"
-msgstr "确认编辑登录重定向"
-
-#: screens/TopologyView/ContentLoading.js:32
-msgid "content-loading-in-progress"
-msgstr "content-loading-in-progress"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:189
-msgid "day"
-msgstr "天"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:151
-msgid "deletion error"
-msgstr "删除错误"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:513
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:37
-msgid "denied"
-msgstr "拒绝"
-
-#: screens/Job/JobOutput/EmptyOutput.js:41
-msgid "details."
-msgstr "详情。"
-
-#: components/DisassociateButton/DisassociateButton.js:91
-msgid "disassociate"
-msgstr "解除关联"
-
-#: components/Lookup/HostFilterLookup.js:406
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:20
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:39
-#: screens/Template/Survey/SurveyQuestionForm.js:269
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:239
-#: screens/Template/shared/JobTemplate.helptext.js:61
-msgid "documentation"
-msgstr "文档"
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:105
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:117
-#: screens/Host/HostDetail/HostDetail.js:104
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:97
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:109
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:99
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:289
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:161
-#: screens/Project/ProjectDetail/ProjectDetail.js:309
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:195
-#: screens/User/UserDetail/UserDetail.js:92
-msgid "edit"
-msgstr "编辑"
-
-#: screens/Template/Survey/SurveyListItem.js:65
-#: screens/Template/Survey/SurveyReorderModal.js:125
-msgid "encrypted"
-msgstr "加密"
-
-#: components/Lookup/HostFilterLookup.js:408
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:241
-msgid "for more info."
-msgstr "更多信息。"
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:21
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:40
-#: screens/Template/Survey/SurveyQuestionForm.js:271
-#: screens/Template/shared/JobTemplate.helptext.js:63
-msgid "for more information."
-msgstr "更多信息。"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:150
-msgid "here"
-msgstr "此处"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:118
-#: components/AdHocCommands/AdHocDetailsStep.js:170
-#: screens/Job/Job.helptext.js:39
-msgid "here."
-msgstr "此处。"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:49
-msgid "host-description-{0}"
-msgstr "host-description-{0}"
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:44
-msgid "host-name-{0}"
-msgstr "host-name-{0}"
-
-#: components/Lookup/HostFilterLookup.js:418
-msgid "hosts"
-msgstr "主机"
-
-#: components/Pagination/Pagination.js:24
-msgid "items"
-msgstr "项"
-
-#: screens/User/UserList/UserListItem.js:44
-msgid "ldap user"
-msgstr "LDAP 用户"
-
-#: screens/User/UserDetail/UserDetail.js:76
-msgid "login type"
-msgstr "登录类型"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:203
-msgid "min"
-msgstr "分钟"
-
-#: screens/Template/Survey/MultipleChoiceField.js:76
-msgid "new choice"
-msgstr "新选择"
-
-#: components/Pagination/Pagination.js:36
-#: components/Schedule/shared/FrequencyDetailSubform.js:480
-msgid "of"
-msgstr "的"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:215
-msgid "option to the"
-msgstr "选项"
-
-#: components/Pagination/Pagination.js:25
-msgid "page"
-msgstr "页"
-
-#: components/Pagination/Pagination.js:26
-msgid "pages"
-msgstr "页"
-
-#: components/Pagination/Pagination.js:28
-msgid "per page"
-msgstr "每页"
-
-#: components/LaunchButton/ReLaunchDropDown.js:77
-#: components/LaunchButton/ReLaunchDropDown.js:100
-msgid "relaunch jobs"
-msgstr "重新启动作业"
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:217
-msgid "sec"
-msgstr "秒"
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:238
-msgid "seconds"
-msgstr "秒"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:58
-msgid "select module"
-msgstr "选择模块"
-
-#: screens/User/UserList/UserListItem.js:49
-msgid "social login"
-msgstr "社交登录"
-
-#: screens/Template/shared/JobTemplateForm.js:346
-#: screens/Template/shared/WorkflowJobTemplateForm.js:188
-msgid "source control branch"
-msgstr "源控制分支"
-
-#: screens/ActivityStream/ActivityStreamListItem.js:30
-msgid "system"
-msgstr "系统"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:511
-msgid "timed out"
-msgstr "超时"
-
-#: components/AdHocCommands/AdHocDetailsStep.js:195
-msgid "toggle changes"
-msgstr "切换更改"
-
-#: screens/ActivityStream/ActivityStreamDescription.js:516
-msgid "updated"
-msgstr "已更新"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:190
-msgid "weekday"
-msgstr "周中日"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:191
-msgid "weekend day"
-msgstr "周末日"
-
-#: screens/Template/shared/WebhookSubForm.js:181
-msgid "workflow job template webhook key"
-msgstr "工作流作业模板 webhook 密钥"
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:65
-msgid "{0, plural, one {# source with sync failures.} other {# sources with sync failures.}}"
-msgstr "{0, plural, one {# source with sync failures.} other {# sources with sync failures.}}"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:115
-msgid "{0, plural, one {Are you sure you want delete the group below?} other {Are you sure you want delete the groups below?}}"
-msgstr "{0, plural, one {Are you sure you want delete the group below?} other {Are you sure you want delete the groups below?}}"
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:86
-msgid "{0, plural, one {Delete Group?} other {Delete Groups?}}"
-msgstr "{0, plural, one {Delete Group?} other {Delete Groups?}}"
-
-#: util/validators.js:138
-msgid "{0, plural, one {Please enter a valid phone number.} other {Please enter valid phone numbers.}}"
-msgstr "{0, plural, one {Please enter a valid phone number.} other {Please enter valid phone numbers.}}"
-
-#: screens/Inventory/InventoryList/InventoryList.js:247
-msgid "{0, plural, one {The inventory will be in a pending status until the final delete is processed.} other {The inventories will be in a pending status until the final delete is processed.}}"
-msgstr "{0, plural, one {The inventory will be in a pending status until the final delete is processed.} other {The inventories will be in a pending status until the final delete is processed.}}"
-
-#: components/JobList/JobList.js:280
-msgid "{0, plural, one {The selected job cannot be deleted due to insufficient permission or a running job status} other {The selected jobs cannot be deleted due to insufficient permissions or a running job status}}"
-msgstr "{0, plural, one {The selected job cannot be deleted due to insufficient permission or a running job status} other {The selected jobs cannot be deleted due to insufficient permissions or a running job status}}"
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:151
-msgid "{0, plural, one {This approval cannot be deleted due to insufficient permissions or a pending job status} other {These approvals cannot be deleted due to insufficient permissions or a pending job status}}"
-msgstr "{0, plural, one {This approval cannot be deleted due to insufficient permissions or a pending job status} other {These approvals cannot be deleted due to insufficient permissions or a pending job status}}"
-
-#: screens/Credential/CredentialList/CredentialList.js:198
-msgid "{0, plural, one {This credential is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these credentials could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This credential is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these credentials could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:164
-msgid "{0, plural, one {This credential type is currently being used by some credentials and cannot be deleted.} other {Credential types that are being used by credentials cannot be deleted. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This credential type is currently being used by some credentials and cannot be deleted.} other {Credential types that are being used by credentials cannot be deleted. Are you sure you want to delete anyway?}}"
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:194
-msgid "{0, plural, one {This execution environment is currently being used by other resources. Are you sure you want to delete it?} other {These execution environments could be in use by other resources that rely on them. Are you sure you want to delete them anyway?}}"
-msgstr "{0, plural, one {This execution environment is currently being used by other resources. Are you sure you want to delete it?} other {These execution environments could be in use by other resources that rely on them. Are you sure you want to delete them anyway?}}"
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:182
-msgid "{0, plural, one {This instance group is currently being by other resources. Are you sure you want to delete it?} other {Deleting these instance groups could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This instance group is currently being by other resources. Are you sure you want to delete it?} other {Deleting these instance groups could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Instances/Shared/RemoveInstanceButton.js:85
-msgid "{0, plural, one {This intance is currently being used by other resources. Are you sure you want to delete it?} other {Deprovisioning these instances could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This intance is currently being used by other resources. Are you sure you want to delete it?} other {Deprovisioning these instances could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Inventory/InventoryList/InventoryList.js:240
-msgid "{0, plural, one {This inventory is currently being used by some templates. Are you sure you want to delete it?} other {Deleting these inventories could impact some templates that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This inventory is currently being used by some templates. Are you sure you want to delete it?} other {Deleting these inventories could impact some templates that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:196
-msgid "{0, plural, one {This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?} other {Deleting these inventory sources could impact other resources that rely on them. Are you sure you want to delete anyway}}"
-msgstr "{0, plural, one {This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?} other {Deleting these inventory sources could impact other resources that rely on them. Are you sure you want to delete anyway}}"
-
-#: screens/Organization/OrganizationList/OrganizationList.js:166
-msgid "{0, plural, one {This organization is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these organizations could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This organization is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these organizations could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: screens/Project/ProjectList/ProjectList.js:252
-msgid "{0, plural, one {This project is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these projects could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This project is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these projects could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:209
-#: components/TemplateList/TemplateList.js:266
-msgid "{0, plural, one {This template is currently being used by some workflow nodes. Are you sure you want to delete it?} other {Deleting these templates could impact some workflow nodes that rely on them. Are you sure you want to delete anyway?}}"
-msgstr "{0, plural, one {This template is currently being used by some workflow nodes. Are you sure you want to delete it?} other {Deleting these templates could impact some workflow nodes that rely on them. Are you sure you want to delete anyway?}}"
-
-#: components/JobList/JobListCancelButton.js:72
-msgid "{0, plural, one {You cannot cancel the following job because it is not running:} other {You cannot cancel the following jobs because they are not running:}}"
-msgstr "{0, plural, one {You cannot cancel the following job because it is not running:} other {You cannot cancel the following jobs because they are not running:}}"
-
-#: components/JobList/JobListCancelButton.js:56
-msgid "{0, plural, one {You do not have permission to cancel the following job:} other {You do not have permission to cancel the following jobs:}}"
-msgstr "{0, plural, one {You do not have permission to cancel the following job:} other {You do not have permission to cancel the following jobs:}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:143
-msgid "{0, selectordinal, one {The first {dayOfWeek}} two {The second {dayOfWeek}} =3 {The third {dayOfWeek}} =4 {The fourth {dayOfWeek}} =5 {The fifth {dayOfWeek}}}"
-msgstr "{0, selectordinal, one {The first {dayOfWeek}} two {The second {dayOfWeek}} =3 {The third {dayOfWeek}} =4 {The fourth {dayOfWeek}} =5 {The fifth {dayOfWeek}}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:202
-msgid "{0, selectordinal, one {The first {weekday} of {month}} two {The second {weekday} of {month}} =3 {The third {weekday} of {month}} =4 {The fourth {weekday} of {month}} =5 {The fifth {weekday} of {month}}}"
-msgstr "{0, selectordinal, one {The first {weekday} of {month}} two {The second {weekday} of {month}} =3 {The third {weekday} of {month}} =4 {The fourth {weekday} of {month}} =5 {The fifth {weekday} of {month}}}"
-
-#: screens/ActivityStream/ActivityStreamListItem.js:28
-msgid "{0} (deleted)"
-msgstr "{0}(已删除)"
-
-#: components/ChipGroup/ChipGroup.js:13
-msgid "{0} more"
-msgstr "{0} 更多"
-
-#: screens/Job/JobDetail/JobDetail.js:399
-msgid "{0} seconds"
-msgstr "{0} 秒"
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:95
-msgid "{automatedInstancesCount} since {automatedInstancesSinceDateTime}"
-msgstr "{automatedInstancesCount} 自 {automatedInstancesSinceDateTime}"
-
-#: components/AppContainer/AppContainer.js:55
-msgid "{brandName} logo"
-msgstr "{brandName} 标志"
-
-#: components/DetailList/UserDateDetail.js:23
-msgid "{dateStr} by <0>{username}0>"
-msgstr "{dateStr}(由 <0>{username}0>)"
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:231
-#: screens/InstanceGroup/Instances/InstanceListItem.js:148
-#: screens/Instances/InstanceDetail/InstanceDetail.js:274
-#: screens/Instances/InstanceList/InstanceListItem.js:158
-#: screens/TopologyView/Tooltip.js:288
-msgid "{forks, plural, one {# fork} other {# forks}}"
-msgstr "{forks, plural, one {# fork} other {# forks}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:41
-msgid "{interval, plural, one {{interval} day} other {{interval} days}}"
-msgstr "{interval, plural, one {{interval} 天} other {{interval} 天}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:33
-msgid "{interval, plural, one {{interval} hour} other {{interval} hours}}"
-msgstr "{interval, plural, one {{interval} 小时} other {{interval} 小时}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:25
-msgid "{interval, plural, one {{interval} minute} other {{interval} minutes}}"
-msgstr "{interval, plural, one {{interval} 分钟} other {{interval} 分钟}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:57
-msgid "{interval, plural, one {{interval} month} other {{interval} months}}"
-msgstr "{interval, plural, one {{interval} 月} other {{interval} 月}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:49
-msgid "{interval, plural, one {{interval} week} other {{interval} weeks}}"
-msgstr "{interval, plural, one {{interval} 周} other {{interval} 周}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:65
-msgid "{interval, plural, one {{interval} year} other {{interval} years}}"
-msgstr "{interval, plural, one {{interval} 年} other {{interval} 年}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:198
-msgid "{intervalValue, plural, one {day} other {days}}"
-msgstr "{intervalValue, plural, one {day} other {days}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:196
-msgid "{intervalValue, plural, one {hour} other {hours}}"
-msgstr "{intervalValue, plural, one {hour} other {hours}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:194
-msgid "{intervalValue, plural, one {minute} other {minutes}}"
-msgstr "{intervalValue, plural, one {minute} other {minutes}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:202
-msgid "{intervalValue, plural, one {month} other {months}}"
-msgstr "{intervalValue, plural, one {month} other {months}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:200
-msgid "{intervalValue, plural, one {week} other {weeks}}"
-msgstr "{intervalValue, plural, one {week} other {weeks}}"
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:204
-msgid "{intervalValue, plural, one {year} other {years}}"
-msgstr "{intervalValue, plural, one {year} other {years}}"
-
-#: components/PromptDetail/PromptDetail.js:44
-msgid "{minutes} min {seconds} sec"
-msgstr "{minutes} 分 {seconds} 秒"
-
-#: components/JobList/JobListCancelButton.js:106
-msgid "{numJobsToCancel, plural, one {Cancel job} other {Cancel jobs}}"
-msgstr "{numJobsToCancel, plural, one {Cancel job} other {Cancel jobs}}"
-
-#: components/JobList/JobListCancelButton.js:168
-msgid "{numJobsToCancel, plural, one {This action will cancel the following job:} other {This action will cancel the following jobs:}}"
-msgstr "{numJobsToCancel, plural, one {This action will cancel the following job:} other {This action will cancel the following jobs:}}"
-
-#: components/JobList/JobListCancelButton.js:91
-msgid "{numJobsToCancel, plural, one {{0}} other {{1}}}"
-msgstr "{numJobsToCancel, plural, one {{0}} other {{1}}}"
-
-#: components/Schedule/ScheduleDetail/FrequencyDetails.js:226
-msgid "{numOccurrences, plural, one {After {numOccurrences} occurrence} other {After {numOccurrences} occurrences}}"
-msgstr "{numOccurrences, plural, one {After {numOccurrences} 事件发生} other {After {numOccurrences} 事件发生}}"
-
-#: components/PaginatedTable/PaginatedTable.js:79
-msgid "{pluralizedItemName} List"
-msgstr "{pluralizedItemName} 列表"
-
-#: components/HealthCheckButton/HealthCheckButton.js:13
-msgid "{selectedItemsCount, plural, one {Click to run a health check on the selected instance.} other {Click to run a health check on the selected instances.}}"
-msgstr "{selectedItemsCount, plural, one {Click to run a health check on the selected instance.} other {Click to run a health check on the selected instances.}}"
-
-#: components/AppContainer/AppContainer.js:154
-msgid "{sessionCountdown, plural, one {You will be logged out in # second due to inactivity} other {You will be logged out in # seconds due to inactivity}}"
-msgstr "{sessionCountdown, plural, one {You will be logged out in # second due to inactivity} other {You will be logged out in # seconds due to inactivity}}"
-
diff --git a/awx/ui/src/locales/zu/messages.po b/awx/ui/src/locales/zu/messages.po
deleted file mode 100644
index bddcf1ea5a43..000000000000
--- a/awx/ui/src/locales/zu/messages.po
+++ /dev/null
@@ -1,10738 +0,0 @@
-msgid ""
-msgstr ""
-"POT-Creation-Date: 2021-04-06 17:06-0400\n"
-"Mime-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: @lingui/cli\n"
-"Language: zu\n"
-"Project-Id-Version: \n"
-"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: \n"
-"Last-Translator: \n"
-"Language-Team: \n"
-"Plural-Forms: \n"
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:43
-msgid "(Limited to first 10)"
-msgstr ""
-
-#: components/TemplateList/TemplateListItem.js:103
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:154
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:90
-msgid "(Prompt on launch)"
-msgstr ""
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:284
-msgid "* This field will be retrieved from an external secret management system using the specified credential."
-msgstr ""
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:243
-msgid "/ (project root)"
-msgstr ""
-
-#: components/VerbositySelectField/VerbositySelectField.js:13
-#: constants.js:19
-msgid "0 (Normal)"
-msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:34
-msgid "0 (Warning)"
-msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:35
-msgid "1 (Info)"
-msgstr ""
-
-#: components/VerbositySelectField/VerbositySelectField.js:14
-#: constants.js:20
-msgid "1 (Verbose)"
-msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:36
-msgid "2 (Debug)"
-msgstr ""
-
-#: components/VerbositySelectField/VerbositySelectField.js:15
-#: constants.js:21
-msgid "2 (More Verbose)"
-msgstr ""
-
-#: components/VerbositySelectField/VerbositySelectField.js:16
-#: constants.js:22
-msgid "3 (Debug)"
-msgstr ""
-
-#: components/VerbositySelectField/VerbositySelectField.js:17
-#: constants.js:23
-msgid "4 (Connection Debug)"
-msgstr ""
-
-#: components/VerbositySelectField/VerbositySelectField.js:18
-#: constants.js:24
-msgid "5 (WinRM Debug)"
-msgstr ""
-
-#: screens/Project/shared/Project.helptext.js:76
-msgid ""
-"A refspec to fetch (passed to the Ansible git\n"
-"module). This parameter allows access to references via\n"
-"the branch field not otherwise available."
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:122
-msgid "A subscription manifest is an export of a Red Hat Subscription. To generate a subscription manifest, go to <0>access.redhat.com0>. For more information, see the <1>User Guide1>."
-msgstr ""
-
-#: screens/Job/WorkflowOutput/WorkflowOutputNode.js:143
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:299
-msgid "ALL"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:279
-msgid "API Service/Integration Key"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:282
-msgid "API Token"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:297
-msgid "API service/integration key"
-msgstr ""
-
-#: components/AppContainer/PageHeaderToolbar.js:130
-msgid "About"
-msgstr ""
-
-#: routeConfig.js:92
-#: screens/ActivityStream/ActivityStream.js:179
-#: screens/Credential/Credential.js:74
-#: screens/Credential/Credentials.js:29
-#: screens/Inventory/Inventories.js:59
-#: screens/Inventory/Inventory.js:65
-#: screens/Inventory/SmartInventory.js:67
-#: screens/Organization/Organization.js:124
-#: screens/Organization/Organizations.js:31
-#: screens/Project/Project.js:105
-#: screens/Project/Projects.js:27
-#: screens/Team/Team.js:58
-#: screens/Team/Teams.js:31
-#: screens/Template/Template.js:136
-#: screens/Template/Templates.js:45
-#: screens/Template/WorkflowJobTemplate.js:118
-msgid "Access"
-msgstr ""
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:75
-msgid "Access Token Expiration"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:347
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:408
-msgid "Account SID"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:383
-msgid "Account token"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:50
-msgid "Action"
-msgstr ""
-
-#: components/JobList/JobList.js:249
-#: components/JobList/JobListItem.js:103
-#: components/RelatedTemplateList/RelatedTemplateList.js:189
-#: components/Schedule/ScheduleList/ScheduleList.js:171
-#: components/Schedule/ScheduleList/ScheduleListItem.js:114
-#: components/SelectedList/DraggableSelectedList.js:101
-#: components/TemplateList/TemplateList.js:246
-#: components/TemplateList/TemplateListItem.js:195
-#: screens/ActivityStream/ActivityStream.js:266
-#: screens/ActivityStream/ActivityStreamListItem.js:49
-#: screens/Application/ApplicationsList/ApplicationListItem.js:48
-#: screens/Application/ApplicationsList/ApplicationsList.js:160
-#: screens/Credential/CredentialList/CredentialList.js:166
-#: screens/Credential/CredentialList/CredentialListItem.js:66
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:177
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:38
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:168
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:87
-#: screens/Host/HostGroups/HostGroupItem.js:34
-#: screens/Host/HostGroups/HostGroupsList.js:177
-#: screens/Host/HostList/HostList.js:172
-#: screens/Host/HostList/HostListItem.js:70
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:214
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:75
-#: screens/InstanceGroup/Instances/InstanceList.js:260
-#: screens/InstanceGroup/Instances/InstanceListItem.js:171
-#: screens/Instances/InstanceList/InstanceList.js:155
-#: screens/Instances/InstanceList/InstanceListItem.js:183
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:218
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:53
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:39
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:142
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:41
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:187
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:44
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:140
-#: screens/Inventory/InventoryList/InventoryList.js:222
-#: screens/Inventory/InventoryList/InventoryListItem.js:131
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:233
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:44
-#: screens/Inventory/InventorySources/InventorySourceList.js:214
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:101
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:102
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:73
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:181
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:124
-#: screens/Organization/OrganizationList/OrganizationList.js:146
-#: screens/Organization/OrganizationList/OrganizationListItem.js:69
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:86
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:19
-#: screens/Project/ProjectList/ProjectList.js:225
-#: screens/Project/ProjectList/ProjectListItem.js:222
-#: screens/Team/TeamList/TeamList.js:144
-#: screens/Team/TeamList/TeamListItem.js:47
-#: screens/Template/Survey/SurveyList.js:105
-#: screens/Template/Survey/SurveyList.js:105
-#: screens/Template/Survey/SurveyListItem.js:90
-#: screens/User/UserList/UserList.js:164
-#: screens/User/UserList/UserListItem.js:56
-msgid "Actions"
-msgstr ""
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:98
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:61
-#: components/TemplateList/TemplateListItem.js:277
-#: screens/Host/HostDetail/HostDetail.js:71
-#: screens/Host/HostList/HostListItem.js:95
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:217
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:50
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:77
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:101
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:33
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:116
-msgid "Activity"
-msgstr ""
-
-#: routeConfig.js:49
-#: screens/ActivityStream/ActivityStream.js:43
-#: screens/ActivityStream/ActivityStream.js:121
-#: screens/Setting/Settings.js:43
-msgid "Activity Stream"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStream.js:124
-msgid "Activity Stream type selector"
-msgstr ""
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:210
-msgid "Actor"
-msgstr ""
-
-#: components/AddDropDownButton/AddDropDownButton.js:40
-#: components/PaginatedTable/ToolbarAddButton.js:14
-msgid "Add"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkAddModal.js:14
-msgid "Add Link"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.js:78
-msgid "Add Node"
-msgstr ""
-
-#: screens/Template/Templates.js:49
-msgid "Add Question"
-msgstr ""
-
-#: components/AddRole/AddResourceRole.js:164
-msgid "Add Roles"
-msgstr ""
-
-#: components/AddRole/AddResourceRole.js:161
-msgid "Add Team Roles"
-msgstr ""
-
-#: components/AddRole/AddResourceRole.js:158
-msgid "Add User Roles"
-msgstr ""
-
-#: components/Workflow/WorkflowStartNode.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:211
-msgid "Add a new node"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:49
-msgid "Add a new node between these two nodes"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:113
-msgid "Add container group"
-msgstr ""
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:140
-msgid "Add existing group"
-msgstr ""
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:152
-msgid "Add existing host"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:114
-msgid "Add instance group"
-msgstr ""
-
-#: screens/Inventory/InventoryList/InventoryList.js:136
-msgid "Add inventory"
-msgstr ""
-
-#: components/TemplateList/TemplateList.js:151
-msgid "Add job template"
-msgstr ""
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:141
-msgid "Add new group"
-msgstr ""
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:153
-msgid "Add new host"
-msgstr ""
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:77
-msgid "Add resource type"
-msgstr ""
-
-#: screens/Inventory/InventoryList/InventoryList.js:137
-msgid "Add smart inventory"
-msgstr ""
-
-#: screens/Team/TeamRoles/TeamRolesList.js:202
-msgid "Add team permissions"
-msgstr ""
-
-#: screens/User/UserRoles/UserRolesList.js:198
-msgid "Add user permissions"
-msgstr ""
-
-#: components/TemplateList/TemplateList.js:152
-msgid "Add workflow template"
-msgstr ""
-
-#: routeConfig.js:113
-#: screens/ActivityStream/ActivityStream.js:190
-msgid "Administration"
-msgstr ""
-
-#: components/DataListToolbar/DataListToolbar.js:139
-#: screens/Job/JobOutput/JobOutputSearch.js:137
-msgid "Advanced"
-msgstr ""
-
-#: components/Search/AdvancedSearch.js:318
-msgid "Advanced search documentation"
-msgstr ""
-
-#: components/Search/AdvancedSearch.js:211
-#: components/Search/AdvancedSearch.js:225
-msgid "Advanced search value input"
-msgstr ""
-
-#: screens/Inventory/shared/Inventory.helptext.js:131
-msgid ""
-"After every project update where the SCM revision\n"
-"changes, refresh the inventory from the selected source\n"
-"before executing job tasks. This is intended for static content,\n"
-"like the Ansible inventory .ini file format."
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:517
-msgid "After number of occurrences"
-msgstr ""
-
-#: components/AlertModal/AlertModal.js:75
-msgid "Alert modal"
-msgstr ""
-
-#: components/LaunchButton/ReLaunchDropDown.js:48
-#: components/PromptDetail/PromptDetail.js:123
-#: screens/Metrics/Metrics.js:82
-#: screens/Metrics/Metrics.js:82
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:266
-msgid "All"
-msgstr ""
-
-#: screens/Dashboard/DashboardGraph.js:137
-msgid "All job types"
-msgstr ""
-
-#: screens/Dashboard/DashboardGraph.js:162
-msgid "All jobs"
-msgstr ""
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:97
-msgid "Allow Branch Override"
-msgstr ""
-
-#: components/PromptDetail/PromptProjectDetail.js:66
-#: screens/Project/ProjectDetail/ProjectDetail.js:120
-msgid "Allow branch override"
-msgstr ""
-
-#: screens/Project/shared/Project.helptext.js:122
-msgid ""
-"Allow changing the Source Control branch or revision in a job\n"
-"template that uses this project."
-msgstr ""
-
-#: screens/Application/shared/Application.helptext.js:6
-msgid "Allowed URIs list, space separated"
-msgstr ""
-
-#: components/Workflow/WorkflowLegend.js:130
-#: components/Workflow/WorkflowLinkHelp.js:24
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:58
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:46
-msgid "Always"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:98
-msgid "Amazon EC2"
-msgstr ""
-
-#: components/Lookup/shared/LookupErrorMessage.js:12
-msgid "An error occurred"
-msgstr ""
-
-#: components/LaunchPrompt/steps/useInventoryStep.js:35
-msgid "An inventory must be selected"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:105
-#~ msgid "Ansible Controller"
-#~ msgstr ""
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:96
-msgid "Ansible Controller Documentation."
-msgstr ""
-
-#: screens/Template/Survey/SurveyQuestionForm.js:43
-msgid "Answer type"
-msgstr ""
-
-#: screens/Template/Survey/SurveyQuestionForm.js:177
-msgid "Answer variable name"
-msgstr ""
-
-#: components/PromptDetail/PromptDetail.js:123
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:263
-msgid "Any"
-msgstr ""
-
-#: components/Lookup/ApplicationLookup.js:83
-#: screens/User/UserTokenDetail/UserTokenDetail.js:38
-#: screens/User/shared/UserTokenForm.js:48
-msgid "Application"
-msgstr ""
-
-#: screens/User/UserTokenList/UserTokenList.js:187
-msgid "Application Name"
-msgstr ""
-
-#: screens/Application/Applications.js:67
-#: screens/Application/Applications.js:70
-msgid "Application information"
-msgstr ""
-
-#: screens/User/UserTokenList/UserTokenList.js:123
-#: screens/User/UserTokenList/UserTokenList.js:134
-msgid "Application name"
-msgstr ""
-
-#: screens/Application/Application/Application.js:95
-msgid "Application not found."
-msgstr ""
-
-#: components/Lookup/ApplicationLookup.js:95
-#: routeConfig.js:142
-#: screens/Application/Applications.js:26
-#: screens/Application/Applications.js:35
-#: screens/Application/ApplicationsList/ApplicationsList.js:113
-#: screens/Application/ApplicationsList/ApplicationsList.js:148
-#: util/getRelatedResourceDeleteDetails.js:208
-msgid "Applications"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStream.js:211
-msgid "Applications & Tokens"
-msgstr ""
-
-#: components/NotificationList/NotificationListItem.js:39
-#: components/NotificationList/NotificationListItem.js:40
-#: components/Workflow/WorkflowLegend.js:114
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:67
-msgid "Approval"
-msgstr ""
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:99
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:106
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:130
-msgid "Approve"
-msgstr ""
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:115
-msgid "Approve, cancel or deny"
-msgstr ""
-
-#: components/StatusLabel/StatusLabel.js:31
-msgid "Approved"
-msgstr ""
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:11
-msgid "Approved - {0}. See the Activity Stream for more information."
-msgstr ""
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:7
-msgid "Approved by {0} - {1}"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:128
-msgid "April"
-msgstr ""
-
-#: components/JobCancelButton/JobCancelButton.js:89
-msgid "Are you sure you want to cancel this job?"
-msgstr ""
-
-#: components/DeleteButton/DeleteButton.js:127
-msgid "Are you sure you want to delete:"
-msgstr ""
-
-#: screens/Setting/shared/SharedFields.js:142
-msgid "Are you sure you want to disable local authentication? Doing so could impact users' ability to log in and the system administrator's ability to reverse this change."
-msgstr ""
-
-#: screens/Setting/shared/SharedFields.js:350
-msgid "Are you sure you want to edit login redirect override URL? Doing so could impact users' ability to log in to the system once local authentication is also disabled."
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:41
-msgid "Are you sure you want to exit the Workflow Creator without saving your changes?"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:40
-msgid "Are you sure you want to remove all the nodes in this workflow?"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:56
-msgid "Are you sure you want to remove the node below:"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:43
-msgid "Are you sure you want to remove this link?"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:63
-msgid "Are you sure you want to remove this node?"
-msgstr ""
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:43
-msgid "Are you sure you want to remove {0} access from {1}? Doing so affects all members of the team."
-msgstr ""
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:50
-msgid "Are you sure you want to remove {0} access from {username}?"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutput.js:771
-msgid "Are you sure you want to submit the request to cancel this job?"
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:102
-#: components/AdHocCommands/AdHocDetailsStep.js:104
-msgid "Arguments"
-msgstr ""
-
-#: screens/Job/JobDetail/JobDetail.js:541
-msgid "Artifacts"
-msgstr ""
-
-#: screens/InstanceGroup/Instances/InstanceList.js:222
-#: screens/User/UserTeams/UserTeamList.js:208
-msgid "Associate"
-msgstr ""
-
-#: screens/Team/TeamRoles/TeamRolesList.js:244
-#: screens/User/UserRoles/UserRolesList.js:240
-msgid "Associate role error"
-msgstr ""
-
-#: components/AssociateModal/AssociateModal.js:98
-msgid "Association modal"
-msgstr ""
-
-#: components/LaunchPrompt/steps/SurveyStep.js:166
-msgid "At least one value must be selected for this field."
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:148
-msgid "August"
-msgstr ""
-
-#: screens/Setting/SettingList.js:52
-msgid "Authentication"
-msgstr ""
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:88
-msgid "Authorization Code Expiration"
-msgstr ""
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:80
-#: screens/Application/shared/ApplicationForm.js:84
-msgid "Authorization grant type"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:208
-#: screens/InstanceGroup/Instances/InstanceListItem.js:204
-#: screens/Instances/InstanceDetail/InstanceDetail.js:159
-#: screens/Instances/InstanceList/InstanceListItem.js:219
-msgid "Auto"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:71
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:72
-msgid "Automation Analytics"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:111
-msgid "Automation Analytics dashboard"
-msgstr ""
-
-#: screens/Setting/Settings.js:46
-msgid "Azure AD"
-msgstr ""
-
-#: screens/Setting/SettingList.js:57
-msgid "Azure AD settings"
-msgstr ""
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:50
-#: components/AddRole/AddResourceRole.js:267
-#: components/LaunchPrompt/LaunchPrompt.js:128
-#: components/Schedule/shared/SchedulePromptableFields.js:132
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:90
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:70
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:152
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:155
-msgid "Back"
-msgstr ""
-
-#: screens/Credential/Credential.js:65
-msgid "Back to Credentials"
-msgstr ""
-
-#: components/ContentError/ContentError.js:43
-msgid "Back to Dashboard."
-msgstr ""
-
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:49
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:50
-msgid "Back to Groups"
-msgstr ""
-
-#: screens/Host/Host.js:50
-#: screens/Inventory/InventoryHost/InventoryHost.js:66
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:53
-msgid "Back to Hosts"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceGroup.js:61
-msgid "Back to Instance Groups"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:171
-#: screens/Instances/Instance.js:18
-msgid "Back to Instances"
-msgstr ""
-
-#: screens/Inventory/Inventory.js:57
-#: screens/Inventory/SmartInventory.js:60
-msgid "Back to Inventories"
-msgstr ""
-
-#: screens/Job/Job.js:110
-msgid "Back to Jobs"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplate.js:76
-msgid "Back to Notifications"
-msgstr ""
-
-#: screens/Organization/Organization.js:116
-msgid "Back to Organizations"
-msgstr ""
-
-#: screens/Project/Project.js:97
-msgid "Back to Projects"
-msgstr ""
-
-#: components/Schedule/Schedule.js:64
-msgid "Back to Schedules"
-msgstr ""
-
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:44
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:78
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:44
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:58
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:95
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:69
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:43
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:90
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:49
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:45
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:25
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:49
-#: screens/Setting/UI/UIDetail/UIDetail.js:59
-msgid "Back to Settings"
-msgstr ""
-
-#: screens/Inventory/InventorySource/InventorySource.js:76
-msgid "Back to Sources"
-msgstr ""
-
-#: screens/Team/Team.js:50
-msgid "Back to Teams"
-msgstr ""
-
-#: screens/Template/Template.js:128
-#: screens/Template/WorkflowJobTemplate.js:110
-msgid "Back to Templates"
-msgstr ""
-
-#: screens/User/UserToken/UserToken.js:47
-msgid "Back to Tokens"
-msgstr ""
-
-#: screens/User/User.js:57
-msgid "Back to Users"
-msgstr ""
-
-#: screens/WorkflowApproval/WorkflowApproval.js:69
-msgid "Back to Workflow Approvals"
-msgstr ""
-
-#: screens/Application/Application/Application.js:72
-msgid "Back to applications"
-msgstr ""
-
-#: screens/CredentialType/CredentialType.js:55
-msgid "Back to credential types"
-msgstr ""
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:57
-msgid "Back to execution environments"
-msgstr ""
-
-#: screens/InstanceGroup/ContainerGroup.js:59
-msgid "Back to instance groups"
-msgstr ""
-
-#: screens/ManagementJob/ManagementJob.js:98
-msgid "Back to management jobs"
-msgstr ""
-
-#: screens/Project/shared/Project.helptext.js:8
-msgid ""
-"Base path used for locating playbooks. Directories\n"
-"found inside this path will be listed in the playbook directory drop-down.\n"
-"Together the base path and selected playbook directory provide the full\n"
-"path used to locate playbooks."
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:433
-msgid "Basic auth password"
-msgstr ""
-
-#: screens/Project/shared/Project.helptext.js:104
-msgid ""
-"Branch to checkout. In addition to branches,\n"
-"you can input tags, commit hashes, and arbitrary refs. Some\n"
-"commit hashes and refs may not be available unless you also\n"
-"provide a custom refspec."
-msgstr ""
-
-#: components/About/About.js:45
-msgid "Brand Image"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:158
-msgid "Browse"
-msgstr ""
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:94
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:115
-msgid "Browse…"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:36
-#~ msgid "By default, we collect and transmit analytics data on the serice usage to Red Hat. There are two categories of data collected by the service. For more information, see <0>this Tower documentation page0>. Uncheck the following boxes to disable this feature."
-#~ msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:36
-msgid "By default, we collect and transmit analytics data on the service usage to Red Hat. There are two categories of data collected by the service. For more information, see <0>this Tower documentation page0>. Uncheck the following boxes to disable this feature."
-msgstr ""
-
-#: screens/TopologyView/Legend.js:74
-msgid "C"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:221
-#: screens/InstanceGroup/Instances/InstanceListItem.js:145
-#: screens/Instances/InstanceDetail/InstanceDetail.js:171
-#: screens/Instances/InstanceList/InstanceListItem.js:155
-msgid "CPU {0}"
-msgstr ""
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:113
-#: components/PromptDetail/PromptProjectDetail.js:136
-#: screens/Project/ProjectDetail/ProjectDetail.js:250
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:114
-msgid "Cache Timeout"
-msgstr ""
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:252
-msgid "Cache timeout"
-msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:114
-msgid "Cache timeout (seconds)"
-msgstr ""
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:51
-#: components/AddRole/AddResourceRole.js:268
-#: components/AssociateModal/AssociateModal.js:114
-#: components/AssociateModal/AssociateModal.js:119
-#: components/DeleteButton/DeleteButton.js:120
-#: components/DeleteButton/DeleteButton.js:123
-#: components/DisassociateButton/DisassociateButton.js:139
-#: components/DisassociateButton/DisassociateButton.js:142
-#: components/FormActionGroup/FormActionGroup.js:23
-#: components/FormActionGroup/FormActionGroup.js:29
-#: components/LaunchPrompt/LaunchPrompt.js:129
-#: components/Lookup/HostFilterLookup.js:387
-#: components/Lookup/Lookup.js:203
-#: components/PaginatedTable/ToolbarDeleteButton.js:282
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:37
-#: components/Schedule/shared/ScheduleForm.js:462
-#: components/Schedule/shared/ScheduleForm.js:467
-#: components/Schedule/shared/ScheduleForm.js:678
-#: components/Schedule/shared/ScheduleForm.js:683
-#: components/Schedule/shared/SchedulePromptableFields.js:133
-#: screens/Credential/shared/CredentialForm.js:343
-#: screens/Credential/shared/CredentialForm.js:348
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:100
-#: screens/Credential/shared/ExternalTestModal.js:98
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:111
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:63
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:66
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:80
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:101
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:107
-#: screens/Setting/shared/RevertAllAlert.js:32
-#: screens/Setting/shared/RevertFormActionGroup.js:31
-#: screens/Setting/shared/RevertFormActionGroup.js:37
-#: screens/Setting/shared/SharedFields.js:133
-#: screens/Setting/shared/SharedFields.js:139
-#: screens/Setting/shared/SharedFields.js:346
-#: screens/Team/TeamRoles/TeamRolesList.js:228
-#: screens/Team/TeamRoles/TeamRolesList.js:231
-#: screens/Template/Survey/SurveyList.js:78
-#: screens/Template/Survey/SurveyReorderModal.js:211
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:31
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:39
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:45
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:50
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:162
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:165
-#: screens/User/UserRoles/UserRolesList.js:224
-#: screens/User/UserRoles/UserRolesList.js:227
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:84
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:92
-msgid "Cancel"
-msgstr ""
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:316
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:112
-msgid "Cancel Inventory Source Sync"
-msgstr ""
-
-#: components/JobCancelButton/JobCancelButton.js:55
-#: screens/Job/JobOutput/JobOutput.js:747
-#: screens/Job/JobOutput/JobOutput.js:748
-msgid "Cancel Job"
-msgstr ""
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:303
-#: screens/Project/ProjectList/ProjectListItem.js:230
-msgid "Cancel Project Sync"
-msgstr ""
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:318
-#: screens/Project/ProjectDetail/ProjectDetail.js:305
-msgid "Cancel Sync"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutput.js:755
-#: screens/Job/JobOutput/JobOutput.js:758
-msgid "Cancel job"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:42
-msgid "Cancel link changes"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:34
-msgid "Cancel link removal"
-msgstr ""
-
-#: components/Lookup/Lookup.js:201
-msgid "Cancel lookup"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:28
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:47
-msgid "Cancel node removal"
-msgstr ""
-
-#: screens/Setting/shared/RevertAllAlert.js:29
-msgid "Cancel revert"
-msgstr ""
-
-#: components/JobList/JobListCancelButton.js:93
-msgid "Cancel selected job"
-msgstr ""
-
-#: components/JobList/JobListCancelButton.js:94
-msgid "Cancel selected jobs"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:77
-msgid "Cancel subscription edit"
-msgstr ""
-
-#: components/JobList/JobListItem.js:113
-#: screens/Job/JobDetail/JobDetail.js:582
-#: screens/Job/JobOutput/shared/OutputToolbar.js:137
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:89
-msgid "Cancel {0}"
-msgstr ""
-
-#: components/JobList/JobList.js:234
-#: components/StatusLabel/StatusLabel.js:46
-#: components/Workflow/WorkflowNodeHelp.js:111
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:253
-msgid "Canceled"
-msgstr ""
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:58
-msgid "Cannot approve, cancel or deny completed workflow approvals"
-msgstr ""
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:129
-msgid ""
-"Cannot enable log aggregator without providing\n"
-"logging aggregator host and logging aggregator type."
-msgstr ""
-
-#: screens/Instances/InstanceList/InstanceList.js:148
-msgid "Cannot run health check on hop nodes."
-msgstr ""
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:213
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:74
-msgid "Capacity"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:218
-#: screens/InstanceGroup/Instances/InstanceList.js:258
-#: screens/InstanceGroup/Instances/InstanceListItem.js:143
-#: screens/Instances/InstanceDetail/InstanceDetail.js:168
-#: screens/Instances/InstanceList/InstanceList.js:153
-#: screens/Instances/InstanceList/InstanceListItem.js:153
-msgid "Capacity Adjustment"
-msgstr ""
-
-#: components/Search/LookupTypeInput.js:59
-msgid "Case-insensitive version of contains"
-msgstr ""
-
-#: components/Search/LookupTypeInput.js:87
-msgid "Case-insensitive version of endswith."
-msgstr ""
-
-#: components/Search/LookupTypeInput.js:45
-msgid "Case-insensitive version of exact."
-msgstr ""
-
-#: components/Search/LookupTypeInput.js:100
-msgid "Case-insensitive version of regex."
-msgstr ""
-
-#: components/Search/LookupTypeInput.js:73
-msgid "Case-insensitive version of startswith."
-msgstr ""
-
-#: screens/Project/shared/Project.helptext.js:14
-msgid ""
-"Change PROJECTS_ROOT when deploying\n"
-"{brandName} to change this location."
-msgstr ""
-
-#: components/StatusLabel/StatusLabel.js:47
-#: screens/Job/JobOutput/shared/HostStatusBar.js:43
-msgid "Changed"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:53
-msgid "Changes"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:253
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:257
-msgid "Channel"
-msgstr ""
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:103
-#: screens/Template/shared/JobTemplateForm.js:214
-msgid "Check"
-msgstr ""
-
-#: components/Search/LookupTypeInput.js:134
-msgid "Check whether the given field or related object is null; expects a boolean value."
-msgstr ""
-
-#: components/Search/LookupTypeInput.js:140
-msgid "Check whether the given field's value is present in the list provided; expects a comma-separated list of items."
-msgstr ""
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:32
-msgid "Choose a .json file"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:78
-msgid "Choose a Notification Type"
-msgstr ""
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:24
-msgid "Choose a Playbook Directory"
-msgstr ""
-
-#: screens/Project/shared/ProjectForm.js:224
-msgid "Choose a Source Control Type"
-msgstr ""
-
-#: screens/Template/shared/WebhookSubForm.js:99
-msgid "Choose a Webhook Service"
-msgstr ""
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:96
-#: screens/Template/shared/JobTemplateForm.js:207
-msgid "Choose a job type"
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:82
-msgid "Choose a module"
-msgstr ""
-
-#: screens/Inventory/shared/InventorySourceForm.js:140
-msgid "Choose a source"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:475
-msgid "Choose an HTTP method"
-msgstr ""
-
-#: screens/Template/Survey/SurveyQuestionForm.js:46
-msgid ""
-"Choose an answer type or format you want as the prompt for the user.\n"
-"Refer to the Ansible Controller Documentation for more additional\n"
-"information about each option."
-msgstr ""
-
-#: components/AddRole/SelectRoleStep.js:20
-msgid "Choose roles to apply to the selected resources. Note that all selected roles will be applied to all selected resources."
-msgstr ""
-
-#: components/AddRole/SelectResourceStep.js:81
-msgid "Choose the resources that will be receiving new roles. You'll be able to select the roles to apply in the next step. Note that the resources chosen here will receive all roles chosen in the next step."
-msgstr ""
-
-#: components/AddRole/AddResourceRole.js:174
-msgid "Choose the type of resource that will be receiving new roles. For example, if you'd like to add new roles to a set of users please choose Users and click Next. You'll be able to select the specific resources in the next step."
-msgstr ""
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:70
-msgid "Clean"
-msgstr ""
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:95
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:116
-msgid "Clear"
-msgstr ""
-
-#: components/DataListToolbar/DataListToolbar.js:95
-#: screens/Job/JobOutput/JobOutputSearch.js:145
-msgid "Clear all filters"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:247
-msgid "Clear subscription"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:252
-msgid "Clear subscription selection"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerGraph.js:245
-msgid "Click an available node to create a new link. Click outside the graph to cancel."
-msgstr ""
-
-#: screens/TopologyView/Tooltip.js:60
-msgid "Click on a node icon to display the details."
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:134
-msgid "Click the Edit button below to reconfigure the node."
-msgstr ""
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:71
-msgid "Click this button to verify connection to the secret management system using the selected credential and specified inputs."
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:152
-msgid "Click to create a new link to this node."
-msgstr ""
-
-#: screens/Template/Survey/SurveyToolbar.js:64
-msgid "Click to rearrange the order of the survey questions"
-msgstr ""
-
-#: screens/Template/Survey/MultipleChoiceField.js:117
-msgid "Click to toggle default value"
-msgstr ""
-
-#: components/Workflow/WorkflowNodeHelp.js:202
-msgid "Click to view job details"
-msgstr ""
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:88
-#: screens/Application/Applications.js:84
-msgid "Client ID"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:284
-msgid "Client Identifier"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:305
-msgid "Client identifier"
-msgstr ""
-
-#: screens/Application/Applications.js:97
-msgid "Client secret"
-msgstr ""
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:99
-#: screens/Application/shared/ApplicationForm.js:126
-msgid "Client type"
-msgstr ""
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:105
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:169
-msgid "Close"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:123
-msgid "Close subscription modal"
-msgstr ""
-
-#: components/CredentialChip/CredentialChip.js:11
-msgid "Cloud"
-msgstr ""
-
-#: components/ExpandCollapse/ExpandCollapse.js:41
-msgid "Collapse"
-msgstr ""
-
-#: screens/Job/JobOutput/PageControls.js:50
-msgid "Collapse all job events"
-msgstr ""
-
-#: screens/Job/JobOutput/shared/JobEventLineToggle.js:39
-msgid "Collapse section"
-msgstr ""
-
-#: components/JobList/JobList.js:214
-#: components/JobList/JobListItem.js:45
-#: screens/Job/JobOutput/HostEventModal.js:129
-msgid "Command"
-msgstr ""
-
-#: components/Schedule/shared/ScheduleForm.js:453
-msgid "Complex schedules are not supported in the UI yet, please use the API to manage this schedule."
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:49
-msgid "Compliant"
-msgstr ""
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:68
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:36
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:130
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:58
-#: screens/Template/shared/JobTemplateForm.js:539
-msgid "Concurrent Jobs"
-msgstr ""
-
-#: screens/Template/shared/JobTemplate.helptext.js:35
-msgid "Concurrent jobs: If enabled, simultaneous runs of this job template will be allowed."
-msgstr ""
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:23
-msgid "Concurrent jobs: If enabled, simultaneous runs of this workflow job template will be allowed."
-msgstr ""
-
-#: screens/Setting/shared/SharedFields.js:121
-#: screens/Setting/shared/SharedFields.js:127
-#: screens/Setting/shared/SharedFields.js:336
-msgid "Confirm"
-msgstr ""
-
-#: components/DeleteButton/DeleteButton.js:107
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:95
-msgid "Confirm Delete"
-msgstr ""
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:191
-msgid "Confirm Disable Local Authorization"
-msgstr ""
-
-#: screens/User/shared/UserForm.js:99
-msgid "Confirm Password"
-msgstr ""
-
-#: components/JobCancelButton/JobCancelButton.js:71
-msgid "Confirm cancel job"
-msgstr ""
-
-#: components/JobCancelButton/JobCancelButton.js:75
-msgid "Confirm cancellation"
-msgstr ""
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:26
-msgid "Confirm delete"
-msgstr ""
-
-#: screens/User/UserRoles/UserRolesList.js:215
-msgid "Confirm disassociate"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:24
-msgid "Confirm link removal"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:37
-msgid "Confirm node removal"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:18
-msgid "Confirm removal of all nodes"
-msgstr ""
-
-#: screens/Setting/shared/RevertAllAlert.js:20
-msgid "Confirm revert all"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:91
-msgid "Confirm selection"
-msgstr ""
-
-#: screens/Job/JobDetail/JobDetail.js:361
-msgid "Container Group"
-msgstr ""
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:47
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:57
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:68
-msgid "Container group"
-msgstr ""
-
-#: screens/InstanceGroup/ContainerGroup.js:84
-msgid "Container group not found."
-msgstr ""
-
-#: components/LaunchPrompt/LaunchPrompt.js:123
-#: components/Schedule/shared/SchedulePromptableFields.js:127
-msgid "Content Loading"
-msgstr ""
-
-#: components/AppContainer/AppContainer.js:142
-msgid "Continue"
-msgstr ""
-
-#: screens/InstanceGroup/Instances/InstanceList.js:196
-#: screens/Instances/InstanceList/InstanceList.js:116
-msgid "Control"
-msgstr ""
-
-#: screens/TopologyView/Legend.js:77
-msgid "Control node"
-msgstr ""
-
-#: screens/Inventory/shared/Inventory.helptext.js:79
-msgid ""
-"Control the level of output Ansible\n"
-"will produce for inventory source update jobs."
-msgstr ""
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:139
-msgid ""
-"Control the level of output ansible\n"
-"will produce as the playbook executes."
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:464
-#~ msgid ""
-#~ "Control the level of output ansible will\n"
-#~ "produce as the playbook executes."
-#~ msgstr ""
-
-#: screens/Job/Job.helptext.js:14
-#: screens/Template/shared/JobTemplate.helptext.js:15
-msgid "Control the level of output ansible will produce as the playbook executes."
-msgstr ""
-
-#: screens/Job/JobDetail/JobDetail.js:346
-msgid "Controller Node"
-msgstr ""
-
-#: components/PromptDetail/PromptDetail.js:121
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:225
-msgid "Convergence"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:256
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:257
-msgid "Convergence select"
-msgstr ""
-
-#: components/CopyButton/CopyButton.js:40
-msgid "Copy"
-msgstr ""
-
-#: screens/Credential/CredentialList/CredentialListItem.js:80
-msgid "Copy Credential"
-msgstr ""
-
-#: components/CopyButton/CopyButton.js:48
-msgid "Copy Error"
-msgstr ""
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:104
-msgid "Copy Execution Environment"
-msgstr ""
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:154
-msgid "Copy Inventory"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:152
-msgid "Copy Notification Template"
-msgstr ""
-
-#: screens/Project/ProjectList/ProjectListItem.js:262
-msgid "Copy Project"
-msgstr ""
-
-#: components/TemplateList/TemplateListItem.js:248
-msgid "Copy Template"
-msgstr ""
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:201
-#: screens/Project/ProjectList/ProjectListItem.js:98
-msgid "Copy full revision to clipboard."
-msgstr ""
-
-#: components/About/About.js:35
-msgid "Copyright"
-msgstr ""
-
-#: components/MultiSelect/TagMultiSelect.js:62
-#: screens/Credential/shared/CredentialFormFields/BecomeMethodField.js:66
-#: screens/Inventory/shared/InventoryForm.js:83
-#: screens/Template/shared/JobTemplateForm.js:387
-#: screens/Template/shared/WorkflowJobTemplateForm.js:197
-msgid "Create"
-msgstr ""
-
-#: screens/Application/Applications.js:27
-#: screens/Application/Applications.js:36
-msgid "Create New Application"
-msgstr ""
-
-#: screens/Credential/Credentials.js:15
-#: screens/Credential/Credentials.js:25
-msgid "Create New Credential"
-msgstr ""
-
-#: screens/Host/Hosts.js:15
-#: screens/Host/Hosts.js:24
-msgid "Create New Host"
-msgstr ""
-
-#: screens/Template/Templates.js:18
-msgid "Create New Job Template"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplates.js:15
-#: screens/NotificationTemplate/NotificationTemplates.js:22
-msgid "Create New Notification Template"
-msgstr ""
-
-#: screens/Organization/Organizations.js:17
-#: screens/Organization/Organizations.js:27
-msgid "Create New Organization"
-msgstr ""
-
-#: screens/Project/Projects.js:13
-#: screens/Project/Projects.js:23
-msgid "Create New Project"
-msgstr ""
-
-#: screens/Inventory/Inventories.js:91
-#: screens/ManagementJob/ManagementJobs.js:24
-#: screens/Project/Projects.js:32
-#: screens/Template/Templates.js:52
-msgid "Create New Schedule"
-msgstr ""
-
-#: screens/Team/Teams.js:16
-#: screens/Team/Teams.js:26
-msgid "Create New Team"
-msgstr ""
-
-#: screens/User/Users.js:16
-#: screens/User/Users.js:27
-msgid "Create New User"
-msgstr ""
-
-#: screens/Template/Templates.js:19
-msgid "Create New Workflow Template"
-msgstr ""
-
-#: screens/Host/HostList/SmartInventoryButton.js:26
-msgid "Create a new Smart Inventory with the applied filter"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceGroups.js:47
-#: screens/InstanceGroup/InstanceGroups.js:57
-msgid "Create new container group"
-msgstr ""
-
-#: screens/CredentialType/CredentialTypes.js:23
-msgid "Create new credential Type"
-msgstr ""
-
-#: screens/CredentialType/CredentialTypes.js:14
-msgid "Create new credential type"
-msgstr ""
-
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:14
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:23
-msgid "Create new execution environment"
-msgstr ""
-
-#: screens/Inventory/Inventories.js:75
-#: screens/Inventory/Inventories.js:82
-msgid "Create new group"
-msgstr ""
-
-#: screens/Inventory/Inventories.js:66
-#: screens/Inventory/Inventories.js:80
-msgid "Create new host"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceGroups.js:46
-#: screens/InstanceGroup/InstanceGroups.js:56
-msgid "Create new instance group"
-msgstr ""
-
-#: screens/Inventory/Inventories.js:18
-msgid "Create new inventory"
-msgstr ""
-
-#: screens/Inventory/Inventories.js:19
-msgid "Create new smart inventory"
-msgstr ""
-
-#: screens/Inventory/Inventories.js:85
-msgid "Create new source"
-msgstr ""
-
-#: screens/User/Users.js:35
-msgid "Create user token"
-msgstr ""
-
-#: components/Lookup/ApplicationLookup.js:114
-#: components/PromptDetail/PromptDetail.js:145
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:270
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:104
-#: screens/Credential/CredentialDetail/CredentialDetail.js:257
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:90
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:102
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:151
-#: screens/Host/HostDetail/HostDetail.js:84
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:67
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:93
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:134
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:43
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:82
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:293
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:151
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:47
-#: screens/Job/JobDetail/JobDetail.js:516
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:388
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:115
-#: screens/Project/ProjectDetail/ProjectDetail.js:274
-#: screens/Team/TeamDetail/TeamDetail.js:47
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:339
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:188
-#: screens/User/UserDetail/UserDetail.js:82
-#: screens/User/UserTokenDetail/UserTokenDetail.js:60
-#: screens/User/UserTokenList/UserTokenList.js:150
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:240
-msgid "Created"
-msgstr ""
-
-#: components/AdHocCommands/AdHocCredentialStep.js:122
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:112
-#: components/AddRole/AddResourceRole.js:57
-#: components/AssociateModal/AssociateModal.js:144
-#: components/LaunchPrompt/steps/CredentialsStep.js:173
-#: components/LaunchPrompt/steps/InventoryStep.js:89
-#: components/Lookup/CredentialLookup.js:193
-#: components/Lookup/InventoryLookup.js:161
-#: components/Lookup/InventoryLookup.js:216
-#: components/Lookup/MultiCredentialsLookup.js:193
-#: components/Lookup/OrganizationLookup.js:133
-#: components/Lookup/ProjectLookup.js:150
-#: components/NotificationList/NotificationList.js:206
-#: components/RelatedTemplateList/RelatedTemplateList.js:166
-#: components/Schedule/ScheduleList/ScheduleList.js:197
-#: components/TemplateList/TemplateList.js:226
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:27
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:58
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:104
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:127
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:173
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:196
-#: screens/Credential/CredentialList/CredentialList.js:150
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:96
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:132
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:102
-#: screens/Host/HostGroups/HostGroupsList.js:164
-#: screens/Host/HostList/HostList.js:157
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:199
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:129
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:174
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:128
-#: screens/Inventory/InventoryList/InventoryList.js:199
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:185
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:94
-#: screens/Organization/OrganizationList/OrganizationList.js:131
-#: screens/Project/ProjectList/ProjectList.js:213
-#: screens/Team/TeamList/TeamList.js:130
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:163
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:112
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:108
-msgid "Created By (Username)"
-msgstr ""
-
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:81
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:147
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:73
-msgid "Created by (username)"
-msgstr ""
-
-#: components/AdHocCommands/AdHocPreviewStep.js:54
-#: components/AdHocCommands/useAdHocCredentialStep.js:24
-#: components/PromptDetail/PromptInventorySourceDetail.js:119
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:40
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:52
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:50
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:274
-#: screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.js:36
-#: screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.js:36
-#: screens/Inventory/shared/InventorySourceSubForms/GCESubForm.js:36
-#: screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.js:36
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:83
-#: screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.js:35
-#: screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.js:37
-#: screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.js:37
-#: util/getRelatedResourceDeleteDetails.js:166
-msgid "Credential"
-msgstr ""
-
-#: util/getRelatedResourceDeleteDetails.js:73
-msgid "Credential Input Sources"
-msgstr ""
-
-#: components/Lookup/InstanceGroupsLookup.js:108
-msgid "Credential Name"
-msgstr ""
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:241
-#: screens/Credential/CredentialList/CredentialList.js:158
-#: screens/Credential/shared/CredentialForm.js:128
-#: screens/Credential/shared/CredentialForm.js:196
-msgid "Credential Type"
-msgstr ""
-
-#: routeConfig.js:117
-#: screens/ActivityStream/ActivityStream.js:192
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:118
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:161
-#: screens/CredentialType/CredentialTypes.js:13
-#: screens/CredentialType/CredentialTypes.js:22
-msgid "Credential Types"
-msgstr ""
-
-#: screens/Credential/CredentialList/CredentialList.js:113
-msgid "Credential copied successfully"
-msgstr ""
-
-#: screens/Credential/Credential.js:98
-msgid "Credential not found."
-msgstr ""
-
-#: components/AdHocCommands/useAdHocCredentialPasswordStep.js:23
-#: components/LaunchPrompt/steps/useCredentialPasswordsStep.js:28
-msgid "Credential passwords"
-msgstr ""
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:53
-msgid "Credential to authenticate with Kubernetes or OpenShift"
-msgstr ""
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:57
-msgid "Credential to authenticate with Kubernetes or OpenShift. Must be of type \"Kubernetes/OpenShift API Bearer Token\". If left blank, the underlying Pod's service account will be used."
-msgstr ""
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:21
-msgid "Credential to authenticate with a protected container registry."
-msgstr ""
-
-#: screens/CredentialType/CredentialType.js:76
-msgid "Credential type not found."
-msgstr ""
-
-#: components/JobList/JobListItem.js:260
-#: components/LaunchPrompt/steps/CredentialsStep.js:190
-#: components/LaunchPrompt/steps/useCredentialsStep.js:62
-#: components/Lookup/MultiCredentialsLookup.js:138
-#: components/Lookup/MultiCredentialsLookup.js:210
-#: components/PromptDetail/PromptDetail.js:183
-#: components/PromptDetail/PromptJobTemplateDetail.js:186
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:324
-#: components/TemplateList/TemplateListItem.js:322
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:77
-#: routeConfig.js:70
-#: screens/ActivityStream/ActivityStream.js:167
-#: screens/Credential/CredentialList/CredentialList.js:195
-#: screens/Credential/Credentials.js:14
-#: screens/Credential/Credentials.js:24
-#: screens/Job/JobDetail/JobDetail.js:414
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:360
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:51
-#: screens/Template/shared/JobTemplateForm.js:365
-#: util/getRelatedResourceDeleteDetails.js:90
-msgid "Credentials"
-msgstr ""
-
-#: components/LaunchPrompt/steps/credentialsValidator.js:52
-msgid "Credentials that require passwords on launch are not permitted. Please remove or replace the following credentials with a credential of the same type in order to proceed: {0}"
-msgstr ""
-
-#: components/Pagination/Pagination.js:34
-msgid "Current page"
-msgstr ""
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:85
-msgid "Custom Kubernetes or OpenShift Pod specification."
-msgstr ""
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:79
-msgid "Custom pod spec"
-msgstr ""
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:79
-#: screens/Organization/OrganizationList/OrganizationListItem.js:55
-#: screens/Project/ProjectList/ProjectListItem.js:188
-msgid "Custom virtual environment {0} must be replaced by an execution environment."
-msgstr ""
-
-#: components/TemplateList/TemplateListItem.js:163
-msgid "Custom virtual environment {0} must be replaced by an execution environment. For more information about migrating to execution environments see <0>the documentation.0>"
-msgstr ""
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:73
-msgid "Custom virtual environment {virtualEnvironment} must be replaced by an execution environment. For more information about migrating to execution environments see <0>the documentation.0>"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:61
-msgid "Customize messages…"
-msgstr ""
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:65
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:66
-msgid "Customize pod specification"
-msgstr ""
-
-#: screens/Job/WorkflowOutput/WorkflowOutputNode.js:109
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:185
-msgid "DELETED"
-msgstr ""
-
-#: routeConfig.js:34
-#: screens/Dashboard/Dashboard.js:74
-msgid "Dashboard"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStream.js:147
-msgid "Dashboard (all activity)"
-msgstr ""
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:75
-msgid "Data retention period"
-msgstr ""
-
-#: screens/Dashboard/shared/LineChart.js:168
-msgid "Date"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:349
-#: components/Schedule/shared/FrequencyDetailSubform.js:453
-#: components/Schedule/shared/ScheduleForm.js:156
-msgid "Day"
-msgstr ""
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:266
-#: components/Schedule/shared/ScheduleForm.js:167
-msgid "Days of Data to Keep"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/DaysToKeepStep.js:28
-msgid "Days of data to be retained"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:105
-msgid "Days remaining"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useDaysToKeepStep.js:35
-msgid "Days to keep"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:103
-msgid "Debug"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:168
-msgid "December"
-msgstr ""
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:102
-#: screens/Template/Survey/SurveyList.js:104
-#: screens/Template/Survey/SurveyList.js:104
-#: screens/Template/Survey/SurveyListItem.js:63
-msgid "Default"
-msgstr ""
-
-#: screens/Template/Survey/SurveyReorderModal.js:219
-#: screens/Template/Survey/SurveyReorderModal.js:219
-#: screens/Template/Survey/SurveyReorderModal.js:241
-msgid "Default Answer(s)"
-msgstr ""
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:40
-msgid "Default Execution Environment"
-msgstr ""
-
-#: screens/Template/Survey/SurveyQuestionForm.js:238
-#: screens/Template/Survey/SurveyQuestionForm.js:246
-#: screens/Template/Survey/SurveyQuestionForm.js:253
-msgid "Default answer"
-msgstr ""
-
-#: screens/Setting/SettingList.js:99
-msgid "Define system-level features and functions"
-msgstr ""
-
-#: components/DeleteButton/DeleteButton.js:75
-#: components/DeleteButton/DeleteButton.js:80
-#: components/DeleteButton/DeleteButton.js:90
-#: components/DeleteButton/DeleteButton.js:94
-#: components/DeleteButton/DeleteButton.js:114
-#: components/PaginatedTable/ToolbarDeleteButton.js:158
-#: components/PaginatedTable/ToolbarDeleteButton.js:235
-#: components/PaginatedTable/ToolbarDeleteButton.js:246
-#: components/PaginatedTable/ToolbarDeleteButton.js:250
-#: components/PaginatedTable/ToolbarDeleteButton.js:273
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:29
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:419
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:127
-#: screens/Credential/CredentialDetail/CredentialDetail.js:307
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:124
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:133
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:115
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:127
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:162
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:101
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:332
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:176
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:64
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:68
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:73
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:78
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:102
-#: screens/Job/JobDetail/JobDetail.js:594
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:431
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:199
-#: screens/Project/ProjectDetail/ProjectDetail.js:322
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:76
-#: screens/Team/TeamDetail/TeamDetail.js:70
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:528
-#: screens/Template/Survey/SurveyList.js:66
-#: screens/Template/Survey/SurveyToolbar.js:93
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:271
-#: screens/User/UserDetail/UserDetail.js:107
-#: screens/User/UserTokenDetail/UserTokenDetail.js:77
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:365
-msgid "Delete"
-msgstr ""
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:130
-msgid "Delete All Groups and Hosts"
-msgstr ""
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:301
-msgid "Delete Credential"
-msgstr ""
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:126
-msgid "Delete Execution Environment"
-msgstr ""
-
-#: screens/Host/HostDetail/HostDetail.js:112
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:110
-msgid "Delete Host"
-msgstr ""
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:157
-msgid "Delete Inventory"
-msgstr ""
-
-#: screens/Job/JobDetail/JobDetail.js:590
-#: screens/Job/JobOutput/shared/OutputToolbar.js:195
-#: screens/Job/JobOutput/shared/OutputToolbar.js:199
-msgid "Delete Job"
-msgstr ""
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:522
-msgid "Delete Job Template"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:427
-msgid "Delete Notification"
-msgstr ""
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:193
-msgid "Delete Organization"
-msgstr ""
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:316
-msgid "Delete Project"
-msgstr ""
-
-#: screens/Template/Survey/SurveyList.js:52
-msgid "Delete Questions"
-msgstr ""
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:415
-msgid "Delete Schedule"
-msgstr ""
-
-#: screens/Template/Survey/SurveyList.js:52
-msgid "Delete Survey"
-msgstr ""
-
-#: screens/Team/TeamDetail/TeamDetail.js:66
-msgid "Delete Team"
-msgstr ""
-
-#: screens/User/UserDetail/UserDetail.js:103
-msgid "Delete User"
-msgstr ""
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:73
-msgid "Delete User Token"
-msgstr ""
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:361
-msgid "Delete Workflow Approval"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:265
-msgid "Delete Workflow Job Template"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:138
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:141
-msgid "Delete all nodes"
-msgstr ""
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:123
-msgid "Delete application"
-msgstr ""
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:116
-msgid "Delete credential type"
-msgstr ""
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:249
-msgid "Delete error"
-msgstr ""
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:109
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:121
-msgid "Delete instance group"
-msgstr ""
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:326
-msgid "Delete inventory source"
-msgstr ""
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:172
-msgid "Delete smart inventory"
-msgstr ""
-
-#: screens/Template/Survey/SurveyToolbar.js:83
-msgid "Delete survey question"
-msgstr ""
-
-#: screens/Project/shared/Project.helptext.js:110
-msgid ""
-"Delete the local repository in its entirety prior to\n"
-"performing an update. Depending on the size of the\n"
-"repository this may significantly increase the amount\n"
-"of time required to complete an update."
-msgstr ""
-
-#: components/PromptDetail/PromptProjectDetail.js:51
-#: screens/Project/ProjectDetail/ProjectDetail.js:99
-msgid "Delete the project before syncing"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:83
-msgid "Delete this link"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:247
-msgid "Delete this node"
-msgstr ""
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:163
-msgid "Delete {pluralizedItemName}?"
-msgstr ""
-
-#: components/DetailList/DeletedDetail.js:19
-#: components/Workflow/WorkflowNodeHelp.js:157
-#: components/Workflow/WorkflowNodeHelp.js:193
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:272
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:40
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:51
-msgid "Deleted"
-msgstr ""
-
-#: components/TemplateList/TemplateList.js:296
-#: screens/Credential/CredentialList/CredentialList.js:211
-#: screens/Inventory/InventoryList/InventoryList.js:284
-#: screens/Project/ProjectList/ProjectList.js:290
-msgid "Deletion Error"
-msgstr ""
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:202
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:227
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:233
-msgid "Deletion error"
-msgstr ""
-
-#: components/StatusLabel/StatusLabel.js:32
-msgid "Denied"
-msgstr ""
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:21
-msgid "Denied - {0}. See the Activity Stream for more information."
-msgstr ""
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:17
-msgid "Denied by {0} - {1}"
-msgstr ""
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:72
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:80
-msgid "Deny"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:104
-msgid "Deprecated"
-msgstr ""
-
-#: components/HostForm/HostForm.js:104
-#: components/Lookup/ApplicationLookup.js:104
-#: components/Lookup/ApplicationLookup.js:122
-#: components/Lookup/HostFilterLookup.js:422
-#: components/Lookup/HostListItem.js:9
-#: components/NotificationList/NotificationList.js:186
-#: components/PromptDetail/PromptDetail.js:110
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:253
-#: components/Schedule/ScheduleList/ScheduleList.js:193
-#: components/Schedule/shared/ScheduleForm.js:115
-#: components/TemplateList/TemplateList.js:210
-#: components/TemplateList/TemplateListItem.js:271
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:64
-#: screens/Application/ApplicationsList/ApplicationsList.js:123
-#: screens/Application/shared/ApplicationForm.js:61
-#: screens/Credential/CredentialDetail/CredentialDetail.js:223
-#: screens/Credential/CredentialList/CredentialList.js:146
-#: screens/Credential/shared/CredentialForm.js:169
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:72
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:128
-#: screens/CredentialType/shared/CredentialTypeForm.js:29
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:59
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:159
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:126
-#: screens/Host/HostDetail/HostDetail.js:73
-#: screens/Host/HostList/HostList.js:153
-#: screens/Host/HostList/HostList.js:170
-#: screens/Host/HostList/HostListItem.js:57
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:71
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:35
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:216
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:81
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:40
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:124
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:139
-#: screens/Inventory/InventoryList/InventoryList.js:195
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:213
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:105
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:37
-#: screens/Inventory/shared/InventoryForm.js:50
-#: screens/Inventory/shared/InventoryGroupForm.js:40
-#: screens/Inventory/shared/InventorySourceForm.js:109
-#: screens/Inventory/shared/SmartInventoryForm.js:55
-#: screens/Job/JobOutput/HostEventModal.js:112
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:101
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:72
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:108
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:127
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:49
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:95
-#: screens/Organization/OrganizationList/OrganizationList.js:127
-#: screens/Organization/shared/OrganizationForm.js:64
-#: screens/Project/ProjectDetail/ProjectDetail.js:176
-#: screens/Project/ProjectList/ProjectList.js:190
-#: screens/Project/ProjectList/ProjectListItem.js:281
-#: screens/Project/shared/ProjectForm.js:178
-#: screens/Team/TeamDetail/TeamDetail.js:38
-#: screens/Team/TeamList/TeamList.js:122
-#: screens/Team/shared/TeamForm.js:37
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:180
-#: screens/Template/Survey/SurveyQuestionForm.js:171
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:112
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:183
-#: screens/Template/shared/JobTemplateForm.js:247
-#: screens/Template/shared/WorkflowJobTemplateForm.js:112
-#: screens/User/UserOrganizations/UserOrganizationList.js:80
-#: screens/User/UserOrganizations/UserOrganizationListItem.js:18
-#: screens/User/UserTeams/UserTeamList.js:182
-#: screens/User/UserTeams/UserTeamListItem.js:32
-#: screens/User/UserTokenDetail/UserTokenDetail.js:44
-#: screens/User/UserTokenList/UserTokenList.js:128
-#: screens/User/UserTokenList/UserTokenList.js:138
-#: screens/User/UserTokenList/UserTokenList.js:188
-#: screens/User/UserTokenList/UserTokenListItem.js:29
-#: screens/User/shared/UserTokenForm.js:59
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:186
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:205
-msgid "Description"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:320
-msgid "Destination Channels"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:228
-msgid "Destination Channels or Users"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:341
-msgid "Destination SMS Number(s)"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:399
-msgid "Destination SMS number(s)"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:353
-msgid "Destination channels"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:222
-msgid "Destination channels or users"
-msgstr ""
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:35
-#: components/ErrorDetail/ErrorDetail.js:80
-#: components/Schedule/Schedule.js:71
-#: screens/Application/Application/Application.js:79
-#: screens/Application/Applications.js:39
-#: screens/Credential/Credential.js:72
-#: screens/Credential/Credentials.js:28
-#: screens/CredentialType/CredentialType.js:63
-#: screens/CredentialType/CredentialTypes.js:26
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:65
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:26
-#: screens/Host/Host.js:58
-#: screens/Host/Hosts.js:27
-#: screens/InstanceGroup/ContainerGroup.js:66
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:178
-#: screens/InstanceGroup/InstanceGroup.js:69
-#: screens/InstanceGroup/InstanceGroups.js:59
-#: screens/InstanceGroup/InstanceGroups.js:67
-#: screens/Instances/Instance.js:25
-#: screens/Instances/Instances.js:22
-#: screens/Inventory/Inventories.js:61
-#: screens/Inventory/Inventories.js:87
-#: screens/Inventory/Inventory.js:64
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:57
-#: screens/Inventory/InventoryHost/InventoryHost.js:73
-#: screens/Inventory/InventorySource/InventorySource.js:83
-#: screens/Inventory/SmartInventory.js:66
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:60
-#: screens/Job/Job.js:117
-#: screens/Job/JobOutput/HostEventModal.js:103
-#: screens/Job/Jobs.js:35
-#: screens/ManagementJob/ManagementJobs.js:26
-#: screens/NotificationTemplate/NotificationTemplate.js:84
-#: screens/NotificationTemplate/NotificationTemplates.js:25
-#: screens/Organization/Organization.js:123
-#: screens/Organization/Organizations.js:30
-#: screens/Project/Project.js:104
-#: screens/Project/Projects.js:26
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:51
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:51
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:65
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:76
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:50
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:97
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:56
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:52
-#: screens/Setting/Settings.js:44
-#: screens/Setting/Settings.js:47
-#: screens/Setting/Settings.js:51
-#: screens/Setting/Settings.js:54
-#: screens/Setting/Settings.js:57
-#: screens/Setting/Settings.js:60
-#: screens/Setting/Settings.js:63
-#: screens/Setting/Settings.js:66
-#: screens/Setting/Settings.js:69
-#: screens/Setting/Settings.js:72
-#: screens/Setting/Settings.js:81
-#: screens/Setting/Settings.js:82
-#: screens/Setting/Settings.js:83
-#: screens/Setting/Settings.js:84
-#: screens/Setting/Settings.js:85
-#: screens/Setting/Settings.js:86
-#: screens/Setting/Settings.js:94
-#: screens/Setting/Settings.js:97
-#: screens/Setting/Settings.js:100
-#: screens/Setting/Settings.js:103
-#: screens/Setting/Settings.js:106
-#: screens/Setting/Settings.js:109
-#: screens/Setting/Settings.js:112
-#: screens/Setting/Settings.js:115
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:56
-#: screens/Setting/UI/UIDetail/UIDetail.js:66
-#: screens/Team/Team.js:57
-#: screens/Team/Teams.js:29
-#: screens/Template/Template.js:135
-#: screens/Template/Templates.js:43
-#: screens/Template/WorkflowJobTemplate.js:117
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:140
-#: screens/TopologyView/Tooltip.js:56
-#: screens/TopologyView/Tooltip.js:70
-#: screens/User/User.js:64
-#: screens/User/UserToken/UserToken.js:54
-#: screens/User/Users.js:30
-#: screens/User/Users.js:36
-#: screens/WorkflowApproval/WorkflowApproval.js:77
-#: screens/WorkflowApproval/WorkflowApprovals.js:24
-msgid "Details"
-msgstr ""
-
-#: screens/Job/JobOutput/HostEventModal.js:100
-msgid "Details tab"
-msgstr ""
-
-#: components/Search/AdvancedSearch.js:271
-msgid "Direct Keys"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:204
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:263
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:308
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:366
-msgid "Disable SSL Verification"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:180
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:231
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:270
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:341
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:446
-msgid "Disable SSL verification"
-msgstr ""
-
-#: components/InstanceToggle/InstanceToggle.js:56
-#: components/StatusLabel/StatusLabel.js:45
-#: screens/TopologyView/Legend.js:133
-msgid "Disabled"
-msgstr ""
-
-#: components/DisassociateButton/DisassociateButton.js:73
-#: components/DisassociateButton/DisassociateButton.js:97
-#: components/DisassociateButton/DisassociateButton.js:109
-#: components/DisassociateButton/DisassociateButton.js:113
-#: components/DisassociateButton/DisassociateButton.js:133
-#: screens/Team/TeamRoles/TeamRolesList.js:222
-#: screens/User/UserRoles/UserRolesList.js:218
-msgid "Disassociate"
-msgstr ""
-
-#: screens/Host/HostGroups/HostGroupsList.js:211
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:229
-msgid "Disassociate group from host?"
-msgstr ""
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:247
-msgid "Disassociate host from group?"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:289
-#: screens/InstanceGroup/Instances/InstanceList.js:234
-msgid "Disassociate instance from instance group?"
-msgstr ""
-
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:225
-msgid "Disassociate related group(s)?"
-msgstr ""
-
-#: screens/User/UserTeams/UserTeamList.js:216
-msgid "Disassociate related team(s)?"
-msgstr ""
-
-#: screens/Team/TeamRoles/TeamRolesList.js:209
-#: screens/User/UserRoles/UserRolesList.js:205
-msgid "Disassociate role"
-msgstr ""
-
-#: screens/Team/TeamRoles/TeamRolesList.js:212
-#: screens/User/UserRoles/UserRolesList.js:208
-msgid "Disassociate role!"
-msgstr ""
-
-#: components/DisassociateButton/DisassociateButton.js:18
-msgid "Disassociate?"
-msgstr ""
-
-#: components/PromptDetail/PromptProjectDetail.js:46
-#: screens/Project/ProjectDetail/ProjectDetail.js:93
-msgid "Discard local changes before syncing"
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:479
-#~ msgid ""
-#~ "Divide the work done by this job template\n"
-#~ "into the specified number of job slices, each running the\n"
-#~ "same tasks against a portion of the inventory."
-#~ msgstr ""
-
-#: screens/Job/Job.helptext.js:15
-#: screens/Template/shared/JobTemplate.helptext.js:16
-msgid "Divide the work done by this job template into the specified number of job slices, each running the same tasks against a portion of the inventory."
-msgstr ""
-
-#: screens/Project/shared/Project.helptext.js:100
-msgid "Documentation."
-msgstr ""
-
-#: components/CodeEditor/VariablesDetail.js:117
-#: components/CodeEditor/VariablesDetail.js:123
-#: components/CodeEditor/VariablesField.js:139
-#: components/CodeEditor/VariablesField.js:145
-msgid "Done"
-msgstr ""
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:179
-#: screens/Job/JobOutput/shared/OutputToolbar.js:184
-msgid "Download Output"
-msgstr ""
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:93
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:114
-msgid "Drag a file here or browse to upload"
-msgstr ""
-
-#: components/SelectedList/DraggableSelectedList.js:68
-msgid "Draggable list to reorder and remove selected items."
-msgstr ""
-
-#: components/SelectedList/DraggableSelectedList.js:43
-msgid "Dragging cancelled. List is unchanged."
-msgstr ""
-
-#: components/SelectedList/DraggableSelectedList.js:38
-msgid "Dragging item {id}. Item with index {oldIndex} in now {newIndex}."
-msgstr ""
-
-#: components/SelectedList/DraggableSelectedList.js:32
-msgid "Dragging started for item id: {newId}."
-msgstr ""
-
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:81
-msgid "E-mail"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:121
-msgid "E-mail options"
-msgstr ""
-
-#: screens/Inventory/shared/Inventory.helptext.js:113
-msgid ""
-"Each time a job runs using this inventory,\n"
-"refresh the inventory from the selected source before\n"
-"executing job tasks."
-msgstr ""
-
-#: screens/Project/shared/Project.helptext.js:120
-msgid ""
-"Each time a job runs using this project, update the\n"
-"revision of the project prior to starting the job."
-msgstr ""
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:405
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:409
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:114
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:116
-#: screens/Credential/CredentialDetail/CredentialDetail.js:294
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:109
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:120
-#: screens/Host/HostDetail/HostDetail.js:106
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:101
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:113
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:151
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:55
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:62
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:104
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:308
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:127
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:166
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:413
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:415
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:138
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:182
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:186
-#: screens/Project/ProjectDetail/ProjectDetail.js:295
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:85
-#: screens/Setting/AzureAD/AzureADDetail/AzureADDetail.js:89
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:148
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:152
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:85
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2Detail/GoogleOAuth2Detail.js:89
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:96
-#: screens/Setting/Jobs/JobsDetail/JobsDetail.js:100
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:164
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:168
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:107
-#: screens/Setting/Logging/LoggingDetail/LoggingDetail.js:111
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:84
-#: screens/Setting/MiscAuthentication/MiscAuthenticationDetail/MiscAuthenticationDetail.js:88
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:152
-#: screens/Setting/MiscSystem/MiscSystemDetail/MiscSystemDetail.js:156
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:99
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:103
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:86
-#: screens/Setting/SAML/SAMLDetail/SAMLDetail.js:90
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:169
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:103
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:108
-#: screens/Setting/UI/UIDetail/UIDetail.js:105
-#: screens/Setting/UI/UIDetail/UIDetail.js:110
-#: screens/Team/TeamDetail/TeamDetail.js:55
-#: screens/Team/TeamDetail/TeamDetail.js:59
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:497
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:499
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:241
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:243
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:241
-#: screens/User/UserDetail/UserDetail.js:96
-msgid "Edit"
-msgstr ""
-
-#: screens/Credential/CredentialList/CredentialListItem.js:67
-#: screens/Credential/CredentialList/CredentialListItem.js:71
-msgid "Edit Credential"
-msgstr ""
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:37
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:42
-msgid "Edit Credential Plugin Configuration"
-msgstr ""
-
-#: screens/Application/Applications.js:38
-#: screens/Credential/Credentials.js:27
-#: screens/Host/Hosts.js:26
-#: screens/ManagementJob/ManagementJobs.js:27
-#: screens/NotificationTemplate/NotificationTemplates.js:24
-#: screens/Organization/Organizations.js:29
-#: screens/Project/Projects.js:25
-#: screens/Project/Projects.js:35
-#: screens/Setting/Settings.js:45
-#: screens/Setting/Settings.js:48
-#: screens/Setting/Settings.js:52
-#: screens/Setting/Settings.js:55
-#: screens/Setting/Settings.js:58
-#: screens/Setting/Settings.js:61
-#: screens/Setting/Settings.js:64
-#: screens/Setting/Settings.js:67
-#: screens/Setting/Settings.js:70
-#: screens/Setting/Settings.js:73
-#: screens/Setting/Settings.js:87
-#: screens/Setting/Settings.js:88
-#: screens/Setting/Settings.js:89
-#: screens/Setting/Settings.js:90
-#: screens/Setting/Settings.js:91
-#: screens/Setting/Settings.js:92
-#: screens/Setting/Settings.js:95
-#: screens/Setting/Settings.js:98
-#: screens/Setting/Settings.js:101
-#: screens/Setting/Settings.js:104
-#: screens/Setting/Settings.js:107
-#: screens/Setting/Settings.js:110
-#: screens/Setting/Settings.js:113
-#: screens/Setting/Settings.js:116
-#: screens/Team/Teams.js:28
-#: screens/Template/Templates.js:44
-#: screens/User/Users.js:29
-msgid "Edit Details"
-msgstr ""
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:90
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:94
-msgid "Edit Execution Environment"
-msgstr ""
-
-#: screens/Host/HostGroups/HostGroupItem.js:37
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:46
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:46
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:51
-msgid "Edit Group"
-msgstr ""
-
-#: screens/Host/HostList/HostListItem.js:74
-#: screens/Host/HostList/HostListItem.js:78
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:61
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:64
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:67
-msgid "Edit Host"
-msgstr ""
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:134
-#: screens/Inventory/InventoryList/InventoryListItem.js:139
-msgid "Edit Inventory"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkEditModal.js:14
-msgid "Edit Link"
-msgstr ""
-
-#: screens/Setting/shared/SharedFields.js:290
-msgid "Edit Login redirect override URL"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeEditModal.js:64
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:238
-msgid "Edit Node"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:142
-msgid "Edit Notification Template"
-msgstr ""
-
-#: screens/Template/Survey/SurveyToolbar.js:73
-msgid "Edit Order"
-msgstr ""
-
-#: screens/Organization/OrganizationList/OrganizationListItem.js:72
-#: screens/Organization/OrganizationList/OrganizationListItem.js:76
-msgid "Edit Organization"
-msgstr ""
-
-#: screens/Project/ProjectList/ProjectListItem.js:248
-#: screens/Project/ProjectList/ProjectListItem.js:253
-msgid "Edit Project"
-msgstr ""
-
-#: screens/Template/Templates.js:50
-msgid "Edit Question"
-msgstr ""
-
-#: components/Schedule/ScheduleList/ScheduleListItem.js:118
-#: components/Schedule/ScheduleList/ScheduleListItem.js:122
-#: screens/Template/Templates.js:55
-msgid "Edit Schedule"
-msgstr ""
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:131
-msgid "Edit Source"
-msgstr ""
-
-#: screens/Template/Survey/SurveyListItem.js:92
-msgid "Edit Survey"
-msgstr ""
-
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:22
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:26
-#: screens/Team/TeamList/TeamListItem.js:50
-#: screens/Team/TeamList/TeamListItem.js:54
-msgid "Edit Team"
-msgstr ""
-
-#: components/TemplateList/TemplateListItem.js:233
-#: components/TemplateList/TemplateListItem.js:239
-msgid "Edit Template"
-msgstr ""
-
-#: screens/User/UserList/UserListItem.js:59
-#: screens/User/UserList/UserListItem.js:63
-msgid "Edit User"
-msgstr ""
-
-#: screens/Application/ApplicationsList/ApplicationListItem.js:51
-#: screens/Application/ApplicationsList/ApplicationListItem.js:55
-msgid "Edit application"
-msgstr ""
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:41
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:45
-msgid "Edit credential type"
-msgstr ""
-
-#: screens/CredentialType/CredentialTypes.js:25
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:25
-#: screens/InstanceGroup/InstanceGroups.js:64
-#: screens/InstanceGroup/InstanceGroups.js:69
-#: screens/Inventory/Inventories.js:63
-#: screens/Inventory/Inventories.js:68
-#: screens/Inventory/Inventories.js:77
-#: screens/Inventory/Inventories.js:88
-msgid "Edit details"
-msgstr ""
-
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:42
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:44
-msgid "Edit group"
-msgstr ""
-
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:48
-msgid "Edit host"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:78
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:82
-msgid "Edit instance group"
-msgstr ""
-
-#: screens/Setting/shared/SharedFields.js:320
-#: screens/Setting/shared/SharedFields.js:322
-msgid "Edit login redirect override URL"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerLink.js:70
-msgid "Edit this link"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:221
-msgid "Edit this node"
-msgstr ""
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:97
-msgid "Edit workflow"
-msgstr ""
-
-#: components/Workflow/WorkflowNodeHelp.js:170
-#: screens/Job/JobOutput/shared/OutputToolbar.js:125
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:257
-msgid "Elapsed"
-msgstr ""
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:124
-msgid "Elapsed Time"
-msgstr ""
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:126
-msgid "Elapsed time that the job ran"
-msgstr ""
-
-#: components/NotificationList/NotificationList.js:193
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:134
-#: screens/User/UserDetail/UserDetail.js:66
-#: screens/User/UserList/UserList.js:115
-#: screens/User/shared/UserForm.js:73
-msgid "Email"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:175
-msgid "Email Options"
-msgstr ""
-
-#: screens/Template/shared/WorkflowJobTemplateForm.js:232
-msgid "Enable Concurrent Jobs"
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:545
-msgid "Enable Fact Storage"
-msgstr ""
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:192
-msgid "Enable HTTPS certificate verification"
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:521
-#: screens/Template/shared/JobTemplateForm.js:524
-#: screens/Template/shared/WorkflowJobTemplateForm.js:213
-#: screens/Template/shared/WorkflowJobTemplateForm.js:216
-msgid "Enable Webhook"
-msgstr ""
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:15
-msgid "Enable Webhook for this workflow job template."
-msgstr ""
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:136
-msgid "Enable external logging"
-msgstr ""
-
-#: screens/Setting/Logging/LoggingEdit/LoggingEdit.js:168
-msgid "Enable log system tracking facts individually"
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:201
-#: components/AdHocCommands/AdHocDetailsStep.js:204
-msgid "Enable privilege escalation"
-msgstr ""
-
-#: screens/Setting/SettingList.js:52
-#~ msgid "Enable simplified login for your {0} applications"
-#~ msgstr ""
-
-#: screens/Setting/SettingList.js:53
-msgid "Enable simplified login for your {brandName} applications"
-msgstr ""
-
-#: screens/Template/shared/JobTemplate.helptext.js:30
-msgid "Enable webhook for this template."
-msgstr ""
-
-#: components/InstanceToggle/InstanceToggle.js:55
-#: components/Lookup/HostFilterLookup.js:110
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:46
-msgid "Enabled"
-msgstr ""
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:183
-#: components/PromptDetail/PromptJobTemplateDetail.js:182
-#: components/PromptDetail/PromptProjectDetail.js:130
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:97
-#: screens/Credential/CredentialDetail/CredentialDetail.js:269
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:281
-#: screens/Project/ProjectDetail/ProjectDetail.js:284
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:351
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:200
-msgid "Enabled Options"
-msgstr ""
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:267
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:135
-msgid "Enabled Value"
-msgstr ""
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:262
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:125
-msgid "Enabled Variable"
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:568
-#~ msgid ""
-#~ "Enables creation of a provisioning\n"
-#~ "callback URL. Using the URL a host can contact {0}\n"
-#~ "and request a configuration update using this job\n"
-#~ "template."
-#~ msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:209
-msgid ""
-"Enables creation of a provisioning\n"
-"callback URL. Using the URL a host can contact {brandName}\n"
-"and request a configuration update using this job\n"
-"template"
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:568
-#~ msgid ""
-#~ "Enables creation of a provisioning\n"
-#~ "callback URL. Using the URL a host can contact {brandName}\n"
-#~ "and request a configuration update using this job\n"
-#~ "template."
-#~ msgstr ""
-
-#: screens/Template/shared/JobTemplate.helptext.js:28
-msgid "Enables creation of a provisioning callback URL. Using the URL a host can contact {brandName} and request a configuration update using this job template."
-msgstr ""
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:160
-#: screens/Setting/shared/SettingDetail.js:87
-msgid "Encrypted"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:500
-msgid "End"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/EulaStep.js:14
-msgid "End User License Agreement"
-msgstr ""
-
-#: components/Schedule/shared/DateTimePicker.js:51
-msgid "End date"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:555
-msgid "End date/time"
-msgstr ""
-
-#: components/Schedule/shared/buildRuleObj.js:97
-msgid "End did not match an expected value"
-msgstr ""
-
-#: components/Schedule/shared/DateTimePicker.js:61
-msgid "End time"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:209
-msgid "End user license agreement"
-msgstr ""
-
-#: screens/Host/HostList/SmartInventoryButton.js:23
-msgid "Enter at least one search filter to create a new Smart Inventory"
-msgstr ""
-
-#: screens/CredentialType/shared/CredentialTypeForm.js:43
-msgid "Enter injectors using either JSON or YAML syntax. Refer to the Ansible Controller documentation for example syntax."
-msgstr ""
-
-#: screens/CredentialType/shared/CredentialTypeForm.js:35
-msgid "Enter inputs using either JSON or YAML syntax. Refer to the Ansible Controller documentation for example syntax."
-msgstr ""
-
-#: screens/Inventory/shared/SmartInventoryForm.js:94
-msgid ""
-"Enter inventory variables using either JSON or YAML syntax.\n"
-"Use the radio button to toggle between the two. Refer to the\n"
-"Ansible Controller documentation for example syntax."
-msgstr ""
-
-#: screens/Inventory/shared/InventoryForm.js:65
-msgid "Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Controller documentation for example syntax"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:181
-#~ msgid "Enter one Annotation Tag per line, without commas."
-#~ msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:232
-#~ msgid ""
-#~ "Enter one IRC channel or username per line. The pound\n"
-#~ "symbol (#) for channels, and the at (@) symbol for users, are not\n"
-#~ "required."
-#~ msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:365
-#~ msgid ""
-#~ "Enter one Slack channel per line. The pound symbol (#)\n"
-#~ "is required for channels."
-#~ msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:367
-#~ msgid ""
-#~ "Enter one Slack channel per line. The pound symbol (#)\n"
-#~ "is required for channels. To respond to or start a thread to a specific message add the parent message Id to the channel where the parent message Id is 16 digits. A dot (.) must be manually inserted after the 10th digit. ie:#destination-channel, 1231257890.006423. See Slack"
-#~ msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:90
-#~ msgid ""
-#~ "Enter one email address per line to create a recipient\n"
-#~ "list for this type of notification."
-#~ msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:413
-#~ msgid ""
-#~ "Enter one phone number per line to specify where to\n"
-#~ "route SMS messages."
-#~ msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:416
-#~ msgid ""
-#~ "Enter one phone number per line to specify where to\n"
-#~ "route SMS messages. Phone numbers should be formatted +11231231234. For more information see"
-#~ msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:420
-#~ msgid ""
-#~ "Enter one phone number per line to specify where to\n"
-#~ "route SMS messages. Phone numbers should be formatted +11231231234. For more information see Twilio documentation"
-#~ msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:410
-#~ msgid ""
-#~ "Enter the number associated with the \"Messaging\n"
-#~ "Service\" in Twilio in the format +18005550199."
-#~ msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.js:60
-#~ msgid "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>Insights1> plugin configuration guide."
-#~ msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.js:60
-#~ msgid "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>Tower1> plugin configuration guide."
-#~ msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.js:53
-#~ msgid "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>aws_ec21> plugin configuration guide."
-#~ msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.js:59
-#~ msgid "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>azure_rm1> plugin configuration guide."
-#~ msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.js:59
-#~ msgid "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>foreman1> plugin configuration guide."
-#~ msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSubForms/GCESubForm.js:59
-#~ msgid "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>gcp_compute1> plugin configuration guide."
-#~ msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.js:59
-#~ msgid "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>openstack1> plugin configuration guide."
-#~ msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.js:59
-#~ msgid "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>ovirt1> plugin configuration guide."
-#~ msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.js:59
-#~ msgid "Enter variables to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>vmware_vm_inventory1> plugin configuration guide."
-#~ msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:35
-#~ msgid "Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two."
-#~ msgstr ""
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:87
-msgid "Environment variables or extra variables that specify the values a credential type can inject."
-msgstr ""
-
-#: components/JobList/JobList.js:233
-#: components/StatusLabel/StatusLabel.js:38
-#: components/Workflow/WorkflowNodeHelp.js:108
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:133
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:205
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:142
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:230
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:123
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:135
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:236
-#: screens/Job/JobOutput/JobOutputSearch.js:105
-#: screens/TopologyView/Legend.js:124
-msgid "Error"
-msgstr ""
-
-#: screens/Project/ProjectList/ProjectList.js:302
-msgid "Error fetching updated project"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:496
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:141
-msgid "Error message"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:505
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:150
-msgid "Error message body"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:617
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:619
-msgid "Error saving the workflow!"
-msgstr ""
-
-#: components/AdHocCommands/AdHocCommands.js:104
-#: components/CopyButton/CopyButton.js:51
-#: components/DeleteButton/DeleteButton.js:56
-#: components/HostToggle/HostToggle.js:76
-#: components/InstanceToggle/InstanceToggle.js:67
-#: components/JobList/JobList.js:315
-#: components/JobList/JobList.js:326
-#: components/LaunchButton/LaunchButton.js:162
-#: components/LaunchPrompt/LaunchPrompt.js:66
-#: components/NotificationList/NotificationList.js:246
-#: components/PaginatedTable/ToolbarDeleteButton.js:205
-#: components/RelatedTemplateList/RelatedTemplateList.js:241
-#: components/ResourceAccessList/ResourceAccessList.js:277
-#: components/ResourceAccessList/ResourceAccessList.js:289
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:427
-#: components/Schedule/ScheduleList/ScheduleList.js:238
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:73
-#: components/Schedule/shared/SchedulePromptableFields.js:70
-#: components/TemplateList/TemplateList.js:299
-#: contexts/Config.js:94
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:135
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:155
-#: screens/Application/ApplicationsList/ApplicationsList.js:185
-#: screens/Credential/CredentialDetail/CredentialDetail.js:315
-#: screens/Credential/CredentialList/CredentialList.js:214
-#: screens/Host/HostDetail/HostDetail.js:56
-#: screens/Host/HostDetail/HostDetail.js:121
-#: screens/Host/HostGroups/HostGroupsList.js:244
-#: screens/Host/HostList/HostList.js:233
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:303
-#: screens/InstanceGroup/Instances/InstanceList.js:297
-#: screens/InstanceGroup/Instances/InstanceListItem.js:218
-#: screens/Instances/InstanceDetail/InstanceDetail.js:249
-#: screens/Instances/InstanceList/InstanceList.js:178
-#: screens/Instances/InstanceList/InstanceListItem.js:234
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:171
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:78
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:285
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:296
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:56
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:119
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:261
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:201
-#: screens/Inventory/InventoryList/InventoryList.js:285
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:264
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:339
-#: screens/Inventory/InventorySources/InventorySourceList.js:239
-#: screens/Inventory/InventorySources/InventorySourceList.js:252
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:185
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:152
-#: screens/Inventory/shared/InventorySourceSyncButton.js:49
-#: screens/Login/Login.js:217
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:125
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:439
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:233
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:169
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:208
-#: screens/Organization/OrganizationList/OrganizationList.js:195
-#: screens/Project/ProjectDetail/ProjectDetail.js:330
-#: screens/Project/ProjectList/ProjectList.js:291
-#: screens/Project/ProjectList/ProjectList.js:303
-#: screens/Project/shared/ProjectSyncButton.js:59
-#: screens/Team/TeamDetail/TeamDetail.js:78
-#: screens/Team/TeamList/TeamList.js:192
-#: screens/Team/TeamRoles/TeamRolesList.js:247
-#: screens/Team/TeamRoles/TeamRolesList.js:258
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:537
-#: screens/Template/TemplateSurvey.js:130
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:279
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:178
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:193
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:318
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:346
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:357
-#: screens/User/UserDetail/UserDetail.js:115
-#: screens/User/UserList/UserList.js:189
-#: screens/User/UserRoles/UserRolesList.js:243
-#: screens/User/UserRoles/UserRolesList.js:254
-#: screens/User/UserTeams/UserTeamList.js:259
-#: screens/User/UserTokenDetail/UserTokenDetail.js:84
-#: screens/User/UserTokenList/UserTokenList.js:214
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:373
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:384
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:395
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:406
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:277
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:288
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:299
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:310
-msgid "Error!"
-msgstr ""
-
-#: components/CodeEditor/VariablesDetail.js:105
-msgid "Error:"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:260
-#: screens/Instances/InstanceDetail/InstanceDetail.js:214
-msgid "Errors"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStream.js:265
-#: screens/ActivityStream/ActivityStreamListItem.js:46
-#: screens/Job/JobOutput/JobOutputSearch.js:100
-msgid "Event"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:35
-msgid "Event detail"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:36
-msgid "Event detail modal"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStreamDescription.js:555
-msgid "Event summary not available"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStream.js:234
-msgid "Events"
-msgstr ""
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:144
-msgid "Every minute for {0} times"
-msgstr ""
-
-#: screens/TopologyView/Legend.js:82
-msgid "Ex"
-msgstr ""
-
-#: components/Search/LookupTypeInput.js:39
-msgid "Exact match (default lookup if not specified)."
-msgstr ""
-
-#: components/Search/RelatedLookupTypeInput.js:38
-msgid "Exact search on id field."
-msgstr ""
-
-#: screens/Project/shared/Project.helptext.js:23
-msgid "Example URLs for GIT Source Control include:"
-msgstr ""
-
-#: screens/Project/shared/Project.helptext.js:62
-msgid "Example URLs for Remote Archive Source Control include:"
-msgstr ""
-
-#: screens/Project/shared/Project.helptext.js:45
-msgid "Example URLs for Subversion Source Control include:"
-msgstr ""
-
-#: screens/Project/shared/Project.helptext.js:84
-msgid "Examples include:"
-msgstr ""
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:10
-msgid "Examples:"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:47
-msgid "Execute regardless of the parent node's final state."
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:40
-msgid "Execute when the parent node results in a failure state."
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:33
-msgid "Execute when the parent node results in a successful state."
-msgstr ""
-
-#: screens/InstanceGroup/Instances/InstanceList.js:197
-#: screens/Instances/InstanceList/InstanceList.js:117
-msgid "Execution"
-msgstr ""
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:90
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:91
-#: components/AdHocCommands/AdHocPreviewStep.js:58
-#: components/AdHocCommands/useAdHocExecutionEnvironmentStep.js:15
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:41
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:105
-#: components/Lookup/ExecutionEnvironmentLookup.js:155
-#: components/Lookup/ExecutionEnvironmentLookup.js:186
-#: components/Lookup/ExecutionEnvironmentLookup.js:201
-msgid "Execution Environment"
-msgstr ""
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:70
-#: components/TemplateList/TemplateListItem.js:160
-msgid "Execution Environment Missing"
-msgstr ""
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:103
-#: routeConfig.js:147
-#: screens/ActivityStream/ActivityStream.js:217
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:129
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:191
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:13
-#: screens/ExecutionEnvironment/ExecutionEnvironments.js:22
-#: screens/Organization/Organization.js:127
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:78
-#: screens/Organization/Organizations.js:34
-#: util/getRelatedResourceDeleteDetails.js:80
-#: util/getRelatedResourceDeleteDetails.js:187
-msgid "Execution Environments"
-msgstr ""
-
-#: screens/Job/JobDetail/JobDetail.js:340
-msgid "Execution Node"
-msgstr ""
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:103
-msgid "Execution environment copied successfully"
-msgstr ""
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:112
-msgid "Execution environment is missing or deleted."
-msgstr ""
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:83
-msgid "Execution environment not found."
-msgstr ""
-
-#: screens/TopologyView/Legend.js:86
-msgid "Execution node"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:23
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:26
-msgid "Exit Without Saving"
-msgstr ""
-
-#: components/ExpandCollapse/ExpandCollapse.js:52
-msgid "Expand"
-msgstr ""
-
-#: components/DataListToolbar/DataListToolbar.js:105
-msgid "Expand all rows"
-msgstr ""
-
-#: components/CodeEditor/VariablesDetail.js:212
-#: components/CodeEditor/VariablesField.js:248
-msgid "Expand input"
-msgstr ""
-
-#: screens/Job/JobOutput/PageControls.js:50
-msgid "Expand job events"
-msgstr ""
-
-#: screens/Job/JobOutput/shared/JobEventLineToggle.js:37
-msgid "Expand section"
-msgstr ""
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:46
-msgid "Expected at least one of client_email, project_id or private_key to be present in the file."
-msgstr ""
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:137
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:34
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:148
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:172
-#: screens/User/UserTokenDetail/UserTokenDetail.js:55
-#: screens/User/UserTokenList/UserTokenList.js:146
-#: screens/User/UserTokenList/UserTokenList.js:190
-#: screens/User/UserTokenList/UserTokenListItem.js:35
-#: screens/User/UserTokens/UserTokens.js:89
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:192
-msgid "Expires"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:84
-msgid "Expires on"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:94
-msgid "Expires on UTC"
-msgstr ""
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:50
-msgid "Expires on {0}"
-msgstr ""
-
-#: components/JobList/JobListItem.js:306
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:222
-msgid "Explanation"
-msgstr ""
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:113
-msgid "External Secret Management System"
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:272
-#: components/AdHocCommands/AdHocDetailsStep.js:273
-msgid "Extra variables"
-msgstr ""
-
-#: components/Sparkline/Sparkline.js:35
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:179
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:43
-#: screens/Project/ProjectDetail/ProjectDetail.js:139
-#: screens/Project/ProjectList/ProjectListItem.js:77
-msgid "FINISHED:"
-msgstr ""
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:73
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:135
-msgid "Fact Storage"
-msgstr ""
-
-#: screens/Template/shared/JobTemplate.helptext.js:36
-msgid "Fact storage: If enabled, this will store gathered facts so they can be viewed at the host level. Facts are persisted and injected into the fact cache at runtime.."
-msgstr ""
-
-#: screens/Host/Host.js:63
-#: screens/Host/HostFacts/HostFacts.js:45
-#: screens/Host/Hosts.js:28
-#: screens/Inventory/Inventories.js:71
-#: screens/Inventory/InventoryHost/InventoryHost.js:78
-#: screens/Inventory/InventoryHostFacts/InventoryHostFacts.js:39
-msgid "Facts"
-msgstr ""
-
-#: components/JobList/JobList.js:232
-#: components/StatusLabel/StatusLabel.js:37
-#: components/Workflow/WorkflowNodeHelp.js:105
-#: screens/Dashboard/shared/ChartTooltip.js:66
-#: screens/Job/JobOutput/shared/HostStatusBar.js:47
-#: screens/Job/JobOutput/shared/OutputToolbar.js:113
-msgid "Failed"
-msgstr ""
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:112
-msgid "Failed Host Count"
-msgstr ""
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:114
-msgid "Failed Hosts"
-msgstr ""
-
-#: components/LaunchButton/ReLaunchDropDown.js:61
-#: screens/Dashboard/Dashboard.js:87
-msgid "Failed hosts"
-msgstr ""
-
-#: screens/Dashboard/DashboardGraph.js:170
-msgid "Failed jobs"
-msgstr ""
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:291
-msgid "Failed to approve one or more workflow approval."
-msgstr ""
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:387
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:398
-msgid "Failed to approve workflow approval."
-msgstr ""
-
-#: components/ResourceAccessList/ResourceAccessList.js:281
-msgid "Failed to assign roles properly"
-msgstr ""
-
-#: screens/Team/TeamRoles/TeamRolesList.js:250
-#: screens/User/UserRoles/UserRolesList.js:246
-msgid "Failed to associate role"
-msgstr ""
-
-#: screens/Host/HostGroups/HostGroupsList.js:248
-#: screens/InstanceGroup/Instances/InstanceList.js:300
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:288
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:265
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:268
-#: screens/User/UserTeams/UserTeamList.js:263
-msgid "Failed to associate."
-msgstr ""
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:317
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:111
-msgid "Failed to cancel Inventory Source Sync"
-msgstr ""
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:304
-#: screens/Project/ProjectList/ProjectListItem.js:232
-msgid "Failed to cancel Project Sync"
-msgstr ""
-
-#: components/JobList/JobList.js:329
-msgid "Failed to cancel one or more jobs."
-msgstr ""
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:302
-msgid "Failed to cancel one or more workflow approval."
-msgstr ""
-
-#: components/JobList/JobListItem.js:114
-#: screens/Job/JobDetail/JobDetail.js:583
-#: screens/Job/JobOutput/shared/OutputToolbar.js:138
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:90
-msgid "Failed to cancel {0}"
-msgstr ""
-
-#: screens/Credential/CredentialList/CredentialListItem.js:88
-msgid "Failed to copy credential."
-msgstr ""
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:112
-msgid "Failed to copy execution environment"
-msgstr ""
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:162
-msgid "Failed to copy inventory."
-msgstr ""
-
-#: screens/Project/ProjectList/ProjectListItem.js:270
-msgid "Failed to copy project."
-msgstr ""
-
-#: components/TemplateList/TemplateListItem.js:253
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:160
-msgid "Failed to copy template."
-msgstr ""
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:138
-msgid "Failed to delete application."
-msgstr ""
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:318
-msgid "Failed to delete credential."
-msgstr ""
-
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:82
-msgid "Failed to delete group {0}."
-msgstr ""
-
-#: screens/Host/HostDetail/HostDetail.js:124
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:122
-msgid "Failed to delete host."
-msgstr ""
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:343
-msgid "Failed to delete inventory source {name}."
-msgstr ""
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:174
-msgid "Failed to delete inventory."
-msgstr ""
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:540
-msgid "Failed to delete job template."
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:443
-msgid "Failed to delete notification."
-msgstr ""
-
-#: screens/Application/ApplicationsList/ApplicationsList.js:188
-msgid "Failed to delete one or more applications."
-msgstr ""
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:208
-msgid "Failed to delete one or more credential types."
-msgstr ""
-
-#: screens/Credential/CredentialList/CredentialList.js:217
-msgid "Failed to delete one or more credentials."
-msgstr ""
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:233
-msgid "Failed to delete one or more execution environments"
-msgstr ""
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:155
-msgid "Failed to delete one or more groups."
-msgstr ""
-
-#: screens/Host/HostList/HostList.js:236
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:204
-msgid "Failed to delete one or more hosts."
-msgstr ""
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:239
-msgid "Failed to delete one or more instance groups."
-msgstr ""
-
-#: screens/Inventory/InventoryList/InventoryList.js:288
-msgid "Failed to delete one or more inventories."
-msgstr ""
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:255
-msgid "Failed to delete one or more inventory sources."
-msgstr ""
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:244
-msgid "Failed to delete one or more job templates."
-msgstr ""
-
-#: components/JobList/JobList.js:318
-msgid "Failed to delete one or more jobs."
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:236
-msgid "Failed to delete one or more notification template."
-msgstr ""
-
-#: screens/Organization/OrganizationList/OrganizationList.js:198
-msgid "Failed to delete one or more organizations."
-msgstr ""
-
-#: screens/Project/ProjectList/ProjectList.js:294
-msgid "Failed to delete one or more projects."
-msgstr ""
-
-#: components/Schedule/ScheduleList/ScheduleList.js:241
-msgid "Failed to delete one or more schedules."
-msgstr ""
-
-#: screens/Team/TeamList/TeamList.js:195
-msgid "Failed to delete one or more teams."
-msgstr ""
-
-#: components/TemplateList/TemplateList.js:302
-msgid "Failed to delete one or more templates."
-msgstr ""
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:158
-msgid "Failed to delete one or more tokens."
-msgstr ""
-
-#: screens/User/UserTokenList/UserTokenList.js:217
-msgid "Failed to delete one or more user tokens."
-msgstr ""
-
-#: screens/User/UserList/UserList.js:192
-msgid "Failed to delete one or more users."
-msgstr ""
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:280
-msgid "Failed to delete one or more workflow approval."
-msgstr ""
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:211
-msgid "Failed to delete organization."
-msgstr ""
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:333
-msgid "Failed to delete project."
-msgstr ""
-
-#: components/ResourceAccessList/ResourceAccessList.js:292
-msgid "Failed to delete role"
-msgstr ""
-
-#: screens/Team/TeamRoles/TeamRolesList.js:261
-#: screens/User/UserRoles/UserRolesList.js:257
-msgid "Failed to delete role."
-msgstr ""
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:430
-msgid "Failed to delete schedule."
-msgstr ""
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:188
-msgid "Failed to delete smart inventory."
-msgstr ""
-
-#: screens/Team/TeamDetail/TeamDetail.js:81
-msgid "Failed to delete team."
-msgstr ""
-
-#: screens/User/UserDetail/UserDetail.js:118
-msgid "Failed to delete user."
-msgstr ""
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:376
-msgid "Failed to delete workflow approval."
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:282
-msgid "Failed to delete workflow job template."
-msgstr ""
-
-#: screens/Host/HostDetail/HostDetail.js:59
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:59
-msgid "Failed to delete {name}."
-msgstr ""
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:313
-msgid "Failed to deny one or more workflow approval."
-msgstr ""
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:409
-msgid "Failed to deny workflow approval."
-msgstr ""
-
-#: screens/Host/HostGroups/HostGroupsList.js:249
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:266
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:269
-msgid "Failed to disassociate one or more groups."
-msgstr ""
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:299
-msgid "Failed to disassociate one or more hosts."
-msgstr ""
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:308
-#: screens/InstanceGroup/Instances/InstanceList.js:302
-#: screens/Instances/InstanceDetail/InstanceDetail.js:254
-msgid "Failed to disassociate one or more instances."
-msgstr ""
-
-#: screens/User/UserTeams/UserTeamList.js:264
-msgid "Failed to disassociate one or more teams."
-msgstr ""
-
-#: screens/Login/Login.js:221
-msgid "Failed to fetch custom login configuration settings. System defaults will be shown instead."
-msgstr ""
-
-#: screens/Project/ProjectList/ProjectList.js:306
-msgid "Failed to fetch the updated project data."
-msgstr ""
-
-#: components/AdHocCommands/AdHocCommands.js:112
-#: components/LaunchButton/LaunchButton.js:165
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:128
-msgid "Failed to launch job."
-msgstr ""
-
-#: contexts/Config.js:98
-msgid "Failed to retrieve configuration."
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:349
-msgid "Failed to retrieve full node resource object."
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:360
-msgid "Failed to retrieve node credentials."
-msgstr ""
-
-#: screens/InstanceGroup/Instances/InstanceList.js:304
-#: screens/Instances/InstanceList/InstanceList.js:181
-msgid "Failed to run a health check on one or more instances."
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:172
-msgid "Failed to send test notification."
-msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:52
-msgid "Failed to sync inventory source."
-msgstr ""
-
-#: screens/Project/shared/ProjectSyncButton.js:62
-msgid "Failed to sync project."
-msgstr ""
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:242
-msgid "Failed to sync some or all inventory sources."
-msgstr ""
-
-#: components/HostToggle/HostToggle.js:80
-msgid "Failed to toggle host."
-msgstr ""
-
-#: components/InstanceToggle/InstanceToggle.js:71
-msgid "Failed to toggle instance."
-msgstr ""
-
-#: components/NotificationList/NotificationList.js:250
-msgid "Failed to toggle notification."
-msgstr ""
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:77
-msgid "Failed to toggle schedule."
-msgstr ""
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:307
-#: screens/InstanceGroup/Instances/InstanceListItem.js:222
-#: screens/Instances/InstanceDetail/InstanceDetail.js:253
-#: screens/Instances/InstanceList/InstanceListItem.js:238
-msgid "Failed to update capacity adjustment."
-msgstr ""
-
-#: screens/Template/TemplateSurvey.js:133
-msgid "Failed to update survey."
-msgstr ""
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:87
-msgid "Failed to user token."
-msgstr ""
-
-#: components/NotificationList/NotificationListItem.js:85
-#: components/NotificationList/NotificationListItem.js:86
-msgid "Failure"
-msgstr ""
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:65
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:205
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:235
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:265
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:310
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:368
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:80
-msgid "False"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:118
-msgid "February"
-msgstr ""
-
-#: components/Search/LookupTypeInput.js:52
-msgid "Field contains value."
-msgstr ""
-
-#: components/Search/LookupTypeInput.js:80
-msgid "Field ends with value."
-msgstr ""
-
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:76
-msgid "Field for passing a custom Kubernetes or OpenShift Pod specification."
-msgstr ""
-
-#: components/Search/LookupTypeInput.js:94
-msgid "Field matches the given regular expression."
-msgstr ""
-
-#: components/Search/LookupTypeInput.js:66
-msgid "Field starts with value."
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:409
-msgid "Fifth"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:106
-msgid "File Difference"
-msgstr ""
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:72
-msgid "File upload rejected. Please select a single .json file."
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:96
-msgid "File, directory or script"
-msgstr ""
-
-#: components/Search/Search.js:198
-#: components/Search/Search.js:222
-msgid "Filter By {name}"
-msgstr ""
-
-#: components/JobList/JobList.js:248
-#: components/JobList/JobListItem.js:100
-msgid "Finish Time"
-msgstr ""
-
-#: screens/Job/JobDetail/JobDetail.js:206
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:249
-msgid "Finished"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:397
-msgid "First"
-msgstr ""
-
-#: components/AddRole/AddResourceRole.js:28
-#: components/AddRole/AddResourceRole.js:42
-#: components/ResourceAccessList/ResourceAccessList.js:178
-#: screens/User/UserDetail/UserDetail.js:64
-#: screens/User/UserList/UserList.js:124
-#: screens/User/UserList/UserList.js:161
-#: screens/User/UserList/UserListItem.js:53
-#: screens/User/shared/UserForm.js:63
-msgid "First Name"
-msgstr ""
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:255
-msgid "First Run"
-msgstr ""
-
-#: components/ResourceAccessList/ResourceAccessList.js:227
-#: components/ResourceAccessList/ResourceAccessListItem.js:67
-msgid "First name"
-msgstr ""
-
-#: components/Search/AdvancedSearch.js:213
-#: components/Search/AdvancedSearch.js:227
-msgid "First, select a key"
-msgstr ""
-
-#: components/Workflow/WorkflowTools.js:88
-msgid "Fit the graph to the available screen size"
-msgstr ""
-
-#: screens/TopologyView/Header.js:75
-#: screens/TopologyView/Header.js:78
-msgid "Fit to screen"
-msgstr ""
-
-#: screens/Template/Survey/SurveyQuestionForm.js:94
-msgid "Float"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:184
-msgid "Follow"
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:253
-#~ msgid ""
-#~ "For job templates, select run to execute\n"
-#~ "the playbook. Select check to only check playbook syntax,\n"
-#~ "test environment setup, and report problems without\n"
-#~ "executing the playbook."
-#~ msgstr ""
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:114
-msgid ""
-"For job templates, select run to execute the playbook.\n"
-"Select check to only check playbook syntax, test environment setup,\n"
-"and report problems without executing the playbook."
-msgstr ""
-
-#: screens/Job/Job.helptext.js:5
-#: screens/Template/shared/JobTemplate.helptext.js:5
-msgid "For job templates, select run to execute the playbook. Select check to only check playbook syntax, test environment setup, and report problems without executing the playbook."
-msgstr ""
-
-#: screens/Project/shared/Project.helptext.js:98
-msgid "For more information, refer to the"
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:160
-#: components/AdHocCommands/AdHocDetailsStep.js:161
-#: components/PromptDetail/PromptJobTemplateDetail.js:147
-#: screens/Job/JobDetail/JobDetail.js:384
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:252
-#: screens/Template/shared/JobTemplateForm.js:403
-msgid "Forks"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:407
-msgid "Fourth"
-msgstr ""
-
-#: components/Schedule/shared/ScheduleForm.js:177
-msgid "Frequency Details"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:201
-#: components/Schedule/shared/buildRuleObj.js:71
-msgid "Frequency did not match an expected value"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:303
-msgid "Fri"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:308
-#: components/Schedule/shared/FrequencyDetailSubform.js:446
-msgid "Friday"
-msgstr ""
-
-#: components/Search/RelatedLookupTypeInput.js:45
-msgid "Fuzzy search on id, name or description fields."
-msgstr ""
-
-#: components/Search/RelatedLookupTypeInput.js:32
-msgid "Fuzzy search on name field."
-msgstr ""
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:153
-#: screens/Organization/shared/OrganizationForm.js:101
-msgid "Galaxy Credentials"
-msgstr ""
-
-#: screens/Credential/shared/CredentialForm.js:185
-msgid "Galaxy credentials must be owned by an Organization."
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:107
-msgid "Gathering Facts"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:222
-msgid "Get subscription"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:216
-msgid "Get subscriptions"
-msgstr ""
-
-#: components/Lookup/ProjectLookup.js:135
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:89
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:158
-#: screens/Job/JobDetail/JobDetail.js:74
-#: screens/Project/ProjectList/ProjectList.js:198
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:97
-msgid "Git"
-msgstr ""
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:305
-#: screens/Template/shared/WebhookSubForm.js:105
-msgid "GitHub"
-msgstr ""
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:85
-#: screens/Setting/Settings.js:50
-msgid "GitHub Default"
-msgstr ""
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:100
-#: screens/Setting/Settings.js:59
-msgid "GitHub Enterprise"
-msgstr ""
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:105
-#: screens/Setting/Settings.js:62
-msgid "GitHub Enterprise Organization"
-msgstr ""
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:110
-#: screens/Setting/Settings.js:65
-msgid "GitHub Enterprise Team"
-msgstr ""
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:90
-#: screens/Setting/Settings.js:53
-msgid "GitHub Organization"
-msgstr ""
-
-#: screens/Setting/GitHub/GitHubDetail/GitHubDetail.js:95
-#: screens/Setting/Settings.js:56
-msgid "GitHub Team"
-msgstr ""
-
-#: screens/Setting/SettingList.js:61
-msgid "GitHub settings"
-msgstr ""
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:305
-#: screens/Template/shared/WebhookSubForm.js:111
-msgid "GitLab"
-msgstr ""
-
-#: components/Lookup/ExecutionEnvironmentLookup.js:206
-#~ msgid "Global Default Execution Environment"
-#~ msgstr ""
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:78
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:84
-msgid "Globally Available"
-msgstr ""
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:133
-msgid "Globally available execution environment can not be reassigned to a specific Organization"
-msgstr ""
-
-#: components/Pagination/Pagination.js:29
-msgid "Go to first page"
-msgstr ""
-
-#: components/Pagination/Pagination.js:31
-msgid "Go to last page"
-msgstr ""
-
-#: components/Pagination/Pagination.js:32
-msgid "Go to next page"
-msgstr ""
-
-#: components/Pagination/Pagination.js:30
-msgid "Go to previous page"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:99
-msgid "Google Compute Engine"
-msgstr ""
-
-#: screens/Setting/SettingList.js:65
-msgid "Google OAuth 2 settings"
-msgstr ""
-
-#: screens/Setting/Settings.js:68
-msgid "Google OAuth2"
-msgstr ""
-
-#: components/NotificationList/NotificationList.js:194
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:135
-msgid "Grafana"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:153
-msgid "Grafana API key"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:182
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:144
-msgid "Grafana URL"
-msgstr ""
-
-#: components/Search/LookupTypeInput.js:106
-msgid "Greater than comparison."
-msgstr ""
-
-#: components/Search/LookupTypeInput.js:113
-msgid "Greater than or equal to comparison."
-msgstr ""
-
-#: components/Lookup/HostFilterLookup.js:102
-msgid "Group"
-msgstr ""
-
-#: screens/Inventory/Inventories.js:78
-msgid "Group details"
-msgstr ""
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:124
-msgid "Group type"
-msgstr ""
-
-#: screens/Host/Host.js:68
-#: screens/Host/HostGroups/HostGroupsList.js:231
-#: screens/Host/Hosts.js:29
-#: screens/Inventory/Inventories.js:72
-#: screens/Inventory/Inventories.js:74
-#: screens/Inventory/Inventory.js:66
-#: screens/Inventory/InventoryHost/InventoryHost.js:83
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:248
-#: screens/Inventory/InventoryList/InventoryListItem.js:127
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:251
-#: util/getRelatedResourceDeleteDetails.js:118
-msgid "Groups"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:378
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:453
-msgid "HTTP Headers"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:373
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:466
-msgid "HTTP Method"
-msgstr ""
-
-#: components/HealthCheckButton/HealthCheckButton.js:32
-#: components/HealthCheckButton/HealthCheckButton.js:45
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:273
-#: screens/Instances/InstanceDetail/InstanceDetail.js:228
-#~ msgid "Health Check"
-#~ msgstr ""
-
-#: components/StatusLabel/StatusLabel.js:34
-#: screens/TopologyView/Legend.js:118
-msgid "Healthy"
-msgstr ""
-
-#: components/AppContainer/PageHeaderToolbar.js:121
-msgid "Help"
-msgstr ""
-
-#: components/FormField/PasswordInput.js:35
-msgid "Hide"
-msgstr ""
-
-#: components/LaunchPrompt/LaunchPrompt.js:105
-#: components/Schedule/shared/SchedulePromptableFields.js:109
-msgid "Hide description"
-msgstr ""
-
-#: components/NotificationList/NotificationList.js:195
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:136
-msgid "Hipchat"
-msgstr ""
-
-#: screens/Instances/InstanceList/InstanceList.js:119
-msgid "Hop"
-msgstr ""
-
-#: screens/TopologyView/Legend.js:103
-msgid "Hop node"
-msgstr ""
-
-#: screens/Job/JobOutput/HostEventModal.js:109
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:148
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:76
-msgid "Host"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:108
-msgid "Host Async Failure"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:109
-msgid "Host Async OK"
-msgstr ""
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:154
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:290
-#: screens/Template/shared/JobTemplateForm.js:575
-msgid "Host Config Key"
-msgstr ""
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:96
-msgid "Host Count"
-msgstr ""
-
-#: screens/Job/JobOutput/HostEventModal.js:88
-msgid "Host Details"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:110
-msgid "Host Failed"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:111
-msgid "Host Failure"
-msgstr ""
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:257
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:145
-msgid "Host Filter"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:196
-#: screens/Instances/InstanceDetail/InstanceDetail.js:144
-msgid "Host Name"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:112
-msgid "Host OK"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:113
-msgid "Host Polling"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:114
-msgid "Host Retry"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:115
-msgid "Host Skipped"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:116
-msgid "Host Started"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:117
-msgid "Host Unreachable"
-msgstr ""
-
-#: screens/Inventory/Inventories.js:69
-msgid "Host details"
-msgstr ""
-
-#: screens/Job/JobOutput/HostEventModal.js:89
-msgid "Host details modal"
-msgstr ""
-
-#: screens/Host/Host.js:96
-#: screens/Inventory/InventoryHost/InventoryHost.js:100
-msgid "Host not found."
-msgstr ""
-
-#: screens/Job/JobOutput/shared/HostStatusBar.js:76
-msgid "Host status information for this job is unavailable."
-msgstr ""
-
-#: routeConfig.js:85
-#: screens/ActivityStream/ActivityStream.js:176
-#: screens/Dashboard/Dashboard.js:81
-#: screens/Host/HostList/HostList.js:143
-#: screens/Host/HostList/HostList.js:191
-#: screens/Host/Hosts.js:14
-#: screens/Host/Hosts.js:23
-#: screens/Inventory/Inventories.js:65
-#: screens/Inventory/Inventories.js:79
-#: screens/Inventory/Inventory.js:67
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:67
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:189
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:272
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:112
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:172
-#: screens/Inventory/SmartInventory.js:68
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:71
-#: screens/Job/JobOutput/shared/OutputToolbar.js:97
-#: util/getRelatedResourceDeleteDetails.js:122
-msgid "Hosts"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:132
-msgid "Hosts automated"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:114
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:121
-msgid "Hosts available"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:127
-msgid "Hosts imported"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:145
-msgid "Hosts remaining"
-msgstr ""
-
-#: components/Schedule/shared/ScheduleForm.js:155
-msgid "Hour"
-msgstr ""
-
-#: screens/TopologyView/Legend.js:92
-msgid "Hy"
-msgstr ""
-
-#: screens/InstanceGroup/Instances/InstanceList.js:198
-#: screens/Instances/InstanceList/InstanceList.js:118
-msgid "Hybrid"
-msgstr ""
-
-#: screens/TopologyView/Legend.js:95
-msgid "Hybrid node"
-msgstr ""
-
-#: components/JobList/JobList.js:200
-#: components/Lookup/HostFilterLookup.js:98
-#: screens/Team/TeamRoles/TeamRolesList.js:155
-msgid "ID"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:188
-msgid "ID of the Dashboard"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:193
-msgid "ID of the Panel"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:160
-msgid "ID of the dashboard (optional)"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:166
-msgid "ID of the panel (optional)"
-msgstr ""
-
-#: components/NotificationList/NotificationList.js:196
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:137
-msgid "IRC"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:223
-msgid "IRC Nick"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:218
-msgid "IRC Server Address"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:213
-msgid "IRC Server Port"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:214
-msgid "IRC nick"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:206
-msgid "IRC server address"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:192
-msgid "IRC server password"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:197
-msgid "IRC server port"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:258
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:303
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:263
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:334
-msgid "Icon URL"
-msgstr ""
-
-#: screens/Inventory/shared/Inventory.helptext.js:100
-msgid ""
-"If checked, all variables for child groups\n"
-"and hosts will be removed and replaced by those found\n"
-"on the external source."
-msgstr ""
-
-#: screens/Inventory/shared/Inventory.helptext.js:84
-msgid ""
-"If checked, any hosts and groups that were\n"
-"previously present on the external source but are now removed\n"
-"will be removed from the inventory. Hosts and groups\n"
-"that were not managed by the inventory source will be promoted\n"
-"to the next manually created group or if there is no manually\n"
-"created group to promote them into, they will be left in the \"all\"\n"
-"default group for the inventory."
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:558
-#~ msgid ""
-#~ "If enabled, run this playbook as an\n"
-#~ "administrator."
-#~ msgstr ""
-
-#: screens/Template/shared/JobTemplate.helptext.js:29
-msgid "If enabled, run this playbook as an administrator."
-msgstr ""
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:156
-msgid ""
-"If enabled, show the changes made\n"
-"by Ansible tasks, where supported. This is equivalent to Ansible’s\n"
-"--diff mode."
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:498
-#~ msgid ""
-#~ "If enabled, show the changes made by\n"
-#~ "Ansible tasks, where supported. This is equivalent\n"
-#~ "to Ansible's --diff mode."
-#~ msgstr ""
-
-#: screens/Template/shared/JobTemplate.helptext.js:18
-msgid "If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible's --diff mode."
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:181
-msgid "If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible’s --diff mode."
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:604
-#~ msgid ""
-#~ "If enabled, simultaneous runs of this job\n"
-#~ "template will be allowed."
-#~ msgstr ""
-
-#: screens/Template/shared/JobTemplate.helptext.js:31
-msgid "If enabled, simultaneous runs of this job template will be allowed."
-msgstr ""
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:16
-msgid "If enabled, simultaneous runs of this workflow job template will be allowed."
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:611
-#~ msgid ""
-#~ "If enabled, this will store gathered facts so they can\n"
-#~ "be viewed at the host level. Facts are persisted and\n"
-#~ "injected into the fact cache at runtime."
-#~ msgstr ""
-
-#: screens/Template/shared/JobTemplate.helptext.js:32
-msgid "If enabled, this will store gathered facts so they can be viewed at the host level. Facts are persisted and injected into the fact cache at runtime."
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:275
-msgid "If specified, this field will be shown on the node instead of the resource name when viewing the workflow"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:150
-msgid "If you are ready to upgrade or renew, please <0>contact us.0>"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:63
-msgid ""
-"If you do not have a subscription, you can visit\n"
-"Red Hat to obtain a trial subscription."
-msgstr ""
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:46
-msgid "If you only want to remove access for this particular user, please remove them from the team."
-msgstr ""
-
-#: screens/Inventory/shared/Inventory.helptext.js:120
-#: screens/Inventory/shared/Inventory.helptext.js:139
-msgid ""
-"If you want the Inventory Source to update on\n"
-"launch and on project update, click on Update on launch, and also go to"
-msgstr ""
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:53
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:141
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:147
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:166
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:75
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:97
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:89
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:108
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.js:21
-msgid "Image"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:118
-msgid "Including File"
-msgstr ""
-
-#: components/HostToggle/HostToggle.js:16
-msgid ""
-"Indicates if a host is available and should be included in running\n"
-"jobs. For hosts that are part of an external inventory, this may be\n"
-"reset by the inventory sync process."
-msgstr ""
-
-#: components/AppContainer/PageHeaderToolbar.js:108
-msgid "Info"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStreamListItem.js:45
-msgid "Initiated By"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStream.js:253
-#: screens/ActivityStream/ActivityStream.js:263
-#: screens/ActivityStream/ActivityStreamDetailButton.js:44
-msgid "Initiated by"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStream.js:243
-msgid "Initiated by (username)"
-msgstr ""
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:82
-#: screens/CredentialType/shared/CredentialTypeForm.js:46
-msgid "Injector configuration"
-msgstr ""
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:74
-#: screens/CredentialType/shared/CredentialTypeForm.js:38
-msgid "Input configuration"
-msgstr ""
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:79
-msgid "Input schema which defines a set of ordered fields for that type."
-msgstr ""
-
-#: screens/Project/shared/ProjectSubForms/InsightsSubForm.js:31
-msgid "Insights Credential"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:71
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:72
-#~ msgid "Insights for Ansible Automation Platform"
-#~ msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:111
-#~ msgid "Insights for Ansible Automation Platform dashboard"
-#~ msgstr ""
-
-#: components/Lookup/HostFilterLookup.js:123
-msgid "Insights system ID"
-msgstr ""
-
-#: screens/Metrics/Metrics.js:187
-msgid "Instance"
-msgstr ""
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:147
-msgid "Instance Filters"
-msgstr ""
-
-#: screens/Job/JobDetail/JobDetail.js:353
-msgid "Instance Group"
-msgstr ""
-
-#: components/Lookup/InstanceGroupsLookup.js:69
-#: components/Lookup/InstanceGroupsLookup.js:75
-#: components/Lookup/InstanceGroupsLookup.js:121
-#: components/PromptDetail/PromptJobTemplateDetail.js:222
-#: routeConfig.js:132
-#: screens/ActivityStream/ActivityStream.js:205
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:111
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:193
-#: screens/InstanceGroup/InstanceGroups.js:45
-#: screens/InstanceGroup/InstanceGroups.js:55
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:85
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:127
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:407
-msgid "Instance Groups"
-msgstr ""
-
-#: components/Lookup/HostFilterLookup.js:115
-msgid "Instance ID"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceGroups.js:62
-msgid "Instance details"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:58
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:69
-msgid "Instance group"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceGroup.js:92
-msgid "Instance group not found."
-msgstr ""
-
-#: screens/InstanceGroup/Instances/InstanceListItem.js:165
-#: screens/Instances/InstanceList/InstanceListItem.js:176
-msgid "Instance group used capacity"
-msgstr ""
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:124
-msgid "Instance groups"
-msgstr ""
-
-#: routeConfig.js:137
-#: screens/ActivityStream/ActivityStream.js:203
-#: screens/InstanceGroup/InstanceGroup.js:74
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:212
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:73
-#: screens/InstanceGroup/InstanceGroups.js:60
-#: screens/InstanceGroup/Instances/InstanceList.js:181
-#: screens/InstanceGroup/Instances/InstanceList.js:279
-#: screens/Instances/InstanceList/InstanceList.js:101
-#: screens/Instances/Instances.js:12
-#: screens/Instances/Instances.js:20
-msgid "Instances"
-msgstr ""
-
-#: screens/Template/Survey/SurveyQuestionForm.js:93
-msgid "Integer"
-msgstr ""
-
-#: util/validators.js:94
-msgid "Invalid email address"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:116
-msgid "Invalid file format. Please upload a valid Red Hat Subscription Manifest."
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:151
-#~ msgid "Invalid link target. Unable to link to children or ancestor nodes. Graph cycles are not supported."
-#~ msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:151
-msgid "Invalid link target. Unable to link to children or ancestor nodes. Graph cycles are not supported."
-msgstr ""
-
-#: util/validators.js:33
-msgid "Invalid time format"
-msgstr ""
-
-#: screens/Login/Login.js:142
-msgid "Invalid username or password. Please try again."
-msgstr ""
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:119
-#: routeConfig.js:80
-#: screens/ActivityStream/ActivityStream.js:173
-#: screens/Dashboard/Dashboard.js:92
-#: screens/Inventory/Inventories.js:17
-#: screens/Inventory/InventoryList/InventoryList.js:174
-#: screens/Inventory/InventoryList/InventoryList.js:237
-#: util/getRelatedResourceDeleteDetails.js:201
-#: util/getRelatedResourceDeleteDetails.js:269
-msgid "Inventories"
-msgstr ""
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:153
-msgid "Inventories with sources cannot be copied"
-msgstr ""
-
-#: components/HostForm/HostForm.js:48
-#: components/JobList/JobListItem.js:223
-#: components/LaunchPrompt/steps/InventoryStep.js:105
-#: components/LaunchPrompt/steps/useInventoryStep.js:48
-#: components/Lookup/HostFilterLookup.js:423
-#: components/Lookup/HostListItem.js:10
-#: components/Lookup/InventoryLookup.js:129
-#: components/Lookup/InventoryLookup.js:138
-#: components/Lookup/InventoryLookup.js:178
-#: components/Lookup/InventoryLookup.js:193
-#: components/Lookup/InventoryLookup.js:233
-#: components/PromptDetail/PromptDetail.js:205
-#: components/PromptDetail/PromptInventorySourceDetail.js:87
-#: components/PromptDetail/PromptJobTemplateDetail.js:117
-#: components/PromptDetail/PromptJobTemplateDetail.js:127
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:77
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:290
-#: components/TemplateList/TemplateListItem.js:284
-#: components/TemplateList/TemplateListItem.js:294
-#: screens/Host/HostDetail/HostDetail.js:75
-#: screens/Host/HostList/HostList.js:171
-#: screens/Host/HostList/HostListItem.js:61
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:72
-#: screens/Inventory/InventoryList/InventoryList.js:186
-#: screens/Inventory/InventoryList/InventoryListItem.js:117
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:39
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:113
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:41
-#: screens/Job/JobDetail/JobDetail.js:106
-#: screens/Job/JobDetail/JobDetail.js:121
-#: screens/Job/JobDetail/JobDetail.js:128
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:207
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:217
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:142
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:33
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:293
-msgid "Inventory"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:104
-msgid "Inventory (Name)"
-msgstr ""
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:110
-msgid "Inventory File"
-msgstr ""
-
-#: components/Lookup/HostFilterLookup.js:106
-msgid "Inventory ID"
-msgstr ""
-
-#: screens/Job/JobDetail/JobDetail.js:262
-msgid "Inventory Source"
-msgstr ""
-
-#: screens/Job/JobDetail/JobDetail.js:285
-msgid "Inventory Source Project"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:73
-msgid "Inventory Source Sync"
-msgstr ""
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:315
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:110
-msgid "Inventory Source Sync Error"
-msgstr ""
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:176
-#: screens/Inventory/InventorySources/InventorySourceList.js:193
-#: util/getRelatedResourceDeleteDetails.js:66
-#: util/getRelatedResourceDeleteDetails.js:146
-msgid "Inventory Sources"
-msgstr ""
-
-#: components/JobList/JobList.js:212
-#: components/JobList/JobListItem.js:43
-#: components/Schedule/ScheduleList/ScheduleListItem.js:36
-#: components/Workflow/WorkflowLegend.js:100
-#: screens/Job/JobDetail/JobDetail.js:65
-msgid "Inventory Sync"
-msgstr ""
-
-#: screens/Inventory/InventoryList/InventoryList.js:183
-msgid "Inventory Type"
-msgstr ""
-
-#: components/Workflow/WorkflowNodeHelp.js:71
-msgid "Inventory Update"
-msgstr ""
-
-#: screens/Inventory/InventoryList/InventoryList.js:121
-msgid "Inventory copied successfully"
-msgstr ""
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:241
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:108
-msgid "Inventory file"
-msgstr ""
-
-#: screens/Inventory/Inventory.js:94
-msgid "Inventory not found."
-msgstr ""
-
-#: screens/Dashboard/DashboardGraph.js:140
-msgid "Inventory sync"
-msgstr ""
-
-#: screens/Dashboard/Dashboard.js:98
-msgid "Inventory sync failures"
-msgstr ""
-
-#: components/DataListToolbar/DataListToolbar.js:110
-msgid "Is expanded"
-msgstr ""
-
-#: components/DataListToolbar/DataListToolbar.js:112
-msgid "Is not expanded"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:119
-msgid "Item Failed"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:120
-msgid "Item OK"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:121
-msgid "Item Skipped"
-msgstr ""
-
-#: components/AssociateModal/AssociateModal.js:20
-#: components/PaginatedTable/PaginatedTable.js:42
-msgid "Items"
-msgstr ""
-
-#: components/Pagination/Pagination.js:27
-msgid "Items per page"
-msgstr ""
-
-#: components/Sparkline/Sparkline.js:28
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:172
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:36
-#: screens/Project/ProjectDetail/ProjectDetail.js:132
-#: screens/Project/ProjectList/ProjectListItem.js:70
-msgid "JOB ID:"
-msgstr ""
-
-#: screens/Job/JobOutput/HostEventModal.js:136
-msgid "JSON"
-msgstr ""
-
-#: screens/Job/JobOutput/HostEventModal.js:137
-msgid "JSON tab"
-msgstr ""
-
-#: screens/Inventory/shared/Inventory.helptext.js:49
-msgid "JSON:"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:113
-msgid "January"
-msgstr ""
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:221
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:66
-#~ msgid "Job"
-#~ msgstr ""
-
-#: components/JobList/JobListItem.js:112
-#: screens/Job/JobDetail/JobDetail.js:581
-#: screens/Job/JobOutput/JobOutput.js:790
-#: screens/Job/JobOutput/JobOutput.js:791
-#: screens/Job/JobOutput/shared/OutputToolbar.js:136
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:88
-msgid "Job Cancel Error"
-msgstr ""
-
-#: screens/Job/JobDetail/JobDetail.js:603
-#: screens/Job/JobOutput/JobOutput.js:779
-#: screens/Job/JobOutput/JobOutput.js:780
-msgid "Job Delete Error"
-msgstr ""
-
-#: screens/Job/JobDetail/JobDetail.js:184
-msgid "Job ID"
-msgstr ""
-
-#: screens/Dashboard/shared/LineChart.js:128
-msgid "Job Runs"
-msgstr ""
-
-#: components/JobList/JobListItem.js:313
-#: screens/Job/JobDetail/JobDetail.js:369
-msgid "Job Slice"
-msgstr ""
-
-#: components/JobList/JobListItem.js:318
-#: screens/Job/JobDetail/JobDetail.js:377
-msgid "Job Slice Parent"
-msgstr ""
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:153
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:282
-#: screens/Template/shared/JobTemplateForm.js:435
-msgid "Job Slicing"
-msgstr ""
-
-#: components/Workflow/WorkflowNodeHelp.js:164
-msgid "Job Status"
-msgstr ""
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:57
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:58
-#: components/PromptDetail/PromptDetail.js:228
-#: components/PromptDetail/PromptJobTemplateDetail.js:241
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:346
-#: screens/Job/JobDetail/JobDetail.js:458
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:434
-#: screens/Template/shared/JobTemplateForm.js:469
-msgid "Job Tags"
-msgstr ""
-
-#: components/JobList/JobListItem.js:191
-#: components/TemplateList/TemplateList.js:217
-#: components/Workflow/WorkflowLegend.js:92
-#: components/Workflow/WorkflowNodeHelp.js:59
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:97
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:19
-#: screens/Job/JobDetail/JobDetail.js:213
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:79
-msgid "Job Template"
-msgstr ""
-
-#: components/LaunchPrompt/steps/credentialsValidator.js:38
-msgid "Job Template default credentials must be replaced with one of the same type. Please select a credential for the following types in order to proceed: {0}"
-msgstr ""
-
-#: screens/Credential/Credential.js:79
-#: screens/Credential/Credentials.js:30
-#: screens/Inventory/Inventories.js:62
-#: screens/Inventory/Inventory.js:74
-#: screens/Inventory/SmartInventory.js:74
-#: screens/Project/Project.js:107
-#: screens/Project/Projects.js:29
-#: util/getRelatedResourceDeleteDetails.js:55
-#: util/getRelatedResourceDeleteDetails.js:100
-#: util/getRelatedResourceDeleteDetails.js:132
-msgid "Job Templates"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.js:25
-msgid "Job Templates with a missing inventory or project cannot be selected when creating or editing nodes. Select another template or fix the missing fields to proceed."
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useWorkflowNodeSteps.js:316
-msgid "Job Templates with credentials that prompt for passwords cannot be selected when creating or editing nodes"
-msgstr ""
-
-#: components/JobList/JobList.js:208
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:111
-#: components/PromptDetail/PromptDetail.js:176
-#: components/PromptDetail/PromptJobTemplateDetail.js:100
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:286
-#: screens/Job/JobDetail/JobDetail.js:247
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:185
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:139
-#: screens/Template/shared/JobTemplateForm.js:252
-msgid "Job Type"
-msgstr ""
-
-#: screens/Dashboard/Dashboard.js:125
-msgid "Job status"
-msgstr ""
-
-#: screens/Dashboard/Dashboard.js:123
-msgid "Job status graph tab"
-msgstr ""
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:156
-#: components/RelatedTemplateList/RelatedTemplateList.js:206
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:15
-msgid "Job templates"
-msgstr ""
-
-#: components/JobList/JobList.js:191
-#: components/JobList/JobList.js:274
-#: routeConfig.js:39
-#: screens/ActivityStream/ActivityStream.js:150
-#: screens/Dashboard/shared/LineChart.js:64
-#: screens/Host/Host.js:73
-#: screens/Host/Hosts.js:30
-#: screens/InstanceGroup/ContainerGroup.js:71
-#: screens/InstanceGroup/InstanceGroup.js:79
-#: screens/InstanceGroup/InstanceGroups.js:63
-#: screens/InstanceGroup/InstanceGroups.js:68
-#: screens/Inventory/Inventories.js:60
-#: screens/Inventory/Inventories.js:70
-#: screens/Inventory/Inventory.js:70
-#: screens/Inventory/InventoryHost/InventoryHost.js:88
-#: screens/Inventory/SmartInventory.js:70
-#: screens/Job/Jobs.js:22
-#: screens/Job/Jobs.js:32
-#: screens/Setting/SettingList.js:87
-#: screens/Setting/Settings.js:71
-#: screens/Template/Template.js:155
-#: screens/Template/Templates.js:47
-#: screens/Template/WorkflowJobTemplate.js:141
-msgid "Jobs"
-msgstr ""
-
-#: screens/Setting/SettingList.js:92
-msgid "Jobs settings"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:143
-msgid "July"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:138
-msgid "June"
-msgstr ""
-
-#: components/Search/AdvancedSearch.js:262
-msgid "Key"
-msgstr ""
-
-#: components/Search/AdvancedSearch.js:253
-msgid "Key select"
-msgstr ""
-
-#: components/Search/AdvancedSearch.js:256
-msgid "Key typeahead"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStream.js:238
-msgid "Keyword"
-msgstr ""
-
-#: screens/User/UserDetail/UserDetail.js:56
-#: screens/User/UserList/UserListItem.js:44
-msgid "LDAP"
-msgstr ""
-
-#: screens/Setting/Settings.js:76
-msgid "LDAP 1"
-msgstr ""
-
-#: screens/Setting/Settings.js:77
-msgid "LDAP 2"
-msgstr ""
-
-#: screens/Setting/Settings.js:78
-msgid "LDAP 3"
-msgstr ""
-
-#: screens/Setting/Settings.js:79
-msgid "LDAP 4"
-msgstr ""
-
-#: screens/Setting/Settings.js:80
-msgid "LDAP 5"
-msgstr ""
-
-#: screens/Setting/Settings.js:75
-msgid "LDAP Default"
-msgstr ""
-
-#: screens/Setting/SettingList.js:69
-msgid "LDAP settings"
-msgstr ""
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:107
-msgid "LDAP1"
-msgstr ""
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:112
-msgid "LDAP2"
-msgstr ""
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:117
-msgid "LDAP3"
-msgstr ""
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:122
-msgid "LDAP4"
-msgstr ""
-
-#: screens/Setting/LDAP/LDAPDetail/LDAPDetail.js:127
-msgid "LDAP5"
-msgstr ""
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:178
-#: components/TemplateList/TemplateList.js:234
-msgid "Label"
-msgstr ""
-
-#: components/JobList/JobList.js:204
-msgid "Label Name"
-msgstr ""
-
-#: components/JobList/JobListItem.js:283
-#: components/PromptDetail/PromptJobTemplateDetail.js:203
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:114
-#: components/TemplateList/TemplateListItem.js:345
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:110
-#: screens/Inventory/shared/InventoryForm.js:75
-#: screens/Job/JobDetail/JobDetail.js:437
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:386
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:208
-#: screens/Template/shared/JobTemplateForm.js:379
-#: screens/Template/shared/WorkflowJobTemplateForm.js:189
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:315
-msgid "Labels"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:410
-msgid "Last"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:213
-#: screens/InstanceGroup/Instances/InstanceListItem.js:133
-#: screens/InstanceGroup/Instances/InstanceListItem.js:208
-#: screens/Instances/InstanceDetail/InstanceDetail.js:164
-#: screens/Instances/InstanceList/InstanceListItem.js:138
-#: screens/Instances/InstanceList/InstanceListItem.js:223
-msgid "Last Health Check"
-msgstr ""
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:198
-#: screens/Project/ProjectDetail/ProjectDetail.js:160
-msgid "Last Job Status"
-msgstr ""
-
-#: screens/User/UserDetail/UserDetail.js:80
-msgid "Last Login"
-msgstr ""
-
-#: components/PromptDetail/PromptDetail.js:152
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:275
-#: components/TemplateList/TemplateListItem.js:315
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:105
-#: screens/Application/ApplicationsList/ApplicationListItem.js:45
-#: screens/Application/ApplicationsList/ApplicationsList.js:159
-#: screens/Credential/CredentialDetail/CredentialDetail.js:263
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:95
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:107
-#: screens/Host/HostDetail/HostDetail.js:87
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:72
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:98
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:139
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:45
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:85
-#: screens/Job/JobDetail/JobDetail.js:520
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:393
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:120
-#: screens/Project/ProjectDetail/ProjectDetail.js:279
-#: screens/Team/TeamDetail/TeamDetail.js:48
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:344
-#: screens/User/UserDetail/UserDetail.js:84
-#: screens/User/UserTokenDetail/UserTokenDetail.js:65
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:245
-msgid "Last Modified"
-msgstr ""
-
-#: components/AddRole/AddResourceRole.js:32
-#: components/AddRole/AddResourceRole.js:46
-#: components/ResourceAccessList/ResourceAccessList.js:182
-#: screens/User/UserDetail/UserDetail.js:65
-#: screens/User/UserList/UserList.js:128
-#: screens/User/UserList/UserList.js:162
-#: screens/User/UserList/UserListItem.js:54
-#: screens/User/shared/UserForm.js:69
-msgid "Last Name"
-msgstr ""
-
-#: components/TemplateList/TemplateList.js:245
-#: components/TemplateList/TemplateListItem.js:194
-msgid "Last Ran"
-msgstr ""
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:262
-msgid "Last Run"
-msgstr ""
-
-#: components/Lookup/HostFilterLookup.js:119
-msgid "Last job"
-msgstr ""
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:296
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:153
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:50
-#: screens/Project/ProjectList/ProjectListItem.js:308
-msgid "Last modified"
-msgstr ""
-
-#: components/ResourceAccessList/ResourceAccessList.js:228
-#: components/ResourceAccessList/ResourceAccessListItem.js:68
-msgid "Last name"
-msgstr ""
-
-#: screens/Project/ProjectList/ProjectListItem.js:313
-msgid "Last used"
-msgstr ""
-
-#: components/AdHocCommands/useAdHocPreviewStep.js:22
-#: components/LaunchPrompt/steps/usePreviewStep.js:35
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:54
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:57
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:503
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:512
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:247
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:256
-msgid "Launch"
-msgstr ""
-
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:74
-#~ msgid "Launch Management Job"
-#~ msgstr ""
-
-#: components/TemplateList/TemplateListItem.js:214
-msgid "Launch Template"
-msgstr ""
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:32
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:34
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:46
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:47
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:87
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:90
-msgid "Launch management job"
-msgstr ""
-
-#: components/TemplateList/TemplateListItem.js:222
-msgid "Launch template"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:119
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:120
-msgid "Launch workflow"
-msgstr ""
-
-#: components/LaunchPrompt/LaunchPrompt.js:100
-msgid "Launch | {0}"
-msgstr ""
-
-#: components/DetailList/LaunchedByDetail.js:54
-msgid "Launched By"
-msgstr ""
-
-#: components/JobList/JobList.js:220
-msgid "Launched By (Username)"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:120
-msgid "Learn more about Automation Analytics"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:120
-#~ msgid "Learn more about Insights for Ansible Automation Platform"
-#~ msgstr ""
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:68
-msgid "Leave this field blank to make the execution environment globally available."
-msgstr ""
-
-#: components/Workflow/WorkflowLegend.js:86
-#: screens/Metrics/LineChart.js:120
-#: screens/TopologyView/Header.js:102
-#: screens/TopologyView/Legend.js:65
-msgid "Legend"
-msgstr ""
-
-#: components/Search/LookupTypeInput.js:120
-msgid "Less than comparison."
-msgstr ""
-
-#: components/Search/LookupTypeInput.js:127
-msgid "Less than or equal to comparison."
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:140
-#: components/AdHocCommands/AdHocDetailsStep.js:141
-#: components/JobList/JobList.js:238
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:36
-#: components/PromptDetail/PromptDetail.js:216
-#: components/PromptDetail/PromptJobTemplateDetail.js:148
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:88
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:314
-#: screens/Job/JobDetail/JobDetail.js:320
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:258
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:152
-#: screens/Template/shared/JobTemplateForm.js:408
-#: screens/Template/shared/WorkflowJobTemplateForm.js:153
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:279
-msgid "Limit"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:234
-msgid "Link to an available node"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:334
-msgid "Loading"
-msgstr ""
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:49
-msgid "Local"
-msgstr ""
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:263
-msgid "Local Time Zone"
-msgstr ""
-
-#: components/Schedule/shared/ScheduleForm.js:132
-msgid "Local time zone"
-msgstr ""
-
-#: screens/Login/Login.js:195
-msgid "Log In"
-msgstr ""
-
-#: screens/Setting/Settings.js:93
-msgid "Logging"
-msgstr ""
-
-#: screens/Setting/SettingList.js:111
-msgid "Logging settings"
-msgstr ""
-
-#: components/AppContainer/AppContainer.js:81
-#: components/AppContainer/AppContainer.js:150
-#: components/AppContainer/PageHeaderToolbar.js:174
-msgid "Logout"
-msgstr ""
-
-#: components/Lookup/HostFilterLookup.js:366
-#: components/Lookup/Lookup.js:181
-msgid "Lookup modal"
-msgstr ""
-
-#: components/Search/LookupTypeInput.js:22
-msgid "Lookup select"
-msgstr ""
-
-#: components/Search/LookupTypeInput.js:31
-msgid "Lookup type"
-msgstr ""
-
-#: components/Search/LookupTypeInput.js:25
-msgid "Lookup typeahead"
-msgstr ""
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:170
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:34
-#: screens/Project/ProjectDetail/ProjectDetail.js:130
-#: screens/Project/ProjectList/ProjectListItem.js:68
-msgid "MOST RECENT SYNC"
-msgstr ""
-
-#: components/AdHocCommands/AdHocCredentialStep.js:97
-#: components/AdHocCommands/AdHocCredentialStep.js:98
-#: components/AdHocCommands/AdHocCredentialStep.js:112
-#: screens/Job/JobDetail/JobDetail.js:392
-msgid "Machine Credential"
-msgstr ""
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:64
-msgid "Managed"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:147
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:169
-msgid "Managed nodes"
-msgstr ""
-
-#: components/JobList/JobList.js:215
-#: components/JobList/JobListItem.js:46
-#: components/Schedule/ScheduleList/ScheduleListItem.js:39
-#: components/Workflow/WorkflowLegend.js:108
-#: components/Workflow/WorkflowNodeHelp.js:79
-#: screens/Job/JobDetail/JobDetail.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:102
-msgid "Management Job"
-msgstr ""
-
-#: routeConfig.js:127
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:84
-msgid "Management Jobs"
-msgstr ""
-
-#: screens/ManagementJob/ManagementJobs.js:20
-msgid "Management job"
-msgstr ""
-
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:109
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:110
-msgid "Management job launch error"
-msgstr ""
-
-#: screens/ManagementJob/ManagementJob.js:133
-msgid "Management job not found."
-msgstr ""
-
-#: screens/ManagementJob/ManagementJobs.js:13
-msgid "Management jobs"
-msgstr ""
-
-#: components/Lookup/ProjectLookup.js:134
-#: components/PromptDetail/PromptProjectDetail.js:98
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:88
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:157
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:208
-#: screens/InstanceGroup/Instances/InstanceListItem.js:204
-#: screens/Instances/InstanceDetail/InstanceDetail.js:159
-#: screens/Instances/InstanceList/InstanceListItem.js:219
-#: screens/Job/JobDetail/JobDetail.js:73
-#: screens/Project/ProjectDetail/ProjectDetail.js:191
-#: screens/Project/ProjectList/ProjectList.js:197
-#: screens/Project/ProjectList/ProjectListItem.js:219
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:96
-msgid "Manual"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:123
-msgid "March"
-msgstr ""
-
-#: components/NotificationList/NotificationList.js:197
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:138
-msgid "Mattermost"
-msgstr ""
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:98
-#: screens/Organization/shared/OrganizationForm.js:71
-msgid "Max Hosts"
-msgstr ""
-
-#: screens/Template/Survey/SurveyQuestionForm.js:220
-msgid "Maximum"
-msgstr ""
-
-#: screens/Template/Survey/SurveyQuestionForm.js:204
-msgid "Maximum length"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:133
-msgid "May"
-msgstr ""
-
-#: screens/Organization/OrganizationList/OrganizationList.js:144
-#: screens/Organization/OrganizationList/OrganizationListItem.js:63
-msgid "Members"
-msgstr ""
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:47
-msgid "Metadata"
-msgstr ""
-
-#: screens/Metrics/Metrics.js:207
-msgid "Metric"
-msgstr ""
-
-#: screens/Metrics/Metrics.js:179
-msgid "Metrics"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:100
-msgid "Microsoft Azure Resource Manager"
-msgstr ""
-
-#: screens/Template/Survey/SurveyQuestionForm.js:214
-msgid "Minimum"
-msgstr ""
-
-#: screens/Template/Survey/SurveyQuestionForm.js:198
-msgid "Minimum length"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:65
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:31
-msgid ""
-"Minimum number of instances that will be automatically\n"
-"assigned to this group when new instances come online."
-msgstr ""
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:71
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:41
-msgid ""
-"Minimum percentage of all instances that will be automatically\n"
-"assigned to this group when new instances come online."
-msgstr ""
-
-#: components/Schedule/shared/ScheduleForm.js:154
-msgid "Minute"
-msgstr ""
-
-#: screens/Setting/Settings.js:96
-msgid "Miscellaneous Authentication"
-msgstr ""
-
-#: screens/Setting/SettingList.js:107
-msgid "Miscellaneous Authentication settings"
-msgstr ""
-
-#: screens/Setting/Settings.js:99
-msgid "Miscellaneous System"
-msgstr ""
-
-#: screens/Setting/SettingList.js:103
-msgid "Miscellaneous System settings"
-msgstr ""
-
-#: components/Workflow/WorkflowNodeHelp.js:120
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:86
-msgid "Missing"
-msgstr ""
-
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:66
-#: components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.js:109
-msgid "Missing resource"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:193
-#: screens/User/UserTokenList/UserTokenList.js:154
-msgid "Modified"
-msgstr ""
-
-#: components/AdHocCommands/AdHocCredentialStep.js:126
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:116
-#: components/AddRole/AddResourceRole.js:61
-#: components/AssociateModal/AssociateModal.js:148
-#: components/LaunchPrompt/steps/CredentialsStep.js:177
-#: components/LaunchPrompt/steps/InventoryStep.js:93
-#: components/Lookup/CredentialLookup.js:197
-#: components/Lookup/InventoryLookup.js:165
-#: components/Lookup/InventoryLookup.js:220
-#: components/Lookup/MultiCredentialsLookup.js:197
-#: components/Lookup/OrganizationLookup.js:137
-#: components/Lookup/ProjectLookup.js:146
-#: components/NotificationList/NotificationList.js:210
-#: components/RelatedTemplateList/RelatedTemplateList.js:170
-#: components/Schedule/ScheduleList/ScheduleList.js:201
-#: components/TemplateList/TemplateList.js:230
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:31
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:62
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:100
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:131
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:169
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:200
-#: screens/Credential/CredentialList/CredentialList.js:154
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:100
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:136
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:106
-#: screens/Host/HostGroups/HostGroupsList.js:168
-#: screens/Host/HostList/HostList.js:161
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:203
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:133
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:178
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:132
-#: screens/Inventory/InventoryList/InventoryList.js:203
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:189
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:98
-#: screens/Organization/OrganizationList/OrganizationList.js:135
-#: screens/Project/ProjectList/ProjectList.js:209
-#: screens/Team/TeamList/TeamList.js:134
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:167
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:108
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:112
-msgid "Modified By (Username)"
-msgstr ""
-
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:85
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:151
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:77
-msgid "Modified by (username)"
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:59
-#: screens/Job/JobOutput/HostEventModal.js:125
-msgid "Module"
-msgstr ""
-
-#: screens/Job/JobDetail/JobDetail.js:512
-msgid "Module Arguments"
-msgstr ""
-
-#: screens/Job/JobDetail/JobDetail.js:506
-msgid "Module Name"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:259
-msgid "Mon"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:264
-#: components/Schedule/shared/FrequencyDetailSubform.js:426
-msgid "Monday"
-msgstr ""
-
-#: components/Schedule/shared/ScheduleForm.js:158
-msgid "Month"
-msgstr ""
-
-#: components/Popover/Popover.js:32
-msgid "More information"
-msgstr ""
-
-#: screens/Setting/shared/SharedFields.js:73
-msgid "More information for"
-msgstr ""
-
-#: screens/Template/Survey/SurveyReorderModal.js:162
-#: screens/Template/Survey/SurveyReorderModal.js:163
-msgid "Multi-Select"
-msgstr ""
-
-#: screens/Template/Survey/SurveyReorderModal.js:146
-#: screens/Template/Survey/SurveyReorderModal.js:147
-msgid "Multiple Choice"
-msgstr ""
-
-#: screens/Template/Survey/SurveyQuestionForm.js:91
-msgid "Multiple Choice (multiple select)"
-msgstr ""
-
-#: screens/Template/Survey/SurveyQuestionForm.js:86
-msgid "Multiple Choice (single select)"
-msgstr ""
-
-#: screens/Template/Survey/SurveyQuestionForm.js:258
-msgid "Multiple Choice Options"
-msgstr ""
-
-#: components/AdHocCommands/AdHocCredentialStep.js:117
-#: components/AdHocCommands/AdHocCredentialStep.js:132
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:107
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:122
-#: components/AddRole/AddResourceRole.js:52
-#: components/AddRole/AddResourceRole.js:68
-#: components/AssociateModal/AssociateModal.js:139
-#: components/AssociateModal/AssociateModal.js:154
-#: components/HostForm/HostForm.js:96
-#: components/JobList/JobList.js:195
-#: components/JobList/JobList.js:244
-#: components/JobList/JobListItem.js:86
-#: components/LaunchPrompt/steps/CredentialsStep.js:168
-#: components/LaunchPrompt/steps/CredentialsStep.js:183
-#: components/LaunchPrompt/steps/InventoryStep.js:84
-#: components/LaunchPrompt/steps/InventoryStep.js:99
-#: components/Lookup/ApplicationLookup.js:99
-#: components/Lookup/ApplicationLookup.js:110
-#: components/Lookup/CredentialLookup.js:188
-#: components/Lookup/CredentialLookup.js:203
-#: components/Lookup/ExecutionEnvironmentLookup.js:172
-#: components/Lookup/ExecutionEnvironmentLookup.js:179
-#: components/Lookup/HostFilterLookup.js:93
-#: components/Lookup/HostFilterLookup.js:421
-#: components/Lookup/HostListItem.js:8
-#: components/Lookup/InstanceGroupsLookup.js:103
-#: components/Lookup/InstanceGroupsLookup.js:114
-#: components/Lookup/InventoryLookup.js:156
-#: components/Lookup/InventoryLookup.js:171
-#: components/Lookup/InventoryLookup.js:211
-#: components/Lookup/InventoryLookup.js:226
-#: components/Lookup/MultiCredentialsLookup.js:188
-#: components/Lookup/MultiCredentialsLookup.js:203
-#: components/Lookup/OrganizationLookup.js:128
-#: components/Lookup/OrganizationLookup.js:143
-#: components/Lookup/ProjectLookup.js:126
-#: components/Lookup/ProjectLookup.js:156
-#: components/NotificationList/NotificationList.js:181
-#: components/NotificationList/NotificationList.js:218
-#: components/NotificationList/NotificationListItem.js:28
-#: components/OptionsList/OptionsList.js:57
-#: components/PaginatedTable/PaginatedTable.js:72
-#: components/PromptDetail/PromptDetail.js:105
-#: components/RelatedTemplateList/RelatedTemplateList.js:161
-#: components/RelatedTemplateList/RelatedTemplateList.js:186
-#: components/ResourceAccessList/ResourceAccessListItem.js:58
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:252
-#: components/Schedule/ScheduleList/ScheduleList.js:168
-#: components/Schedule/ScheduleList/ScheduleList.js:188
-#: components/Schedule/ScheduleList/ScheduleListItem.js:80
-#: components/Schedule/shared/ScheduleForm.js:107
-#: components/TemplateList/TemplateList.js:205
-#: components/TemplateList/TemplateList.js:242
-#: components/TemplateList/TemplateListItem.js:142
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:18
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:37
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:49
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:68
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:80
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:110
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:122
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:137
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:149
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:179
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:191
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:206
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:59
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:109
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:135
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:28
-#: screens/Application/Applications.js:81
-#: screens/Application/ApplicationsList/ApplicationListItem.js:33
-#: screens/Application/ApplicationsList/ApplicationsList.js:118
-#: screens/Application/ApplicationsList/ApplicationsList.js:155
-#: screens/Application/shared/ApplicationForm.js:53
-#: screens/Credential/CredentialDetail/CredentialDetail.js:217
-#: screens/Credential/CredentialList/CredentialList.js:141
-#: screens/Credential/CredentialList/CredentialList.js:164
-#: screens/Credential/CredentialList/CredentialListItem.js:58
-#: screens/Credential/shared/CredentialForm.js:161
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:71
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js:91
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:68
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:123
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:176
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:33
-#: screens/CredentialType/shared/CredentialTypeForm.js:21
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:48
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:136
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:165
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:69
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:89
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:115
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:12
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:88
-#: screens/Host/HostDetail/HostDetail.js:69
-#: screens/Host/HostGroups/HostGroupItem.js:28
-#: screens/Host/HostGroups/HostGroupsList.js:159
-#: screens/Host/HostGroups/HostGroupsList.js:176
-#: screens/Host/HostList/HostList.js:148
-#: screens/Host/HostList/HostList.js:169
-#: screens/Host/HostList/HostListItem.js:50
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:41
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:49
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:175
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:208
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:61
-#: screens/InstanceGroup/Instances/InstanceList.js:188
-#: screens/InstanceGroup/Instances/InstanceList.js:204
-#: screens/InstanceGroup/Instances/InstanceList.js:255
-#: screens/InstanceGroup/Instances/InstanceList.js:288
-#: screens/InstanceGroup/Instances/InstanceListItem.js:124
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:44
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:19
-#: screens/Instances/InstanceList/InstanceList.js:108
-#: screens/Instances/InstanceList/InstanceList.js:125
-#: screens/Instances/InstanceList/InstanceList.js:150
-#: screens/Instances/InstanceList/InstanceListItem.js:128
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:67
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:31
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:194
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:209
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:215
-#: screens/Inventory/InventoryGroups/InventoryGroupItem.js:34
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:119
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:141
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:74
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js:36
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:169
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:186
-#: screens/Inventory/InventoryHosts/InventoryHostItem.js:33
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:119
-#: screens/Inventory/InventoryHosts/InventoryHostList.js:138
-#: screens/Inventory/InventoryList/InventoryList.js:178
-#: screens/Inventory/InventoryList/InventoryList.js:209
-#: screens/Inventory/InventoryList/InventoryList.js:218
-#: screens/Inventory/InventoryList/InventoryListItem.js:92
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:180
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:195
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:232
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:196
-#: screens/Inventory/InventorySources/InventorySourceList.js:211
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:71
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:98
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:30
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:76
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:111
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:33
-#: screens/Inventory/shared/InventoryForm.js:42
-#: screens/Inventory/shared/InventoryGroupForm.js:32
-#: screens/Inventory/shared/InventorySourceForm.js:101
-#: screens/Inventory/shared/SmartInventoryForm.js:47
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:90
-#: screens/ManagementJob/ManagementJobList/ManagementJobList.js:100
-#: screens/ManagementJob/ManagementJobList/ManagementJobListItem.js:67
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:106
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:122
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:178
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:112
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:41
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:91
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:84
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js:107
-#: screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.js:16
-#: screens/Organization/OrganizationList/OrganizationList.js:122
-#: screens/Organization/OrganizationList/OrganizationList.js:143
-#: screens/Organization/OrganizationList/OrganizationListItem.js:45
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:68
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:85
-#: screens/Organization/OrganizationTeams/OrganizationTeamListItem.js:14
-#: screens/Organization/shared/OrganizationForm.js:56
-#: screens/Project/ProjectDetail/ProjectDetail.js:175
-#: screens/Project/ProjectList/ProjectList.js:185
-#: screens/Project/ProjectList/ProjectList.js:221
-#: screens/Project/ProjectList/ProjectListItem.js:179
-#: screens/Project/shared/ProjectForm.js:170
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:146
-#: screens/Team/TeamDetail/TeamDetail.js:37
-#: screens/Team/TeamList/TeamList.js:117
-#: screens/Team/TeamList/TeamList.js:142
-#: screens/Team/TeamList/TeamListItem.js:33
-#: screens/Team/shared/TeamForm.js:29
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:178
-#: screens/Template/Survey/SurveyList.js:102
-#: screens/Template/Survey/SurveyList.js:102
-#: screens/Template/Survey/SurveyListItem.js:39
-#: screens/Template/Survey/SurveyReorderModal.js:218
-#: screens/Template/Survey/SurveyReorderModal.js:218
-#: screens/Template/Survey/SurveyReorderModal.js:238
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:111
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:69
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:88
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:122
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:154
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:178
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:88
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.js:74
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.js:94
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:75
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:95
-#: screens/Template/shared/JobTemplateForm.js:239
-#: screens/Template/shared/WorkflowJobTemplateForm.js:104
-#: screens/User/UserOrganizations/UserOrganizationList.js:75
-#: screens/User/UserOrganizations/UserOrganizationList.js:79
-#: screens/User/UserOrganizations/UserOrganizationListItem.js:13
-#: screens/User/UserRoles/UserRolesList.js:155
-#: screens/User/UserRoles/UserRolesListItem.js:12
-#: screens/User/UserTeams/UserTeamList.js:180
-#: screens/User/UserTeams/UserTeamList.js:232
-#: screens/User/UserTeams/UserTeamListItem.js:18
-#: screens/User/UserTokenList/UserTokenListItem.js:22
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:181
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:200
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:251
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:34
-msgid "Name"
-msgstr ""
-
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:21
-#~ msgid "Name cannot be changed on this Instance Group"
-#~ msgstr ""
-
-#: components/AppContainer/AppContainer.js:95
-msgid "Navigation"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:505
-#: screens/Dashboard/shared/ChartTooltip.js:106
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:57
-msgid "Never"
-msgstr ""
-
-#: components/Workflow/WorkflowNodeHelp.js:114
-msgid "Never Updated"
-msgstr ""
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalUtils.js:47
-msgid "Never expires"
-msgstr ""
-
-#: components/JobList/JobList.js:227
-#: components/Workflow/WorkflowNodeHelp.js:90
-msgid "New"
-msgstr ""
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:52
-#: components/AdHocCommands/useAdHocCredentialStep.js:29
-#: components/AdHocCommands/useAdHocDetailsStep.js:40
-#: components/AdHocCommands/useAdHocExecutionEnvironmentStep.js:22
-#: components/AddRole/AddResourceRole.js:196
-#: components/AddRole/AddResourceRole.js:231
-#: components/LaunchPrompt/LaunchPrompt.js:130
-#: components/Schedule/shared/SchedulePromptableFields.js:134
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:66
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:59
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:130
-msgid "Next"
-msgstr ""
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:259
-#: components/Schedule/ScheduleList/ScheduleList.js:170
-#: components/Schedule/ScheduleList/ScheduleListItem.js:104
-#: components/Schedule/ScheduleList/ScheduleListItem.js:108
-msgid "Next Run"
-msgstr ""
-
-#: components/Search/Search.js:232
-msgid "No"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:122
-msgid "No Hosts Matched"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:123
-#: screens/Job/JobOutput/JobOutputSearch.js:124
-msgid "No Hosts Remaining"
-msgstr ""
-
-#: screens/Job/JobOutput/HostEventModal.js:150
-msgid "No JSON Available"
-msgstr ""
-
-#: screens/Dashboard/shared/ChartTooltip.js:82
-msgid "No Jobs"
-msgstr ""
-
-#: screens/Job/JobOutput/HostEventModal.js:185
-#~ msgid "No Standard Error Available"
-#~ msgstr ""
-
-#: screens/Job/JobOutput/HostEventModal.js:166
-#~ msgid "No Standard Out Available"
-#~ msgstr ""
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:72
-msgid "No inventory sync failures."
-msgstr ""
-
-#: components/ContentEmpty/ContentEmpty.js:20
-msgid "No items found."
-msgstr ""
-
-#: screens/Host/HostList/HostListItem.js:100
-msgid "No job data available"
-msgstr ""
-
-#: screens/Job/JobOutput/EmptyOutput.js:25
-msgid "No output found for this job."
-msgstr ""
-
-#: screens/Job/JobOutput/HostEventModal.js:126
-msgid "No result found"
-msgstr ""
-
-#: components/LabelSelect/LabelSelect.js:102
-#: components/LaunchPrompt/steps/SurveyStep.js:134
-#: components/LaunchPrompt/steps/SurveyStep.js:193
-#: components/MultiSelect/TagMultiSelect.js:60
-#: components/Search/AdvancedSearch.js:151
-#: components/Search/AdvancedSearch.js:266
-#: components/Search/LookupTypeInput.js:33
-#: components/Search/RelatedLookupTypeInput.js:26
-#: components/Search/Search.js:153
-#: components/Search/Search.js:202
-#: components/Search/Search.js:226
-#: screens/ActivityStream/ActivityStream.js:142
-#: screens/Credential/shared/CredentialForm.js:143
-#: screens/Credential/shared/CredentialFormFields/BecomeMethodField.js:65
-#: screens/Dashboard/DashboardGraph.js:106
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:136
-#: screens/Template/Survey/SurveyReorderModal.js:166
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:260
-#: screens/Template/shared/PlaybookSelect.js:72
-msgid "No results found"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:116
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:137
-msgid "No subscriptions found"
-msgstr ""
-
-#: screens/Template/Survey/SurveyList.js:147
-msgid "No survey questions found."
-msgstr ""
-
-#: components/PaginatedTable/PaginatedTable.js:80
-msgid "No {pluralizedItemName} Found"
-msgstr ""
-
-#: components/Workflow/WorkflowNodeHelp.js:148
-#: components/Workflow/WorkflowNodeHelp.js:184
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:273
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:274
-msgid "Node Alias"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:216
-#: screens/InstanceGroup/Instances/InstanceList.js:193
-#: screens/InstanceGroup/Instances/InstanceList.js:257
-#: screens/InstanceGroup/Instances/InstanceList.js:289
-#: screens/InstanceGroup/Instances/InstanceListItem.js:142
-#: screens/Instances/InstanceDetail/InstanceDetail.js:154
-#: screens/Instances/InstanceList/InstanceList.js:113
-#: screens/Instances/InstanceList/InstanceList.js:152
-#: screens/Instances/InstanceList/InstanceListItem.js:150
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:118
-msgid "Node Type"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.js:75
-msgid "Node type"
-msgstr ""
-
-#: screens/TopologyView/Legend.js:68
-msgid "Node types"
-msgstr ""
-
-#: components/Workflow/WorkflowNodeHelp.js:123
-msgid "None"
-msgstr ""
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:136
-msgid "None (Run Once)"
-msgstr ""
-
-#: components/Schedule/shared/ScheduleForm.js:153
-msgid "None (run once)"
-msgstr ""
-
-#: screens/User/UserDetail/UserDetail.js:51
-#: screens/User/UserList/UserListItem.js:23
-#: screens/User/shared/UserForm.js:29
-msgid "Normal User"
-msgstr ""
-
-#: components/ContentError/ContentError.js:37
-msgid "Not Found"
-msgstr ""
-
-#: screens/Setting/shared/SettingDetail.js:71
-#: screens/Setting/shared/SettingDetail.js:112
-msgid "Not configured"
-msgstr ""
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:75
-msgid "Not configured for inventory sync."
-msgstr ""
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:248
-msgid ""
-"Note that only hosts directly in this group can\n"
-"be disassociated. Hosts in sub-groups must be disassociated\n"
-"directly from the sub-group level that they belong."
-msgstr ""
-
-#: screens/Host/HostGroups/HostGroupsList.js:212
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:230
-msgid ""
-"Note that you may still see the group in the list after\n"
-"disassociating if the host is also a member of that group’s\n"
-"children. This list shows all groups the host is associated\n"
-"with directly and indirectly."
-msgstr ""
-
-#: components/Lookup/InstanceGroupsLookup.js:90
-msgid "Note: The order in which these are selected sets the execution precedence. Select more than one to enable drag."
-msgstr ""
-
-#: screens/Organization/shared/OrganizationForm.js:116
-msgid "Note: The order of these credentials sets precedence for the sync and lookup of the content. Select more than one to enable drag."
-msgstr ""
-
-#: screens/Project/shared/Project.helptext.js:81
-msgid "Note: This field assumes the remote name is \"origin\"."
-msgstr ""
-
-#: screens/Project/shared/Project.helptext.js:35
-msgid ""
-"Note: When using SSH protocol for GitHub or\n"
-"Bitbucket, enter an SSH key only, do not enter a username\n"
-"(other than git). Additionally, GitHub and Bitbucket do\n"
-"not support password authentication when using SSH. GIT\n"
-"read only protocol (git://) does not use username or\n"
-"password information."
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:326
-msgid "Notification Color"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplate.js:58
-#: screens/NotificationTemplate/NotificationTemplateAdd.js:50
-msgid "Notification Template not found."
-msgstr ""
-
-#: screens/ActivityStream/ActivityStream.js:198
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:117
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:171
-#: screens/NotificationTemplate/NotificationTemplates.js:14
-#: screens/NotificationTemplate/NotificationTemplates.js:21
-#: util/getRelatedResourceDeleteDetails.js:180
-msgid "Notification Templates"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:133
-msgid "Notification Type"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:369
-msgid "Notification color"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:193
-msgid "Notification sent successfully"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:444
-msgid "Notification test failed."
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:197
-msgid "Notification timed out"
-msgstr ""
-
-#: components/NotificationList/NotificationList.js:190
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:131
-msgid "Notification type"
-msgstr ""
-
-#: components/NotificationList/NotificationList.js:177
-#: routeConfig.js:122
-#: screens/Inventory/Inventories.js:93
-#: screens/Inventory/InventorySource/InventorySource.js:99
-#: screens/ManagementJob/ManagementJob.js:116
-#: screens/ManagementJob/ManagementJobs.js:22
-#: screens/Organization/Organization.js:135
-#: screens/Organization/Organizations.js:33
-#: screens/Project/Project.js:114
-#: screens/Project/Projects.js:28
-#: screens/Template/Template.js:141
-#: screens/Template/Templates.js:46
-#: screens/Template/WorkflowJobTemplate.js:123
-msgid "Notifications"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:163
-msgid "November"
-msgstr ""
-
-#: components/StatusLabel/StatusLabel.js:36
-#: components/Workflow/WorkflowNodeHelp.js:117
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:66
-#: screens/Job/JobOutput/shared/HostStatusBar.js:35
-msgid "OK"
-msgstr ""
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:42
-#: components/Schedule/shared/FrequencyDetailSubform.js:542
-msgid "Occurrences"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:158
-msgid "October"
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:189
-#: components/HostToggle/HostToggle.js:61
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:164
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:167
-#: components/PromptDetail/PromptDetail.js:284
-#: components/PromptDetail/PromptJobTemplateDetail.js:151
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:318
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:58
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:46
-#: screens/Setting/shared/SettingDetail.js:98
-#: screens/Setting/shared/SharedFields.js:150
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:277
-#: screens/Template/shared/JobTemplateForm.js:455
-msgid "Off"
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:188
-#: components/HostToggle/HostToggle.js:60
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:164
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:166
-#: components/PromptDetail/PromptDetail.js:284
-#: components/PromptDetail/PromptJobTemplateDetail.js:151
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:318
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:57
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:46
-#: screens/Setting/shared/SettingDetail.js:98
-#: screens/Setting/shared/SharedFields.js:149
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:277
-#: screens/Template/shared/JobTemplateForm.js:455
-msgid "On"
-msgstr ""
-
-#: components/Workflow/WorkflowLegend.js:126
-#: components/Workflow/WorkflowLinkHelp.js:30
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:68
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:39
-msgid "On Failure"
-msgstr ""
-
-#: components/Workflow/WorkflowLegend.js:122
-#: components/Workflow/WorkflowLinkHelp.js:27
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:63
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:32
-msgid "On Success"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:529
-msgid "On date"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:244
-msgid "On days"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:18
-msgid ""
-"One Slack channel per line. The pound symbol (#)\n"
-"is required for channels. To respond to or start a thread to a specific message add the parent message Id to the channel where the parent message Id is 16 digits. A dot (.) must be manually inserted after the 10th digit. ie:#destination-channel, 1231257890.006423. See Slack"
-msgstr ""
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:166
-msgid "Only Group By"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:103
-msgid "OpenStack"
-msgstr ""
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:107
-msgid "Option Details"
-msgstr ""
-
-#: screens/Inventory/shared/Inventory.helptext.js:25
-msgid ""
-"Optional labels that describe this inventory,\n"
-"such as 'dev' or 'test'. Labels can be used to group and filter\n"
-"inventories and completed jobs."
-msgstr ""
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:11
-msgid ""
-"Optional labels that describe this job template,\n"
-"such as 'dev' or 'test'. Labels can be used to group and filter\n"
-"job templates and completed jobs."
-msgstr ""
-
-#: screens/Job/Job.helptext.js:11
-#: screens/Template/shared/JobTemplate.helptext.js:12
-msgid "Optional labels that describe this job template, such as 'dev' or 'test'. Labels can be used to group and filter job templates and completed jobs."
-msgstr ""
-
-#: screens/Template/shared/JobTemplate.helptext.js:25
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:19
-msgid "Optionally select the credential to use to send status updates back to the webhook service."
-msgstr ""
-
-#: components/NotificationList/NotificationList.js:220
-#: components/NotificationList/NotificationListItem.js:34
-#: screens/Credential/shared/TypeInputsSubForm.js:47
-#: screens/InstanceGroup/shared/ContainerGroupForm.js:61
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:65
-#: screens/Template/shared/JobTemplateForm.js:493
-#: screens/Template/shared/WorkflowJobTemplateForm.js:210
-msgid "Options"
-msgstr ""
-
-#: screens/Template/Survey/SurveyReorderModal.js:217
-#: screens/Template/Survey/SurveyReorderModal.js:217
-#: screens/Template/Survey/SurveyReorderModal.js:233
-msgid "Order"
-msgstr ""
-
-#: components/Lookup/ApplicationLookup.js:118
-#: components/Lookup/OrganizationLookup.js:101
-#: components/Lookup/OrganizationLookup.js:107
-#: components/Lookup/OrganizationLookup.js:123
-#: components/PromptDetail/PromptInventorySourceDetail.js:73
-#: components/PromptDetail/PromptInventorySourceDetail.js:83
-#: components/PromptDetail/PromptJobTemplateDetail.js:103
-#: components/PromptDetail/PromptJobTemplateDetail.js:113
-#: components/PromptDetail/PromptProjectDetail.js:77
-#: components/PromptDetail/PromptProjectDetail.js:88
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:65
-#: components/TemplateList/TemplateList.js:244
-#: components/TemplateList/TemplateListItem.js:185
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:69
-#: screens/Application/ApplicationsList/ApplicationListItem.js:38
-#: screens/Application/ApplicationsList/ApplicationsList.js:157
-#: screens/Credential/CredentialDetail/CredentialDetail.js:230
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:69
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:155
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:167
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:76
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:74
-#: screens/Inventory/InventoryList/InventoryList.js:191
-#: screens/Inventory/InventoryList/InventoryList.js:221
-#: screens/Inventory/InventoryList/InventoryListItem.js:119
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:217
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:108
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:120
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:130
-#: screens/Project/ProjectDetail/ProjectDetail.js:179
-#: screens/Project/ProjectList/ProjectListItem.js:287
-#: screens/Project/ProjectList/ProjectListItem.js:298
-#: screens/Team/TeamDetail/TeamDetail.js:40
-#: screens/Team/TeamList/TeamList.js:143
-#: screens/Team/TeamList/TeamListItem.js:38
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:192
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:203
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:121
-#: screens/User/UserTeams/UserTeamList.js:181
-#: screens/User/UserTeams/UserTeamList.js:237
-#: screens/User/UserTeams/UserTeamListItem.js:23
-msgid "Organization"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/WorkflowJobTemplatesList.js:100
-msgid "Organization (Name)"
-msgstr ""
-
-#: screens/Team/TeamList/TeamList.js:126
-msgid "Organization Name"
-msgstr ""
-
-#: screens/Organization/Organization.js:154
-msgid "Organization not found."
-msgstr ""
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:188
-#: routeConfig.js:96
-#: screens/ActivityStream/ActivityStream.js:181
-#: screens/Organization/OrganizationList/OrganizationList.js:117
-#: screens/Organization/OrganizationList/OrganizationList.js:163
-#: screens/Organization/Organizations.js:16
-#: screens/Organization/Organizations.js:26
-#: screens/User/User.js:66
-#: screens/User/UserOrganizations/UserOrganizationList.js:72
-#: screens/User/Users.js:33
-#: util/getRelatedResourceDeleteDetails.js:231
-#: util/getRelatedResourceDeleteDetails.js:265
-msgid "Organizations"
-msgstr ""
-
-#: components/LaunchPrompt/steps/useOtherPromptsStep.js:85
-msgid "Other prompts"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:57
-msgid "Out of compliance"
-msgstr ""
-
-#: screens/Job/Job.js:118
-#: screens/Job/JobOutput/HostEventModal.js:156
-#: screens/Job/Jobs.js:34
-msgid "Output"
-msgstr ""
-
-#: screens/Job/JobOutput/HostEventModal.js:157
-msgid "Output tab"
-msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:76
-msgid "Overwrite"
-msgstr ""
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:47
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:126
-msgid "Overwrite local groups and hosts from remote inventory source"
-msgstr ""
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:52
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:132
-msgid "Overwrite local variables from remote inventory source"
-msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:82
-msgid "Overwrite variables"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:478
-msgid "POST"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:479
-msgid "PUT"
-msgstr ""
-
-#: components/NotificationList/NotificationList.js:198
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:139
-msgid "Pagerduty"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:274
-msgid "Pagerduty Subdomain"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:289
-msgid "Pagerduty subdomain"
-msgstr ""
-
-#: components/Pagination/Pagination.js:35
-msgid "Pagination"
-msgstr ""
-
-#: components/Workflow/WorkflowTools.js:165
-msgid "Pan Down"
-msgstr ""
-
-#: components/Workflow/WorkflowTools.js:132
-msgid "Pan Left"
-msgstr ""
-
-#: components/Workflow/WorkflowTools.js:176
-msgid "Pan Right"
-msgstr ""
-
-#: components/Workflow/WorkflowTools.js:143
-msgid "Pan Up"
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:243
-msgid "Pass extra command line changes. There are two ansible command line parameters:"
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:413
-#~ msgid ""
-#~ "Pass extra command line variables to the playbook. This is the\n"
-#~ "-e or --extra-vars command line parameter for ansible-playbook.\n"
-#~ "Provide key/value pairs using either YAML or JSON. Refer to the\n"
-#~ "documentation for example syntax."
-#~ msgstr ""
-
-#: screens/Template/shared/WorkflowJobTemplateForm.js:215
-msgid "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. Refer to the Ansible Controller documentation for example syntax."
-msgstr ""
-
-#: screens/Job/Job.helptext.js:12
-#: screens/Template/shared/JobTemplate.helptext.js:13
-msgid "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. Refer to the documentation for example syntax."
-msgstr ""
-
-#: screens/Login/Login.js:205
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:71
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:101
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:212
-#: screens/Template/Survey/SurveyQuestionForm.js:82
-#: screens/User/shared/UserForm.js:88
-msgid "Password"
-msgstr ""
-
-#: screens/Dashboard/DashboardGraph.js:119
-msgid "Past 24 hours"
-msgstr ""
-
-#: screens/Dashboard/DashboardGraph.js:110
-msgid "Past month"
-msgstr ""
-
-#: screens/Dashboard/DashboardGraph.js:113
-msgid "Past two weeks"
-msgstr ""
-
-#: screens/Dashboard/DashboardGraph.js:116
-msgid "Past week"
-msgstr ""
-
-#: components/JobList/JobList.js:228
-#: components/StatusLabel/StatusLabel.js:41
-#: components/Workflow/WorkflowNodeHelp.js:93
-msgid "Pending"
-msgstr ""
-
-#: components/AppContainer/PageHeaderToolbar.js:83
-msgid "Pending Workflow Approvals"
-msgstr ""
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:128
-msgid "Pending delete"
-msgstr ""
-
-#: components/Lookup/HostFilterLookup.js:369
-msgid "Perform a search to define a host filter"
-msgstr ""
-
-#: screens/User/UserTokenDetail/UserTokenDetail.js:72
-#: screens/User/UserTokenList/UserTokenList.js:105
-msgid "Personal Access Token"
-msgstr ""
-
-#: screens/User/UserTokenList/UserTokenListItem.js:26
-msgid "Personal access token"
-msgstr ""
-
-#: screens/Job/JobOutput/HostEventModal.js:122
-msgid "Play"
-msgstr ""
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:84
-msgid "Play Count"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:125
-msgid "Play Started"
-msgstr ""
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:146
-#: screens/Job/JobDetail/JobDetail.js:314
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:246
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:43
-#: screens/Template/shared/JobTemplateForm.js:350
-msgid "Playbook"
-msgstr ""
-
-#: components/JobList/JobListItem.js:44
-#: screens/Job/JobDetail/JobDetail.js:66
-msgid "Playbook Check"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:126
-msgid "Playbook Complete"
-msgstr ""
-
-#: components/PromptDetail/PromptProjectDetail.js:150
-#: screens/Project/ProjectDetail/ProjectDetail.js:270
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:71
-msgid "Playbook Directory"
-msgstr ""
-
-#: components/JobList/JobList.js:213
-#: components/JobList/JobListItem.js:44
-#: components/Schedule/ScheduleList/ScheduleListItem.js:37
-#: screens/Job/JobDetail/JobDetail.js:66
-msgid "Playbook Run"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:127
-msgid "Playbook Started"
-msgstr ""
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:174
-#: components/TemplateList/TemplateList.js:222
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:23
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:54
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:159
-msgid "Playbook name"
-msgstr ""
-
-#: screens/Dashboard/DashboardGraph.js:146
-msgid "Playbook run"
-msgstr ""
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:85
-msgid "Plays"
-msgstr ""
-
-#: components/Schedule/ScheduleList/ScheduleList.js:149
-msgid "Please add a Schedule to populate this list."
-msgstr ""
-
-#: components/Schedule/ScheduleList/ScheduleList.js:152
-msgid "Please add a Schedule to populate this list. Schedules can be added to a Template, Project, or Inventory Source."
-msgstr ""
-
-#: screens/Template/Survey/SurveyList.js:146
-msgid "Please add survey questions."
-msgstr ""
-
-#: components/PaginatedTable/PaginatedTable.js:93
-msgid "Please add {pluralizedItemName} to populate this list"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:43
-msgid "Please click the Start button to begin."
-msgstr ""
-
-#: util/validators.js:160
-msgid "Please enter a valid URL"
-msgstr ""
-
-#: screens/User/shared/UserTokenForm.js:20
-msgid "Please enter a value."
-msgstr ""
-
-#: screens/Login/Login.js:169
-msgid "Please log in"
-msgstr ""
-
-#: components/JobList/JobList.js:190
-msgid "Please run a job to populate this list."
-msgstr ""
-
-#: components/Schedule/shared/ScheduleForm.js:622
-msgid "Please select a day number between 1 and 31."
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:170
-msgid "Please select an Inventory or check the Prompt on Launch option"
-msgstr ""
-
-#: components/Schedule/shared/ScheduleForm.js:614
-msgid "Please select an end date/time that comes after the start date/time."
-msgstr ""
-
-#: components/Lookup/HostFilterLookup.js:358
-msgid "Please select an organization before editing the host filter"
-msgstr ""
-
-#: screens/Job/JobOutput/EmptyOutput.js:20
-msgid "Please try another search using the filter above"
-msgstr ""
-
-#: screens/TopologyView/ContentLoading.js:40
-msgid "Please wait until the topology view is populated..."
-msgstr ""
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:78
-msgid "Pod spec override"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:207
-#: screens/InstanceGroup/Instances/InstanceListItem.js:203
-#: screens/Instances/InstanceDetail/InstanceDetail.js:158
-#: screens/Instances/InstanceList/InstanceListItem.js:218
-msgid "Policy Type"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:63
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:26
-msgid "Policy instance minimum"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:70
-#: screens/InstanceGroup/shared/InstanceGroupForm.js:36
-msgid "Policy instance percentage"
-msgstr ""
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginField.js:64
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginField.js:70
-msgid "Populate field from an external secret management system"
-msgstr ""
-
-#: components/Lookup/HostFilterLookup.js:322
-#~ msgid ""
-#~ "Populate the hosts for this inventory by using a search\n"
-#~ "filter. Example: ansible_facts.ansible_distribution:\"RedHat\".\n"
-#~ "Refer to the documentation for further syntax and\n"
-#~ "examples. Refer to the Ansible Tower documentation for further syntax and\n"
-#~ "examples."
-#~ msgstr ""
-
-#: components/Lookup/HostFilterLookup.js:348
-msgid ""
-"Populate the hosts for this inventory by using a search\n"
-"filter. Example: ansible_facts__ansible_distribution:\"RedHat\".\n"
-"Refer to the documentation for further syntax and\n"
-"examples. Refer to the Ansible Controller documentation for further syntax and\n"
-"examples."
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:164
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:102
-msgid "Port"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:231
-msgid "Preconditions for running this node when there are multiple parents. Refer to the"
-msgstr ""
-
-#: screens/Template/Survey/MultipleChoiceField.js:59
-msgid ""
-"Press 'Enter' to add more answer choices. One answer\n"
-"choice per line."
-msgstr ""
-
-#: components/CodeEditor/CodeEditor.js:181
-msgid "Press Enter to edit. Press ESC to stop editing."
-msgstr ""
-
-#: components/SelectedList/DraggableSelectedList.js:85
-msgid ""
-"Press space or enter to begin dragging,\n"
-"and use the arrow keys to navigate up or down.\n"
-"Press enter to confirm the drag, or any other key to\n"
-"cancel the drag operation."
-msgstr ""
-
-#: components/AdHocCommands/useAdHocPreviewStep.js:17
-#: components/LaunchPrompt/steps/usePreviewStep.js:23
-msgid "Preview"
-msgstr ""
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:103
-msgid "Private key passphrase"
-msgstr ""
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:58
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:120
-#: screens/Template/shared/JobTemplateForm.js:499
-msgid "Privilege Escalation"
-msgstr ""
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:111
-msgid "Privilege escalation password"
-msgstr ""
-
-#: screens/Template/shared/JobTemplate.helptext.js:37
-msgid "Privilege escalation: If enabled, run this playbook as an administrator."
-msgstr ""
-
-#: components/JobList/JobListItem.js:239
-#: components/Lookup/ProjectLookup.js:104
-#: components/Lookup/ProjectLookup.js:109
-#: components/Lookup/ProjectLookup.js:165
-#: components/PromptDetail/PromptInventorySourceDetail.js:98
-#: components/PromptDetail/PromptJobTemplateDetail.js:131
-#: components/PromptDetail/PromptJobTemplateDetail.js:139
-#: components/TemplateList/TemplateListItem.js:299
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:231
-#: screens/Job/JobDetail/JobDetail.js:158
-#: screens/Job/JobDetail/JobDetail.js:176
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:222
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:232
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js:38
-msgid "Project"
-msgstr ""
-
-#: components/PromptDetail/PromptProjectDetail.js:143
-#: screens/Project/ProjectDetail/ProjectDetail.js:263
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:60
-msgid "Project Base Path"
-msgstr ""
-
-#: screens/Job/JobDetail/JobDetail.js:227
-#~ msgid "Project Status"
-#~ msgstr ""
-
-#: components/Workflow/WorkflowLegend.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:85
-msgid "Project Sync"
-msgstr ""
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:302
-#: screens/Project/ProjectList/ProjectListItem.js:229
-msgid "Project Sync Error"
-msgstr ""
-
-#: components/Workflow/WorkflowNodeHelp.js:67
-msgid "Project Update"
-msgstr ""
-
-#: screens/Job/JobDetail/JobDetail.js:164
-msgid "Project Update Status"
-msgstr ""
-
-#: screens/Job/Job.helptext.js:21
-msgid "Project checkout results"
-msgstr ""
-
-#: screens/Project/ProjectList/ProjectList.js:132
-msgid "Project copied successfully"
-msgstr ""
-
-#: screens/Project/Project.js:136
-msgid "Project not found."
-msgstr ""
-
-#: screens/Dashboard/Dashboard.js:109
-msgid "Project sync failures"
-msgstr ""
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:146
-#: routeConfig.js:75
-#: screens/ActivityStream/ActivityStream.js:170
-#: screens/Dashboard/Dashboard.js:103
-#: screens/Project/ProjectList/ProjectList.js:180
-#: screens/Project/ProjectList/ProjectList.js:249
-#: screens/Project/Projects.js:12
-#: screens/Project/Projects.js:22
-#: util/getRelatedResourceDeleteDetails.js:59
-#: util/getRelatedResourceDeleteDetails.js:194
-#: util/getRelatedResourceDeleteDetails.js:224
-msgid "Projects"
-msgstr ""
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:139
-msgid "Promote Child Groups and Hosts"
-msgstr ""
-
-#: components/Schedule/shared/ScheduleForm.js:670
-#: components/Schedule/shared/ScheduleForm.js:673
-msgid "Prompt"
-msgstr ""
-
-#: components/PromptDetail/PromptDetail.js:173
-msgid "Prompt Overrides"
-msgstr ""
-
-#: components/CodeEditor/VariablesField.js:241
-#: components/FieldWithPrompt/FieldWithPrompt.js:46
-#: screens/Credential/CredentialDetail/CredentialDetail.js:175
-msgid "Prompt on launch"
-msgstr ""
-
-#: components/Schedule/shared/SchedulePromptableFields.js:104
-msgid "Prompt | {0}"
-msgstr ""
-
-#: components/PromptDetail/PromptDetail.js:171
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:282
-msgid "Prompted Values"
-msgstr ""
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:6
-msgid ""
-"Provide a host pattern to further constrain\n"
-"the list of hosts that will be managed or affected by the\n"
-"playbook. Multiple patterns are allowed. Refer to Ansible\n"
-"documentation for more information and examples on patterns."
-msgstr ""
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:37
-msgid ""
-"Provide a host pattern to further constrain the list\n"
-"of hosts that will be managed or affected by the playbook. Multiple\n"
-"patterns are allowed. Refer to Ansible documentation for more\n"
-"information and examples on patterns."
-msgstr ""
-
-#: screens/Job/Job.helptext.js:13
-#: screens/Template/shared/JobTemplate.helptext.js:14
-msgid "Provide a host pattern to further constrain the list of hosts that will be managed or affected by the playbook. Multiple patterns are allowed. Refer to Ansible documentation for more information and examples on patterns."
-msgstr ""
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:179
-msgid "Provide a value for this field or select the Prompt on launch option."
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:247
-msgid ""
-"Provide key/value pairs using either\n"
-"YAML or JSON."
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:191
-msgid ""
-"Provide your Red Hat or Red Hat Satellite credentials\n"
-"below and you can choose from a list of your available subscriptions.\n"
-"The credentials you use will be stored for future use in\n"
-"retrieving renewal or expanded subscriptions."
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:83
-msgid "Provide your Red Hat or Red Hat Satellite credentials to enable Automation Analytics."
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:83
-#~ msgid "Provide your Red Hat or Red Hat Satellite credentials to enable Insights for Ansible Automation Platform."
-#~ msgstr ""
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:157
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:295
-#: screens/Template/shared/JobTemplateForm.js:562
-msgid "Provisioning Callback URL"
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:557
-msgid "Provisioning Callback details"
-msgstr ""
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:63
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:125
-#: screens/Template/shared/JobTemplateForm.js:503
-#: screens/Template/shared/JobTemplateForm.js:506
-msgid "Provisioning Callbacks"
-msgstr ""
-
-#: screens/Template/shared/JobTemplate.helptext.js:38
-msgid "Provisioning callbacks: Enables creation of a provisioning callback URL. Using the URL a host can contact Ansible AWX and request a configuration update using this job template."
-msgstr ""
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:85
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:113
-msgid "Pull"
-msgstr ""
-
-#: screens/Template/Survey/SurveyQuestionForm.js:163
-msgid "Question"
-msgstr ""
-
-#: screens/Setting/Settings.js:102
-msgid "RADIUS"
-msgstr ""
-
-#: screens/Setting/SettingList.js:73
-msgid "RADIUS settings"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:237
-#: screens/InstanceGroup/Instances/InstanceListItem.js:161
-#: screens/Instances/InstanceDetail/InstanceDetail.js:187
-#: screens/Instances/InstanceList/InstanceListItem.js:171
-msgid "RAM {0}"
-msgstr ""
-
-#: screens/User/shared/UserTokenForm.js:76
-msgid "Read"
-msgstr ""
-
-#: screens/Dashboard/Dashboard.js:133
-msgid "Recent Jobs"
-msgstr ""
-
-#: screens/Dashboard/Dashboard.js:131
-msgid "Recent Jobs list tab"
-msgstr ""
-
-#: screens/Dashboard/Dashboard.js:145
-msgid "Recent Templates"
-msgstr ""
-
-#: screens/Dashboard/Dashboard.js:143
-msgid "Recent Templates list tab"
-msgstr ""
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:188
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js:112
-#: screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js:38
-msgid "Recent jobs"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:153
-msgid "Recipient List"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:84
-msgid "Recipient list"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:105
-msgid "Red Hat Ansible Automation Platform"
-msgstr ""
-
-#: components/Lookup/ProjectLookup.js:138
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:92
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:161
-#: screens/Job/JobDetail/JobDetail.js:76
-#: screens/Project/ProjectList/ProjectList.js:201
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:100
-msgid "Red Hat Insights"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:102
-msgid "Red Hat Satellite 6"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:104
-msgid "Red Hat Virtualization"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:117
-msgid "Red Hat subscription manifest"
-msgstr ""
-
-#: components/About/About.js:36
-msgid "Red Hat, Inc."
-msgstr ""
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:93
-#: screens/Application/shared/ApplicationForm.js:106
-msgid "Redirect URIs"
-msgstr ""
-
-#: screens/Application/ApplicationDetails/ApplicationDetails.js:91
-#~ msgid "Redirect uris"
-#~ msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:259
-msgid "Redirecting to dashboard"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:263
-msgid "Redirecting to subscription detail"
-msgstr ""
-
-#: screens/Template/Survey/SurveyQuestionForm.js:261
-msgid "Refer to the"
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:433
-#~ msgid ""
-#~ "Refer to the Ansible documentation for details\n"
-#~ "about the configuration file."
-#~ msgstr ""
-
-#: screens/Job/Job.helptext.js:26
-#: screens/Template/shared/JobTemplate.helptext.js:46
-msgid "Refer to the Ansible documentation for details about the configuration file."
-msgstr ""
-
-#: screens/User/UserTokens/UserTokens.js:77
-msgid "Refresh Token"
-msgstr ""
-
-#: screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js:81
-msgid "Refresh Token Expiration"
-msgstr ""
-
-#: screens/Project/ProjectList/ProjectListItem.js:132
-msgid "Refresh for revision"
-msgstr ""
-
-#: screens/Project/ProjectList/ProjectListItem.js:134
-msgid "Refresh project revision"
-msgstr ""
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:128
-msgid "Regions"
-msgstr ""
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:91
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:142
-msgid "Registry credential"
-msgstr ""
-
-#: screens/Inventory/shared/Inventory.helptext.js:156
-msgid "Regular expression where only matching host names will be imported. The filter is applied as a post-processing step after any inventory plugin filters are applied."
-msgstr ""
-
-#: screens/Inventory/Inventories.js:81
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:62
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:175
-msgid "Related Groups"
-msgstr ""
-
-#: components/Search/AdvancedSearch.js:287
-msgid "Related Keys"
-msgstr ""
-
-#: components/Search/RelatedLookupTypeInput.js:16
-#: components/Search/RelatedLookupTypeInput.js:24
-msgid "Related search type"
-msgstr ""
-
-#: components/Search/RelatedLookupTypeInput.js:19
-msgid "Related search type typeahead"
-msgstr ""
-
-#: components/JobList/JobListItem.js:146
-#: components/LaunchButton/ReLaunchDropDown.js:82
-#: screens/Job/JobDetail/JobDetail.js:562
-#: screens/Job/JobDetail/JobDetail.js:570
-#: screens/Job/JobOutput/shared/OutputToolbar.js:167
-msgid "Relaunch"
-msgstr ""
-
-#: components/JobList/JobListItem.js:126
-#: screens/Job/JobOutput/shared/OutputToolbar.js:147
-msgid "Relaunch Job"
-msgstr ""
-
-#: components/LaunchButton/ReLaunchDropDown.js:41
-msgid "Relaunch all hosts"
-msgstr ""
-
-#: components/LaunchButton/ReLaunchDropDown.js:54
-msgid "Relaunch failed hosts"
-msgstr ""
-
-#: components/LaunchButton/ReLaunchDropDown.js:30
-#: components/LaunchButton/ReLaunchDropDown.js:35
-msgid "Relaunch on"
-msgstr ""
-
-#: components/JobList/JobListItem.js:125
-#: screens/Job/JobOutput/shared/OutputToolbar.js:146
-msgid "Relaunch using host parameters"
-msgstr ""
-
-#: components/Lookup/ProjectLookup.js:137
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:91
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:160
-#: screens/Job/JobDetail/JobDetail.js:77
-#: screens/Project/ProjectList/ProjectList.js:200
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:99
-msgid "Remote Archive"
-msgstr ""
-
-#: components/SelectedList/DraggableSelectedList.js:105
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:21
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:29
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:40
-msgid "Remove"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.js:36
-msgid "Remove All Nodes"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:17
-msgid "Remove Link"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:18
-#~ msgid "Remove Node"
-#~ msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.js:28
-msgid "Remove Node {nodeName}"
-msgstr ""
-
-#: screens/Project/shared/Project.helptext.js:109
-msgid "Remove any local modifications prior to performing an update."
-msgstr ""
-
-#: components/Search/AdvancedSearch.js:206
-msgid "Remove the current search related to ansible facts to enable another search using this key."
-msgstr ""
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:14
-msgid "Remove {0} Access"
-msgstr ""
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:45
-msgid "Remove {0} chip"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.js:48
-msgid "Removing this link will orphan the rest of the branch and cause it to be executed immediately on launch."
-msgstr ""
-
-#: components/SelectedList/DraggableSelectedList.js:83
-msgid "Reorder"
-msgstr ""
-
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:264
-msgid "Repeat Frequency"
-msgstr ""
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:52
-msgid "Replace"
-msgstr ""
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:60
-msgid "Replace field with new value"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:67
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:74
-msgid "Request subscription"
-msgstr ""
-
-#: screens/Template/Survey/SurveyListItem.js:51
-#: screens/Template/Survey/SurveyQuestionForm.js:188
-msgid "Required"
-msgstr ""
-
-#: screens/TopologyView/Header.js:87
-#: screens/TopologyView/Header.js:90
-msgid "Reset zoom"
-msgstr ""
-
-#: components/Workflow/WorkflowNodeHelp.js:154
-#: components/Workflow/WorkflowNodeHelp.js:190
-#: screens/Team/TeamRoles/TeamRoleListItem.js:12
-#: screens/Team/TeamRoles/TeamRolesList.js:180
-msgid "Resource Name"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:227
-msgid "Resource deleted"
-msgstr ""
-
-#: routeConfig.js:61
-#: screens/ActivityStream/ActivityStream.js:159
-msgid "Resources"
-msgstr ""
-
-#: components/TemplateList/TemplateListItem.js:149
-msgid "Resources are missing from this template."
-msgstr ""
-
-#: screens/Setting/shared/RevertButton.js:43
-msgid "Restore initial value."
-msgstr ""
-
-#: screens/Inventory/shared/Inventory.helptext.js:153
-msgid ""
-"Retrieve the enabled state from the given dict of host variables.\n"
-"The enabled variable may be specified using dot notation, e.g: 'foo.bar'"
-msgstr ""
-
-#: components/JobCancelButton/JobCancelButton.js:81
-#: components/JobCancelButton/JobCancelButton.js:85
-#: components/JobList/JobListCancelButton.js:160
-#: components/JobList/JobListCancelButton.js:163
-#: screens/Job/JobOutput/JobOutput.js:764
-#: screens/Job/JobOutput/JobOutput.js:767
-msgid "Return"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:129
-msgid "Return to subscription management."
-msgstr ""
-
-#: components/Search/AdvancedSearch.js:171
-msgid "Returns results that have values other than this one as well as other filters."
-msgstr ""
-
-#: components/Search/AdvancedSearch.js:158
-msgid "Returns results that satisfy this one as well as other filters. This is the default set type if nothing is selected."
-msgstr ""
-
-#: components/Search/AdvancedSearch.js:164
-msgid "Returns results that satisfy this one or any other filters."
-msgstr ""
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:52
-#: screens/Setting/shared/RevertButton.js:53
-#: screens/Setting/shared/RevertButton.js:62
-msgid "Revert"
-msgstr ""
-
-#: screens/Setting/shared/RevertAllAlert.js:23
-msgid "Revert all"
-msgstr ""
-
-#: screens/Setting/shared/RevertFormActionGroup.js:21
-#: screens/Setting/shared/RevertFormActionGroup.js:27
-msgid "Revert all to default"
-msgstr ""
-
-#: screens/Credential/shared/CredentialFormFields/CredentialField.js:59
-msgid "Revert field to previously saved value"
-msgstr ""
-
-#: screens/Setting/shared/RevertAllAlert.js:11
-msgid "Revert settings"
-msgstr ""
-
-#: screens/Setting/shared/RevertButton.js:42
-msgid "Revert to factory default."
-msgstr ""
-
-#: screens/Job/JobDetail/JobDetail.js:309
-#: screens/Project/ProjectList/ProjectList.js:224
-#: screens/Project/ProjectList/ProjectListItem.js:221
-msgid "Revision"
-msgstr ""
-
-#: screens/Project/shared/ProjectSubForms/SvnSubForm.js:20
-msgid "Revision #"
-msgstr ""
-
-#: components/NotificationList/NotificationList.js:199
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:140
-msgid "Rocket.Chat"
-msgstr ""
-
-#: screens/Team/TeamRoles/TeamRoleListItem.js:20
-#: screens/Team/TeamRoles/TeamRolesList.js:148
-#: screens/Team/TeamRoles/TeamRolesList.js:182
-#: screens/User/UserList/UserList.js:163
-#: screens/User/UserList/UserListItem.js:55
-#: screens/User/UserRoles/UserRolesList.js:146
-#: screens/User/UserRoles/UserRolesList.js:157
-#: screens/User/UserRoles/UserRolesListItem.js:26
-msgid "Role"
-msgstr ""
-
-#: components/ResourceAccessList/ResourceAccessList.js:189
-#: components/ResourceAccessList/ResourceAccessList.js:202
-#: components/ResourceAccessList/ResourceAccessList.js:229
-#: components/ResourceAccessList/ResourceAccessListItem.js:69
-#: screens/Team/Team.js:59
-#: screens/Team/Teams.js:32
-#: screens/User/User.js:71
-#: screens/User/Users.js:31
-msgid "Roles"
-msgstr ""
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:99
-#: components/Workflow/WorkflowLinkHelp.js:39
-#: screens/Credential/shared/ExternalTestModal.js:89
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:49
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:23
-#: screens/Template/shared/JobTemplateForm.js:210
-msgid "Run"
-msgstr ""
-
-#: components/AdHocCommands/AdHocCommands.js:131
-#: components/AdHocCommands/AdHocCommands.js:135
-#: components/AdHocCommands/AdHocCommands.js:141
-#: components/AdHocCommands/AdHocCommands.js:145
-#: screens/Job/JobDetail/JobDetail.js:67
-msgid "Run Command"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:270
-#: screens/Instances/InstanceDetail/InstanceDetail.js:225
-msgid "Run a health check on the instance"
-msgstr ""
-
-#: components/AdHocCommands/AdHocCommands.js:125
-msgid "Run ad hoc command"
-msgstr ""
-
-#: components/AdHocCommands/AdHocCommandsWizard.js:49
-msgid "Run command"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:216
-msgid "Run every"
-msgstr ""
-
-#: components/Schedule/shared/ScheduleForm.js:148
-msgid "Run frequency"
-msgstr ""
-
-#: components/HealthCheckButton/HealthCheckButton.js:32
-#: components/HealthCheckButton/HealthCheckButton.js:45
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:279
-#: screens/Instances/InstanceDetail/InstanceDetail.js:234
-msgid "Run health check"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:337
-msgid "Run on"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useRunTypeStep.js:32
-msgid "Run type"
-msgstr ""
-
-#: components/JobList/JobList.js:230
-#: components/StatusLabel/StatusLabel.js:40
-#: components/TemplateList/TemplateListItem.js:118
-#: components/Workflow/WorkflowNodeHelp.js:99
-msgid "Running"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:128
-msgid "Running Handlers"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:210
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:210
-#: screens/InstanceGroup/Instances/InstanceListItem.js:194
-#: screens/Instances/InstanceDetail/InstanceDetail.js:161
-#: screens/Instances/InstanceList/InstanceListItem.js:209
-msgid "Running Jobs"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:277
-#: screens/Instances/InstanceDetail/InstanceDetail.js:232
-msgid "Running health check"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:71
-msgid "Running jobs"
-msgstr ""
-
-#: screens/Setting/Settings.js:105
-msgid "SAML"
-msgstr ""
-
-#: screens/Setting/SettingList.js:77
-msgid "SAML settings"
-msgstr ""
-
-#: screens/Dashboard/DashboardGraph.js:143
-msgid "SCM update"
-msgstr ""
-
-#: screens/User/UserDetail/UserDetail.js:58
-#: screens/User/UserList/UserListItem.js:49
-msgid "SOCIAL"
-msgstr ""
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:95
-msgid "SSH password"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:234
-msgid "SSL Connection"
-msgstr ""
-
-#: components/Workflow/WorkflowStartNode.js:60
-#: components/Workflow/workflowReducer.js:413
-msgid "START"
-msgstr ""
-
-#: components/Sparkline/Sparkline.js:31
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:175
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:39
-#: screens/Project/ProjectDetail/ProjectDetail.js:135
-#: screens/Project/ProjectList/ProjectListItem.js:73
-msgid "STATUS:"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:314
-msgid "Sat"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:319
-#: components/Schedule/shared/FrequencyDetailSubform.js:451
-msgid "Saturday"
-msgstr ""
-
-#: components/AddRole/AddResourceRole.js:247
-#: components/AssociateModal/AssociateModal.js:104
-#: components/AssociateModal/AssociateModal.js:110
-#: components/FormActionGroup/FormActionGroup.js:13
-#: components/FormActionGroup/FormActionGroup.js:19
-#: components/Schedule/shared/ScheduleForm.js:656
-#: components/Schedule/shared/ScheduleForm.js:662
-#: components/Schedule/shared/useSchedulePromptSteps.js:45
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:130
-#: screens/Credential/shared/CredentialForm.js:318
-#: screens/Credential/shared/CredentialForm.js:323
-#: screens/Setting/shared/RevertFormActionGroup.js:12
-#: screens/Setting/shared/RevertFormActionGroup.js:18
-#: screens/Template/Survey/SurveyReorderModal.js:205
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:35
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js:129
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:158
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:162
-msgid "Save"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:33
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:36
-msgid "Save & Exit"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:32
-msgid "Save link changes"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:254
-msgid "Save successful!"
-msgstr ""
-
-#: components/JobList/JobListItem.js:181
-#: components/JobList/JobListItem.js:187
-msgid "Schedule"
-msgstr ""
-
-#: screens/Project/Projects.js:34
-#: screens/Template/Templates.js:54
-msgid "Schedule Details"
-msgstr ""
-
-#: components/Schedule/shared/ScheduleForm.js:455
-msgid "Schedule Rules"
-msgstr ""
-
-#: screens/Inventory/Inventories.js:92
-msgid "Schedule details"
-msgstr ""
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:49
-msgid "Schedule is active"
-msgstr ""
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:49
-msgid "Schedule is inactive"
-msgstr ""
-
-#: components/Schedule/shared/ScheduleForm.js:566
-msgid "Schedule is missing rrule"
-msgstr ""
-
-#: components/Schedule/Schedule.js:82
-msgid "Schedule not found."
-msgstr ""
-
-#: components/Schedule/ScheduleList/ScheduleList.js:163
-#: components/Schedule/ScheduleList/ScheduleList.js:228
-#: routeConfig.js:44
-#: screens/ActivityStream/ActivityStream.js:153
-#: screens/Inventory/Inventories.js:89
-#: screens/Inventory/InventorySource/InventorySource.js:88
-#: screens/ManagementJob/ManagementJob.js:108
-#: screens/ManagementJob/ManagementJobs.js:23
-#: screens/Project/Project.js:120
-#: screens/Project/Projects.js:31
-#: screens/Schedule/AllSchedules.js:21
-#: screens/Template/Template.js:148
-#: screens/Template/Templates.js:51
-#: screens/Template/WorkflowJobTemplate.js:130
-msgid "Schedules"
-msgstr ""
-
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:136
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:33
-#: screens/User/UserTokenDetail/UserTokenDetail.js:49
-#: screens/User/UserTokenList/UserTokenList.js:142
-#: screens/User/UserTokenList/UserTokenList.js:189
-#: screens/User/UserTokenList/UserTokenListItem.js:32
-#: screens/User/shared/UserTokenForm.js:68
-msgid "Scope"
-msgstr ""
-
-#: screens/User/shared/User.helptext.js:5
-msgid "Scope for the token's access"
-msgstr ""
-
-#: screens/Job/JobOutput/PageControls.js:79
-msgid "Scroll first"
-msgstr ""
-
-#: screens/Job/JobOutput/PageControls.js:87
-msgid "Scroll last"
-msgstr ""
-
-#: screens/Job/JobOutput/PageControls.js:71
-msgid "Scroll next"
-msgstr ""
-
-#: screens/Job/JobOutput/PageControls.js:63
-msgid "Scroll previous"
-msgstr ""
-
-#: components/Lookup/HostFilterLookup.js:289
-#: components/Lookup/Lookup.js:137
-msgid "Search"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:152
-msgid "Search is disabled while the job is running"
-msgstr ""
-
-#: components/Search/AdvancedSearch.js:311
-#: components/Search/Search.js:259
-msgid "Search submit button"
-msgstr ""
-
-#: components/Search/Search.js:248
-msgid "Search text input"
-msgstr ""
-
-#: components/Lookup/HostFilterLookup.js:397
-msgid "Searching by ansible_facts requires special syntax. Refer to the"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:401
-msgid "Second"
-msgstr ""
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:114
-#: components/PromptDetail/PromptProjectDetail.js:138
-#: screens/Project/ProjectDetail/ProjectDetail.js:251
-msgid "Seconds"
-msgstr ""
-
-#: components/AdHocCommands/AdHocPreviewStep.js:35
-#: components/LaunchPrompt/steps/PreviewStep.js:63
-msgid "See errors on the left"
-msgstr ""
-
-#: components/JobList/JobListItem.js:84
-#: components/Lookup/HostFilterLookup.js:379
-#: components/Lookup/Lookup.js:194
-#: components/Pagination/Pagination.js:33
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:98
-msgid "Select"
-msgstr ""
-
-#: screens/Credential/shared/CredentialForm.js:129
-msgid "Select Credential Type"
-msgstr ""
-
-#: screens/Host/HostGroups/HostGroupsList.js:237
-#: screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js:254
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js:257
-msgid "Select Groups"
-msgstr ""
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js:278
-msgid "Select Hosts"
-msgstr ""
-
-#: components/AnsibleSelect/AnsibleSelect.js:38
-msgid "Select Input"
-msgstr ""
-
-#: screens/InstanceGroup/Instances/InstanceList.js:284
-msgid "Select Instances"
-msgstr ""
-
-#: components/AssociateModal/AssociateModal.js:22
-msgid "Select Items"
-msgstr ""
-
-#: components/AddRole/AddResourceRole.js:201
-msgid "Select Items from List"
-msgstr ""
-
-#: components/LabelSelect/LabelSelect.js:99
-msgid "Select Labels"
-msgstr ""
-
-#: components/AddRole/AddResourceRole.js:236
-msgid "Select Roles to Apply"
-msgstr ""
-
-#: screens/User/UserTeams/UserTeamList.js:251
-msgid "Select Teams"
-msgstr ""
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:25
-msgid "Select a JSON formatted service account key to autopopulate the following fields."
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:122
-msgid "Select a Node Type"
-msgstr ""
-
-#: components/AddRole/AddResourceRole.js:170
-msgid "Select a Resource Type"
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:334
-#~ msgid ""
-#~ "Select a branch for the job template. This branch is applied to\n"
-#~ "all job template nodes that prompt for a branch."
-#~ msgstr ""
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:48
-msgid "Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch"
-msgstr ""
-
-#: screens/Job/Job.helptext.js:20
-#: screens/Template/shared/JobTemplate.helptext.js:26
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:10
-msgid "Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch."
-msgstr ""
-
-#: screens/Credential/shared/CredentialForm.js:139
-msgid "Select a credential Type"
-msgstr ""
-
-#: screens/Metrics/Metrics.js:193
-#~ msgid "Select a instance"
-#~ msgstr ""
-
-#: components/JobList/JobListCancelButton.js:98
-msgid "Select a job to cancel"
-msgstr ""
-
-#: screens/Metrics/Metrics.js:211
-msgid "Select a metric"
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:75
-msgid "Select a module"
-msgstr ""
-
-#: screens/Template/shared/PlaybookSelect.js:60
-#: screens/Template/shared/PlaybookSelect.js:61
-msgid "Select a playbook"
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:319
-msgid "Select a project before editing the execution environment."
-msgstr ""
-
-#: screens/Template/Survey/SurveyToolbar.js:82
-msgid "Select a question to delete"
-msgstr ""
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListApproveButton.js:19
-#~ msgid "Select a row to approve"
-#~ msgstr ""
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:160
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:103
-msgid "Select a row to delete"
-msgstr ""
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListDenyButton.js:19
-#~ msgid "Select a row to deny"
-#~ msgstr ""
-
-#: components/DisassociateButton/DisassociateButton.js:75
-msgid "Select a row to disassociate"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:87
-msgid "Select a subscription"
-msgstr ""
-
-#: components/HostForm/HostForm.js:39
-#: components/Schedule/shared/FrequencyDetailSubform.js:59
-#: components/Schedule/shared/FrequencyDetailSubform.js:87
-#: components/Schedule/shared/FrequencyDetailSubform.js:91
-#: components/Schedule/shared/FrequencyDetailSubform.js:99
-#: components/Schedule/shared/ScheduleForm.js:95
-#: components/Schedule/shared/ScheduleForm.js:99
-#: screens/Credential/shared/CredentialForm.js:44
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js:78
-#: screens/Inventory/shared/InventoryForm.js:64
-#: screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.js:44
-#: screens/Inventory/shared/InventorySourceSubForms/GCESubForm.js:44
-#: screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.js:44
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:36
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:96
-#: screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.js:43
-#: screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.js:45
-#: screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.js:45
-#: screens/Inventory/shared/SmartInventoryForm.js:67
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:24
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:61
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:421
-#: screens/Project/shared/ProjectForm.js:190
-#: screens/Project/shared/ProjectSubForms/InsightsSubForm.js:39
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:36
-#: screens/Team/shared/TeamForm.js:49
-#: screens/Template/Survey/SurveyQuestionForm.js:30
-#: screens/Template/shared/WorkflowJobTemplateForm.js:125
-#: screens/User/shared/UserForm.js:139
-msgid "Select a value for this field"
-msgstr ""
-
-#: screens/Template/shared/JobTemplate.helptext.js:22
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:20
-msgid "Select a webhook service."
-msgstr ""
-
-#: components/DataListToolbar/DataListToolbar.js:121
-#: components/DataListToolbar/DataListToolbar.js:125
-#: screens/Template/Survey/SurveyToolbar.js:49
-msgid "Select all"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStream.js:129
-msgid "Select an activity type"
-msgstr ""
-
-#: screens/Metrics/Metrics.js:200
-msgid "Select an instance"
-msgstr ""
-
-#: screens/Metrics/Metrics.js:242
-msgid "Select an instance and a metric to show chart"
-msgstr ""
-
-#: components/HealthCheckButton/HealthCheckButton.js:19
-msgid "Select an instance to run a health check."
-msgstr ""
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:5
-msgid "Select an inventory for the workflow. This inventory is applied to all workflow nodes that prompt for an inventory."
-msgstr ""
-
-#: components/LaunchPrompt/steps/SurveyStep.js:129
-msgid "Select an option"
-msgstr ""
-
-#: screens/Project/shared/ProjectForm.js:201
-msgid "Select an organization before editing the default execution environment."
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:376
-#~ msgid ""
-#~ "Select credentials for accessing the nodes this job will be ran\n"
-#~ "against. You can only select one credential of each type. For machine credentials (SSH),\n"
-#~ "checking \"Prompt on launch\" without selecting credentials will require you to select a machine\n"
-#~ "credential at run time. If you select credentials and check \"Prompt on launch\", the selected\n"
-#~ "credential(s) become the defaults that can be updated at run time."
-#~ msgstr ""
-
-#: screens/Job/Job.helptext.js:10
-#: screens/Template/shared/JobTemplate.helptext.js:11
-msgid "Select credentials for accessing the nodes this job will be ran against. You can only select one credential of each type. For machine credentials (SSH), checking \"Prompt on launch\" without selecting credentials will require you to select a machine credential at run time. If you select credentials and check \"Prompt on launch\", the selected credential(s) become the defaults that can be updated at run time."
-msgstr ""
-
-#: screens/Project/shared/Project.helptext.js:18
-msgid ""
-"Select from the list of directories found in\n"
-"the Project Base Path. Together the base path and the playbook\n"
-"directory provide the full path used to locate playbooks."
-msgstr ""
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:98
-msgid "Select items from list"
-msgstr ""
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:55
-msgid "Select items to approve, deny, or cancel"
-msgstr ""
-
-#: screens/Dashboard/DashboardGraph.js:124
-#: screens/Dashboard/DashboardGraph.js:125
-msgid "Select job type"
-msgstr ""
-
-#: components/LaunchPrompt/steps/SurveyStep.js:177
-msgid "Select option(s)"
-msgstr ""
-
-#: screens/Dashboard/DashboardGraph.js:95
-#: screens/Dashboard/DashboardGraph.js:96
-#: screens/Dashboard/DashboardGraph.js:97
-msgid "Select period"
-msgstr ""
-
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:117
-msgid "Select roles to apply"
-msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:127
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:128
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:129
-msgid "Select source path"
-msgstr ""
-
-#: screens/Dashboard/DashboardGraph.js:151
-#: screens/Dashboard/DashboardGraph.js:152
-msgid "Select status"
-msgstr ""
-
-#: components/MultiSelect/TagMultiSelect.js:59
-msgid "Select tags"
-msgstr ""
-
-#: components/AdHocCommands/AdHocExecutionEnvironmentStep.js:94
-msgid "Select the Execution Environment you want this command to run inside."
-msgstr ""
-
-#: screens/Inventory/shared/SmartInventoryForm.js:87
-msgid "Select the Instance Groups for this Inventory to run on."
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:513
-#~ msgid ""
-#~ "Select the Instance Groups for this Job Template\n"
-#~ "to run on."
-#~ msgstr ""
-
-#: screens/Job/Job.helptext.js:17
-#: screens/Template/shared/JobTemplate.helptext.js:19
-msgid "Select the Instance Groups for this Job Template to run on."
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:513
-#~ msgid ""
-#~ "Select the Instance Groups for this Organization\n"
-#~ "to run on."
-#~ msgstr ""
-
-#: screens/Organization/shared/OrganizationForm.js:83
-msgid "Select the Instance Groups for this Organization to run on."
-msgstr ""
-
-#: screens/User/shared/UserTokenForm.js:49
-#~ msgid "Select the application that this token will belong to, or leave this field empty to create a Personal Access Token."
-#~ msgstr ""
-
-#: screens/User/shared/UserTokenForm.js:49
-#~ msgid "Select the application that this token will belong to."
-#~ msgstr ""
-
-#: components/AdHocCommands/AdHocCredentialStep.js:104
-msgid "Select the credential you want to use when accessing the remote hosts to run the command. Choose the credential containing the username and SSH key or password that Ansible will need to log into the remote hosts."
-msgstr ""
-
-#: screens/Template/shared/JobTemplate.helptext.js:8
-msgid "Select the execution environment for this job template."
-msgstr ""
-
-#: components/Lookup/InventoryLookup.js:133
-msgid ""
-"Select the inventory containing the hosts\n"
-"you want this job to manage."
-msgstr ""
-
-#: screens/Job/Job.helptext.js:6
-#: screens/Template/shared/JobTemplate.helptext.js:6
-msgid "Select the inventory containing the hosts you want this job to manage."
-msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:107
-#~ msgid ""
-#~ "Select the inventory file\n"
-#~ "to be synced by this source. You can select from\n"
-#~ "the dropdown or enter a file within the input."
-#~ msgstr ""
-
-#: components/HostForm/HostForm.js:32
-#: components/HostForm/HostForm.js:51
-msgid "Select the inventory that this host will belong to."
-msgstr ""
-
-#: screens/Job/Job.helptext.js:9
-#: screens/Template/shared/JobTemplate.helptext.js:10
-msgid "Select the playbook to be executed by this job."
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:300
-#~ msgid ""
-#~ "Select the project containing the playbook\n"
-#~ "you want this job to execute."
-#~ msgstr ""
-
-#: screens/Job/Job.helptext.js:7
-#: screens/Template/shared/JobTemplate.helptext.js:7
-msgid "Select the project containing the playbook you want this job to execute."
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:79
-msgid "Select your Ansible Automation Platform subscription to use."
-msgstr ""
-
-#: components/Lookup/Lookup.js:180
-msgid "Select {0}"
-msgstr ""
-
-#: components/AddRole/AddResourceRole.js:212
-#: components/AddRole/AddResourceRole.js:224
-#: components/AddRole/AddResourceRole.js:242
-#: components/AddRole/SelectRoleStep.js:27
-#: components/CheckboxListItem/CheckboxListItem.js:44
-#: components/Lookup/InstanceGroupsLookup.js:87
-#: components/OptionsList/OptionsList.js:74
-#: components/Schedule/ScheduleList/ScheduleListItem.js:78
-#: components/TemplateList/TemplateListItem.js:140
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:107
-#: components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.js:125
-#: screens/Application/ApplicationTokens/ApplicationTokenListItem.js:26
-#: screens/Application/ApplicationsList/ApplicationListItem.js:31
-#: screens/Credential/CredentialList/CredentialListItem.js:56
-#: screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js:31
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js:65
-#: screens/Host/HostGroups/HostGroupItem.js:26
-#: screens/Host/HostList/HostListItem.js:48
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:59
-#: screens/InstanceGroup/Instances/InstanceListItem.js:122
-#: screens/Instances/InstanceList/InstanceListItem.js:126
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:42
-#: screens/Inventory/InventoryList/InventoryListItem.js:90
-#: screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js:37
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:110
-#: screens/Organization/OrganizationList/OrganizationListItem.js:43
-#: screens/Organization/shared/OrganizationForm.js:113
-#: screens/Project/ProjectList/ProjectListItem.js:177
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:242
-#: screens/Team/TeamList/TeamListItem.js:31
-#: screens/Template/Survey/SurveyListItem.js:34
-#: screens/User/UserTokenList/UserTokenListItem.js:19
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:32
-msgid "Selected"
-msgstr ""
-
-#: components/LaunchPrompt/steps/CredentialsStep.js:142
-#: components/LaunchPrompt/steps/CredentialsStep.js:147
-#: components/Lookup/MultiCredentialsLookup.js:161
-#: components/Lookup/MultiCredentialsLookup.js:166
-msgid "Selected Category"
-msgstr ""
-
-#: components/Schedule/shared/ScheduleForm.js:605
-#: components/Schedule/shared/ScheduleForm.js:606
-msgid "Selected date range must have at least 1 schedule occurrence."
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:159
-msgid "Sender Email"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:94
-msgid "Sender e-mail"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:153
-msgid "September"
-msgstr ""
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:24
-msgid "Service account JSON file"
-msgstr ""
-
-#: screens/Inventory/shared/InventorySourceForm.js:46
-#: screens/Project/shared/ProjectForm.js:94
-msgid "Set a value for this field"
-msgstr ""
-
-#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:70
-msgid "Set how many days of data should be retained."
-msgstr ""
-
-#: screens/Setting/SettingList.js:118
-msgid "Set preferences for data collection, logos, and logins"
-msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js:130
-msgid "Set source path to"
-msgstr ""
-
-#: components/InstanceToggle/InstanceToggle.js:48
-msgid "Set the instance enabled or disabled. If disabled, jobs will not be assigned to this instance."
-msgstr ""
-
-#: components/InstanceToggle/InstanceToggle.js:48
-#~ msgid "Set the instance online or offline. If offline, jobs will not be assigned to this instance."
-#~ msgstr ""
-
-#: screens/Application/shared/Application.helptext.js:5
-msgid "Set to Public or Confidential depending on how secure the client device is."
-msgstr ""
-
-#: components/Search/AdvancedSearch.js:149
-msgid "Set type"
-msgstr ""
-
-#: components/Search/AdvancedSearch.js:239
-msgid "Set type disabled for related search field fuzzy searches"
-msgstr ""
-
-#: components/Search/AdvancedSearch.js:140
-msgid "Set type select"
-msgstr ""
-
-#: components/Search/AdvancedSearch.js:143
-msgid "Set type typeahead"
-msgstr ""
-
-#: components/Workflow/WorkflowTools.js:154
-msgid "Set zoom to 100% and center graph"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:46
-msgid "Setting category"
-msgstr ""
-
-#: screens/Setting/shared/RevertButton.js:46
-msgid "Setting matches factory default."
-msgstr ""
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:49
-msgid "Setting name"
-msgstr ""
-
-#: routeConfig.js:159
-#: routeConfig.js:163
-#: screens/ActivityStream/ActivityStream.js:220
-#: screens/ActivityStream/ActivityStream.js:222
-#: screens/Setting/Settings.js:42
-msgid "Settings"
-msgstr ""
-
-#: components/FormField/PasswordInput.js:35
-msgid "Show"
-msgstr ""
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:154
-#: components/PromptDetail/PromptDetail.js:283
-#: components/PromptDetail/PromptJobTemplateDetail.js:151
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:317
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:276
-#: screens/Template/shared/JobTemplateForm.js:448
-msgid "Show Changes"
-msgstr ""
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:131
-#~ msgid "Show all groups"
-#~ msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:177
-#: components/AdHocCommands/AdHocDetailsStep.js:178
-msgid "Show changes"
-msgstr ""
-
-#: components/LaunchPrompt/LaunchPrompt.js:105
-#: components/Schedule/shared/SchedulePromptableFields.js:109
-msgid "Show description"
-msgstr ""
-
-#: components/ChipGroup/ChipGroup.js:12
-msgid "Show less"
-msgstr ""
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:126
-msgid "Show only root groups"
-msgstr ""
-
-#: screens/Login/Login.js:240
-msgid "Sign in with Azure AD"
-msgstr ""
-
-#: screens/Login/Login.js:254
-msgid "Sign in with GitHub"
-msgstr ""
-
-#: screens/Login/Login.js:296
-msgid "Sign in with GitHub Enterprise"
-msgstr ""
-
-#: screens/Login/Login.js:311
-msgid "Sign in with GitHub Enterprise Organizations"
-msgstr ""
-
-#: screens/Login/Login.js:327
-msgid "Sign in with GitHub Enterprise Teams"
-msgstr ""
-
-#: screens/Login/Login.js:268
-msgid "Sign in with GitHub Organizations"
-msgstr ""
-
-#: screens/Login/Login.js:282
-msgid "Sign in with GitHub Teams"
-msgstr ""
-
-#: screens/Login/Login.js:342
-msgid "Sign in with Google"
-msgstr ""
-
-#: screens/Login/Login.js:361
-msgid "Sign in with SAML"
-msgstr ""
-
-#: screens/Login/Login.js:360
-msgid "Sign in with SAML {samlIDP}"
-msgstr ""
-
-#: components/Search/Search.js:145
-#: components/Search/Search.js:146
-msgid "Simple key select"
-msgstr ""
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:69
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:70
-#: components/PromptDetail/PromptDetail.js:256
-#: components/PromptDetail/PromptJobTemplateDetail.js:260
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:369
-#: screens/Job/JobDetail/JobDetail.js:483
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:459
-#: screens/Template/shared/JobTemplateForm.js:481
-msgid "Skip Tags"
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:538
-#~ msgid ""
-#~ "Skip tags are useful when you have a\n"
-#~ "large playbook, and you want to skip specific parts of a\n"
-#~ "play or task. Use commas to separate multiple tags. Refer\n"
-#~ "to the documentation for details on the usage\n"
-#~ "of tags."
-#~ msgstr ""
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:71
-msgid ""
-"Skip tags are useful when you have a large\n"
-"playbook, and you want to skip specific parts of a play or task.\n"
-"Use commas to separate multiple tags. Refer to Ansible Controller\n"
-"documentation for details on the usage of tags."
-msgstr ""
-
-#: screens/Job/Job.helptext.js:19
-#: screens/Template/shared/JobTemplate.helptext.js:21
-msgid "Skip tags are useful when you have a large playbook, and you want to skip specific parts of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags."
-msgstr ""
-
-#: screens/Job/JobOutput/shared/HostStatusBar.js:39
-msgid "Skipped"
-msgstr ""
-
-#: components/StatusLabel/StatusLabel.js:42
-msgid "Skipped'"
-msgstr ""
-
-#: components/NotificationList/NotificationList.js:200
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:141
-msgid "Slack"
-msgstr ""
-
-#: screens/Host/HostList/SmartInventoryButton.js:39
-#: screens/Host/HostList/SmartInventoryButton.js:48
-#: screens/Host/HostList/SmartInventoryButton.js:52
-#: screens/Inventory/InventoryList/InventoryList.js:187
-#: screens/Inventory/InventoryList/InventoryListItem.js:117
-msgid "Smart Inventory"
-msgstr ""
-
-#: screens/Inventory/SmartInventory.js:94
-msgid "Smart Inventory not found."
-msgstr ""
-
-#: components/Lookup/HostFilterLookup.js:344
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:117
-msgid "Smart host filter"
-msgstr ""
-
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:106
-msgid "Smart inventory"
-msgstr ""
-
-#: components/AdHocCommands/AdHocPreviewStep.js:32
-#: components/LaunchPrompt/steps/PreviewStep.js:60
-msgid "Some of the previous step(s) have errors"
-msgstr ""
-
-#: screens/Host/HostList/SmartInventoryButton.js:17
-msgid "Some search modifiers like not__ and __search are not supported in Smart Inventory host filters. Remove these to create a new Smart Inventory with this filter."
-msgstr ""
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.js:39
-msgid "Something went wrong with the request to test this credential and metadata."
-msgstr ""
-
-#: components/ContentError/ContentError.js:37
-msgid "Something went wrong..."
-msgstr ""
-
-#: components/Sort/Sort.js:139
-msgid "Sort"
-msgstr ""
-
-#: screens/Template/Survey/SurveyListItem.js:72
-#: screens/Template/Survey/SurveyListItem.js:73
-#~ msgid "Sort question order"
-#~ msgstr ""
-
-#: components/JobList/JobListItem.js:170
-#: components/PromptDetail/PromptInventorySourceDetail.js:95
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:214
-#: screens/Inventory/shared/InventorySourceForm.js:131
-#: screens/Job/JobDetail/JobDetail.js:274
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:93
-msgid "Source"
-msgstr ""
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:47
-#: components/PromptDetail/PromptDetail.js:211
-#: components/PromptDetail/PromptJobTemplateDetail.js:145
-#: components/PromptDetail/PromptProjectDetail.js:106
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:87
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:312
-#: screens/Job/JobDetail/JobDetail.js:302
-#: screens/Project/ProjectDetail/ProjectDetail.js:229
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:241
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:134
-#: screens/Template/shared/JobTemplateForm.js:328
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:286
-msgid "Source Control Branch"
-msgstr ""
-
-#: screens/Project/shared/ProjectSubForms/GitSubForm.js:29
-msgid "Source Control Branch/Tag/Commit"
-msgstr ""
-
-#: components/PromptDetail/PromptProjectDetail.js:117
-#: screens/Project/ProjectDetail/ProjectDetail.js:239
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:53
-msgid "Source Control Credential"
-msgstr ""
-
-#: screens/Project/shared/ProjectForm.js:214
-#~ msgid "Source Control Credential Type"
-#~ msgstr ""
-
-#: components/PromptDetail/PromptProjectDetail.js:111
-#: screens/Project/ProjectDetail/ProjectDetail.js:234
-#: screens/Project/shared/ProjectSubForms/GitSubForm.js:32
-msgid "Source Control Refspec"
-msgstr ""
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:194
-msgid "Source Control Revision"
-msgstr ""
-
-#: components/PromptDetail/PromptProjectDetail.js:96
-#: screens/Job/JobDetail/JobDetail.js:253
-#: screens/Project/ProjectDetail/ProjectDetail.js:190
-#: screens/Project/shared/ProjectForm.js:215
-msgid "Source Control Type"
-msgstr ""
-
-#: components/Lookup/ProjectLookup.js:142
-#: components/PromptDetail/PromptProjectDetail.js:101
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:96
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:165
-#: screens/Project/ProjectDetail/ProjectDetail.js:224
-#: screens/Project/ProjectList/ProjectList.js:205
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:16
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:104
-msgid "Source Control URL"
-msgstr ""
-
-#: components/JobList/JobList.js:211
-#: components/JobList/JobListItem.js:42
-#: components/Schedule/ScheduleList/ScheduleListItem.js:38
-#: screens/Job/JobDetail/JobDetail.js:64
-msgid "Source Control Update"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:335
-msgid "Source Phone Number"
-msgstr ""
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:188
-msgid "Source Variables"
-msgstr ""
-
-#: components/JobList/JobListItem.js:213
-#: screens/Job/JobDetail/JobDetail.js:237
-msgid "Source Workflow Job"
-msgstr ""
-
-#: screens/Template/shared/WorkflowJobTemplateForm.js:172
-msgid "Source control branch"
-msgstr ""
-
-#: screens/Inventory/shared/InventorySourceForm.js:153
-msgid "Source details"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:390
-msgid "Source phone number"
-msgstr ""
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:285
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:19
-msgid "Source variables"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:97
-msgid "Sourced from a project"
-msgstr ""
-
-#: screens/Inventory/Inventories.js:84
-#: screens/Inventory/Inventory.js:68
-msgid "Sources"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:30
-msgid ""
-"Specify HTTP Headers in JSON format. Refer to\n"
-"the Ansible Controller documentation for example syntax."
-msgstr ""
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:24
-msgid ""
-"Specify a notification color. Acceptable colors are hex\n"
-"color code (example: #3af or #789abc)."
-msgstr ""
-
-#: screens/User/shared/UserTokenForm.js:71
-#~ msgid "Specify a scope for the token's access"
-#~ msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.js:26
-msgid "Specify the conditions under which this node should be executed"
-msgstr ""
-
-#: screens/Job/JobOutput/HostEventModal.js:173
-msgid "Standard Error"
-msgstr ""
-
-#: screens/Job/JobOutput/HostEventModal.js:152
-#~ msgid "Standard Out"
-#~ msgstr ""
-
-#: screens/Job/JobOutput/HostEventModal.js:174
-msgid "Standard error tab"
-msgstr ""
-
-#: screens/Job/JobOutput/HostEventModal.js:153
-#~ msgid "Standard out tab"
-#~ msgstr ""
-
-#: components/NotificationList/NotificationListItem.js:57
-#: components/NotificationList/NotificationListItem.js:58
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:47
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:53
-msgid "Start"
-msgstr ""
-
-#: components/JobList/JobList.js:247
-#: components/JobList/JobListItem.js:99
-msgid "Start Time"
-msgstr ""
-
-#: components/Schedule/shared/DateTimePicker.js:51
-msgid "Start date"
-msgstr ""
-
-#: components/Schedule/shared/ScheduleForm.js:122
-msgid "Start date/time"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:460
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:105
-msgid "Start message"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:469
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:114
-msgid "Start message body"
-msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:33
-msgid "Start sync process"
-msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSyncButton.js:37
-msgid "Start sync source"
-msgstr ""
-
-#: components/Schedule/shared/DateTimePicker.js:61
-msgid "Start time"
-msgstr ""
-
-#: screens/Job/JobDetail/JobDetail.js:200
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:253
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:54
-msgid "Started"
-msgstr ""
-
-#: components/JobList/JobList.js:224
-#: components/JobList/JobList.js:245
-#: components/JobList/JobListItem.js:95
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:201
-#: screens/InstanceGroup/Instances/InstanceList.js:256
-#: screens/InstanceGroup/Instances/InstanceListItem.js:129
-#: screens/Instances/InstanceDetail/InstanceDetail.js:149
-#: screens/Instances/InstanceList/InstanceList.js:151
-#: screens/Instances/InstanceList/InstanceListItem.js:134
-#: screens/Inventory/InventoryList/InventoryList.js:219
-#: screens/Inventory/InventoryList/InventoryListItem.js:101
-#: screens/Inventory/InventorySources/InventorySourceList.js:212
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:87
-#: screens/Job/JobDetail/JobDetail.js:188
-#: screens/Job/JobOutput/HostEventModal.js:118
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:114
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:179
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:117
-#: screens/Project/ProjectList/ProjectList.js:222
-#: screens/Project/ProjectList/ProjectListItem.js:197
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:45
-#: screens/TopologyView/Tooltip.js:98
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:203
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:254
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListItem.js:57
-msgid "Status"
-msgstr ""
-
-#: screens/TopologyView/Legend.js:107
-msgid "Status types"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:92
-msgid "Stdout"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:37
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:49
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:212
-msgid "Submit"
-msgstr ""
-
-#: screens/Project/shared/Project.helptext.js:114
-msgid ""
-"Submodules will track the latest commit on\n"
-"their master branch (or other branch specified in\n"
-".gitmodules). If no, submodules will be kept at\n"
-"the revision specified by the main project.\n"
-"This is equivalent to specifying the --remote\n"
-"flag to git submodule update."
-msgstr ""
-
-#: screens/Setting/SettingList.js:128
-#: screens/Setting/Settings.js:108
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:74
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:195
-msgid "Subscription"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:32
-msgid "Subscription Details"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:194
-msgid "Subscription Management"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:82
-msgid "Subscription manifest"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:84
-msgid "Subscription selection modal"
-msgstr ""
-
-#: screens/Setting/SettingList.js:133
-msgid "Subscription settings"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:69
-msgid "Subscription type"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:142
-msgid "Subscriptions table"
-msgstr ""
-
-#: components/Lookup/ProjectLookup.js:136
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:90
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:159
-#: screens/Job/JobDetail/JobDetail.js:75
-#: screens/Project/ProjectList/ProjectList.js:199
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:98
-msgid "Subversion"
-msgstr ""
-
-#: components/NotificationList/NotificationListItem.js:71
-#: components/NotificationList/NotificationListItem.js:72
-#: components/StatusLabel/StatusLabel.js:33
-msgid "Success"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:478
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:123
-msgid "Success message"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:487
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:132
-msgid "Success message body"
-msgstr ""
-
-#: components/JobList/JobList.js:231
-#: components/StatusLabel/StatusLabel.js:35
-#: components/Workflow/WorkflowNodeHelp.js:102
-#: screens/Dashboard/shared/ChartTooltip.js:59
-msgid "Successful"
-msgstr ""
-
-#: screens/Dashboard/DashboardGraph.js:166
-msgid "Successful jobs"
-msgstr ""
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:200
-#: screens/Project/ProjectList/ProjectListItem.js:97
-msgid "Successfully copied to clipboard!"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:248
-msgid "Sun"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:253
-#: components/Schedule/shared/FrequencyDetailSubform.js:421
-msgid "Sunday"
-msgstr ""
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:26
-#: screens/Template/Template.js:159
-#: screens/Template/Templates.js:48
-#: screens/Template/WorkflowJobTemplate.js:145
-msgid "Survey"
-msgstr ""
-
-#: screens/Template/Survey/SurveyToolbar.js:105
-msgid "Survey Disabled"
-msgstr ""
-
-#: screens/Template/Survey/SurveyToolbar.js:104
-msgid "Survey Enabled"
-msgstr ""
-
-#: screens/Template/Survey/SurveyList.js:132
-#~ msgid "Survey List"
-#~ msgstr ""
-
-#: screens/Template/Survey/SurveyPreviewModal.js:31
-#~ msgid "Survey Preview"
-#~ msgstr ""
-
-#: screens/Template/Survey/SurveyReorderModal.js:191
-msgid "Survey Question Order"
-msgstr ""
-
-#: screens/Template/Survey/SurveyToolbar.js:102
-msgid "Survey Toggle"
-msgstr ""
-
-#: screens/Template/Survey/SurveyReorderModal.js:192
-msgid "Survey preview modal"
-msgstr ""
-
-#: screens/Template/Survey/SurveyListItem.js:66
-#~ msgid "Survey questions"
-#~ msgstr ""
-
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:120
-#: screens/Inventory/shared/InventorySourceSyncButton.js:41
-#: screens/Project/shared/ProjectSyncButton.js:40
-#: screens/Project/shared/ProjectSyncButton.js:52
-msgid "Sync"
-msgstr ""
-
-#: screens/Project/ProjectList/ProjectListItem.js:238
-#: screens/Project/shared/ProjectSyncButton.js:36
-#: screens/Project/shared/ProjectSyncButton.js:47
-msgid "Sync Project"
-msgstr ""
-
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:19
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:29
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:32
-msgid "Sync all"
-msgstr ""
-
-#: components/PaginatedTable/ToolbarSyncSourceButton.js:25
-msgid "Sync all sources"
-msgstr ""
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:236
-msgid "Sync error"
-msgstr ""
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:212
-#: screens/Project/ProjectList/ProjectListItem.js:109
-msgid "Sync for revision"
-msgstr ""
-
-#: screens/Project/ProjectList/ProjectListItem.js:122
-msgid "Syncing"
-msgstr ""
-
-#: screens/Setting/SettingList.js:98
-#: screens/User/UserRoles/UserRolesListItem.js:18
-msgid "System"
-msgstr ""
-
-#: screens/Team/TeamRoles/TeamRolesList.js:128
-#: screens/User/UserDetail/UserDetail.js:47
-#: screens/User/UserList/UserListItem.js:19
-#: screens/User/UserRoles/UserRolesList.js:127
-#: screens/User/shared/UserForm.js:41
-msgid "System Administrator"
-msgstr ""
-
-#: screens/User/UserDetail/UserDetail.js:49
-#: screens/User/UserList/UserListItem.js:21
-#: screens/User/shared/UserForm.js:35
-msgid "System Auditor"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:129
-msgid "System Warning"
-msgstr ""
-
-#: screens/Team/TeamRoles/TeamRolesList.js:131
-#: screens/User/UserRoles/UserRolesList.js:130
-msgid "System administrators have unrestricted access to all resources."
-msgstr ""
-
-#: screens/Setting/Settings.js:111
-msgid "TACACS+"
-msgstr ""
-
-#: screens/Setting/SettingList.js:81
-msgid "TACACS+ settings"
-msgstr ""
-
-#: screens/Dashboard/Dashboard.js:117
-#: screens/Job/JobOutput/HostEventModal.js:94
-msgid "Tabs"
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:522
-#~ msgid ""
-#~ "Tags are useful when you have a large\n"
-#~ "playbook, and you want to run a specific part of a\n"
-#~ "play or task. Use commas to separate multiple tags.\n"
-#~ "Refer to the documentation for details on\n"
-#~ "the usage of tags."
-#~ msgstr ""
-
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:59
-msgid ""
-"Tags are useful when you have a large\n"
-"playbook, and you want to run a specific part of a play or task.\n"
-"Use commas to separate multiple tags. Refer to Ansible Controller\n"
-"documentation for details on the usage of tags."
-msgstr ""
-
-#: screens/Job/Job.helptext.js:18
-#: screens/Template/shared/JobTemplate.helptext.js:20
-msgid "Tags are useful when you have a large playbook, and you want to run a specific part of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags."
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:198
-msgid "Tags for the Annotation"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:172
-msgid "Tags for the annotation (optional)"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:243
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:293
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:361
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:243
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:320
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:438
-msgid "Target URL"
-msgstr ""
-
-#: screens/Job/JobOutput/HostEventModal.js:123
-msgid "Task"
-msgstr ""
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:90
-msgid "Task Count"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:130
-msgid "Task Started"
-msgstr ""
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:91
-msgid "Tasks"
-msgstr ""
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:13
-msgid "Team"
-msgstr ""
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:87
-#: screens/Team/TeamRoles/TeamRolesList.js:144
-msgid "Team Roles"
-msgstr ""
-
-#: screens/Team/Team.js:75
-msgid "Team not found."
-msgstr ""
-
-#: components/AddRole/AddResourceRole.js:188
-#: components/AddRole/AddResourceRole.js:189
-#: routeConfig.js:106
-#: screens/ActivityStream/ActivityStream.js:187
-#: screens/Organization/Organization.js:125
-#: screens/Organization/OrganizationList/OrganizationList.js:145
-#: screens/Organization/OrganizationList/OrganizationListItem.js:66
-#: screens/Organization/OrganizationTeams/OrganizationTeamList.js:64
-#: screens/Organization/Organizations.js:32
-#: screens/Team/TeamList/TeamList.js:112
-#: screens/Team/TeamList/TeamList.js:166
-#: screens/Team/Teams.js:15
-#: screens/Team/Teams.js:25
-#: screens/User/User.js:70
-#: screens/User/UserTeams/UserTeamList.js:175
-#: screens/User/UserTeams/UserTeamList.js:246
-#: screens/User/Users.js:32
-#: util/getRelatedResourceDeleteDetails.js:173
-msgid "Teams"
-msgstr ""
-
-#: screens/Setting/Jobs/JobsEdit/JobsEdit.js:130
-msgid "Template"
-msgstr ""
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:115
-#: components/TemplateList/TemplateList.js:133
-msgid "Template copied successfully"
-msgstr ""
-
-#: screens/Template/Template.js:175
-#: screens/Template/WorkflowJobTemplate.js:175
-msgid "Template not found."
-msgstr ""
-
-#: components/TemplateList/TemplateList.js:200
-#: components/TemplateList/TemplateList.js:263
-#: routeConfig.js:65
-#: screens/ActivityStream/ActivityStream.js:164
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:70
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:83
-#: screens/Template/Templates.js:17
-#: util/getRelatedResourceDeleteDetails.js:217
-#: util/getRelatedResourceDeleteDetails.js:274
-msgid "Templates"
-msgstr ""
-
-#: screens/Credential/shared/CredentialForm.js:331
-#: screens/Credential/shared/CredentialForm.js:337
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js:80
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:421
-msgid "Test"
-msgstr ""
-
-#: screens/Credential/shared/ExternalTestModal.js:77
-msgid "Test External Credential"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:128
-msgid "Test Notification"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:125
-msgid "Test notification"
-msgstr ""
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.js:44
-msgid "Test passed"
-msgstr ""
-
-#: screens/Template/Survey/SurveyQuestionForm.js:80
-#: screens/Template/Survey/SurveyReorderModal.js:181
-msgid "Text"
-msgstr ""
-
-#: screens/Template/Survey/SurveyReorderModal.js:135
-msgid "Text Area"
-msgstr ""
-
-#: screens/Template/Survey/SurveyQuestionForm.js:81
-msgid "Textarea"
-msgstr ""
-
-#: components/Lookup/Lookup.js:62
-msgid "That value was not found. Please enter or select a valid value."
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:391
-msgid "The"
-msgstr ""
-
-#: screens/Setting/MiscSystem/MiscSystemEdit/MiscSystemEdit.js:200
-#~ msgid "The Execution Environment to be used when one has not been configured for a job template."
-#~ msgstr ""
-
-#: screens/Application/shared/ApplicationForm.js:86
-#~ msgid "The Grant type the user must use for acquire tokens for this application"
-#~ msgstr ""
-
-#: screens/Application/shared/Application.helptext.js:4
-msgid "The Grant type the user must use to acquire tokens for this application"
-msgstr ""
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:128
-msgid "The Instance Groups for this Organization to run on."
-msgstr ""
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:6
-msgid ""
-"The amount of time (in seconds) before the email\n"
-"notification stops trying to reach the host and times out. Ranges\n"
-"from 1 to 120 seconds."
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:489
-#~ msgid ""
-#~ "The amount of time (in seconds) to run\n"
-#~ "before the job is canceled. Defaults to 0 for no job\n"
-#~ "timeout."
-#~ msgstr ""
-
-#: screens/Job/Job.helptext.js:16
-#: screens/Template/shared/JobTemplate.helptext.js:17
-msgid "The amount of time (in seconds) to run before the job is canceled. Defaults to 0 for no job timeout."
-msgstr ""
-
-#: screens/User/shared/User.helptext.js:4
-msgid "The application that this token belongs to, or leave this field empty to create a Personal Access Token."
-msgstr ""
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:9
-msgid ""
-"The base URL of the Grafana server - the\n"
-"/api/annotations endpoint will be added automatically to the base\n"
-"Grafana URL."
-msgstr ""
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:109
-msgid ""
-"The execution environment that will be used for jobs\n"
-"inside of this organization. This will be used a fallback when\n"
-"an execution environment has not been explicitly assigned at the\n"
-"project, job template or workflow level."
-msgstr ""
-
-#: screens/Organization/shared/OrganizationForm.js:93
-msgid "The execution environment that will be used for jobs inside of this organization. This will be used a fallback when an execution environment has not been explicitly assigned at the project, job template or workflow level."
-msgstr ""
-
-#: screens/Project/shared/Project.helptext.js:5
-msgid "The execution environment that will be used for jobs that use this project. This will be used as fallback when an execution environment has not been explicitly assigned at the job template or workflow level."
-msgstr ""
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:239
-#~ msgid ""
-#~ "The execution environment that will be used when launching\n"
-#~ "this job template. The resolved execution environment can be overridden by\n"
-#~ "explicitly assigning a different one to this job template."
-#~ msgstr ""
-
-#: screens/Job/Job.helptext.js:8
-#: screens/Template/shared/JobTemplate.helptext.js:9
-msgid "The execution environment that will be used when launching this job template. The resolved execution environment can be overridden by explicitly assigning a different one to this job template."
-msgstr ""
-
-#: screens/Project/shared/Project.helptext.js:93
-msgid ""
-"The first fetches all references. The second\n"
-"fetches the Github pull request number 62, in this example\n"
-"the branch needs to be \"pull/62/head\"."
-msgstr ""
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:66
-msgid "The following selected items are complete and cannot be acted on: {completedItems}"
-msgstr ""
-
-#: screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js:7
-msgid "The full image location, including the container registry, image name, and version tag."
-msgstr ""
-
-#: screens/Inventory/shared/Inventory.helptext.js:191
-msgid ""
-"The inventory file\n"
-"to be synced by this source. You can select from\n"
-"the dropdown or enter a file within the input."
-msgstr ""
-
-#: screens/Host/HostDetail/HostDetail.js:77
-msgid "The inventory that this host belongs to."
-msgstr ""
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:100
-msgid ""
-"The maximum number of hosts allowed to be managed by\n"
-"this organization. Value defaults to 0 which means no limit.\n"
-"Refer to the Ansible documentation for more details."
-msgstr ""
-
-#: screens/Organization/shared/OrganizationForm.js:72
-msgid ""
-"The maximum number of hosts allowed to be managed by this organization.\n"
-"Value defaults to 0 which means no limit. Refer to the Ansible\n"
-"documentation for more details."
-msgstr ""
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:26
-msgid ""
-"The number associated with the \"Messaging\n"
-"Service\" in Twilio with the format +18005550199."
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:427
-#~ msgid ""
-#~ "The number of parallel or simultaneous\n"
-#~ "processes to use while executing the playbook. An empty value,\n"
-#~ "or a value less than 1 will use the Ansible default which is\n"
-#~ "usually 5. The default number of forks can be overwritten\n"
-#~ "with a change to"
-#~ msgstr ""
-
-#: screens/Job/Job.helptext.js:24
-#: screens/Template/shared/JobTemplate.helptext.js:44
-msgid "The number of parallel or simultaneous processes to use while executing the playbook. An empty value, or a value less than 1 will use the Ansible default which is usually 5. The default number of forks can be overwritten with a change to"
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:164
-msgid "The number of parallel or simultaneous processes to use while executing the playbook. Inputting no value will use the default value from the ansible configuration file. You can find more information"
-msgstr ""
-
-#: components/ContentError/ContentError.js:41
-#: screens/Job/Job.js:138
-msgid "The page you requested could not be found."
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:144
-msgid "The pattern used to target hosts in the inventory. Leaving the field blank, all, and * will all target all hosts in the inventory. You can find more information about Ansible's host patterns"
-msgstr ""
-
-#: screens/Project/ProjectList/ProjectListItem.js:120
-msgid "The project is currently syncing and the revision will be available after the sync is complete."
-msgstr ""
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:210
-#: screens/Project/ProjectList/ProjectListItem.js:107
-msgid "The project must be synced before a revision is available."
-msgstr ""
-
-#: screens/Project/ProjectList/ProjectListItem.js:130
-msgid "The project revision is currently out of date. Please refresh to fetch the most recent revision."
-msgstr ""
-
-#: components/Workflow/WorkflowNodeHelp.js:138
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:131
-msgid "The resource associated with this node has been deleted."
-msgstr ""
-
-#: screens/Job/JobOutput/EmptyOutput.js:19
-msgid "The search filter did not produce any results…"
-msgstr ""
-
-#: screens/Template/Survey/SurveyQuestionForm.js:180
-msgid ""
-"The suggested format for variable names is lowercase and\n"
-"underscore-separated (for example, foo_bar, user_id, host_name,\n"
-"etc.). Variable names with spaces are not allowed."
-msgstr ""
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:47
-#~ msgid ""
-#~ "There are no available playbook directories in {project_base_dir}.\n"
-#~ "Either that directory is empty, or all of the contents are already\n"
-#~ "assigned to other projects. Create a new directory there and make\n"
-#~ "sure the playbook files can be read by the \"awx\" system user,\n"
-#~ "or have {0} directly retrieve your playbooks from\n"
-#~ "source control using the Source Control Type option above."
-#~ msgstr ""
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:49
-msgid ""
-"There are no available playbook directories in {project_base_dir}.\n"
-"Either that directory is empty, or all of the contents are already\n"
-"assigned to other projects. Create a new directory there and make\n"
-"sure the playbook files can be read by the \"awx\" system user,\n"
-"or have {brandName} directly retrieve your playbooks from\n"
-"source control using the Source Control Type option above."
-msgstr ""
-
-#: screens/Template/Survey/MultipleChoiceField.js:34
-msgid "There must be a value in at least one input"
-msgstr ""
-
-#: screens/Login/Login.js:144
-msgid "There was a problem logging in. Please try again."
-msgstr ""
-
-#: components/ContentError/ContentError.js:42
-msgid "There was an error loading this content. Please reload the page."
-msgstr ""
-
-#: screens/Credential/shared/CredentialFormFields/GceFileUploadField.js:56
-msgid "There was an error parsing the file. Please check the file formatting and try again."
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js:621
-msgid "There was an error saving the workflow."
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:68
-#~ msgid "These are the modules that {0} supports running commands against."
-#~ msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:69
-msgid "These are the modules that {brandName} supports running commands against."
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:129
-msgid "These are the verbosity levels for standard out of the command run that are supported."
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:122
-#: screens/Job/Job.helptext.js:42
-msgid "These arguments are used with the specified module."
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:111
-msgid "These arguments are used with the specified module. You can find information about {0} by clicking"
-msgstr ""
-
-#: screens/Job/Job.helptext.js:32
-msgid "These arguments are used with the specified module. You can find information about {moduleName} by clicking"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:403
-msgid "Third"
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:153
-msgid "This Project needs to be updated"
-msgstr ""
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:286
-#: screens/Template/Survey/SurveyList.js:82
-msgid "This action will delete the following:"
-msgstr ""
-
-#: screens/User/UserTeams/UserTeamList.js:217
-msgid "This action will disassociate all roles for this user from the selected teams."
-msgstr ""
-
-#: screens/Team/TeamRoles/TeamRolesList.js:236
-#: screens/User/UserRoles/UserRolesList.js:232
-msgid "This action will disassociate the following role from {0}:"
-msgstr ""
-
-#: components/DisassociateButton/DisassociateButton.js:148
-msgid "This action will disassociate the following:"
-msgstr ""
-
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:113
-msgid "This container group is currently being by other resources. Are you sure you want to delete it?"
-msgstr ""
-
-#: screens/Credential/CredentialDetail/CredentialDetail.js:305
-msgid "This credential is currently being used by other resources. Are you sure you want to delete it?"
-msgstr ""
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:121
-msgid "This credential type is currently being used by some credentials and cannot be deleted"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:74
-msgid ""
-"This data is used to enhance\n"
-"future releases of the Software and to provide\n"
-"Automation Analytics."
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:74
-#~ msgid ""
-#~ "This data is used to enhance\n"
-#~ "future releases of the Software and to provide\n"
-#~ "Insights for Ansible Automation Platform."
-#~ msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:62
-msgid ""
-"This data is used to enhance\n"
-"future releases of the Tower Software and help\n"
-"streamline customer experience and success."
-msgstr ""
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:131
-msgid "This execution environment is currently being used by other resources. Are you sure you want to delete it?"
-msgstr ""
-
-#: screens/Setting/RADIUS/RADIUSDetail/RADIUSDetail.js:74
-#: screens/Setting/TACACS/TACACSDetail/TACACSDetail.js:79
-msgid "This feature is deprecated and will be removed in a future release."
-msgstr ""
-
-#: screens/Inventory/shared/Inventory.helptext.js:155
-msgid "This field is ignored unless an Enabled Variable is set. If the enabled variable matches this value, the host will be enabled on import."
-msgstr ""
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:61
-#~ msgid "This field is must not be blank"
-#~ msgstr ""
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:55
-#~ msgid "This field is must not be blank."
-#~ msgstr ""
-
-#: components/AdHocCommands/useAdHocCredentialPasswordStep.js:44
-#: components/LaunchPrompt/steps/useCredentialPasswordsStep.js:50
-msgid "This field may not be blank"
-msgstr ""
-
-#: util/validators.js:127
-msgid "This field must be a number"
-msgstr ""
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:107
-msgid "This field must be a number and have a value between {0} and {1}"
-msgstr ""
-
-#: util/validators.js:67
-msgid "This field must be a number and have a value between {min} and {max}"
-msgstr ""
-
-#: util/validators.js:64
-msgid "This field must be a number and have a value greater than {min}"
-msgstr ""
-
-#: util/validators.js:61
-msgid "This field must be a number and have a value less than {max}"
-msgstr ""
-
-#: util/validators.js:184
-msgid "This field must be a regular expression"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:52
-#: util/validators.js:111
-msgid "This field must be an integer"
-msgstr ""
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:99
-msgid "This field must be at least {0} characters"
-msgstr ""
-
-#: util/validators.js:52
-msgid "This field must be at least {min} characters"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:55
-msgid "This field must be greater than 0"
-msgstr ""
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:52
-#: components/LaunchPrompt/steps/useSurveyStep.js:111
-#: screens/Template/shared/JobTemplateForm.js:150
-#: screens/User/shared/UserForm.js:92
-#: screens/User/shared/UserForm.js:103
-#: util/validators.js:5
-#: util/validators.js:76
-msgid "This field must not be blank"
-msgstr ""
-
-#: components/AdHocCommands/useAdHocDetailsStep.js:46
-msgid "This field must not be blank."
-msgstr ""
-
-#: util/validators.js:101
-msgid "This field must not contain spaces"
-msgstr ""
-
-#: components/LaunchPrompt/steps/useSurveyStep.js:102
-msgid "This field must not exceed {0} characters"
-msgstr ""
-
-#: util/validators.js:43
-msgid "This field must not exceed {max} characters"
-msgstr ""
-
-#: screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js:51
-msgid "This field will be retrieved from an external secret management system using the specified credential."
-msgstr ""
-
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:125
-msgid "This instance group is currently being by other resources. Are you sure you want to delete it?"
-msgstr ""
-
-#: components/LaunchPrompt/steps/useInventoryStep.js:59
-msgid "This inventory is applied to all workflow nodes within this workflow ({0}) that prompt for an inventory."
-msgstr ""
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:160
-msgid "This inventory is currently being used by other resources. Are you sure you want to delete it?"
-msgstr ""
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:329
-msgid "This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?"
-msgstr ""
-
-#: screens/Application/Applications.js:77
-msgid "This is the only time the client secret will be shown."
-msgstr ""
-
-#: screens/User/UserTokens/UserTokens.js:59
-msgid "This is the only time the token value and associated refresh token value will be shown."
-msgstr ""
-
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:526
-msgid "This job template is currently being used by other resources. Are you sure you want to delete it?"
-msgstr ""
-
-#: screens/Organization/OrganizationDetail/OrganizationDetail.js:197
-msgid "This organization is currently being by other resources. Are you sure you want to delete it?"
-msgstr ""
-
-#: screens/Project/ProjectDetail/ProjectDetail.js:320
-msgid "This project is currently being used by other resources. Are you sure you want to delete it?"
-msgstr ""
-
-#: screens/Project/shared/Project.helptext.js:59
-msgid "This project is currently on sync and cannot be clicked until sync process completed"
-msgstr ""
-
-#: components/Schedule/ScheduleList/ScheduleList.js:122
-msgid "This schedule is missing an Inventory"
-msgstr ""
-
-#: components/Schedule/ScheduleList/ScheduleList.js:147
-msgid "This schedule is missing required survey values"
-msgstr ""
-
-#: components/LaunchPrompt/steps/StepName.js:26
-msgid "This step contains errors"
-msgstr ""
-
-#: screens/User/shared/UserForm.js:150
-msgid "This value does not match the password you entered previously. Please confirm that password."
-msgstr ""
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:90
-msgid "This will cancel the workflow and no subsequent nodes will execute."
-msgstr ""
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:105
-msgid "This will continue the workflow"
-msgstr ""
-
-#: screens/WorkflowApproval/shared/WorkflowApprovalControls.js:78
-msgid "This will continue the workflow along failure and always paths."
-msgstr ""
-
-#: screens/Setting/shared/RevertAllAlert.js:36
-msgid ""
-"This will revert all configuration values on this page to\n"
-"their factory defaults. Are you sure you want to proceed?"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerStartScreen.js:40
-msgid "This workflow does not have any nodes configured."
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:269
-msgid "This workflow job template is currently being used by other resources. Are you sure you want to delete it?"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:292
-msgid "Thu"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:297
-#: components/Schedule/shared/FrequencyDetailSubform.js:441
-msgid "Thursday"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStream.js:249
-#: screens/ActivityStream/ActivityStream.js:261
-#: screens/ActivityStream/ActivityStreamDetailButton.js:41
-#: screens/ActivityStream/ActivityStreamListItem.js:42
-msgid "Time"
-msgstr ""
-
-#: screens/Project/shared/Project.helptext.js:124
-msgid ""
-"Time in seconds to consider a project\n"
-"to be current. During job runs and callbacks the task\n"
-"system will evaluate the timestamp of the latest project\n"
-"update. If it is older than Cache Timeout, it is not\n"
-"considered current, and a new project update will be\n"
-"performed."
-msgstr ""
-
-#: screens/Inventory/shared/Inventory.helptext.js:147
-msgid ""
-"Time in seconds to consider an inventory sync\n"
-"to be current. During job runs and callbacks the task system will\n"
-"evaluate the timestamp of the latest sync. If it is older than\n"
-"Cache Timeout, it is not considered current, and a new\n"
-"inventory sync will be performed."
-msgstr ""
-
-#: components/StatusLabel/StatusLabel.js:43
-msgid "Timed out"
-msgstr ""
-
-#: components/PromptDetail/PromptDetail.js:127
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:169
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:112
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:270
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:186
-#: screens/Template/shared/JobTemplateForm.js:443
-msgid "Timeout"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:193
-msgid "Timeout minutes"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:207
-msgid "Timeout seconds"
-msgstr ""
-
-#: screens/Host/HostList/SmartInventoryButton.js:20
-msgid "To create a smart inventory using ansible facts, go to the smart inventory screen."
-msgstr ""
-
-#: screens/Template/Survey/SurveyReorderModal.js:182
-#~ msgid "To reoder the survey questions drag and drop them in the desired location."
-#~ msgstr ""
-
-#: screens/Template/Survey/SurveyReorderModal.js:194
-msgid "To reorder the survey questions drag and drop them in the desired location."
-msgstr ""
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:106
-msgid "Toggle Legend"
-msgstr ""
-
-#: components/FormField/PasswordInput.js:39
-msgid "Toggle Password"
-msgstr ""
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:116
-msgid "Toggle Tools"
-msgstr ""
-
-#: components/HostToggle/HostToggle.js:70
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:56
-msgid "Toggle host"
-msgstr ""
-
-#: components/InstanceToggle/InstanceToggle.js:61
-msgid "Toggle instance"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:80
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:82
-#: screens/TopologyView/Header.js:99
-msgid "Toggle legend"
-msgstr ""
-
-#: components/NotificationList/NotificationListItem.js:50
-msgid "Toggle notification approvals"
-msgstr ""
-
-#: components/NotificationList/NotificationListItem.js:92
-msgid "Toggle notification failure"
-msgstr ""
-
-#: components/NotificationList/NotificationListItem.js:64
-msgid "Toggle notification start"
-msgstr ""
-
-#: components/NotificationList/NotificationListItem.js:78
-msgid "Toggle notification success"
-msgstr ""
-
-#: components/Schedule/ScheduleToggle/ScheduleToggle.js:66
-msgid "Toggle schedule"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:92
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:94
-msgid "Toggle tools"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:362
-#: screens/User/UserTokens/UserTokens.js:64
-msgid "Token"
-msgstr ""
-
-#: screens/User/UserTokens/UserTokens.js:50
-#: screens/User/UserTokens/UserTokens.js:53
-msgid "Token information"
-msgstr ""
-
-#: screens/User/UserToken/UserToken.js:73
-msgid "Token not found."
-msgstr ""
-
-#: screens/Application/Application/Application.js:80
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:105
-#: screens/Application/ApplicationTokens/ApplicationTokenList.js:128
-#: screens/Application/Applications.js:40
-#: screens/User/User.js:76
-#: screens/User/UserTokenList/UserTokenList.js:118
-#: screens/User/Users.js:34
-msgid "Tokens"
-msgstr ""
-
-#: components/Workflow/WorkflowTools.js:83
-msgid "Tools"
-msgstr ""
-
-#: components/PaginatedTable/PaginatedTable.js:133
-#~ msgid "Top Pagination"
-#~ msgstr ""
-
-#: routeConfig.js:152
-#: screens/TopologyView/TopologyView.js:40
-msgid "Topology View"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:211
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:211
-#: screens/InstanceGroup/Instances/InstanceListItem.js:199
-#: screens/Instances/InstanceDetail/InstanceDetail.js:162
-#: screens/Instances/InstanceList/InstanceListItem.js:214
-msgid "Total Jobs"
-msgstr ""
-
-#: screens/Job/WorkflowOutput/WorkflowOutputToolbar.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:76
-msgid "Total Nodes"
-msgstr ""
-
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:81
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:120
-msgid "Total hosts"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:72
-msgid "Total jobs"
-msgstr ""
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:83
-msgid "Track submodules"
-msgstr ""
-
-#: components/PromptDetail/PromptProjectDetail.js:56
-#: screens/Project/ProjectDetail/ProjectDetail.js:108
-msgid "Track submodules latest commit on branch"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:79
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:168
-msgid "Trial"
-msgstr ""
-
-#: components/JobList/JobListItem.js:318
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:65
-#: screens/Job/JobDetail/JobDetail.js:378
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:205
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:235
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:265
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:310
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:368
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:80
-msgid "True"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:270
-msgid "Tue"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:275
-#: components/Schedule/shared/FrequencyDetailSubform.js:431
-msgid "Tuesday"
-msgstr ""
-
-#: components/NotificationList/NotificationList.js:201
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:142
-msgid "Twilio"
-msgstr ""
-
-#: components/JobList/JobList.js:246
-#: components/JobList/JobListItem.js:98
-#: components/Lookup/ProjectLookup.js:131
-#: components/NotificationList/NotificationList.js:219
-#: components/NotificationList/NotificationListItem.js:33
-#: components/PromptDetail/PromptDetail.js:115
-#: components/RelatedTemplateList/RelatedTemplateList.js:187
-#: components/Schedule/ScheduleList/ScheduleList.js:169
-#: components/Schedule/ScheduleList/ScheduleListItem.js:97
-#: components/TemplateList/TemplateList.js:214
-#: components/TemplateList/TemplateList.js:243
-#: components/TemplateList/TemplateListItem.js:184
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:85
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:154
-#: components/Workflow/WorkflowNodeHelp.js:160
-#: components/Workflow/WorkflowNodeHelp.js:196
-#: screens/Credential/CredentialList/CredentialList.js:165
-#: screens/Credential/CredentialList/CredentialListItem.js:63
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:94
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:116
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:17
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:46
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:54
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:209
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:66
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:72
-#: screens/Inventory/InventoryList/InventoryList.js:220
-#: screens/Inventory/InventoryList/InventoryListItem.js:116
-#: screens/Inventory/InventorySources/InventorySourceList.js:213
-#: screens/Inventory/InventorySources/InventorySourceListItem.js:100
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:106
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:180
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js:120
-#: screens/NotificationTemplate/shared/NotificationTemplateForm.js:68
-#: screens/Project/ProjectList/ProjectList.js:194
-#: screens/Project/ProjectList/ProjectList.js:223
-#: screens/Project/ProjectList/ProjectListItem.js:218
-#: screens/Team/TeamRoles/TeamRoleListItem.js:17
-#: screens/Team/TeamRoles/TeamRolesList.js:181
-#: screens/Template/Survey/SurveyList.js:103
-#: screens/Template/Survey/SurveyList.js:103
-#: screens/Template/Survey/SurveyListItem.js:60
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/ProjectsList.js:93
-#: screens/TopologyView/Tooltip.js:92
-#: screens/User/UserDetail/UserDetail.js:75
-#: screens/User/UserRoles/UserRolesList.js:156
-#: screens/User/UserRoles/UserRolesListItem.js:21
-msgid "Type"
-msgstr ""
-
-#: screens/Credential/shared/TypeInputsSubForm.js:25
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:46
-#: screens/Project/shared/ProjectForm.js:247
-msgid "Type Details"
-msgstr ""
-
-#: screens/Template/Survey/MultipleChoiceField.js:56
-msgid ""
-"Type answer then click checkbox on right to select answer as\n"
-"default."
-msgstr ""
-
-#: components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js:50
-msgid "UTC"
-msgstr ""
-
-#: components/HostForm/HostForm.js:62
-msgid "Unable to change inventory on a host"
-msgstr ""
-
-#: screens/Project/ProjectList/ProjectListItem.js:211
-msgid "Unable to load last job update"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:253
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:87
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:46
-#: screens/InstanceGroup/Instances/InstanceListItem.js:78
-#: screens/Instances/InstanceDetail/InstanceDetail.js:205
-#: screens/Instances/InstanceList/InstanceListItem.js:77
-msgid "Unavailable"
-msgstr ""
-
-#: components/StatusLabel/StatusLabel.js:67
-#~ msgid "Undefined"
-#~ msgstr ""
-
-#: screens/Setting/shared/RevertButton.js:53
-#: screens/Setting/shared/RevertButton.js:62
-msgid "Undo"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:184
-msgid "Unfollow"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:122
-msgid "Unlimited"
-msgstr ""
-
-#: components/StatusLabel/StatusLabel.js:39
-#: screens/Job/JobOutput/shared/HostStatusBar.js:51
-#: screens/Job/JobOutput/shared/OutputToolbar.js:103
-msgid "Unreachable"
-msgstr ""
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:102
-msgid "Unreachable Host Count"
-msgstr ""
-
-#: screens/Job/JobOutput/shared/OutputToolbar.js:104
-msgid "Unreachable Hosts"
-msgstr ""
-
-#: util/dates.js:74
-msgid "Unrecognized day string"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:15
-msgid "Unsaved changes modal"
-msgstr ""
-
-#: screens/Project/shared/ProjectSubForms/SharedFields.js:90
-msgid "Update Revision on Launch"
-msgstr ""
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:57
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:138
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:89
-msgid "Update on launch"
-msgstr ""
-
-#: components/PromptDetail/PromptInventorySourceDetail.js:62
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:148
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:97
-msgid "Update on project update"
-msgstr ""
-
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:71
-msgid "Update options"
-msgstr ""
-
-#: components/PromptDetail/PromptProjectDetail.js:61
-#: screens/Project/ProjectDetail/ProjectDetail.js:114
-msgid "Update revision on job launch"
-msgstr ""
-
-#: screens/Setting/SettingList.js:87
-#~ msgid "Update settings pertaining to Jobs within {0}"
-#~ msgstr ""
-
-#: screens/Setting/SettingList.js:88
-msgid "Update settings pertaining to Jobs within {brandName}"
-msgstr ""
-
-#: screens/Template/shared/WebhookSubForm.js:187
-msgid "Update webhook key"
-msgstr ""
-
-#: components/Workflow/WorkflowNodeHelp.js:126
-msgid "Updating"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:118
-msgid "Upload a .zip file"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:97
-msgid "Upload a Red Hat Subscription Manifest containing your subscription. To generate your subscription manifest, go to <0>subscription allocations0> on the Red Hat Customer Portal."
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:52
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:126
-msgid "Use SSL"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:57
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:131
-msgid "Use TLS"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:72
-msgid ""
-"Use custom messages to change the content of\n"
-"notifications sent when a job starts, succeeds, or fails. Use\n"
-"curly braces to access information about the job:"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:12
-msgid "Use one Annotation Tag per line, without commas."
-msgstr ""
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:13
-msgid ""
-"Use one IRC channel or username per line. The pound\n"
-"symbol (#) for channels, and the at (@) symbol for users, are not\n"
-"required."
-msgstr ""
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:5
-msgid "Use one email address per line to create a recipient list for this type of notification."
-msgstr ""
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:28
-msgid ""
-"Use one phone number per line to specify where to\n"
-"route SMS messages. Phone numbers should be formatted +11231231234. For more information see Twilio documentation"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:242
-#: screens/InstanceGroup/Instances/InstanceList.js:259
-#: screens/Instances/InstanceDetail/InstanceDetail.js:192
-#: screens/Instances/InstanceList/InstanceList.js:154
-msgid "Used Capacity"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:246
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:250
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:78
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:86
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js:42
-#: screens/InstanceGroup/Instances/InstanceListItem.js:74
-#: screens/Instances/InstanceDetail/InstanceDetail.js:196
-#: screens/Instances/InstanceDetail/InstanceDetail.js:202
-#: screens/Instances/InstanceList/InstanceListItem.js:73
-msgid "Used capacity"
-msgstr ""
-
-#: components/ResourceAccessList/DeleteRoleConfirmationModal.js:13
-msgid "User"
-msgstr ""
-
-#: components/AppContainer/PageHeaderToolbar.js:165
-msgid "User Details"
-msgstr ""
-
-#: screens/Setting/SettingList.js:117
-#: screens/Setting/Settings.js:114
-msgid "User Interface"
-msgstr ""
-
-#: screens/Setting/SettingList.js:122
-msgid "User Interface settings"
-msgstr ""
-
-#: components/ResourceAccessList/ResourceAccessListItem.js:73
-#: screens/User/UserRoles/UserRolesList.js:142
-msgid "User Roles"
-msgstr ""
-
-#: screens/User/UserDetail/UserDetail.js:72
-#: screens/User/shared/UserForm.js:119
-msgid "User Type"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:59
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:60
-msgid "User analytics"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:34
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:202
-msgid "User and Automation Analytics"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:34
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionEdit.js:202
-#~ msgid "User and Insights analytics"
-#~ msgstr ""
-
-#: components/AppContainer/PageHeaderToolbar.js:159
-msgid "User details"
-msgstr ""
-
-#: screens/User/User.js:96
-msgid "User not found."
-msgstr ""
-
-#: screens/User/UserTokenList/UserTokenList.js:180
-msgid "User tokens"
-msgstr ""
-
-#: components/AddRole/AddResourceRole.js:23
-#: components/AddRole/AddResourceRole.js:38
-#: components/ResourceAccessList/ResourceAccessList.js:173
-#: components/ResourceAccessList/ResourceAccessList.js:226
-#: screens/Login/Login.js:208
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:143
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:248
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:298
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:356
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:65
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:251
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:328
-#: screens/NotificationTemplate/shared/TypeInputsSubForm.js:427
-#: screens/Setting/Subscription/SubscriptionEdit/AnalyticsStep.js:92
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:204
-#: screens/User/UserDetail/UserDetail.js:68
-#: screens/User/UserList/UserList.js:120
-#: screens/User/UserList/UserList.js:160
-#: screens/User/UserList/UserListItem.js:38
-#: screens/User/shared/UserForm.js:76
-msgid "Username"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:88
-msgid "Username / password"
-msgstr ""
-
-#: components/AddRole/AddResourceRole.js:178
-#: components/AddRole/AddResourceRole.js:179
-#: routeConfig.js:101
-#: screens/ActivityStream/ActivityStream.js:184
-#: screens/Team/Teams.js:30
-#: screens/User/UserList/UserList.js:110
-#: screens/User/UserList/UserList.js:153
-#: screens/User/Users.js:15
-#: screens/User/Users.js:26
-msgid "Users"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/InventorySourcesList.js:101
-msgid "VMware vCenter"
-msgstr ""
-
-#: components/AdHocCommands/AdHocPreviewStep.js:69
-#: components/HostForm/HostForm.js:113
-#: components/LaunchPrompt/steps/OtherPromptsStep.js:81
-#: components/PromptDetail/PromptDetail.js:159
-#: components/PromptDetail/PromptDetail.js:291
-#: components/PromptDetail/PromptJobTemplateDetail.js:278
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:132
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:393
-#: screens/Host/HostDetail/HostDetail.js:91
-#: screens/Inventory/InventoryDetail/InventoryDetail.js:126
-#: screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js:37
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:89
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:145
-#: screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js:54
-#: screens/Inventory/shared/InventoryForm.js:90
-#: screens/Inventory/shared/InventoryGroupForm.js:46
-#: screens/Inventory/shared/SmartInventoryForm.js:93
-#: screens/Job/JobDetail/JobDetail.js:528
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:484
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:228
-#: screens/Template/shared/JobTemplateForm.js:393
-#: screens/Template/shared/WorkflowJobTemplateForm.js:205
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:335
-msgid "Variables"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:131
-msgid "Variables Prompted"
-msgstr ""
-
-#: screens/Inventory/shared/Inventory.helptext.js:43
-msgid "Variables must be in JSON or YAML syntax. Use the radio button to toggle between the two."
-msgstr ""
-
-#: screens/Inventory/shared/Inventory.helptext.js:166
-msgid "Variables used to configure the inventory source. For a detailed description of how to configure this plugin, see <0>Inventory Plugins0> in the documentation and the <1>{sourceType}1> plugin configuration guide."
-msgstr ""
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:121
-msgid "Vault password"
-msgstr ""
-
-#: components/LaunchPrompt/steps/CredentialPasswordsStep.js:121
-msgid "Vault password | {credId}"
-msgstr ""
-
-#: screens/Job/JobOutput/JobOutputSearch.js:132
-msgid "Verbose"
-msgstr ""
-
-#: components/AdHocCommands/AdHocPreviewStep.js:63
-#: components/PromptDetail/PromptDetail.js:221
-#: components/PromptDetail/PromptInventorySourceDetail.js:111
-#: components/PromptDetail/PromptJobTemplateDetail.js:149
-#: components/Schedule/ScheduleDetail/ScheduleDetail.js:309
-#: components/VerbositySelectField/VerbositySelectField.js:47
-#: components/VerbositySelectField/VerbositySelectField.js:58
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:247
-#: screens/Inventory/shared/InventorySourceSubForms/SharedFields.js:43
-#: screens/Job/JobDetail/JobDetail.js:326
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:264
-msgid "Verbosity"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:64
-msgid "Version"
-msgstr ""
-
-#: screens/Setting/AzureAD/AzureAD.js:25
-msgid "View Azure AD settings"
-msgstr ""
-
-#: screens/Credential/Credential.js:143
-#: screens/Credential/Credential.js:155
-msgid "View Credential Details"
-msgstr ""
-
-#: components/Schedule/Schedule.js:151
-msgid "View Details"
-msgstr ""
-
-#: screens/Setting/GitHub/GitHub.js:58
-msgid "View GitHub Settings"
-msgstr ""
-
-#: screens/Setting/GoogleOAuth2/GoogleOAuth2.js:26
-msgid "View Google OAuth 2.0 settings"
-msgstr ""
-
-#: screens/Host/Host.js:137
-msgid "View Host Details"
-msgstr ""
-
-#: screens/Instances/Instance.js:41
-msgid "View Instance Details"
-msgstr ""
-
-#: screens/Inventory/Inventory.js:192
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:142
-#: screens/Inventory/SmartInventory.js:175
-msgid "View Inventory Details"
-msgstr ""
-
-#: screens/Inventory/InventoryGroup/InventoryGroup.js:92
-msgid "View Inventory Groups"
-msgstr ""
-
-#: screens/Inventory/InventoryHost/InventoryHost.js:160
-msgid "View Inventory Host Details"
-msgstr ""
-
-#: screens/Inventory/shared/Inventory.helptext.js:55
-msgid "View JSON examples at <0>www.json.org0>"
-msgstr ""
-
-#: screens/Job/Job.js:183
-msgid "View Job Details"
-msgstr ""
-
-#: screens/Setting/Jobs/Jobs.js:25
-msgid "View Jobs settings"
-msgstr ""
-
-#: screens/Setting/LDAP/LDAP.js:38
-msgid "View LDAP Settings"
-msgstr ""
-
-#: screens/Setting/Logging/Logging.js:32
-msgid "View Logging settings"
-msgstr ""
-
-#: screens/Setting/MiscAuthentication/MiscAuthentication.js:32
-msgid "View Miscellaneous Authentication settings"
-msgstr ""
-
-#: screens/Setting/MiscSystem/MiscSystem.js:32
-msgid "View Miscellaneous System settings"
-msgstr ""
-
-#: screens/Organization/Organization.js:225
-msgid "View Organization Details"
-msgstr ""
-
-#: screens/Project/Project.js:200
-msgid "View Project Details"
-msgstr ""
-
-#: screens/Setting/RADIUS/RADIUS.js:25
-msgid "View RADIUS settings"
-msgstr ""
-
-#: screens/Setting/SAML/SAML.js:25
-msgid "View SAML settings"
-msgstr ""
-
-#: components/Schedule/Schedule.js:83
-#: components/Schedule/Schedule.js:101
-msgid "View Schedules"
-msgstr ""
-
-#: screens/Setting/Subscription/Subscription.js:30
-msgid "View Settings"
-msgstr ""
-
-#: screens/Template/Template.js:159
-#: screens/Template/WorkflowJobTemplate.js:145
-msgid "View Survey"
-msgstr ""
-
-#: screens/Setting/TACACS/TACACS.js:25
-msgid "View TACACS+ settings"
-msgstr ""
-
-#: screens/Team/Team.js:118
-msgid "View Team Details"
-msgstr ""
-
-#: screens/Template/Template.js:260
-#: screens/Template/WorkflowJobTemplate.js:275
-msgid "View Template Details"
-msgstr ""
-
-#: screens/User/UserToken/UserToken.js:100
-msgid "View Tokens"
-msgstr ""
-
-#: screens/User/User.js:141
-msgid "View User Details"
-msgstr ""
-
-#: screens/Setting/UI/UI.js:26
-msgid "View User Interface settings"
-msgstr ""
-
-#: screens/WorkflowApproval/WorkflowApproval.js:102
-msgid "View Workflow Approval Details"
-msgstr ""
-
-#: screens/Inventory/shared/Inventory.helptext.js:66
-msgid "View YAML examples at <0>docs.ansible.com0>"
-msgstr ""
-
-#: components/ScreenHeader/ScreenHeader.js:65
-#: components/ScreenHeader/ScreenHeader.js:68
-msgid "View activity stream"
-msgstr ""
-
-#: screens/Credential/Credential.js:99
-msgid "View all Credentials."
-msgstr ""
-
-#: screens/Host/Host.js:97
-msgid "View all Hosts."
-msgstr ""
-
-#: screens/Inventory/Inventory.js:95
-#: screens/Inventory/SmartInventory.js:95
-msgid "View all Inventories."
-msgstr ""
-
-#: screens/Inventory/InventoryHost/InventoryHost.js:101
-msgid "View all Inventory Hosts."
-msgstr ""
-
-#: screens/Job/JobTypeRedirect.js:40
-msgid "View all Jobs"
-msgstr ""
-
-#: screens/Job/Job.js:139
-msgid "View all Jobs."
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplate.js:60
-#: screens/NotificationTemplate/NotificationTemplateAdd.js:52
-msgid "View all Notification Templates."
-msgstr ""
-
-#: screens/Organization/Organization.js:155
-msgid "View all Organizations."
-msgstr ""
-
-#: screens/Project/Project.js:137
-msgid "View all Projects."
-msgstr ""
-
-#: screens/Team/Team.js:76
-msgid "View all Teams."
-msgstr ""
-
-#: screens/Template/Template.js:176
-#: screens/Template/WorkflowJobTemplate.js:176
-msgid "View all Templates."
-msgstr ""
-
-#: screens/User/User.js:97
-msgid "View all Users."
-msgstr ""
-
-#: screens/WorkflowApproval/WorkflowApproval.js:54
-msgid "View all Workflow Approvals."
-msgstr ""
-
-#: screens/Application/Application/Application.js:96
-msgid "View all applications."
-msgstr ""
-
-#: screens/CredentialType/CredentialType.js:78
-msgid "View all credential types"
-msgstr ""
-
-#: screens/ExecutionEnvironment/ExecutionEnvironment.js:85
-msgid "View all execution environments"
-msgstr ""
-
-#: screens/InstanceGroup/ContainerGroup.js:86
-#: screens/InstanceGroup/InstanceGroup.js:94
-msgid "View all instance groups"
-msgstr ""
-
-#: screens/ManagementJob/ManagementJob.js:135
-msgid "View all management jobs"
-msgstr ""
-
-#: screens/Setting/Settings.js:197
-msgid "View all settings"
-msgstr ""
-
-#: screens/User/UserToken/UserToken.js:74
-msgid "View all tokens."
-msgstr ""
-
-#: screens/Setting/SettingList.js:129
-msgid "View and edit your subscription information"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStreamDetailButton.js:25
-#: screens/ActivityStream/ActivityStreamListItem.js:50
-msgid "View event details"
-msgstr ""
-
-#: screens/Inventory/InventorySource/InventorySource.js:167
-msgid "View inventory source details"
-msgstr ""
-
-#: components/Sparkline/Sparkline.js:44
-msgid "View job {0}"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerNode.js:193
-msgid "View node details"
-msgstr ""
-
-#: screens/Inventory/SmartInventoryHost/SmartInventoryHost.js:85
-msgid "View smart inventory host details"
-msgstr ""
-
-#: routeConfig.js:30
-#: screens/ActivityStream/ActivityStream.js:145
-msgid "Views"
-msgstr ""
-
-#: components/TemplateList/TemplateListItem.js:198
-#: components/TemplateList/TemplateListItem.js:204
-#: screens/Template/WorkflowJobTemplate.js:137
-msgid "Visualizer"
-msgstr ""
-
-#: screens/Project/shared/ProjectSubForms/ManualSubForm.js:43
-msgid "WARNING:"
-msgstr ""
-
-#: components/JobList/JobList.js:229
-#: components/StatusLabel/StatusLabel.js:44
-#: components/Workflow/WorkflowNodeHelp.js:96
-msgid "Waiting"
-msgstr ""
-
-#: screens/Job/JobOutput/EmptyOutput.js:23
-msgid "Waiting for job output…"
-msgstr ""
-
-#: components/Workflow/WorkflowLegend.js:118
-#: screens/Job/JobOutput/JobOutputSearch.js:133
-msgid "Warning"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/UnsavedChangesModal.js:14
-msgid "Warning: Unsaved Changes"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:119
-msgid "We were unable to locate licenses associated with this account."
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js:138
-msgid "We were unable to locate subscriptions associated with this account."
-msgstr ""
-
-#: components/DetailList/LaunchedByDetail.js:24
-#: components/NotificationList/NotificationList.js:202
-#: screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js:143
-msgid "Webhook"
-msgstr ""
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:172
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:101
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:326
-#: screens/Template/shared/WebhookSubForm.js:198
-msgid "Webhook Credential"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:176
-msgid "Webhook Credentials"
-msgstr ""
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:168
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:90
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:319
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:169
-#: screens/Template/shared/WebhookSubForm.js:172
-msgid "Webhook Key"
-msgstr ""
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:161
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:89
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:304
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:157
-#: screens/Template/shared/WebhookSubForm.js:128
-msgid "Webhook Service"
-msgstr ""
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:164
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:93
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:312
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:163
-#: screens/Template/shared/WebhookSubForm.js:160
-#: screens/Template/shared/WebhookSubForm.js:166
-msgid "Webhook URL"
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:588
-#: screens/Template/shared/WorkflowJobTemplateForm.js:240
-msgid "Webhook details"
-msgstr ""
-
-#: screens/Template/shared/JobTemplate.helptext.js:23
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:17
-msgid "Webhook services can launch jobs with this workflow job template by making a POST request to this URL."
-msgstr ""
-
-#: screens/Template/shared/JobTemplate.helptext.js:24
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:18
-msgid "Webhook services can use this as a shared secret."
-msgstr ""
-
-#: components/PromptDetail/PromptJobTemplateDetail.js:78
-#: components/PromptDetail/PromptWFJobTemplateDetail.js:41
-#: screens/Template/JobTemplateDetail/JobTemplateDetail.js:140
-#: screens/Template/WorkflowJobTemplateDetail/WorkflowJobTemplateDetail.js:63
-msgid "Webhooks"
-msgstr ""
-
-#: screens/Template/shared/WorkflowJobTemplate.helptext.js:24
-msgid "Webhooks: Enable Webhook for this workflow job template."
-msgstr ""
-
-#: screens/Template/shared/JobTemplate.helptext.js:39
-msgid "Webhooks: Enable webhook for this template."
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:281
-msgid "Wed"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:286
-#: components/Schedule/shared/FrequencyDetailSubform.js:436
-msgid "Wednesday"
-msgstr ""
-
-#: components/Schedule/shared/ScheduleForm.js:157
-msgid "Week"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:457
-msgid "Weekday"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:462
-msgid "Weekend day"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js:59
-msgid ""
-"Welcome to Red Hat Ansible Automation Platform!\n"
-"Please complete the steps below to activate your subscription."
-msgstr ""
-
-#: screens/Login/Login.js:168
-msgid "Welcome to {brandName}!"
-msgstr ""
-
-#: screens/Inventory/shared/Inventory.helptext.js:105
-msgid ""
-"When not checked, a merge will be performed,\n"
-"combining local variables with those found on the\n"
-"external source."
-msgstr ""
-
-#: screens/Inventory/shared/Inventory.helptext.js:93
-msgid ""
-"When not checked, local child\n"
-"hosts and groups not found on the external source will remain\n"
-"untouched by the inventory update process."
-msgstr ""
-
-#: components/Workflow/WorkflowLegend.js:96
-msgid "Workflow"
-msgstr ""
-
-#: components/Workflow/WorkflowNodeHelp.js:75
-msgid "Workflow Approval"
-msgstr ""
-
-#: screens/WorkflowApproval/WorkflowApproval.js:52
-msgid "Workflow Approval not found."
-msgstr ""
-
-#: routeConfig.js:54
-#: screens/ActivityStream/ActivityStream.js:156
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:195
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:233
-#: screens/WorkflowApproval/WorkflowApprovals.js:13
-#: screens/WorkflowApproval/WorkflowApprovals.js:22
-msgid "Workflow Approvals"
-msgstr ""
-
-#: components/JobList/JobList.js:216
-#: components/JobList/JobListItem.js:47
-#: components/Schedule/ScheduleList/ScheduleListItem.js:40
-#: screens/Job/JobDetail/JobDetail.js:69
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:265
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:252
-msgid "Workflow Job"
-msgstr ""
-
-#: components/JobList/JobListItem.js:201
-#: components/Workflow/WorkflowNodeHelp.js:63
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js:20
-#: screens/Job/JobDetail/JobDetail.js:224
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:91
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:227
-#: util/getRelatedResourceDeleteDetails.js:104
-msgid "Workflow Job Template"
-msgstr ""
-
-#: util/getRelatedResourceDeleteDetails.js:114
-#: util/getRelatedResourceDeleteDetails.js:156
-#: util/getRelatedResourceDeleteDetails.js:259
-msgid "Workflow Job Template Nodes"
-msgstr ""
-
-#: util/getRelatedResourceDeleteDetails.js:139
-msgid "Workflow Job Templates"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:23
-msgid "Workflow Link"
-msgstr ""
-
-#: components/TemplateList/TemplateList.js:218
-#: screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js:98
-msgid "Workflow Template"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:514
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:159
-msgid "Workflow approved message"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:526
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:168
-msgid "Workflow approved message body"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:538
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:177
-msgid "Workflow denied message"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:550
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:186
-msgid "Workflow denied message body"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:104
-#: screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.js:106
-msgid "Workflow documentation"
-msgstr ""
-
-#: screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js:261
-msgid "Workflow job details"
-msgstr ""
-
-#: components/UserAndTeamAccessAdd/getResourceAccessConfig.js:46
-msgid "Workflow job templates"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.js:24
-msgid "Workflow link modal"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js:228
-msgid "Workflow node view modal"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:562
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:195
-msgid "Workflow pending message"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:574
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:204
-msgid "Workflow pending message body"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:586
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:213
-msgid "Workflow timed out message"
-msgstr ""
-
-#: screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js:598
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:222
-msgid "Workflow timed out message body"
-msgstr ""
-
-#: screens/User/shared/UserTokenForm.js:77
-msgid "Write"
-msgstr ""
-
-#: screens/Inventory/shared/Inventory.helptext.js:52
-msgid "YAML:"
-msgstr ""
-
-#: components/Schedule/shared/ScheduleForm.js:159
-msgid "Year"
-msgstr ""
-
-#: components/Search/Search.js:229
-msgid "Yes"
-msgstr ""
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListApproveButton.js:28
-#~ msgid "You are unable to act on the following workflow approvals: {itemsUnableToApprove}"
-#~ msgstr ""
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalListDenyButton.js:28
-#~ msgid "You are unable to act on the following workflow approvals: {itemsUnableToDeny}"
-#~ msgstr ""
-
-#: components/Lookup/MultiCredentialsLookup.js:154
-msgid "You cannot select multiple vault credentials with the same vault ID. Doing so will automatically deselect the other with the same vault ID."
-msgstr ""
-
-#: screens/Inventory/InventoryGroups/InventoryGroupsList.js:96
-msgid "You do not have permission to delete the following Groups: {itemsUnableToDelete}"
-msgstr ""
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:152
-msgid "You do not have permission to delete {pluralizedItemName}: {itemsUnableToDelete}"
-msgstr ""
-
-#: components/DisassociateButton/DisassociateButton.js:66
-msgid "You do not have permission to disassociate the following: {itemsUnableToDisassociate}"
-msgstr ""
-
-#: screens/NotificationTemplate/shared/CustomMessagesSubForm.js:87
-msgid ""
-"You may apply a number of possible variables in the\n"
-"message. For more information, refer to the"
-msgstr ""
-
-#: screens/Login/Login.js:176
-msgid "Your session has expired. Please log in to continue where you left off."
-msgstr ""
-
-#: components/AppContainer/AppContainer.js:130
-msgid "Your session is about to expire"
-msgstr ""
-
-#: components/Workflow/WorkflowTools.js:121
-msgid "Zoom In"
-msgstr ""
-
-#: components/Workflow/WorkflowTools.js:100
-msgid "Zoom Out"
-msgstr ""
-
-#: screens/TopologyView/Header.js:51
-#: screens/TopologyView/Header.js:54
-msgid "Zoom in"
-msgstr ""
-
-#: screens/TopologyView/Header.js:63
-#: screens/TopologyView/Header.js:66
-msgid "Zoom out"
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:685
-#: screens/Template/shared/WebhookSubForm.js:149
-msgid "a new webhook key will be generated on save."
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:682
-#: screens/Template/shared/WebhookSubForm.js:139
-msgid "a new webhook url will be generated on save."
-msgstr ""
-
-#: screens/Template/Survey/SurveyListItem.js:157
-#~ msgid "actions"
-#~ msgstr ""
-
-#: screens/Inventory/shared/Inventory.helptext.js:123
-#: screens/Inventory/shared/Inventory.helptext.js:142
-msgid "and click on Update Revision on Launch"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStreamDescription.js:505
-msgid "approved"
-msgstr ""
-
-#: components/AppContainer/AppContainer.js:55
-msgid "brand logo"
-msgstr ""
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:279
-#: screens/Template/Survey/SurveyList.js:72
-msgid "cancel delete"
-msgstr ""
-
-#: screens/Setting/shared/SharedFields.js:341
-msgid "cancel edit login redirect"
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:217
-msgid "command"
-msgstr ""
-
-#: components/PaginatedTable/ToolbarDeleteButton.js:267
-#: screens/Template/Survey/SurveyList.js:63
-msgid "confirm delete"
-msgstr ""
-
-#: components/DisassociateButton/DisassociateButton.js:130
-#: screens/Team/TeamRoles/TeamRolesList.js:219
-msgid "confirm disassociate"
-msgstr ""
-
-#: screens/Setting/shared/SharedFields.js:330
-msgid "confirm edit login redirect"
-msgstr ""
-
-#: screens/TopologyView/ContentLoading.js:32
-msgid "content-loading-in-progress"
-msgstr ""
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:151
-msgid "deletion error"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStreamDescription.js:513
-msgid "denied"
-msgstr ""
-
-#: components/DisassociateButton/DisassociateButton.js:91
-msgid "disassociate"
-msgstr ""
-
-#: components/Lookup/HostFilterLookup.js:405
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:20
-#: screens/Template/Survey/SurveyQuestionForm.js:269
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:239
-msgid "documentation"
-msgstr ""
-
-#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:105
-#: screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js:116
-#: screens/Host/HostDetail/HostDetail.js:102
-#: screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js:97
-#: screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js:109
-#: screens/Inventory/InventoryHostDetail/InventoryHostDetail.js:100
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:305
-#: screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js:163
-#: screens/Project/ProjectDetail/ProjectDetail.js:291
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:165
-#: screens/User/UserDetail/UserDetail.js:92
-msgid "edit"
-msgstr ""
-
-#: screens/Template/Survey/SurveyListItem.js:163
-#~ msgid "edit survey"
-#~ msgstr ""
-
-#: screens/Template/Survey/SurveyListItem.js:65
-#: screens/Template/Survey/SurveyReorderModal.js:125
-msgid "encrypted"
-msgstr ""
-
-#: components/Lookup/HostFilterLookup.js:407
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:241
-msgid "for more info."
-msgstr ""
-
-#: screens/NotificationTemplate/shared/Notifications.helptext.js:21
-#: screens/Template/Survey/SurveyQuestionForm.js:271
-msgid "for more information."
-msgstr ""
-
-#: screens/TopologyView/Legend.js:100
-msgid "h"
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:150
-msgid "here"
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:118
-#: components/AdHocCommands/AdHocDetailsStep.js:170
-#: screens/Job/Job.helptext.js:38
-msgid "here."
-msgstr ""
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:49
-msgid "host-description-{0}"
-msgstr ""
-
-#: screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js:44
-msgid "host-name-{0}"
-msgstr ""
-
-#: components/Lookup/HostFilterLookup.js:417
-msgid "hosts"
-msgstr ""
-
-#: components/Pagination/Pagination.js:24
-msgid "items"
-msgstr ""
-
-#: screens/User/UserList/UserListItem.js:44
-msgid "ldap user"
-msgstr ""
-
-#: screens/User/UserDetail/UserDetail.js:76
-msgid "login type"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:203
-msgid "min"
-msgstr ""
-
-#: screens/Template/Survey/SurveyListItem.js:91
-#~ msgid "move down"
-#~ msgstr ""
-
-#: screens/Template/Survey/SurveyListItem.js:80
-#~ msgid "move up"
-#~ msgstr ""
-
-#: screens/Template/Survey/MultipleChoiceField.js:76
-msgid "new choice"
-msgstr ""
-
-#: screens/TopologyView/Tooltip.js:94
-msgid "node"
-msgstr ""
-
-#: components/Pagination/Pagination.js:36
-#: components/Schedule/shared/FrequencyDetailSubform.js:473
-msgid "of"
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:215
-msgid "option to the"
-msgstr ""
-
-#: components/Pagination/Pagination.js:25
-msgid "page"
-msgstr ""
-
-#: components/Pagination/Pagination.js:26
-msgid "pages"
-msgstr ""
-
-#: components/Pagination/Pagination.js:28
-msgid "per page"
-msgstr ""
-
-#: components/LaunchButton/ReLaunchDropDown.js:77
-#: components/LaunchButton/ReLaunchDropDown.js:100
-msgid "relaunch jobs"
-msgstr ""
-
-#: screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js:217
-msgid "sec"
-msgstr ""
-
-#: screens/Inventory/InventorySourceDetail/InventorySourceDetail.js:253
-msgid "seconds"
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:58
-msgid "select module"
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:127
-#~ msgid "select verbosity"
-#~ msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:135
-msgid "since"
-msgstr ""
-
-#: screens/User/UserList/UserListItem.js:49
-msgid "social login"
-msgstr ""
-
-#: screens/Template/shared/JobTemplateForm.js:339
-#: screens/Template/shared/WorkflowJobTemplateForm.js:183
-msgid "source control branch"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStreamListItem.js:30
-msgid "system"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStreamDescription.js:511
-msgid "timed out"
-msgstr ""
-
-#: components/AdHocCommands/AdHocDetailsStep.js:195
-msgid "toggle changes"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStreamDescription.js:516
-msgid "updated"
-msgstr ""
-
-#: screens/Template/shared/WebhookSubForm.js:180
-msgid "workflow job template webhook key"
-msgstr ""
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:65
-msgid "{0, plural, one {# source with sync failures.} other {# sources with sync failures.}}"
-msgstr ""
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:115
-msgid "{0, plural, one {Are you sure you want delete the group below?} other {Are you sure you want delete the groups below?}}"
-msgstr ""
-
-#: components/HealthCheckButton/HealthCheckButton.js:23
-#~ msgid "{0, plural, one {Click to run a health check on the selected instance.} other {Click to run a health check on the selected instances.}}"
-#~ msgstr ""
-
-#: screens/Inventory/shared/InventoryGroupsDeleteModal.js:86
-msgid "{0, plural, one {Delete Group?} other {Delete Groups?}}"
-msgstr ""
-
-#: util/validators.js:138
-msgid "{0, plural, one {Please enter a valid phone number.} other {Please enter valid phone numbers.}}"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:175
-#~ msgid "{0, plural, one {The following Instance Group cannot be deleted} other {The following Instance Groups cannot be deleted}}"
-#~ msgstr ""
-
-#: screens/Inventory/InventoryList/InventoryList.js:247
-msgid "{0, plural, one {The inventory will be in a pending status until the final delete is processed.} other {The inventories will be in a pending status until the final delete is processed.}}"
-msgstr ""
-
-#: components/JobList/JobList.js:280
-msgid "{0, plural, one {The selected job cannot be deleted due to insufficient permission or a running job status} other {The selected jobs cannot be deleted due to insufficient permissions or a running job status}}"
-msgstr ""
-
-#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:239
-msgid "{0, plural, one {This approval cannot be deleted due to insufficient permissions or a pending job status} other {These approvals cannot be deleted due to insufficient permissions or a pending job status}}"
-msgstr ""
-
-#: screens/Credential/CredentialList/CredentialList.js:198
-msgid "{0, plural, one {This credential is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these credentials could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr ""
-
-#: screens/CredentialType/CredentialTypeList/CredentialTypeList.js:164
-msgid "{0, plural, one {This credential type is currently being used by some credentials and cannot be deleted.} other {Credential types that are being used by credentials cannot be deleted. Are you sure you want to delete anyway?}}"
-msgstr ""
-
-#: screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js:194
-msgid "{0, plural, one {This execution environment is currently being used by other resources. Are you sure you want to delete it?} other {These execution environments could be in use by other resources that rely on them. Are you sure you want to delete them anyway?}}"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js:196
-msgid "{0, plural, one {This instance group is currently being by other resources. Are you sure you want to delete it?} other {Deleting these instance groups could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr ""
-
-#: screens/Inventory/InventoryList/InventoryList.js:240
-msgid "{0, plural, one {This inventory is currently being used by some templates. Are you sure you want to delete it?} other {Deleting these inventories could impact some templates that rely on them. Are you sure you want to delete anyway?}}"
-msgstr ""
-
-#: screens/Inventory/InventorySources/InventorySourceList.js:196
-msgid "{0, plural, one {This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?} other {Deleting these inventory sources could impact other resources that rely on them. Are you sure you want to delete anyway}}"
-msgstr ""
-
-#: screens/Organization/OrganizationList/OrganizationList.js:166
-#~ msgid "{0, plural, one {This organization is currently being by other resources. Are you sure you want to delete it?} other {Deleting these organizations could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-#~ msgstr ""
-
-#: screens/Organization/OrganizationList/OrganizationList.js:166
-msgid "{0, plural, one {This organization is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these organizations could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr ""
-
-#: screens/Project/ProjectList/ProjectList.js:252
-msgid "{0, plural, one {This project is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these projects could impact other resources that rely on them. Are you sure you want to delete anyway?}}"
-msgstr ""
-
-#: components/RelatedTemplateList/RelatedTemplateList.js:209
-#: components/TemplateList/TemplateList.js:266
-msgid "{0, plural, one {This template is currently being used by some workflow nodes. Are you sure you want to delete it?} other {Deleting these templates could impact some workflow nodes that rely on them. Are you sure you want to delete anyway?}}"
-msgstr ""
-
-#: components/JobList/JobListCancelButton.js:72
-msgid "{0, plural, one {You cannot cancel the following job because it is not running:} other {You cannot cancel the following jobs because they are not running:}}"
-msgstr ""
-
-#: components/JobList/JobListCancelButton.js:56
-msgid "{0, plural, one {You do not have permission to cancel the following job:} other {You do not have permission to cancel the following jobs:}}"
-msgstr ""
-
-#: screens/ActivityStream/ActivityStreamListItem.js:28
-msgid "{0} (deleted)"
-msgstr ""
-
-#: components/ChipGroup/ChipGroup.js:13
-msgid "{0} more"
-msgstr ""
-
-#: screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js:138
-#~ msgid "{0} since {1}"
-#~ msgstr ""
-
-#: screens/Inventory/InventoryList/InventoryListItem.js:61
-#~ msgid "{0} sources with sync failures."
-#~ msgstr ""
-
-#: components/AppContainer/AppContainer.js:55
-msgid "{brandName} logo"
-msgstr ""
-
-#: components/DetailList/UserDateDetail.js:23
-msgid "{dateStr} by <0>{username}0>"
-msgstr ""
-
-#: screens/InstanceGroup/InstanceDetails/InstanceDetails.js:224
-#: screens/InstanceGroup/Instances/InstanceListItem.js:148
-#: screens/Instances/InstanceDetail/InstanceDetail.js:174
-#: screens/Instances/InstanceList/InstanceListItem.js:158
-msgid "{forks, plural, one {# fork} other {# forks}}"
-msgstr ""
-
-#: components/HealthCheckButton/HealthCheckButton.js:15
-#~ msgid "{hopNodeSelected, plural, one {Cannot run health check on a hop node. Deselect the hop node to run a health check.} other {Cannot run health check on hop nodes. Deselect the hop nodes to run health checks.}}"
-#~ msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:193
-msgid "{intervalValue, plural, one {day} other {days}}"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:191
-msgid "{intervalValue, plural, one {hour} other {hours}}"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:189
-msgid "{intervalValue, plural, one {minute} other {minutes}}"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:197
-msgid "{intervalValue, plural, one {month} other {months}}"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:195
-msgid "{intervalValue, plural, one {week} other {weeks}}"
-msgstr ""
-
-#: components/Schedule/shared/FrequencyDetailSubform.js:199
-msgid "{intervalValue, plural, one {year} other {years}}"
-msgstr ""
-
-#: components/Schedule/shared/DateTimePicker.js:49
-#~ msgid "{label} date"
-#~ msgstr ""
-
-#: components/Schedule/shared/DateTimePicker.js:57
-#~ msgid "{label} time"
-#~ msgstr ""
-
-#: components/PromptDetail/PromptDetail.js:41
-msgid "{minutes} min {seconds} sec"
-msgstr ""
-
-#: components/JobList/JobListCancelButton.js:106
-msgid "{numJobsToCancel, plural, one {Cancel job} other {Cancel jobs}}"
-msgstr ""
-
-#: components/JobList/JobListCancelButton.js:168
-msgid "{numJobsToCancel, plural, one {This action will cancel the following job:} other {This action will cancel the following jobs:}}"
-msgstr ""
-
-#: components/JobList/JobListCancelButton.js:91
-msgid "{numJobsToCancel, plural, one {{0}} other {{1}}}"
-msgstr ""
-
-#: components/DetailList/NumberSinceDetail.js:19
-#~ msgid "{number} since {dateStr}"
-#~ msgstr ""
-
-#: components/PaginatedTable/PaginatedTable.js:79
-msgid "{pluralizedItemName} List"
-msgstr ""
-
-#: components/HealthCheckButton/HealthCheckButton.js:13
-msgid "{selectedItemsCount, plural, one {Click to run a health check on the selected instance.} other {Click to run a health check on the selected instances.}}"
-msgstr ""
-
-#: components/AppContainer/AppContainer.js:154
-msgid "{sessionCountdown, plural, one {You will be logged out in # second due to inactivity} other {You will be logged out in # seconds due to inactivity}}"
-msgstr ""
diff --git a/awx/ui/src/routeConfig.js b/awx/ui/src/routeConfig.js
deleted file mode 100644
index c5b472810733..000000000000
--- a/awx/ui/src/routeConfig.js
+++ /dev/null
@@ -1,195 +0,0 @@
-import React from 'react';
-import { Trans } from '@lingui/macro';
-
-import ActivityStream from 'screens/ActivityStream';
-import Applications from 'screens/Application';
-import CredentialTypes from 'screens/CredentialType';
-import Credentials from 'screens/Credential';
-import Dashboard from 'screens/Dashboard';
-import ExecutionEnvironments from 'screens/ExecutionEnvironment';
-import Hosts from 'screens/Host';
-import Instances from 'screens/Instances';
-import InstanceGroups from 'screens/InstanceGroup';
-import Inventory from 'screens/Inventory';
-import ManagementJobs from 'screens/ManagementJob';
-import NotificationTemplates from 'screens/NotificationTemplate';
-import Organizations from 'screens/Organization';
-import Projects from 'screens/Project';
-import Schedules from 'screens/Schedule';
-import Settings from 'screens/Setting';
-import Teams from 'screens/Team';
-import Templates from 'screens/Template';
-import TopologyView from 'screens/TopologyView';
-import Users from 'screens/User';
-import WorkflowApprovals from 'screens/WorkflowApproval';
-import { Jobs } from 'screens/Job';
-
-function getRouteConfig(userProfile = {}) {
- let routeConfig = [
- {
- groupTitle: Views,
- groupId: 'views_group',
- routes: [
- {
- title: Dashboard,
- path: '/home',
- screen: Dashboard,
- },
- {
- title: Jobs,
- path: '/jobs',
- screen: Jobs,
- },
- {
- title: Schedules,
- path: '/schedules',
- screen: Schedules,
- },
- {
- title: Activity Stream,
- path: '/activity_stream',
- screen: ActivityStream,
- },
- {
- title: Workflow Approvals,
- path: '/workflow_approvals',
- screen: WorkflowApprovals,
- },
- ],
- },
- {
- groupTitle: Resources,
- groupId: 'resources_group',
- routes: [
- {
- title: Templates,
- path: '/templates',
- screen: Templates,
- },
- {
- title: Credentials,
- path: '/credentials',
- screen: Credentials,
- },
- {
- title: Projects,
- path: '/projects',
- screen: Projects,
- },
- {
- title: Inventories,
- path: '/inventories',
- screen: Inventory,
- },
- {
- title: Hosts,
- path: '/hosts',
- screen: Hosts,
- },
- ],
- },
- {
- groupTitle: Access,
- groupId: 'access_group',
- routes: [
- {
- title: Organizations,
- path: '/organizations',
- screen: Organizations,
- },
- {
- title: Users,
- path: '/users',
- screen: Users,
- },
- {
- title: Teams,
- path: '/teams',
- screen: Teams,
- },
- ],
- },
- {
- groupTitle: Administration,
- groupId: 'administration_group',
- routes: [
- {
- title: Credential Types,
- path: '/credential_types',
- screen: CredentialTypes,
- },
- {
- title: Notifications,
- path: '/notification_templates',
- screen: NotificationTemplates,
- },
- {
- title: Management Jobs,
- path: '/management_jobs',
- screen: ManagementJobs,
- },
- {
- title: Instance Groups,
- path: '/instance_groups',
- screen: InstanceGroups,
- },
- {
- title: Instances,
- path: '/instances',
- screen: Instances,
- },
- {
- title: Applications,
- path: '/applications',
- screen: Applications,
- },
- {
- title: Execution Environments,
- path: '/execution_environments',
- screen: ExecutionEnvironments,
- },
- {
- title: Topology View,
- path: '/topology_view',
- screen: TopologyView,
- },
- ],
- },
- {
- groupTitle: Settings,
- groupId: 'settings',
- routes: [
- {
- title: Settings,
- path: '/settings',
- screen: Settings,
- },
- ],
- },
- ];
-
- const deleteRoute = (name) => {
- routeConfig.forEach((group) => {
- group.routes = group.routes.filter(({ path }) => !path.includes(name));
- });
- routeConfig = routeConfig.filter((groups) => groups.routes.length);
- };
-
- const deleteRouteGroup = (name) => {
- routeConfig = routeConfig.filter(({ groupId }) => !groupId.includes(name));
- };
-
- if (userProfile?.isSuperUser || userProfile?.isSystemAuditor)
- return routeConfig;
- deleteRouteGroup('settings');
- deleteRoute('management_jobs');
- if (userProfile?.isOrgAdmin) return routeConfig;
- deleteRoute('instance_groups');
- deleteRoute('topology_view');
- deleteRoute('instances');
- if (!userProfile?.isNotificationAdmin) deleteRoute('notification_templates');
-
- return routeConfig;
-}
-
-export default getRouteConfig;
diff --git a/awx/ui/src/routeConfig.test.js b/awx/ui/src/routeConfig.test.js
deleted file mode 100644
index 6210b5bc325b..000000000000
--- a/awx/ui/src/routeConfig.test.js
+++ /dev/null
@@ -1,270 +0,0 @@
-import getRouteConfig from './routeConfig';
-jest.mock('util/webWorker', () => jest.fn());
-
-const userProfile = {
- isSuperUser: false,
- isSystemAuditor: false,
- isOrgAdmin: false,
- isNotificationAdmin: false,
- isExecEnvAdmin: false,
-};
-
-const filterPaths = (sidebar) => {
- const visibleRoutes = [];
- sidebar.forEach(({ routes }) => {
- routes.forEach((route) => {
- visibleRoutes.push(route.path);
- });
- });
-
- return visibleRoutes;
-};
-describe('getRouteConfig', () => {
- test('routes for system admin', () => {
- const sidebar = getRouteConfig({ ...userProfile, isSuperUser: true });
- const filteredPaths = filterPaths(sidebar);
- expect(filteredPaths).toEqual([
- '/home',
- '/jobs',
- '/schedules',
- '/activity_stream',
- '/workflow_approvals',
- '/templates',
- '/credentials',
- '/projects',
- '/inventories',
- '/hosts',
- '/organizations',
- '/users',
- '/teams',
- '/credential_types',
- '/notification_templates',
- '/management_jobs',
- '/instance_groups',
- '/instances',
- '/applications',
- '/execution_environments',
- '/topology_view',
- '/settings',
- ]);
- });
-
- test('routes for system auditor', () => {
- const sidebar = getRouteConfig({ ...userProfile, isSystemAuditor: true });
- const filteredPaths = filterPaths(sidebar);
- expect(filteredPaths).toEqual([
- '/home',
- '/jobs',
- '/schedules',
- '/activity_stream',
- '/workflow_approvals',
- '/templates',
- '/credentials',
- '/projects',
- '/inventories',
- '/hosts',
- '/organizations',
- '/users',
- '/teams',
- '/credential_types',
- '/notification_templates',
- '/management_jobs',
- '/instance_groups',
- '/instances',
- '/applications',
- '/execution_environments',
- '/topology_view',
- '/settings',
- ]);
- });
-
- test('routes for org admin', () => {
- const sidebar = getRouteConfig({ ...userProfile, isOrgAdmin: true });
- const filteredPaths = filterPaths(sidebar);
- expect(filteredPaths).toEqual([
- '/home',
- '/jobs',
- '/schedules',
- '/activity_stream',
- '/workflow_approvals',
- '/templates',
- '/credentials',
- '/projects',
- '/inventories',
- '/hosts',
- '/organizations',
- '/users',
- '/teams',
- '/credential_types',
- '/notification_templates',
- '/instance_groups',
- '/instances',
- '/applications',
- '/execution_environments',
- '/topology_view',
- ]);
- });
-
- test('routes for notifications admin', () => {
- const sidebar = getRouteConfig({
- ...userProfile,
- isNotificationAdmin: true,
- });
- const filteredPaths = filterPaths(sidebar);
- expect(filteredPaths).toEqual([
- '/home',
- '/jobs',
- '/schedules',
- '/activity_stream',
- '/workflow_approvals',
- '/templates',
- '/credentials',
- '/projects',
- '/inventories',
- '/hosts',
- '/organizations',
- '/users',
- '/teams',
- '/credential_types',
- '/notification_templates',
- '/applications',
- '/execution_environments',
- ]);
- });
-
- test('routes for execution environments admin', () => {
- const sidebar = getRouteConfig({ ...userProfile, isExecEnvAdmin: true });
- const filteredPaths = filterPaths(sidebar);
- expect(filteredPaths).toEqual([
- '/home',
- '/jobs',
- '/schedules',
- '/activity_stream',
- '/workflow_approvals',
- '/templates',
- '/credentials',
- '/projects',
- '/inventories',
- '/hosts',
- '/organizations',
- '/users',
- '/teams',
- '/credential_types',
- '/applications',
- '/execution_environments',
- ]);
- });
-
- test('routes for regular users', () => {
- const sidebar = getRouteConfig(userProfile);
- const filteredPaths = filterPaths(sidebar);
- expect(filteredPaths).toEqual([
- '/home',
- '/jobs',
- '/schedules',
- '/activity_stream',
- '/workflow_approvals',
- '/templates',
- '/credentials',
- '/projects',
- '/inventories',
- '/hosts',
- '/organizations',
- '/users',
- '/teams',
- '/credential_types',
- '/applications',
- '/execution_environments',
- ]);
- });
-
- test('routes for execution environment admins and notification admin', () => {
- const sidebar = getRouteConfig({
- ...userProfile,
- isExecEnvAdmin: true,
- isNotificationAdmin: true,
- });
- const filteredPaths = filterPaths(sidebar);
- expect(filteredPaths).toEqual([
- '/home',
- '/jobs',
- '/schedules',
- '/activity_stream',
- '/workflow_approvals',
- '/templates',
- '/credentials',
- '/projects',
- '/inventories',
- '/hosts',
- '/organizations',
- '/users',
- '/teams',
- '/credential_types',
- '/notification_templates',
- '/applications',
- '/execution_environments',
- ]);
- });
-
- test('routes for execution environment admins and organization admins', () => {
- const sidebar = getRouteConfig({
- ...userProfile,
- isExecEnvAdmin: true,
- isOrgAdmin: true,
- });
- const filteredPaths = filterPaths(sidebar);
- expect(filteredPaths).toEqual([
- '/home',
- '/jobs',
- '/schedules',
- '/activity_stream',
- '/workflow_approvals',
- '/templates',
- '/credentials',
- '/projects',
- '/inventories',
- '/hosts',
- '/organizations',
- '/users',
- '/teams',
- '/credential_types',
- '/notification_templates',
- '/instance_groups',
- '/instances',
- '/applications',
- '/execution_environments',
- '/topology_view',
- ]);
- });
-
- test('routes for notification admins and organization admins', () => {
- const sidebar = getRouteConfig({
- ...userProfile,
- isNotificationAdmin: true,
- isOrgAdmin: true,
- });
- const filteredPaths = filterPaths(sidebar);
- expect(filteredPaths).toEqual([
- '/home',
- '/jobs',
- '/schedules',
- '/activity_stream',
- '/workflow_approvals',
- '/templates',
- '/credentials',
- '/projects',
- '/inventories',
- '/hosts',
- '/organizations',
- '/users',
- '/teams',
- '/credential_types',
- '/notification_templates',
- '/instance_groups',
- '/instances',
- '/applications',
- '/execution_environments',
- '/topology_view',
- ]);
- });
-});
diff --git a/awx/ui/src/screens/ActivityStream/ActivityStream.js b/awx/ui/src/screens/ActivityStream/ActivityStream.js
deleted file mode 100644
index bae143fc5222..000000000000
--- a/awx/ui/src/screens/ActivityStream/ActivityStream.js
+++ /dev/null
@@ -1,282 +0,0 @@
-import React, { useState, useEffect, useCallback } from 'react';
-import { useLocation, useHistory } from 'react-router-dom';
-import styled from 'styled-components';
-import { t } from '@lingui/macro';
-import {
- Card,
- PageSection,
- PageSectionVariants,
- SelectGroup,
- Select as PFSelect,
- SelectVariant,
- SelectOption,
- Title,
-} from '@patternfly/react-core';
-
-import DatalistToolbar from 'components/DataListToolbar';
-import PaginatedTable, {
- HeaderRow,
- HeaderCell,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import useRequest from 'hooks/useRequest';
-import useTitle from 'hooks/useTitle';
-import { getQSConfig, parseQueryString, updateQueryString } from 'util/qs';
-import { ActivityStreamAPI } from 'api';
-
-import ActivityStreamListItem from './ActivityStreamListItem';
-
-const Select = styled(PFSelect)`
- && {
- width: auto;
- white-space: nowrap;
- max-height: 480px;
- }
-`;
-
-function ActivityStream() {
- const { light } = PageSectionVariants;
-
- const [isTypeDropdownOpen, setIsTypeDropdownOpen] = useState(false);
- const location = useLocation();
- const history = useHistory();
- useTitle(t`Activity Stream`);
- const urlParams = new URLSearchParams(location.search);
-
- const activityStreamType = urlParams.get('type') || 'all';
-
- let typeParams = {};
-
- if (activityStreamType !== 'all') {
- typeParams = {
- or__object1__in: activityStreamType,
- or__object2__in: activityStreamType,
- };
- }
-
- const QS_CONFIG = getQSConfig(
- 'activity_stream',
- {
- page: 1,
- page_size: 20,
- order_by: '-timestamp',
- },
- ['id', 'page', 'page_size']
- );
-
- const {
- result: { results, count, relatedSearchableKeys, searchableKeys },
- error: contentError,
- isLoading,
- request: fetchActivityStream,
- } = useRequest(
- useCallback(
- async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
- const [response, actionsResponse] = await Promise.all([
- ActivityStreamAPI.read({ ...params, ...typeParams }),
- ActivityStreamAPI.readOptions(),
- ]);
- return {
- results: response.data.results,
- count: response.data.count,
- relatedSearchableKeys: (
- actionsResponse?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
- };
- },
- [location] // eslint-disable-line react-hooks/exhaustive-deps
- ),
- {
- results: [],
- count: 0,
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
- useEffect(() => {
- fetchActivityStream();
- }, [fetchActivityStream]);
-
- const pushHistoryState = (urlParamsToAdd) => {
- const pageOneQs = updateQueryString(QS_CONFIG, location.search, {
- page: 1,
- });
- const qs = updateQueryString(null, pageOneQs, {
- type: urlParamsToAdd.get('type'),
- });
-
- history.push(qs ? `${location.pathname}?${qs}` : location.pathname);
- };
-
- return (
- <>
-
-
- {t`Activity Stream`}
-
-
- {t`Activity Stream type selector`}
-
-
-
-
-
-
- {t`Time`}
-
- {t`Initiated by`}
-
- {t`Event`}
- {t`Actions`}
-
- }
- renderToolbar={(props) => (
-
- )}
- renderRow={(streamItem, index) => (
-
- )}
- />
-
-
- >
- );
-}
-
-export default ActivityStream;
diff --git a/awx/ui/src/screens/ActivityStream/ActivityStream.test.js b/awx/ui/src/screens/ActivityStream/ActivityStream.test.js
deleted file mode 100644
index 7db636058325..000000000000
--- a/awx/ui/src/screens/ActivityStream/ActivityStream.test.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-
-import ActivityStream from './ActivityStream';
-
-jest.mock('../../api');
-
-describe('', () => {
- test('initially renders without crashing', async () => {
- let pageWrapper;
- await act(async () => {
- pageWrapper = await mountWithContexts();
- });
- expect(pageWrapper.length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/ActivityStream/ActivityStreamDescription.js b/awx/ui/src/screens/ActivityStream/ActivityStreamDescription.js
deleted file mode 100644
index cd4883c16803..000000000000
--- a/awx/ui/src/screens/ActivityStream/ActivityStreamDescription.js
+++ /dev/null
@@ -1,575 +0,0 @@
-import React from 'react';
-import { Link } from 'react-router-dom';
-import { t } from '@lingui/macro';
-
-const buildAnchor = (obj, resource, activity) => {
- let url;
- let name;
- // try/except pattern asserts that:
- // if we encounter a case where a UI url can't or
- // shouldn't be generated, just supply the name of the resource
- try {
- // catch-all case to avoid generating urls if a resource has been deleted
- // if a resource still exists, it'll be serialized in the activity's summary_fields
- if (!activity.summary_fields[resource]) {
- throw new Error('The referenced resource no longer exists');
- }
- switch (resource) {
- case 'custom_inventory_script':
- url = `/inventory_scripts/${obj.id}/`;
- break;
- case 'group':
- if (
- activity.operation === 'create' ||
- activity.operation === 'delete'
- ) {
- // the API formats the changes.inventory field as str 'myInventoryName-PrimaryKey'
- const [inventory_id] = activity.changes.inventory
- .split('-')
- .slice(-1);
- url = `/inventories/inventory/${inventory_id}/groups/${activity.changes.id}/details/`;
- } else {
- url = `/inventories/inventory/${
- activity.summary_fields.inventory[0].id
- }/groups/${
- activity.changes.id || activity.changes.object1_pk
- }/details/`;
- }
- break;
- case 'host':
- url = `/hosts/${obj.id}/`;
- break;
- case 'job':
- url = `/jobs/${obj.id}/`;
- break;
- case 'inventory':
- url =
- obj?.kind === 'smart'
- ? `/inventories/smart_inventory/${obj.id}/`
- : `/inventories/inventory/${obj.id}/`;
- break;
- case 'schedule':
- // schedule urls depend on the resource they're associated with
- if (activity.summary_fields.job_template) {
- const jt_id = activity.summary_fields.job_template[0].id;
- url = `/templates/job_template/${jt_id}/schedules/${obj.id}/`;
- } else if (activity.summary_fields.workflow_job_template) {
- const wfjt_id = activity.summary_fields.workflow_job_template[0].id;
- url = `/templates/workflow_job_template/${wfjt_id}/schedules/${obj.id}/`;
- } else if (activity.summary_fields.project) {
- url = `/projects/${activity.summary_fields.project[0].id}/schedules/${obj.id}/`;
- } else if (activity.summary_fields.system_job_template) {
- url = null;
- } else {
- // urls for inventory sync schedules currently depend on having
- // an inventory id and group id
- throw new Error(
- 'activity.summary_fields to build this url not implemented yet'
- );
- }
- break;
- case 'setting':
- url = `/settings/`;
- break;
- case 'notification_template':
- url = `/notification_templates/${obj.id}/`;
- break;
- case 'role':
- throw new Error(
- 'role object management is not consolidated to a single UI view'
- );
- case 'job_template':
- url = `/templates/job_template/${obj.id}/`;
- break;
- case 'workflow_job_template':
- url = `/templates/workflow_job_template/${obj.id}/`;
- break;
- case 'workflow_job_template_node': {
- const { id: wfjt_id, name: wfjt_name } =
- activity.summary_fields.workflow_job_template[0];
- url = `/templates/workflow_job_template/${wfjt_id}/`;
- name = wfjt_name;
- break;
- }
- case 'workflow_job':
- url = `/jobs/workflow/${obj.id}/`;
- break;
- case 'label':
- url = null;
- break;
- case 'inventory_source': {
- const inventoryId = (obj.inventory || '').split('-').reverse()[0];
- url = `/inventories/inventory/${inventoryId}/sources/${obj.id}/details/`;
- break;
- }
- case 'o_auth2_application':
- url = `/applications/${obj.id}/`;
- break;
- case 'workflow_approval':
- url = `/jobs/workflow/${activity.summary_fields.workflow_job[0].id}/output/`;
- name = `${activity.summary_fields.workflow_job[0].name} | ${activity.summary_fields.workflow_approval[0].name}`;
- break;
- case 'workflow_approval_template':
- url = `/templates/workflow_job_template/${activity.summary_fields.workflow_job_template[0].id}/visualizer/`;
- name = `${activity.summary_fields.workflow_job_template[0].name} | ${activity.summary_fields.workflow_approval_template[0].name}`;
- break;
- default:
- url = `/${resource}s/${obj.id}/`;
- }
-
- name = name || obj.name || obj.username;
-
- if (url) {
- return {name};
- }
-
- return {name};
- } catch (err) {
- return {obj.name || obj.username || ''};
- }
-};
-
-const getPastTense = (item) => (/e$/.test(item) ? `${item}d` : `${item}ed`);
-
-const isGroupRelationship = (item) =>
- item.object1 === 'group' &&
- item.object2 === 'group' &&
- item.summary_fields.group.length > 1;
-
-const buildLabeledLink = (label, link) => (
-
- {label} {link}
-
-);
-
-function ActivityStreamDescription({ activity }) {
- const labeledLinks = [];
- // Activity stream objects will outlive the resources they reference
- // in that case, summary_fields will not be available - show generic error text instead
- try {
- switch (activity.object_association) {
- // explicit role dis+associations
- case 'role': {
- let { object1, object2 } = activity;
-
- // if object1 winds up being the role's resource, we need to swap the objects
- // in order to make the sentence make sense.
- if (activity.object_type === object1) {
- object1 = activity.object2;
- object2 = activity.object1;
- }
-
- // object1 field is resource targeted by the dis+association
- // object2 field is the resource the role is inherited from
- // summary_field.role[0] contains ref info about the role
- switch (activity.operation) {
- // expected outcome: "disassociated role_name from "
- case 'disassociate':
- if (isGroupRelationship(activity)) {
- labeledLinks.push(
- buildLabeledLink(
- getPastTense(activity.operation),
- buildAnchor(
- activity.summary_fields.group[1],
- object2,
- activity
- )
- )
- );
- labeledLinks.push(
- buildLabeledLink(
- `${activity.summary_fields.role[0].role_field} from`,
- buildAnchor(
- activity.summary_fields.group[0],
- object1,
- activity
- )
- )
- );
- } else {
- labeledLinks.push(
- buildLabeledLink(
- getPastTense(activity.operation),
- buildAnchor(
- activity.summary_fields[object2][0],
- object2,
- activity
- )
- )
- );
- labeledLinks.push(
- buildLabeledLink(
- `${activity.summary_fields.role[0].role_field} from`,
- buildAnchor(
- activity.summary_fields[object1][0],
- object1,
- activity
- )
- )
- );
- }
- break;
- // expected outcome: "associated role_name to "
- case 'associate':
- if (isGroupRelationship(activity)) {
- labeledLinks.push(
- buildLabeledLink(
- getPastTense(activity.operation),
- buildAnchor(
- activity.summary_fields.group[1],
- object2,
- activity
- )
- )
- );
- labeledLinks.push(
- buildLabeledLink(
- `${activity.summary_fields.role[0].role_field} to`,
- buildAnchor(
- activity.summary_fields.group[0],
- object1,
- activity
- )
- )
- );
- } else {
- labeledLinks.push(
- buildLabeledLink(
- getPastTense(activity.operation),
- buildAnchor(
- activity.summary_fields[object2][0],
- object2,
- activity
- )
- )
- );
- labeledLinks.push(
- buildLabeledLink(
- `${activity.summary_fields.role[0].role_field} to`,
- buildAnchor(
- activity.summary_fields[object1][0],
- object1,
- activity
- )
- )
- );
- }
- break;
- default:
- break;
- }
- break;
- // inherited role dis+associations (logic identical to case 'role')
- }
- case 'parents':
- // object1 field is resource targeted by the dis+association
- // object2 field is the resource the role is inherited from
- // summary_field.role[0] contains ref info about the role
- switch (activity.operation) {
- // expected outcome: "disassociated role_name from "
- case 'disassociate':
- if (isGroupRelationship(activity)) {
- labeledLinks.push(
- buildLabeledLink(
- `${getPastTense(activity.operation)} ${activity.object2}`,
- buildAnchor(
- activity.summary_fields.group[1],
- activity.object2,
- activity
- )
- )
- );
- labeledLinks.push(
- buildLabeledLink(
- `from ${activity.object1}`,
- buildAnchor(
- activity.summary_fields.group[0],
- activity.object1,
- activity
- )
- )
- );
- } else {
- labeledLinks.push(
- buildLabeledLink(
- getPastTense(activity.operation),
- buildAnchor(
- activity.summary_fields[activity.object2][0],
- activity.object2,
- activity
- )
- )
- );
- labeledLinks.push(
- buildLabeledLink(
- `${activity.summary_fields.role[0].role_field} from`,
- buildAnchor(
- activity.summary_fields[activity.object1][0],
- activity.object1,
- activity
- )
- )
- );
- }
- break;
- // expected outcome: "associated role_name to "
- case 'associate':
- if (isGroupRelationship(activity)) {
- labeledLinks.push(
- buildLabeledLink(
- `${getPastTense(activity.operation)} ${activity.object1}`,
- buildAnchor(
- activity.summary_fields.group[0],
- activity.object1,
- activity
- )
- )
- );
- labeledLinks.push(
- buildLabeledLink(
- `to ${activity.object2}`,
- buildAnchor(
- activity.summary_fields.group[1],
- activity.object2,
- activity
- )
- )
- );
- } else {
- labeledLinks.push(
- buildLabeledLink(
- getPastTense(activity.operation),
- buildAnchor(
- activity.summary_fields[activity.object2][0],
- activity.object2,
- activity
- )
- )
- );
- labeledLinks.push(
- buildLabeledLink(
- `${activity.summary_fields.role[0].role_field} to`,
- buildAnchor(
- activity.summary_fields[activity.object1][0],
- activity.object1,
- activity
- )
- )
- );
- }
- break;
- default:
- break;
- }
- break;
- // CRUD operations / resource on resource dis+associations
- default:
- switch (activity.operation) {
- // expected outcome: "disassociated from "
- case 'disassociate':
- if (isGroupRelationship(activity)) {
- labeledLinks.push(
- buildLabeledLink(
- `${getPastTense(activity.operation)} ${activity.object2}`,
- buildAnchor(
- activity.summary_fields.group[1],
- activity.object2,
- activity
- )
- )
- );
- labeledLinks.push(
- buildLabeledLink(
- `from ${activity.object1}`,
- buildAnchor(
- activity.summary_fields.group[0],
- activity.object1,
- activity
- )
- )
- );
- } else if (
- activity.object1 === 'workflow_job_template_node' &&
- activity.object2 === 'workflow_job_template_node'
- ) {
- labeledLinks.push(
- buildLabeledLink(
- `${getPastTense(activity.operation)} two nodes on workflow`,
- buildAnchor(
- activity.summary_fields[activity.object1[0]],
- activity.object1,
- activity
- )
- )
- );
- } else {
- labeledLinks.push(
- buildLabeledLink(
- `${getPastTense(activity.operation)} ${activity.object2}`,
- buildAnchor(
- activity.summary_fields[activity.object2][0],
- activity.object2,
- activity
- )
- )
- );
- labeledLinks.push(
- buildLabeledLink(
- `from ${activity.object1}`,
- buildAnchor(
- activity.summary_fields[activity.object1][0],
- activity.object1,
- activity
- )
- )
- );
- }
- break;
- // expected outcome "associated to "
- case 'associate':
- // groups are the only resource that can be associated/disassociated into each other
- if (isGroupRelationship(activity)) {
- labeledLinks.push(
- buildLabeledLink(
- `${getPastTense(activity.operation)} ${activity.object1}`,
- buildAnchor(
- activity.summary_fields.group[0],
- activity.object1,
- activity
- )
- )
- );
- labeledLinks.push(
- buildLabeledLink(
- `to ${activity.object2}`,
- buildAnchor(
- activity.summary_fields.group[1],
- activity.object2,
- activity
- )
- )
- );
- } else if (
- activity.object1 === 'workflow_job_template_node' &&
- activity.object2 === 'workflow_job_template_node'
- ) {
- labeledLinks.push(
- buildLabeledLink(
- `${getPastTense(activity.operation)} two nodes on workflow`,
- buildAnchor(
- activity.summary_fields[activity.object1[0]],
- activity.object1,
- activity
- )
- )
- );
- } else {
- labeledLinks.push(
- buildLabeledLink(
- `${getPastTense(activity.operation)} ${activity.object1}`,
- buildAnchor(
- activity.summary_fields[activity.object1][0],
- activity.object1,
- activity
- )
- )
- );
- labeledLinks.push(
- buildLabeledLink(
- `to ${activity.object2}`,
- buildAnchor(
- activity.summary_fields[activity.object2][0],
- activity.object2,
- activity
- )
- )
- );
- }
- break;
- case 'delete':
- labeledLinks.push(
- buildLabeledLink(
- `${getPastTense(activity.operation)} ${activity.object1}`,
- buildAnchor(activity.changes, activity.object1, activity)
- )
- );
- break;
- // expected outcome: "operation "
- case 'update':
- if (
- activity.object1 === 'workflow_approval' &&
- activity?.changes?.status?.length === 2
- ) {
- let operationText = '';
- if (activity.changes.status[1] === 'successful') {
- operationText = t`approved`;
- } else if (activity.changes.status[1] === 'failed') {
- if (
- activity.changes.timed_out &&
- activity.changes.timed_out[1] === true
- ) {
- operationText = t`timed out`;
- } else {
- operationText = t`denied`;
- }
- } else {
- operationText = t`updated`;
- }
- labeledLinks.push(
- buildLabeledLink(
- `${operationText} ${activity.object1}`,
- buildAnchor(
- activity.summary_fields[activity.object1][0],
- activity.object1,
- activity
- )
- )
- );
- } else {
- labeledLinks.push(
- buildLabeledLink(
- `${getPastTense(activity.operation)} ${activity.object1}`,
- buildAnchor(
- activity.summary_fields[activity.object1][0],
- activity.object1,
- activity
- )
- )
- );
- }
- break;
- case 'create':
- labeledLinks.push(
- buildLabeledLink(
- `${getPastTense(activity.operation)} ${activity.object1}`,
- buildAnchor(activity.changes, activity.object1, activity)
- )
- );
- break;
- default:
- break;
- }
- break;
- }
- } catch (err) {
- return {t`Event summary not available`};
- }
-
- return (
-
- {labeledLinks.reduce(
- (acc, x) =>
- acc === null ? (
- x
- ) : (
- <>
- {acc} {x}
- >
- ),
- null
- )}
-
- );
-}
-
-export default ActivityStreamDescription;
diff --git a/awx/ui/src/screens/ActivityStream/ActivityStreamDescription.test.js b/awx/ui/src/screens/ActivityStream/ActivityStreamDescription.test.js
deleted file mode 100644
index c05c66502e85..000000000000
--- a/awx/ui/src/screens/ActivityStream/ActivityStreamDescription.test.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import ActivityStreamDescription from './ActivityStreamDescription';
-
-describe('ActivityStreamDescription', () => {
- test('initially renders successfully', () => {
- const description = mountWithContexts(
-
- );
- expect(description.find('span').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/ActivityStream/ActivityStreamDetailButton.js b/awx/ui/src/screens/ActivityStream/ActivityStreamDetailButton.js
deleted file mode 100644
index 47c82f3f6e83..000000000000
--- a/awx/ui/src/screens/ActivityStream/ActivityStreamDetailButton.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import React, { useState } from 'react';
-
-import { t } from '@lingui/macro';
-import { Button, Modal } from '@patternfly/react-core';
-import { SearchPlusIcon } from '@patternfly/react-icons';
-
-import { formatDateString } from 'util/dates';
-
-import { DetailList, Detail } from 'components/DetailList';
-import { VariablesDetail } from 'components/CodeEditor';
-
-function ActivityStreamDetailButton({ streamItem, user, description }) {
- const [isOpen, setIsOpen] = useState(false);
-
- const setting = streamItem?.summary_fields?.setting;
- const changeRows = Math.max(
- Object.keys(streamItem?.changes || []).length + 2,
- 6
- );
-
- return (
- <>
-
- setIsOpen(false)}
- >
-
-
-
-
-
-
- {streamItem?.changes && (
-
- )}
-
-
- >
- );
-}
-
-export default ActivityStreamDetailButton;
diff --git a/awx/ui/src/screens/ActivityStream/ActivityStreamDetailButton.test.js b/awx/ui/src/screens/ActivityStream/ActivityStreamDetailButton.test.js
deleted file mode 100644
index 33958570aa61..000000000000
--- a/awx/ui/src/screens/ActivityStream/ActivityStreamDetailButton.test.js
+++ /dev/null
@@ -1,80 +0,0 @@
-import React from 'react';
-import { Link } from 'react-router-dom';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import ActivityStreamDetailButton from './ActivityStreamDetailButton';
-
-jest.mock('../../api/models/ActivityStream');
-
-describe('', () => {
- test('initially renders successfully', () => {
- mountWithContexts(
- Bob}
- description={foo}
- />
- );
- });
- test('details are properly rendered', () => {
- function assertDetail(label, value) {
- expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
- expect(wrapper.find(`Detail[label="${label}"] dd`).text()).toBe(value);
- }
-
- const wrapper = mountWithContexts(
- Bob}
- description={foo}
- />
- );
-
- expect(wrapper.find('Modal[title="Event detail"]').prop('isOpen')).toBe(
- false
- );
-
- wrapper.find('Button').simulate('click');
-
- expect(wrapper.find('Modal[title="Event detail"]').prop('isOpen')).toBe(
- true
- );
-
- assertDetail('Time', '5/25/2021, 6:17:59 PM');
- assertDetail('Initiated by', 'Bob');
- assertDetail('Setting category', 'system');
- assertDetail('Setting name', 'INSIGHTS_TRACKING_STATE');
- assertDetail('Action', 'foo');
-
- const input = wrapper.find('VariablesDetail___StyledCodeEditor');
- expect(input).toHaveLength(1);
- expect(input.prop('value')).toEqual('{\n "value": false,\n "id": 6\n}');
- });
-});
diff --git a/awx/ui/src/screens/ActivityStream/ActivityStreamListItem.js b/awx/ui/src/screens/ActivityStream/ActivityStreamListItem.js
deleted file mode 100644
index 8359fcaffc69..000000000000
--- a/awx/ui/src/screens/ActivityStream/ActivityStreamListItem.js
+++ /dev/null
@@ -1,61 +0,0 @@
-import React from 'react';
-import { shape } from 'prop-types';
-
-import { Tr, Td } from '@patternfly/react-table';
-import { t } from '@lingui/macro';
-import { Link } from 'react-router-dom';
-
-import { formatDateString } from 'util/dates';
-import { ActionsTd, ActionItem } from 'components/PaginatedTable';
-
-import ActivityStreamDetailButton from './ActivityStreamDetailButton';
-import ActivityStreamDescription from './ActivityStreamDescription';
-
-function ActivityStreamListItem({ streamItem }) {
- ActivityStreamListItem.propTypes = {
- streamItem: shape({}).isRequired,
- };
-
- const buildUser = (item) => {
- let link;
- if (item?.summary_fields?.actor?.id) {
- link = (
-
- {item.summary_fields.actor.username}
-
- );
- } else if (item?.summary_fields?.actor) {
- link = t`${item.summary_fields.actor.username} (deleted)`;
- } else {
- link = t`system`;
- }
- return link;
- };
-
- const labelId = `check-action-${streamItem.id}`;
- const user = buildUser(streamItem);
- const description = ;
-
- return (
-
- |
-
- {streamItem.timestamp ? formatDateString(streamItem.timestamp) : ''}
- |
- {user} |
-
- {description}
- |
-
-
-
-
-
-
- );
-}
-export default ActivityStreamListItem;
diff --git a/awx/ui/src/screens/ActivityStream/ActivityStreamListItem.test.js b/awx/ui/src/screens/ActivityStream/ActivityStreamListItem.test.js
deleted file mode 100644
index 10e9cbb17333..000000000000
--- a/awx/ui/src/screens/ActivityStream/ActivityStreamListItem.test.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import ActivityStreamListItem from './ActivityStreamListItem';
-
-jest.mock('../../api/models/ActivityStream');
-
-describe('', () => {
- test('initially renders successfully', () => {
- mountWithContexts(
-
- );
- });
-});
diff --git a/awx/ui/src/screens/ActivityStream/index.js b/awx/ui/src/screens/ActivityStream/index.js
deleted file mode 100644
index 5c0c72d9efc1..000000000000
--- a/awx/ui/src/screens/ActivityStream/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ActivityStream';
diff --git a/awx/ui/src/screens/Application/Application/Application.js b/awx/ui/src/screens/Application/Application/Application.js
deleted file mode 100644
index e6d3a04ed056..000000000000
--- a/awx/ui/src/screens/Application/Application/Application.js
+++ /dev/null
@@ -1,141 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import {
- Route,
- Switch,
- Redirect,
- useParams,
- useLocation,
- Link,
-} from 'react-router-dom';
-import { t } from '@lingui/macro';
-
-import { CaretLeftIcon } from '@patternfly/react-icons';
-import { Card, PageSection } from '@patternfly/react-core';
-
-import useRequest from 'hooks/useRequest';
-import { ApplicationsAPI } from 'api';
-import ContentError from 'components/ContentError';
-import RoutedTabs from 'components/RoutedTabs';
-import ApplicationEdit from '../ApplicationEdit';
-import ApplicationDetails from '../ApplicationDetails';
-import ApplicationTokens from '../ApplicationTokens';
-
-function Application({ setBreadcrumb }) {
- const { id } = useParams();
- const { pathname } = useLocation();
- const {
- isLoading,
- error,
- result: { application, authorizationOptions, clientTypeOptions },
- request: fetchApplication,
- } = useRequest(
- useCallback(async () => {
- const [detail, options] = await Promise.all([
- ApplicationsAPI.readDetail(id),
- ApplicationsAPI.readOptions(),
- ]);
- const authorization =
- options.data.actions.GET.authorization_grant_type.choices.map(
- (choice) => ({
- value: choice[0],
- label: choice[1],
- key: choice[0],
- })
- );
- const clientType = options.data.actions.GET.client_type.choices.map(
- (choice) => ({
- value: choice[0],
- label: choice[1],
- key: choice[0],
- })
- );
- setBreadcrumb(detail.data);
-
- return {
- application: detail.data,
- authorizationOptions: authorization,
- clientTypeOptions: clientType,
- };
- }, [setBreadcrumb, id]),
- { authorizationOptions: [], clientTypeOptions: [] }
- );
-
- useEffect(() => {
- fetchApplication();
- }, [fetchApplication, pathname]);
-
- const tabsArray = [
- {
- name: (
- <>
-
- {t`Back to applications`}
- >
- ),
- link: '/applications',
- id: 0,
- isBackButton: true,
- },
- { name: t`Details`, link: `/applications/${id}/details`, id: 1 },
- { name: t`Tokens`, link: `/applications/${id}/tokens`, id: 2 },
- ];
-
- let cardHeader = ;
- if (pathname.endsWith('edit')) {
- cardHeader = null;
- }
-
- if (!isLoading && error) {
- return (
-
-
-
- {error.response?.status === 404 && (
-
- {t`Application not found.`}{' '}
- {t`View all applications.`}
-
- )}
-
-
-
- );
- }
-
- return (
-
-
- {cardHeader}
-
-
- {application && (
- <>
-
-
-
-
-
-
-
-
-
- >
- )}
-
-
-
- );
-}
-export default Application;
diff --git a/awx/ui/src/screens/Application/Application/Application.test.js b/awx/ui/src/screens/Application/Application/Application.test.js
deleted file mode 100644
index 141c5a006b9c..000000000000
--- a/awx/ui/src/screens/Application/Application/Application.test.js
+++ /dev/null
@@ -1,86 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { ApplicationsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import Application from './Application';
-
-jest.mock('../../../api/models/Applications');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- history: () => ({
- location: '/applications',
- }),
- useParams: () => ({ id: 1 }),
-}));
-
-const options = {
- data: {
- actions: {
- GET: {
- client_type: {
- choices: [
- ['confidential', 'Confidential'],
- ['public', 'Public'],
- ],
- },
- authorization_grant_type: {
- choices: [
- ['authorization-code', 'Authorization code'],
- ['password', 'Resource owner password-based'],
- ],
- },
- },
- },
- },
-};
-const application = {
- id: 1,
- name: 'Foo',
- summary_fields: {
- organization: { name: 'Org 1', id: 10 },
- user_capabilities: { edit: true, delete: true },
- },
- url: '',
- organization: 10,
-};
-describe('', () => {
- let wrapper;
- test('mounts properly', async () => {
- ApplicationsAPI.readOptions.mockResolvedValue(options);
- ApplicationsAPI.readDetail.mockResolvedValue(application);
- await act(async () => {
- wrapper = mountWithContexts( {}} />);
- });
- expect(wrapper.find('Application').length).toBe(1);
- expect(ApplicationsAPI.readOptions).toBeCalled();
- expect(ApplicationsAPI.readDetail).toBeCalledWith(1);
- });
- test('should throw error', async () => {
- ApplicationsAPI.readOptions.mockResolvedValue(options);
- ApplicationsAPI.readDetail.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'get',
- url: '/api/v2/applications/1',
- },
- data: 'An error occurred',
- status: 404,
- },
- })
- );
- await act(async () => {
- wrapper = mountWithContexts( {}} />);
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length > 0);
- expect(wrapper.find('ContentError').length).toBe(1);
- expect(wrapper.find('ApplicationAdd').length).toBe(0);
- expect(wrapper.find('ApplicationDetails').length).toBe(0);
- expect(wrapper.find('Application').length).toBe(1);
- expect(ApplicationsAPI.readOptions).toBeCalled();
- expect(ApplicationsAPI.readDetail).toBeCalledWith(1);
- });
-});
diff --git a/awx/ui/src/screens/Application/Application/index.js b/awx/ui/src/screens/Application/Application/index.js
deleted file mode 100644
index f76f133dd52a..000000000000
--- a/awx/ui/src/screens/Application/Application/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './Application';
diff --git a/awx/ui/src/screens/Application/ApplicationAdd/ApplicationAdd.js b/awx/ui/src/screens/Application/ApplicationAdd/ApplicationAdd.js
deleted file mode 100644
index aa472bd78f73..000000000000
--- a/awx/ui/src/screens/Application/ApplicationAdd/ApplicationAdd.js
+++ /dev/null
@@ -1,91 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { useHistory } from 'react-router-dom';
-
-import { Card, PageSection } from '@patternfly/react-core';
-import useRequest from 'hooks/useRequest';
-import ContentError from 'components/ContentError';
-import { ApplicationsAPI } from 'api';
-import { CardBody } from 'components/Card';
-import ApplicationForm from '../shared/ApplicationForm';
-
-function ApplicationAdd({ onSuccessfulAdd }) {
- const history = useHistory();
- const [submitError, setSubmitError] = useState(null);
-
- const {
- error,
- request: fetchOptions,
- result: { authorizationOptions, clientTypeOptions },
- } = useRequest(
- useCallback(async () => {
- const {
- data: {
- actions: {
- GET: {
- authorization_grant_type: { choices: authChoices },
- client_type: { choices: clientChoices },
- },
- },
- },
- } = await ApplicationsAPI.readOptions();
-
- const authorization = authChoices.map((choice) => ({
- value: choice[0],
- label: choice[1],
- key: choice[0],
- }));
- const clientType = clientChoices.map((choice) => ({
- value: choice[0],
- label: choice[1],
- key: choice[0],
- }));
-
- return {
- authorizationOptions: authorization,
- clientTypeOptions: clientType,
- };
- }, []),
- {
- authorizationOptions: [],
- clientTypeOptions: [],
- }
- );
- const handleSubmit = async ({ ...values }) => {
- values.organization = values.organization.id;
- try {
- const { data } = await ApplicationsAPI.create(values);
- onSuccessfulAdd(data);
- history.push(`/applications/${data.id}/details`);
- } catch (err) {
- setSubmitError(err);
- }
- };
-
- const handleCancel = () => {
- history.push(`/applications`);
- };
-
- useEffect(() => {
- fetchOptions();
- }, [fetchOptions]);
-
- if (error) {
- return ;
- }
- return (
-
-
-
-
-
-
-
- );
-}
-export default ApplicationAdd;
diff --git a/awx/ui/src/screens/Application/ApplicationAdd/ApplicationAdd.test.js b/awx/ui/src/screens/Application/ApplicationAdd/ApplicationAdd.test.js
deleted file mode 100644
index 2e02fdda8167..000000000000
--- a/awx/ui/src/screens/Application/ApplicationAdd/ApplicationAdd.test.js
+++ /dev/null
@@ -1,207 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { ApplicationsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import ApplicationAdd from './ApplicationAdd';
-
-jest.mock('../../../api/models/Applications');
-jest.mock('../../../api/models/Organizations');
-
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- history: () => ({
- location: '/applications/add',
- }),
-}));
-const options = {
- data: {
- actions: {
- GET: {
- client_type: {
- choices: [
- ['confidential', 'Confidential'],
- ['public', 'Public'],
- ],
- },
- authorization_grant_type: {
- choices: [
- ['authorization-code', 'Authorization code'],
- ['password', 'Resource owner password-based'],
- ],
- },
- },
- },
- },
-};
-
-const onSuccessfulAdd = jest.fn();
-
-describe('', () => {
- let wrapper;
- test('should render properly', async () => {
- ApplicationsAPI.readOptions.mockResolvedValue(options);
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
-
- expect(wrapper.find('ApplicationAdd').length).toBe(1);
- expect(wrapper.find('ApplicationForm').length).toBe(1);
- expect(ApplicationsAPI.readOptions).toBeCalled();
- });
-
- test('expect values to be updated and submitted properly', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/applications/add'],
- });
- ApplicationsAPI.readOptions.mockResolvedValue(options);
-
- ApplicationsAPI.create.mockResolvedValue({ data: { id: 8 } });
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
-
- await act(async () => {
- wrapper.find('input#name').simulate('change', {
- target: { value: 'new foo', name: 'name' },
- });
- wrapper.find('input#description').simulate('change', {
- target: { value: 'new bar', name: 'description' },
- });
- wrapper
- .find('AnsibleSelect[name="authorization_grant_type"]')
- .prop('onChange')({}, 'authorization code');
-
- wrapper.find('input#redirect_uris').simulate('change', {
- target: { value: 'https://www.google.com', name: 'redirect_uris' },
- });
- wrapper.find('AnsibleSelect[name="client_type"]').prop('onChange')(
- {},
- 'confidential'
- );
- wrapper.find('OrganizationLookup').invoke('onChange')({
- id: 1,
- name: 'organization',
- });
- });
-
- wrapper.update();
- expect(wrapper.find('input#name').prop('value')).toBe('new foo');
- expect(wrapper.find('input#description').prop('value')).toBe('new bar');
- expect(wrapper.find('input#organization').prop('value')).toBe(
- 'organization'
- );
- expect(
- wrapper
- .find('AnsibleSelect[name="authorization_grant_type"]')
- .prop('value')
- ).toBe('authorization code');
- expect(
- wrapper.find('AnsibleSelect[name="client_type"]').prop('value')
- ).toBe('confidential');
- expect(wrapper.find('input#redirect_uris').prop('value')).toBe(
- 'https://www.google.com'
- );
- await act(async () => {
- wrapper.find('Formik').prop('onSubmit')({
- authorization_grant_type: 'authorization-code',
- client_type: 'confidential',
- description: 'bar',
- name: 'foo',
- organization: { id: 1 },
- redirect_uris: 'http://www.google.com',
- });
- });
-
- expect(ApplicationsAPI.create).toBeCalledWith({
- authorization_grant_type: 'authorization-code',
- client_type: 'confidential',
- description: 'bar',
- name: 'foo',
- organization: 1,
- redirect_uris: 'http://www.google.com',
- });
- expect(history.location.pathname).toBe('/applications/8/details');
- expect(onSuccessfulAdd).toHaveBeenCalledWith({ id: 8 });
- });
-
- test('should cancel form properly', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/applications/add'],
- });
- ApplicationsAPI.readOptions.mockResolvedValue(options);
-
- ApplicationsAPI.create.mockResolvedValue({ data: { id: 8 } });
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
- await act(async () => {
- wrapper.find('Button[aria-label="Cancel"]').prop('onClick')();
- });
- expect(history.location.pathname).toBe('/applications');
- });
-
- test('should throw error on submit', async () => {
- const error = {
- response: {
- config: {
- method: 'patch',
- url: '/api/v2/applications/',
- },
- data: { detail: 'An error occurred' },
- },
- };
- ApplicationsAPI.create.mockRejectedValue(error);
- ApplicationsAPI.readOptions.mockResolvedValue(options);
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await act(async () => {
- wrapper.find('Formik').prop('onSubmit')({
- id: 1,
- organization: { id: 1 },
- });
- });
-
- waitForElement(wrapper, 'FormSubmitError', (el) => el.length > 0);
- });
- test('should render content error on failed read options request', async () => {
- ApplicationsAPI.readOptions.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'options',
- },
- data: 'An error occurred',
- status: 403,
- },
- })
- );
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
-
- wrapper.update();
- expect(wrapper.find('ContentError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Application/ApplicationAdd/index.js b/awx/ui/src/screens/Application/ApplicationAdd/index.js
deleted file mode 100644
index 54101fc16bc2..000000000000
--- a/awx/ui/src/screens/Application/ApplicationAdd/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ApplicationAdd';
diff --git a/awx/ui/src/screens/Application/ApplicationDetails/ApplicationDetails.js b/awx/ui/src/screens/Application/ApplicationDetails/ApplicationDetails.js
deleted file mode 100644
index 67f3d26af15c..000000000000
--- a/awx/ui/src/screens/Application/ApplicationDetails/ApplicationDetails.js
+++ /dev/null
@@ -1,146 +0,0 @@
-import React, { useCallback } from 'react';
-
-import { t } from '@lingui/macro';
-import { Link, useHistory } from 'react-router-dom';
-import { Button } from '@patternfly/react-core';
-
-import useRequest, { useDismissableError } from 'hooks/useRequest';
-import AlertModal from 'components/AlertModal';
-import { CardBody, CardActionsRow } from 'components/Card';
-import { Detail, DetailList, UserDateDetail } from 'components/DetailList';
-import { ApplicationsAPI } from 'api';
-import DeleteButton from 'components/DeleteButton';
-import ErrorDetail from 'components/ErrorDetail';
-import getApplicationHelpTextStrings from '../shared/Application.helptext';
-
-function ApplicationDetails({
- application,
- authorizationOptions,
- clientTypeOptions,
-}) {
- const applicationHelpTextStrings = getApplicationHelpTextStrings();
- const history = useHistory();
- const {
- isLoading: deleteLoading,
- error: deletionError,
- request: deleteApplications,
- } = useRequest(
- useCallback(async () => {
- await ApplicationsAPI.destroy(application.id);
- history.push('/applications');
- }, [application.id, history])
- );
-
- const { error, dismissError } = useDismissableError(deletionError);
-
- const getAuthorizationGrantType = (type) => {
- let value;
- authorizationOptions.filter((option) => {
- if (option.value === type) {
- value = option.label;
- }
- return null;
- });
- return value;
- };
- const getClientType = (type) => {
- let value;
- clientTypeOptions.filter((option) => {
- if (option.value === type) {
- value = option.label;
- }
- return null;
- });
- return value;
- };
- return (
-
-
-
-
-
- {application.summary_fields.organization.name}
-
- }
- dataCy="app-detail-organization"
- />
-
-
-
-
-
-
-
-
- {application.summary_fields.user_capabilities &&
- application.summary_fields.user_capabilities.edit && (
-
- )}
- {application.summary_fields.user_capabilities &&
- application.summary_fields.user_capabilities.delete && (
-
- {t`Delete`}
-
- )}
-
- {error && (
-
- {t`Failed to delete application.`}
-
-
- )}
-
- );
-}
-export default ApplicationDetails;
diff --git a/awx/ui/src/screens/Application/ApplicationDetails/ApplicationDetails.test.js b/awx/ui/src/screens/Application/ApplicationDetails/ApplicationDetails.test.js
deleted file mode 100644
index da11536a7bdd..000000000000
--- a/awx/ui/src/screens/Application/ApplicationDetails/ApplicationDetails.test.js
+++ /dev/null
@@ -1,180 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-
-import { ApplicationsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import ApplicationDetails from './ApplicationDetails';
-
-jest.mock('../../../api/models/Applications');
-
-const application = {
- id: 10,
- type: 'o_auth2_application',
- url: '/api/v2/applications/10/',
- related: {
- named_url: '/api/v2/applications/Alex++bar/',
- tokens: '/api/v2/applications/10/tokens/',
- activity_stream: '/api/v2/applications/10/activity_stream/',
- },
- summary_fields: {
- organization: {
- id: 230,
- name: 'bar',
- description:
- 'SaleNameBedPersonalityManagerWhileFinanceBreakToothPerson魲',
- },
- user_capabilities: {
- edit: true,
- delete: true,
- },
- tokens: {
- count: 2,
- results: [
- {
- id: 1,
- token: '************',
- scope: 'read',
- },
- {
- id: 2,
- token: '************',
- scope: 'write',
- },
- ],
- },
- },
- created: '2020-06-11T17:54:33.983993Z',
- modified: '2020-06-11T17:54:33.984039Z',
- name: 'Alex',
- description: 'foo',
- client_id: 'b1dmj8xzkbFm1ZQ27ygw2ZeE9I0AXqqeL74fiyk4',
- client_secret: '************',
- client_type: 'confidential',
- redirect_uris: 'http://www.google.com',
- authorization_grant_type: 'authorization-code',
- skip_authorization: false,
- organization: 230,
-};
-
-const authorizationOptions = [
- {
- key: 'authorization-code',
- label: 'Authorization code',
- value: 'authorization-code',
- },
- {
- key: 'password',
- label: 'Resource owner password-based',
- value: 'password',
- },
-];
-
-const clientTypeOptions = [
- { key: 'confidential', label: 'Confidential', value: 'confidential' },
- { key: 'public', label: 'Public', value: 'public' },
-];
-
-describe('', () => {
- let wrapper;
- test('should mount properly', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('ApplicationDetails').length).toBe(1);
- });
-
- test('should render proper data', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('Detail[label="Name"]').prop('value')).toBe('Alex');
- expect(wrapper.find('Detail[label="Description"]').prop('value')).toBe(
- 'foo'
- );
- expect(wrapper.find('Detail[label="Organization"]').length).toBe(1);
- expect(wrapper.find('Link').at(0).prop('to')).toBe(
- '/organizations/230/details'
- );
- expect(
- wrapper.find('Detail[label="Authorization grant type"]').prop('value')
- ).toBe('Authorization code');
- expect(wrapper.find('Detail[label="Redirect URIs"]').prop('value')).toBe(
- 'http://www.google.com'
- );
- expect(wrapper.find('Detail[label="Client type"]').prop('value')).toBe(
- 'Confidential'
- );
- expect(wrapper.find('Detail[label="Client ID"]').prop('value')).toBe(
- 'b1dmj8xzkbFm1ZQ27ygw2ZeE9I0AXqqeL74fiyk4'
- );
- expect(wrapper.find('Button[aria-label="Edit"]').prop('to')).toBe(
- '/applications/10/edit'
- );
- expect(wrapper.find('Button[aria-label="Delete"]').length).toBe(1);
- });
-
- test('should delete properly', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/applications/1/details'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
- expect(history.location.pathname).toEqual('/applications/1/details');
- await act(async () => {
- wrapper.find('DeleteButton').invoke('onConfirm')();
- });
- expect(ApplicationsAPI.destroy).toHaveBeenCalledTimes(1);
- expect(history.location.pathname).toBe('/applications');
- expect(ApplicationsAPI.destroy).toBeCalledWith(10);
- });
-
- test(' should not render delete button', async () => {
- application.summary_fields.user_capabilities.delete = false;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('Button[aria-label="Delete"]').length).toBe(0);
- });
- test(' should not render edit button', async () => {
- application.summary_fields.user_capabilities.edit = false;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('Button[aria-label="Edit"]').length).toBe(0);
- });
-});
diff --git a/awx/ui/src/screens/Application/ApplicationDetails/index.js b/awx/ui/src/screens/Application/ApplicationDetails/index.js
deleted file mode 100644
index fc3261983bc1..000000000000
--- a/awx/ui/src/screens/Application/ApplicationDetails/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ApplicationDetails';
diff --git a/awx/ui/src/screens/Application/ApplicationEdit/ApplicationEdit.js b/awx/ui/src/screens/Application/ApplicationEdit/ApplicationEdit.js
deleted file mode 100644
index 00d2b148c5a9..000000000000
--- a/awx/ui/src/screens/Application/ApplicationEdit/ApplicationEdit.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import React, { useState } from 'react';
-import { useHistory, useParams } from 'react-router-dom';
-
-import { Card } from '@patternfly/react-core';
-import { ApplicationsAPI } from 'api';
-import { CardBody } from 'components/Card';
-import ApplicationForm from '../shared/ApplicationForm';
-
-function ApplicationEdit({
- application,
- authorizationOptions,
- clientTypeOptions,
-}) {
- const history = useHistory();
- const { id } = useParams();
- const [submitError, setSubmitError] = useState(null);
-
- const handleSubmit = async ({ ...values }) => {
- values.organization = values.organization.id;
- try {
- await ApplicationsAPI.update(id, values);
- history.push(`/applications/${id}/details`);
- } catch (err) {
- setSubmitError(err);
- }
- };
-
- const handleCancel = () => {
- history.push(`/applications/${id}/details`);
- };
- return (
-
-
-
-
-
- );
-}
-export default ApplicationEdit;
diff --git a/awx/ui/src/screens/Application/ApplicationEdit/ApplicationEdit.test.js b/awx/ui/src/screens/Application/ApplicationEdit/ApplicationEdit.test.js
deleted file mode 100644
index 0447d2d47090..000000000000
--- a/awx/ui/src/screens/Application/ApplicationEdit/ApplicationEdit.test.js
+++ /dev/null
@@ -1,228 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { createMemoryHistory } from 'history';
-import { ApplicationsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import ApplicationEdit from './ApplicationEdit';
-
-jest.mock('../../../api/models/Applications');
-jest.mock('../../../api/models/Organizations');
-
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- history: () => ({
- location: '/applications/1/edit',
- }),
- useParams: () => ({ id: 1 }),
-}));
-
-const authorizationOptions = [
- {
- key: 'authorization-code',
- label: 'Authorization code',
- value: 'authorization-code',
- },
- {
- key: 'password',
- label: 'Resource owner password-based',
- value: 'password',
- },
-];
-
-const clientTypeOptions = [
- { key: 'confidential', label: 'Confidential', value: 'confidential' },
- { key: 'public', label: 'Public', value: 'public' },
-];
-
-const application = {
- id: 1,
- type: 'o_auth2_application',
- url: '/api/v2/applications/10/',
- related: {
- named_url: '/api/v2/applications/Alex++bar/',
- tokens: '/api/v2/applications/10/tokens/',
- activity_stream: '/api/v2/applications/10/activity_stream/',
- },
- summary_fields: {
- organization: {
- id: 230,
- name: 'bar',
- description:
- 'SaleNameBedPersonalityManagerWhileFinanceBreakToothPerson魲',
- },
- user_capabilities: {
- edit: true,
- delete: true,
- },
- tokens: {
- count: 0,
- results: [],
- },
- },
- created: '2020-06-11T17:54:33.983993Z',
- modified: '2020-06-11T17:54:33.984039Z',
- name: 'Alex',
- description: '',
- client_id: 'b1dmj8xzkbFm1ZQ27ygw2ZeE9I0AXqqeL74fiyk4',
- client_secret: '************',
- client_type: 'confidential',
- redirect_uris: 'http://www.google.com',
- authorization_grant_type: 'authorization-code',
- skip_authorization: false,
- organization: 230,
-};
-
-describe('', () => {
- let wrapper;
- test('should mount properly', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('ApplicationEdit').length).toBe(1);
- });
-
- test('should cancel form properly', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/applications/1/edit'],
- });
-
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- await act(async () => {
- wrapper.find('Button[aria-label="Cancel"]').prop('onClick')();
- });
- expect(history.location.pathname).toBe('/applications/1/details');
- });
- });
-
- test('should throw error on submit', async () => {
- const error = {
- response: {
- config: {
- method: 'patch',
- url: '/api/v2/applications/',
- },
- data: { detail: 'An error occurred' },
- },
- };
- ApplicationsAPI.update.mockRejectedValue(error);
- const history = createMemoryHistory({
- initialEntries: ['/applications/1/edit'],
- });
-
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
- await act(async () => {
- wrapper.find('Formik').prop('onSubmit')({
- id: 1,
- organization: { id: 4 },
- });
- });
-
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(1);
- });
-
- test('expect values to be updated and submitted properly', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/applications/1/edit'],
- });
-
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
- await act(async () => {
- wrapper.find('input#name').simulate('change', {
- target: { value: 'new foo', name: 'name' },
- });
- wrapper.find('input#description').simulate('change', {
- target: { value: 'new bar', name: 'description' },
- });
-
- wrapper.find('input#redirect_uris').simulate('change', {
- target: { value: 'https://www.google.com', name: 'redirect_uris' },
- });
- wrapper.find('AnsibleSelect[name="client_type"]').prop('onChange')(
- {},
- 'confidential'
- );
- wrapper.find('OrganizationLookup').invoke('onChange')({
- id: 1,
- name: 'organization',
- });
- });
-
- wrapper.update();
- expect(wrapper.find('input#name').prop('value')).toBe('new foo');
- expect(wrapper.find('input#description').prop('value')).toBe('new bar');
- expect(wrapper.find('input#organization').prop('value')).toBe(
- 'organization'
- );
- expect(
- wrapper
- .find('AnsibleSelect[name="authorization_grant_type"]')
- .prop('value')
- ).toBe('authorization-code');
- expect(
- wrapper.find('AnsibleSelect[name="client_type"]').prop('value')
- ).toBe('confidential');
- expect(wrapper.find('input#redirect_uris').prop('value')).toBe(
- 'https://www.google.com'
- );
- await act(async () => {
- wrapper.find('Formik').prop('onSubmit')({
- authorization_grant_type: 'authorization-code',
- client_type: 'confidential',
- description: 'bar',
- name: 'foo',
- organization: { id: 1 },
- redirect_uris: 'http://www.google.com',
- });
- });
-
- expect(ApplicationsAPI.update).toBeCalledWith(1, {
- authorization_grant_type: 'authorization-code',
- client_type: 'confidential',
- description: 'bar',
- name: 'foo',
- organization: 1,
- redirect_uris: 'http://www.google.com',
- });
- });
-});
diff --git a/awx/ui/src/screens/Application/ApplicationEdit/index.js b/awx/ui/src/screens/Application/ApplicationEdit/index.js
deleted file mode 100644
index 2ab4beb8d41d..000000000000
--- a/awx/ui/src/screens/Application/ApplicationEdit/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ApplicationEdit';
diff --git a/awx/ui/src/screens/Application/ApplicationTokens/ApplicationTokenList.js b/awx/ui/src/screens/Application/ApplicationTokens/ApplicationTokenList.js
deleted file mode 100644
index 84ff13fed0aa..000000000000
--- a/awx/ui/src/screens/Application/ApplicationTokens/ApplicationTokenList.js
+++ /dev/null
@@ -1,165 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { useParams, useLocation } from 'react-router-dom';
-import { t } from '@lingui/macro';
-
-import PaginatedTable, {
- HeaderCell,
- HeaderRow,
- ToolbarDeleteButton,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import { TokensAPI, ApplicationsAPI } from 'api';
-import ErrorDetail from 'components/ErrorDetail';
-import AlertModal from 'components/AlertModal';
-import useRequest, { useDeleteItems } from 'hooks/useRequest';
-import useSelected from 'hooks/useSelected';
-import DatalistToolbar from 'components/DataListToolbar';
-import ApplicationTokenListItem from './ApplicationTokenListItem';
-
-const QS_CONFIG = getQSConfig('applications', {
- page: 1,
- page_size: 20,
- order_by: 'user__username',
-});
-
-function ApplicationTokenList() {
- const { id } = useParams();
- const location = useLocation();
- const {
- error,
- isLoading,
- result: { tokens, itemCount, relatedSearchableKeys, searchableKeys },
- request: fetchTokens,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
- const [
- {
- data: { results, count },
- },
- actionsResponse,
- ] = await Promise.all([
- ApplicationsAPI.readTokens(id, params),
- ApplicationsAPI.readTokenOptions(id),
- ]);
- const modifiedResults = results.map((result) => {
- result.summary_fields = {
- user: result.summary_fields.user,
- application: result.summary_fields.application,
- user_capabilities: { delete: true },
- };
- result.name = result.summary_fields.user?.username;
- return result;
- });
- return {
- tokens: modifiedResults,
- itemCount: count,
- relatedSearchableKeys: (
- actionsResponse?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
- };
- }, [id, location.search]),
- { tokens: [], itemCount: 0, relatedSearchableKeys: [], searchableKeys: [] }
- );
-
- useEffect(() => {
- fetchTokens();
- }, [fetchTokens]);
-
- const { selected, isAllSelected, handleSelect, selectAll, clearSelected } =
- useSelected(tokens);
- const {
- isLoading: deleteLoading,
- deletionError,
- deleteItems: handleDeleteApplications,
- clearDeletionError,
- } = useDeleteItems(
- useCallback(
- () =>
- Promise.all(
- selected.map(({ id: tokenId }) => TokensAPI.destroy(tokenId))
- ),
- [selected]
- ),
- {
- qsConfig: QS_CONFIG,
- allItemsSelected: isAllSelected,
- fetchItems: fetchTokens,
- }
- );
-
- const handleDelete = async () => {
- await handleDeleteApplications();
- clearSelected();
- };
-
- return (
- <>
- (
- ,
- ]}
- />
- )}
- headerRow={
-
- {t`Name`}
- {t`Scope`}
- {t`Expires`}
-
- }
- renderRow={(token, index) => (
- handleSelect(token)}
- isSelected={selected.some((row) => row.id === token.id)}
- rowIndex={index}
- />
- )}
- />
-
- {t`Failed to delete one or more tokens.`}
-
-
- >
- );
-}
-
-export default ApplicationTokenList;
diff --git a/awx/ui/src/screens/Application/ApplicationTokens/ApplicationTokenList.test.js b/awx/ui/src/screens/Application/ApplicationTokens/ApplicationTokenList.test.js
deleted file mode 100644
index cdf002b25038..000000000000
--- a/awx/ui/src/screens/Application/ApplicationTokens/ApplicationTokenList.test.js
+++ /dev/null
@@ -1,216 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { ApplicationsAPI, TokensAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import ApplicationTokenList from './ApplicationTokenList';
-
-jest.mock('../../../api/models/Applications');
-jest.mock('../../../api/models/Tokens');
-
-const tokens = {
- data: {
- results: [
- {
- id: 2,
- type: 'o_auth2_access_token',
- url: '/api/v2/tokens/2/',
- related: {
- user: '/api/v2/users/1/',
- application: '/api/v2/applications/3/',
- activity_stream: '/api/v2/tokens/2/activity_stream/',
- },
- summary_fields: {
- user: {
- id: 1,
- username: 'admin',
- first_name: '',
- last_name: '',
- },
- application: {
- id: 3,
- name: 'hg',
- },
- },
- created: '2020-06-23T19:56:38.422053Z',
- modified: '2020-06-23T19:56:38.441353Z',
- description: 'cdfsg',
- user: 1,
- token: '************',
- refresh_token: '************',
- application: 3,
- expires: '3019-10-25T19:56:38.395635Z',
- scope: 'read',
- },
- {
- id: 3,
- type: 'o_auth2_access_token',
- url: '/api/v2/tokens/3/',
- related: {
- user: '/api/v2/users/1/',
- application: '/api/v2/applications/3/',
- activity_stream: '/api/v2/tokens/3/activity_stream/',
- },
- summary_fields: {
- user: {
- id: 1,
- username: 'admin',
- first_name: '',
- last_name: '',
- },
- application: {
- id: 3,
- name: 'hg',
- },
- },
- created: '2020-06-23T19:56:50.536169Z',
- modified: '2020-06-23T19:56:50.549521Z',
- description: 'fgds',
- user: 1,
- token: '************',
- refresh_token: '************',
- application: 3,
- expires: '3019-10-25T19:56:50.529306Z',
- scope: 'write',
- },
- ],
- count: 2,
- },
-};
-describe('', () => {
- let wrapper;
-
- beforeEach(() => {
- ApplicationsAPI.readTokenOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- related_search_fields: [],
- },
- });
- });
-
- test('should mount properly', async () => {
- ApplicationsAPI.readTokens.mockResolvedValue(tokens);
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
- expect(wrapper.find('ApplicationTokenList')).toHaveLength(1);
- });
-
- test('should have data fetched and render 2 rows', async () => {
- ApplicationsAPI.readTokens.mockResolvedValue(tokens);
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
- expect(wrapper.find('ApplicationTokenListItem').length).toBe(2);
- expect(ApplicationsAPI.readTokens).toBeCalled();
- });
-
- test('should delete item successfully', async () => {
- ApplicationsAPI.readTokens.mockResolvedValue(tokens);
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
-
- wrapper
- .find('.pf-c-table__check')
- .at(0)
- .find('input')
- .simulate('change', tokens.data.results[0]);
- wrapper.update();
-
- expect(
- wrapper.find('.pf-c-table__check').at(0).find('input').prop('checked')
- ).toBe(true);
- await act(async () =>
- wrapper.find('Button[aria-label="Delete"]').prop('onClick')()
- );
-
- wrapper.update();
-
- await act(async () =>
- wrapper.find('Button[aria-label="confirm delete"]').prop('onClick')()
- );
- expect(TokensAPI.destroy).toBeCalledWith(tokens.data.results[0].id);
- });
-
- test('should throw content error', async () => {
- ApplicationsAPI.readTokens.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'get',
- url: '/api/v2/applications/',
- },
- data: 'An error occurred',
- },
- })
- );
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
-
- expect(wrapper.find('ContentError').length).toBe(1);
- });
-
- test('should render deletion error modal', async () => {
- TokensAPI.destroy.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'delete',
- url: '/api/v2/tokens/',
- },
- data: 'An error occurred',
- },
- })
- );
- ApplicationsAPI.readTokens.mockResolvedValue(tokens);
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
-
- wrapper
- .find('.pf-c-table__check')
- .at(0)
- .find('input')
- .simulate('change', 'a');
-
- wrapper.update();
-
- expect(
- wrapper.find('.pf-c-table__check').at(0).find('input').prop('checked')
- ).toBe(true);
- await act(async () =>
- wrapper.find('Button[aria-label="Delete"]').prop('onClick')()
- );
-
- wrapper.update();
-
- await act(async () =>
- wrapper.find('Button[aria-label="confirm delete"]').prop('onClick')()
- );
- wrapper.update();
-
- expect(!!wrapper.find('AlertModal').prop('isOpen')).toEqual(true);
- expect(wrapper.find('ErrorDetail')).toHaveLength(1);
- });
-
- test('should not render add button', async () => {
- ApplicationsAPI.readTokens.mockResolvedValue(tokens);
-
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
- expect(wrapper.find('ToolbarAddButton').length).toBe(0);
- });
-});
diff --git a/awx/ui/src/screens/Application/ApplicationTokens/ApplicationTokenListItem.js b/awx/ui/src/screens/Application/ApplicationTokens/ApplicationTokenListItem.js
deleted file mode 100644
index 20ad58e69994..000000000000
--- a/awx/ui/src/screens/Application/ApplicationTokens/ApplicationTokenListItem.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import React from 'react';
-import { string, bool, func, number } from 'prop-types';
-import { t } from '@lingui/macro';
-import { Link } from 'react-router-dom';
-import { Tr, Td } from '@patternfly/react-table';
-
-import { Token } from 'types';
-import { formatDateString } from 'util/dates';
-import { toTitleCase } from 'util/strings';
-
-function ApplicationTokenListItem({
- token,
- isSelected,
- onSelect,
- detailUrl,
- rowIndex,
-}) {
- return (
-
- |
-
-
- {token.summary_fields.user.username}
-
- |
- {toTitleCase(token.scope)} |
- {formatDateString(token.expires)} |
-
- );
-}
-
-ApplicationTokenListItem.propTypes = {
- token: Token.isRequired,
- detailUrl: string.isRequired,
- isSelected: bool.isRequired,
- onSelect: func.isRequired,
- rowIndex: number.isRequired,
-};
-
-export default ApplicationTokenListItem;
diff --git a/awx/ui/src/screens/Application/ApplicationTokens/ApplicationTokenListItem.test.js b/awx/ui/src/screens/Application/ApplicationTokens/ApplicationTokenListItem.test.js
deleted file mode 100644
index 132127c78bbe..000000000000
--- a/awx/ui/src/screens/Application/ApplicationTokens/ApplicationTokenListItem.test.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import ApplicationTokenListItem from './ApplicationTokenListItem';
-
-describe('', () => {
- let wrapper;
- const token = {
- id: 2,
- type: 'o_auth2_access_token',
- url: '/api/v2/tokens/2/',
- related: {
- user: '/api/v2/users/1/',
- application: '/api/v2/applications/3/',
- activity_stream: '/api/v2/tokens/2/activity_stream/',
- },
- summary_fields: {
- user: {
- id: 1,
- username: 'admin',
- first_name: '',
- last_name: '',
- },
- application: {
- id: 3,
- name: 'hg',
- },
- },
- created: '2020-06-23T19:56:38.422053Z',
- modified: '2020-06-23T19:56:38.441353Z',
- description: 'cdfsg',
- user: 1,
- token: '************',
- refresh_token: '************',
- application: 3,
- expires: '3019-10-25T19:56:38.395635Z',
- scope: 'read',
- };
-
- test('should mount successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- {}}
- rowIndex={1}
- />
-
-
- );
- });
- expect(wrapper.find('ApplicationTokenListItem').length).toBe(1);
- });
-
- test('should render the proper data', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- {}}
- rowIndex={1}
- />
-
-
- );
- });
- expect(wrapper.find('Td').at(1).text()).toBe('admin');
- expect(wrapper.find('Td').at(2).text()).toBe('Read');
- expect(wrapper.find('Td').at(3).text()).toBe('10/25/3019, 7:56:38 PM');
- });
-
- test('should be checked', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- {}}
- rowIndex={1}
- />
-
-
- );
- });
- expect(wrapper.find('Td').at(0).prop('select').isSelected).toBe(true);
- });
-});
diff --git a/awx/ui/src/screens/Application/ApplicationTokens/index.js b/awx/ui/src/screens/Application/ApplicationTokens/index.js
deleted file mode 100644
index 34dd4620617f..000000000000
--- a/awx/ui/src/screens/Application/ApplicationTokens/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ApplicationTokenList';
diff --git a/awx/ui/src/screens/Application/Applications.js b/awx/ui/src/screens/Application/Applications.js
deleted file mode 100644
index 37b60fa7ff75..000000000000
--- a/awx/ui/src/screens/Application/Applications.js
+++ /dev/null
@@ -1,115 +0,0 @@
-import React, { useState, useCallback } from 'react';
-
-import { t } from '@lingui/macro';
-import styled from 'styled-components';
-import { Route, Switch } from 'react-router-dom';
-import {
- Alert,
- ClipboardCopy,
- ClipboardCopyVariant,
- Modal,
-} from '@patternfly/react-core';
-import ScreenHeader from 'components/ScreenHeader';
-import { Detail, DetailList } from 'components/DetailList';
-import PersistentFilters from 'components/PersistentFilters';
-import ApplicationsList from './ApplicationsList';
-import ApplicationAdd from './ApplicationAdd';
-import Application from './Application';
-
-const ApplicationAlert = styled(Alert)`
- margin-bottom: 20px;
-`;
-
-function Applications() {
- const [applicationModalSource, setApplicationModalSource] = useState(null);
- const [breadcrumbConfig, setBreadcrumbConfig] = useState({
- '/applications': t`Applications`,
- '/applications/add': t`Create New Application`,
- });
-
- const buildBreadcrumbConfig = useCallback((application) => {
- if (!application) {
- return;
- }
- setBreadcrumbConfig({
- '/applications': t`Applications`,
- '/applications/add': t`Create New Application`,
- [`/applications/${application.id}`]: `${application.name}`,
- [`/applications/${application.id}/edit`]: t`Edit Details`,
- [`/applications/${application.id}/details`]: t`Details`,
- [`/applications/${application.id}/tokens`]: t`Tokens`,
- });
- }, []);
-
- return (
- <>
-
-
-
- setApplicationModalSource(app)}
- />
-
-
-
-
-
-
-
-
-
-
- {applicationModalSource && (
- setApplicationModalSource(null)}
- >
- {applicationModalSource.client_secret && (
-
- )}
-
-
- {applicationModalSource.client_id && (
-
- {applicationModalSource.client_id}
-
- }
- />
- )}
- {applicationModalSource.client_secret && (
-
- {applicationModalSource.client_secret}
-
- }
- />
- )}
-
-
- )}
- >
- );
-}
-
-export default Applications;
diff --git a/awx/ui/src/screens/Application/Applications.test.js b/awx/ui/src/screens/Application/Applications.test.js
deleted file mode 100644
index c241767f48ac..000000000000
--- a/awx/ui/src/screens/Application/Applications.test.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import React from 'react';
-import { createMemoryHistory } from 'history';
-import { shallow } from 'enzyme';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-
-import Applications from './Applications';
-
-describe('', () => {
- let wrapper;
-
- test('renders successfully', () => {
- wrapper = mountWithContexts();
- const pageSections = wrapper.find('PageSection');
- expect(wrapper.length).toBe(1);
- expect(pageSections.length).toBe(1);
- expect(pageSections.first().props().variant).toBe('light');
- });
-
- test('shows Application information modal after successful creation', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/applications/add'],
- });
- wrapper = shallow(, {
- context: { router: { history } },
- });
- expect(wrapper.find('Modal[title="Application information"]').length).toBe(
- 0
- );
- wrapper.find('ApplicationAdd').props().onSuccessfulAdd({
- name: 'test',
- client_id: 'foobar',
- client_secret: 'aaaaaaaaaaaaaaaaaaaaaaaaaa',
- });
- wrapper.update();
- expect(wrapper.find('Modal[title="Application information"]').length).toBe(
- 1
- );
- });
-});
diff --git a/awx/ui/src/screens/Application/ApplicationsList/ApplicationList.test.js b/awx/ui/src/screens/Application/ApplicationsList/ApplicationList.test.js
deleted file mode 100644
index 7d9e957ac1cc..000000000000
--- a/awx/ui/src/screens/Application/ApplicationsList/ApplicationList.test.js
+++ /dev/null
@@ -1,191 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { ApplicationsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import ApplicationsList from './ApplicationsList';
-
-jest.mock('../../../api/models/Applications');
-
-const applications = {
- data: {
- results: [
- {
- id: 1,
- name: 'Foo',
- summary_fields: {
- organization: { name: 'Org 1', id: 10 },
- user_capabilities: { edit: true, delete: true },
- },
- url: '',
- organization: 10,
- },
- {
- id: 2,
- name: 'Bar',
- summary_fields: {
- organization: { name: 'Org 2', id: 20 },
- user_capabilities: { edit: true, delete: true },
- },
- url: '',
- organization: 20,
- },
- ],
- count: 2,
- },
-};
-const options = { data: { actions: { POST: true } } };
-describe('', () => {
- let wrapper;
- test('should mount properly', async () => {
- ApplicationsAPI.read.mockResolvedValue(applications);
- ApplicationsAPI.readOptions.mockResolvedValue(options);
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ApplicationsList', (el) => el.length > 0);
- });
-
- test('should have data fetched and render 2 rows', async () => {
- ApplicationsAPI.read.mockResolvedValue(applications);
- ApplicationsAPI.readOptions.mockResolvedValue(options);
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ApplicationsList', (el) => el.length > 0);
- expect(wrapper.find('ApplicationListItem').length).toBe(2);
- expect(ApplicationsAPI.read).toBeCalled();
- expect(ApplicationsAPI.readOptions).toBeCalled();
- });
-
- test('should delete item successfully', async () => {
- ApplicationsAPI.read.mockResolvedValue(applications);
- ApplicationsAPI.readOptions.mockResolvedValue(options);
- await act(async () => {
- wrapper = mountWithContexts();
- });
- waitForElement(wrapper, 'ApplicationsList', (el) => el.length > 0);
-
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .simulate('change', applications.data.results[0]);
-
- wrapper.update();
-
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').prop('checked')
- ).toBe(true);
- await act(async () =>
- wrapper.find('Button[aria-label="Delete"]').prop('onClick')()
- );
-
- wrapper.update();
-
- await act(async () =>
- wrapper.find('Button[aria-label="confirm delete"]').prop('onClick')()
- );
- expect(ApplicationsAPI.destroy).toBeCalledWith(
- applications.data.results[0].id
- );
- });
-
- test('should throw content error', async () => {
- ApplicationsAPI.read.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'get',
- url: '/api/v2/applications/',
- },
- data: 'An error occurred',
- },
- })
- );
- ApplicationsAPI.readOptions.mockResolvedValue(options);
- await act(async () => {
- wrapper = mountWithContexts();
- });
-
- await waitForElement(wrapper, 'ApplicationsList', (el) => el.length > 0);
- expect(wrapper.find('ContentError').length).toBe(1);
- });
-
- test('should render deletion error modal', async () => {
- ApplicationsAPI.destroy.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'delete',
- url: '/api/v2/applications/',
- },
- data: 'An error occurred',
- },
- })
- );
- ApplicationsAPI.read.mockResolvedValue(applications);
- ApplicationsAPI.readOptions.mockResolvedValue(options);
- await act(async () => {
- wrapper = mountWithContexts();
- });
- waitForElement(wrapper, 'ApplicationsList', (el) => el.length > 0);
-
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .simulate('change', 'a');
-
- wrapper.update();
-
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').prop('checked')
- ).toBe(true);
- await act(async () =>
- wrapper.find('Button[aria-label="Delete"]').prop('onClick')()
- );
-
- wrapper.update();
-
- await act(async () =>
- wrapper.find('Button[aria-label="confirm delete"]').prop('onClick')()
- );
- wrapper.update();
-
- expect(wrapper.find('ErrorDetail').length).toBe(1);
- });
-
- test('should not render add button', async () => {
- ApplicationsAPI.read.mockResolvedValue(applications);
- ApplicationsAPI.readOptions.mockResolvedValue({
- data: { actions: { POST: false } },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- waitForElement(wrapper, 'ApplicationsList', (el) => el.length > 0);
- expect(wrapper.find('ToolbarAddButton').length).toBe(0);
- });
-
- test('should not render edit button for first list item', async () => {
- applications.data.results[0].summary_fields.user_capabilities.edit = false;
- ApplicationsAPI.read.mockResolvedValue(applications);
- ApplicationsAPI.readOptions.mockResolvedValue({
- data: { actions: { POST: false } },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- waitForElement(wrapper, 'ApplicationsList', (el) => el.length > 0);
- expect(
- wrapper.find('ApplicationListItem').at(0).find('PencilAltIcon').length
- ).toBe(0);
- expect(
- wrapper.find('ApplicationListItem').at(1).find('PencilAltIcon').length
- ).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Application/ApplicationsList/ApplicationListItem.js b/awx/ui/src/screens/Application/ApplicationsList/ApplicationListItem.js
deleted file mode 100644
index e6e2a2688b6f..000000000000
--- a/awx/ui/src/screens/Application/ApplicationsList/ApplicationListItem.js
+++ /dev/null
@@ -1,75 +0,0 @@
-import React from 'react';
-import { string, bool, func } from 'prop-types';
-import { Button } from '@patternfly/react-core';
-import { Tr, Td } from '@patternfly/react-table';
-import { t } from '@lingui/macro';
-import { Link } from 'react-router-dom';
-import { PencilAltIcon } from '@patternfly/react-icons';
-import { ActionsTd, ActionItem, TdBreakWord } from 'components/PaginatedTable';
-import { formatDateString } from 'util/dates';
-import { Application } from 'types';
-
-function ApplicationListItem({
- application,
- isSelected,
- onSelect,
- detailUrl,
- rowIndex,
-}) {
- const labelId = `check-action-${application.id}`;
- return (
-
- |
-
-
- {application.name}
-
-
-
-
- {application.summary_fields.organization.name}
-
-
-
- {formatDateString(application.modified)}
- |
-
-
-
-
-
-
- );
-}
-
-ApplicationListItem.propTypes = {
- application: Application.isRequired,
- detailUrl: string.isRequired,
- isSelected: bool.isRequired,
- onSelect: func.isRequired,
-};
-
-export default ApplicationListItem;
diff --git a/awx/ui/src/screens/Application/ApplicationsList/ApplicationListItem.test.js b/awx/ui/src/screens/Application/ApplicationsList/ApplicationListItem.test.js
deleted file mode 100644
index 12265f8c3974..000000000000
--- a/awx/ui/src/screens/Application/ApplicationsList/ApplicationListItem.test.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import ApplicationListItem from './ApplicationListItem';
-
-describe('', () => {
- let wrapper;
- const application = {
- id: 1,
- name: 'Foo',
- summary_fields: {
- organization: { id: 2, name: 'Organization' },
- user_capabilities: { edit: true },
- },
- };
- test('should mount successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('ApplicationListItem').length).toBe(1);
- });
- test('should render the proper data', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('Td').at(1).text()).toBe('Foo');
- expect(wrapper.find('Td').at(2).text()).toBe('Organization');
- expect(wrapper.find('PencilAltIcon').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Application/ApplicationsList/ApplicationsList.js b/awx/ui/src/screens/Application/ApplicationsList/ApplicationsList.js
deleted file mode 100644
index 8737f1d340f9..000000000000
--- a/awx/ui/src/screens/Application/ApplicationsList/ApplicationsList.js
+++ /dev/null
@@ -1,194 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { t } from '@lingui/macro';
-
-import { useLocation, useRouteMatch } from 'react-router-dom';
-
-import { Card, PageSection } from '@patternfly/react-core';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import useRequest, { useDeleteItems } from 'hooks/useRequest';
-import ErrorDetail from 'components/ErrorDetail';
-import AlertModal from 'components/AlertModal';
-
-import DatalistToolbar from 'components/DataListToolbar';
-import { ApplicationsAPI } from 'api';
-import PaginatedTable, {
- HeaderRow,
- HeaderCell,
- ToolbarDeleteButton,
- ToolbarAddButton,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import useSelected from 'hooks/useSelected';
-
-import ApplicationListItem from './ApplicationListItem';
-
-const QS_CONFIG = getQSConfig('applications', {
- page: 1,
- page_size: 20,
- order_by: 'name',
-});
-function ApplicationsList() {
- const location = useLocation();
- const match = useRouteMatch();
-
- const {
- isLoading,
- error,
- request: fetchApplications,
- result: {
- applications,
- itemCount,
- actions,
- relatedSearchableKeys,
- searchableKeys,
- },
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
-
- const [response, actionsResponse] = await Promise.all([
- ApplicationsAPI.read(params),
- ApplicationsAPI.readOptions(),
- ]);
-
- return {
- applications: response.data.results,
- itemCount: response.data.count,
- actions: actionsResponse.data.actions,
- relatedSearchableKeys: (
- actionsResponse?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
- };
- }, [location]),
- {
- applications: [],
- itemCount: 0,
- actions: {},
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- useEffect(() => {
- fetchApplications();
- }, [fetchApplications]);
-
- const { selected, isAllSelected, handleSelect, clearSelected, selectAll } =
- useSelected(applications);
-
- const {
- isLoading: deleteLoading,
- deletionError,
- deleteItems: deleteApplications,
- clearDeletionError,
- } = useDeleteItems(
- useCallback(
- () => Promise.all(selected.map(({ id }) => ApplicationsAPI.destroy(id))),
- [selected]
- ),
- {
- qsConfig: QS_CONFIG,
- allItemsSelected: isAllSelected,
- fetchItems: fetchApplications,
- }
- );
-
- const handleDeleteApplications = async () => {
- await deleteApplications();
- clearSelected();
- };
-
- const canAdd = actions && actions.POST;
-
- return (
- <>
-
-
- (
- ,
- ]
- : []),
- ,
- ]}
- />
- )}
- headerRow={
-
- {t`Name`}
-
- {t`Organization`}
-
- {t`Last Modified`}
- {t`Actions`}
-
- }
- renderRow={(application, index) => (
- handleSelect(application)}
- isSelected={selected.some((row) => row.id === application.id)}
- rowIndex={index}
- />
- )}
- emptyStateControls={
- canAdd && (
-
- )
- }
- />
-
-
-
- {t`Failed to delete one or more applications.`}
-
-
- >
- );
-}
-export default ApplicationsList;
diff --git a/awx/ui/src/screens/Application/ApplicationsList/index.js b/awx/ui/src/screens/Application/ApplicationsList/index.js
deleted file mode 100644
index 34f11070767a..000000000000
--- a/awx/ui/src/screens/Application/ApplicationsList/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ApplicationsList';
diff --git a/awx/ui/src/screens/Application/index.js b/awx/ui/src/screens/Application/index.js
deleted file mode 100644
index a4829065cfa8..000000000000
--- a/awx/ui/src/screens/Application/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './Applications';
diff --git a/awx/ui/src/screens/Application/shared/Application.helptext.js b/awx/ui/src/screens/Application/shared/Application.helptext.js
deleted file mode 100644
index 7b55e0b98085..000000000000
--- a/awx/ui/src/screens/Application/shared/Application.helptext.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import { t } from '@lingui/macro';
-
-const applicationHelpTextStrings = () => ({
- authorizationGrantType: t`The Grant type the user must use to acquire tokens for this application`,
- clientType: t`Set to Public or Confidential depending on how secure the client device is.`,
- redirectURIS: t`Allowed URIs list, space separated`,
-});
-
-export default applicationHelpTextStrings;
diff --git a/awx/ui/src/screens/Application/shared/ApplicationForm.js b/awx/ui/src/screens/Application/shared/ApplicationForm.js
deleted file mode 100644
index 38ee25b97ad9..000000000000
--- a/awx/ui/src/screens/Application/shared/ApplicationForm.js
+++ /dev/null
@@ -1,193 +0,0 @@
-import React, { useCallback } from 'react';
-import { useRouteMatch } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { Formik, useField, useFormikContext } from 'formik';
-import { Form, FormGroup } from '@patternfly/react-core';
-import PropTypes from 'prop-types';
-
-import { required } from 'util/validators';
-import FormField, { FormSubmitError } from 'components/FormField';
-import { FormColumnLayout } from 'components/FormLayout';
-import FormActionGroup from 'components/FormActionGroup/FormActionGroup';
-import OrganizationLookup from 'components/Lookup/OrganizationLookup';
-import AnsibleSelect from 'components/AnsibleSelect';
-import Popover from 'components/Popover';
-import getApplicationHelpTextStrings from './Application.helptext';
-
-function ApplicationFormFields({
- application,
- authorizationOptions,
- clientTypeOptions,
-}) {
- const applicationHelpTextStrings = getApplicationHelpTextStrings();
- const match = useRouteMatch();
- const { setFieldValue, setFieldTouched } = useFormikContext();
- const [organizationField, organizationMeta, organizationHelpers] =
- useField('organization');
- const [
- authorizationTypeField,
- authorizationTypeMeta,
- authorizationTypeHelpers,
- ] = useField({
- name: 'authorization_grant_type',
- validate: required(null),
- });
-
- const [clientTypeField, clientTypeMeta, clientTypeHelpers] = useField({
- name: 'client_type',
- validate: required(null),
- });
-
- const handleOrganizationUpdate = useCallback(
- (value) => {
- setFieldValue('organization', value);
- setFieldTouched('organization', true, false);
- },
- [setFieldValue, setFieldTouched]
- );
-
- return (
- <>
-
-
- organizationHelpers.setTouched()}
- onChange={handleOrganizationUpdate}
- value={organizationField.value}
- required
- autoPopulate={!application?.id}
- validate={required(null)}
- />
-
- }
- >
- {
- authorizationTypeHelpers.setValue(value);
- }}
- />
-
-
- }
- >
- {
- clientTypeHelpers.setValue(value);
- }}
- />
-
- >
- );
-}
-function ApplicationForm({
- onCancel,
- onSubmit,
- submitError,
- application,
- authorizationOptions,
- clientTypeOptions,
-}) {
- const initialValues = {
- name: application?.name || '',
- description: application?.description || '',
- organization: application?.summary_fields?.organization || null,
- authorization_grant_type: application?.authorization_grant_type || '',
- redirect_uris: application?.redirect_uris || '',
- client_type: application?.client_type || '',
- };
-
- return (
- onSubmit(values)}
- >
- {(formik) => (
-
- )}
-
- );
-}
-
-ApplicationForm.propTypes = {
- onSubmit: PropTypes.func.isRequired,
- onCancel: PropTypes.func.isRequired,
- authorizationOptions: PropTypes.arrayOf(PropTypes.object).isRequired,
- clientTypeOptions: PropTypes.arrayOf(PropTypes.object).isRequired,
-};
-
-export default ApplicationForm;
diff --git a/awx/ui/src/screens/Application/shared/ApplicationForm.test.js b/awx/ui/src/screens/Application/shared/ApplicationForm.test.js
deleted file mode 100644
index a01cc40838c9..000000000000
--- a/awx/ui/src/screens/Application/shared/ApplicationForm.test.js
+++ /dev/null
@@ -1,240 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { OrganizationsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import ApplicationForm from './ApplicationForm';
-
-jest.mock('../../../api');
-
-const authorizationOptions = [
- {
- key: 'authorization-code',
- label: 'Authorization code',
- value: 'authorization-code',
- },
- {
- key: 'password',
- label: 'Resource owner password-based',
- value: 'password',
- },
-];
-
-const clientTypeOptions = [
- { key: 'confidential', label: 'Confidential', value: 'confidential' },
- { key: 'public', label: 'Public', value: 'public' },
-];
-
-describe(' {
- let wrapper;
- test('should mount properly', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
- {}}
- application={{}}
- onCancel={() => {}}
- authorizationOptions={authorizationOptions}
- clientTypeOptions={clientTypeOptions}
- />
- );
- });
- expect(wrapper.find('ApplicationForm').length).toBe(1);
- });
-
- test('all fields should render successsfully', async () => {
- OrganizationsAPI.read.mockResolvedValue({
- results: [{ id: 1 }, { id: 2 }],
- });
- await act(async () => {
- wrapper = mountWithContexts(
- {}}
- application={{}}
- onCancel={() => {}}
- authorizationOptions={authorizationOptions}
- clientTypeOptions={clientTypeOptions}
- />
- );
- });
- expect(wrapper.find('input#name').length).toBe(1);
- expect(wrapper.find('input#description').length).toBe(1);
- expect(
- wrapper.find('AnsibleSelect[name="authorization_grant_type"]').length
- ).toBe(1);
- expect(wrapper.find('input#redirect_uris').length).toBe(1);
- expect(wrapper.find('AnsibleSelect[name="client_type"]').length).toBe(1);
- expect(wrapper.find('OrganizationLookup').length).toBe(1);
- });
-
- test('should update field values', async () => {
- OrganizationsAPI.read.mockResolvedValue({
- results: [{ id: 1 }, { id: 2 }],
- });
- await act(async () => {
- wrapper = mountWithContexts(
- {}}
- application={{}}
- onCancel={() => {}}
- authorizationOptions={authorizationOptions}
- clientTypeOptions={clientTypeOptions}
- />
- );
- await act(async () => {
- wrapper.find('input#name').simulate('change', {
- target: { value: 'new foo', name: 'name' },
- });
- wrapper.find('input#description').simulate('change', {
- target: { value: 'new bar', name: 'description' },
- });
- wrapper
- .find('AnsibleSelect[name="authorization_grant_type"]')
- .prop('onChange')({}, 'authorization-code');
-
- wrapper.find('input#redirect_uris').simulate('change', {
- target: { value: 'https://www.google.com', name: 'redirect_uris' },
- });
- wrapper.find('AnsibleSelect[name="client_type"]').prop('onChange')(
- {},
- 'confidential'
- );
- wrapper.find('OrganizationLookup').invoke('onChange')({
- id: 3,
- name: 'organization',
- });
- });
- });
- wrapper.update();
- expect(wrapper.find('input#name').prop('value')).toBe('new foo');
- expect(wrapper.find('input#description').prop('value')).toBe('new bar');
- expect(wrapper.find('input#organization').prop('value')).toBe(
- 'organization'
- );
- expect(
- wrapper
- .find('AnsibleSelect[name="authorization_grant_type"]')
- .prop('value')
- ).toBe('authorization-code');
- expect(
- wrapper.find('AnsibleSelect[name="client_type"]').prop('value')
- ).toBe('confidential');
- expect(
- wrapper.find('FormField[label="Redirect URIs"]').prop('isRequired')
- ).toBe(true);
- expect(wrapper.find('input#redirect_uris').prop('value')).toBe(
- 'https://www.google.com'
- );
- });
- test('should call onCancel', async () => {
- OrganizationsAPI.read.mockResolvedValue({
- results: [{ id: 1 }, { id: 2 }],
- });
- const onSubmit = jest.fn();
- const onCancel = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.find('Button[aria-label="Cancel"]').prop('onClick')();
- expect(onCancel).toBeCalled();
- });
- test('should call onSubmit', async () => {
- OrganizationsAPI.read.mockResolvedValue({
- results: [{ id: 1 }, { id: 2 }],
- });
- const onSubmit = jest.fn();
- const onCancel = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.find('Formik').prop('onSubmit')({
- authorization_grant_type: 'authorization-code',
- client_type: 'confidential',
- description: 'bar',
- name: 'foo',
- organization: 1,
- redirect_uris: 'http://www.google.com',
- });
- expect(onSubmit).toBeCalledWith({
- authorization_grant_type: 'authorization-code',
- client_type: 'confidential',
- description: 'bar',
- name: 'foo',
- organization: 1,
- redirect_uris: 'http://www.google.com',
- });
- });
- test('should render required on Redirect URIs', async () => {
- OrganizationsAPI.read.mockResolvedValue({
- results: [{ id: 1 }, { id: 2 }],
- });
- const onSubmit = jest.fn();
- const onCancel = jest.fn();
- const application = {
- id: 1,
- type: 'o_auth2_application',
- url: '/api/v2/applications/10/',
- related: {
- named_url: '/api/v2/applications/Alex++bar/',
- tokens: '/api/v2/applications/10/tokens/',
- activity_stream: '/api/v2/applications/10/activity_stream/',
- },
- summary_fields: {
- organization: {
- id: 230,
- name: 'bar',
- description:
- 'SaleNameBedPersonalityManagerWhileFinanceBreakToothPerson魲',
- },
- user_capabilities: {
- edit: true,
- delete: true,
- },
- tokens: {
- count: 0,
- results: [],
- },
- },
- created: '2020-06-11T17:54:33.983993Z',
- modified: '2020-06-11T17:54:33.984039Z',
- name: 'Alex',
- description: '',
- client_id: 'b1dmj8xzkbFm1ZQ27ygw2ZeE9I0AXqqeL74fiyk4',
- client_secret: '************',
- client_type: 'confidential',
- redirect_uris: 'http://www.google.com',
- authorization_grant_type: 'authorization-code',
- skip_authorization: false,
- organization: 230,
- };
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(
- wrapper.find('FormField[name="redirect_uris"]').prop('isRequired')
- ).toBe(true);
- });
-});
diff --git a/awx/ui/src/screens/Credential/Credential.js b/awx/ui/src/screens/Credential/Credential.js
deleted file mode 100644
index ce0509146dd0..000000000000
--- a/awx/ui/src/screens/Credential/Credential.js
+++ /dev/null
@@ -1,180 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { t } from '@lingui/macro';
-
-import { CaretLeftIcon } from '@patternfly/react-icons';
-import { Card, PageSection } from '@patternfly/react-core';
-import {
- Switch,
- useParams,
- useLocation,
- useRouteMatch,
- Route,
- Redirect,
- Link,
-} from 'react-router-dom';
-import useRequest from 'hooks/useRequest';
-import { ResourceAccessList } from 'components/ResourceAccessList';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import RoutedTabs from 'components/RoutedTabs';
-import RelatedTemplateList from 'components/RelatedTemplateList';
-import { CredentialsAPI } from 'api';
-import CredentialDetail from './CredentialDetail';
-import CredentialEdit from './CredentialEdit';
-
-const jobTemplateCredentialTypes = [
- 'machine',
- 'cloud',
- 'net',
- 'ssh',
- 'vault',
- 'kubernetes',
- 'cryptography',
-];
-
-function Credential({ setBreadcrumb }) {
- const { pathname } = useLocation();
-
- const match = useRouteMatch({
- path: '/credentials/:id',
- });
- const { id } = useParams();
-
- const {
- request: fetchCredential,
- result: { credential },
- isLoading: hasContentLoading,
- error: contentError,
- } = useRequest(
- useCallback(async () => {
- const { data } = await CredentialsAPI.readDetail(id);
- return {
- credential: data,
- };
- }, [id]),
- {
- credential: null,
- }
- );
-
- useEffect(() => {
- fetchCredential();
- }, [fetchCredential, pathname]);
-
- useEffect(() => {
- if (credential) {
- setBreadcrumb(credential);
- }
- }, [credential, setBreadcrumb]);
-
- const tabsArray = [
- {
- name: (
- <>
-
- {t`Back to Credentials`}
- >
- ),
- link: `/credentials`,
- id: 99,
- isBackButton: true,
- },
- { name: t`Details`, link: `/credentials/${id}/details`, id: 0 },
- {
- name: t`Access`,
- link: `/credentials/${id}/access`,
- id: 1,
- },
- ];
- if (jobTemplateCredentialTypes.includes(credential?.kind)) {
- tabsArray.push({
- name: t`Job Templates`,
- link: `/credentials/${id}/job_templates`,
- id: 2,
- });
- }
- let showCardHeader = true;
-
- if (pathname.endsWith('edit') || pathname.endsWith('add')) {
- showCardHeader = false;
- }
-
- if (!hasContentLoading && contentError) {
- return (
-
-
-
- {contentError.response && contentError.response.status === 404 && (
-
- {t`Credential not found.`}{' '}
- {t`View all Credentials.`}
-
- )}
-
-
-
- );
- }
-
- return (
-
-
- {showCardHeader && }
- {hasContentLoading && }
- {!hasContentLoading && credential && (
-
-
- {credential && [
-
-
- ,
-
-
- ,
-
-
- ,
-
-
- ,
-
- {!hasContentLoading && (
-
- {match.params.id && (
-
- {t`View Credential Details`}
-
- )}
-
- )}
- ,
- ]}
-
- {!hasContentLoading && (
-
- {id && (
-
- {t`View Credential Details`}
-
- )}
-
- )}
-
-
- )}
-
-
- );
-}
-
-export default Credential;
diff --git a/awx/ui/src/screens/Credential/Credential.test.js b/awx/ui/src/screens/Credential/Credential.test.js
deleted file mode 100644
index 2df3162205a5..000000000000
--- a/awx/ui/src/screens/Credential/Credential.test.js
+++ /dev/null
@@ -1,87 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { CredentialsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../testUtils/enzymeHelpers';
-import mockMachineCredential from './shared/data.machineCredential.json';
-import mockSCMCredential from './shared/data.scmCredential.json';
-import Credential from './Credential';
-
-jest.mock('../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useRouteMatch: () => ({
- url: '/credentials/2',
- params: { id: 2 },
- }),
-}));
-
-describe('', () => {
- let wrapper;
-
- test('initially renders user-based machine credential successfully', async () => {
- CredentialsAPI.readDetail.mockResolvedValueOnce({
- data: mockMachineCredential,
- });
- await act(async () => {
- wrapper = mountWithContexts( {}} />);
- });
- wrapper.update();
- expect(wrapper.find('Credential').length).toBe(1);
- expect(wrapper.find('RoutedTabs li').length).toBe(4);
- });
-
- test('initially renders user-based SCM credential successfully', async () => {
- CredentialsAPI.readDetail.mockResolvedValueOnce({
- data: mockSCMCredential,
- });
- await act(async () => {
- wrapper = mountWithContexts( {}} />);
- });
- wrapper.update();
- expect(wrapper.find('Credential').length).toBe(1);
- expect(wrapper.find('RoutedTabs li').length).toBe(3);
- });
-
- test('should render expected tabs', async () => {
- const expectedTabs = [
- 'Back to Credentials',
- 'Details',
- 'Access',
- 'Job Templates',
- ];
- await act(async () => {
- wrapper = mountWithContexts( {}} />);
- });
- wrapper.find('RoutedTabs li').forEach((tab, index) => {
- expect(tab.text()).toEqual(expectedTabs[index]);
- });
- });
-
- test('should show content error when user attempts to navigate to erroneous route', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/credentials/2/foobar'],
- });
- await act(async () => {
- wrapper = mountWithContexts( {}} />, {
- context: {
- router: {
- history,
- route: {
- location: history.location,
- match: {
- params: { id: 1 },
- url: '/credentials/2/foobar',
- path: '/credentials/2/foobar',
- },
- },
- },
- },
- });
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-});
diff --git a/awx/ui/src/screens/Credential/CredentialAdd/CredentialAdd.js b/awx/ui/src/screens/Credential/CredentialAdd/CredentialAdd.js
deleted file mode 100644
index 2525e7482689..000000000000
--- a/awx/ui/src/screens/Credential/CredentialAdd/CredentialAdd.js
+++ /dev/null
@@ -1,161 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { useHistory } from 'react-router-dom';
-import { PageSection, Card } from '@patternfly/react-core';
-import { CardBody } from 'components/Card';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import {
- CredentialInputSourcesAPI,
- CredentialTypesAPI,
- CredentialsAPI,
-} from 'api';
-import useRequest from 'hooks/useRequest';
-import CredentialForm from '../shared/CredentialForm';
-
-const fetchCredentialTypes = async (pageNo = 1, credentialTypes = []) => {
- const { data } = await CredentialTypesAPI.read({
- page_size: 200,
- page: pageNo,
- });
- if (data.next) {
- return fetchCredentialTypes(
- pageNo + 1,
- credentialTypes.concat(data.results)
- );
- }
- return credentialTypes.concat(data.results);
-};
-
-function CredentialAdd({ me }) {
- const history = useHistory();
-
- const {
- error: submitError,
- request: submitRequest,
- result: credentialId,
- } = useRequest(
- useCallback(
- async (values, credentialTypesMap) => {
- const { inputs: credentialTypeInputs } =
- credentialTypesMap[values.credential_type];
-
- const { inputs, organization, passwordPrompts, ...remainingValues } =
- values;
-
- const nonPluginInputs = {};
- const pluginInputs = {};
- const possibleFields = credentialTypeInputs.fields || [];
-
- possibleFields.forEach((field) => {
- const input = inputs[field.id];
- if (input?.credential && input?.inputs) {
- pluginInputs[field.id] = input;
- } else if (passwordPrompts[field.id]) {
- nonPluginInputs[field.id] = 'ASK';
- } else {
- nonPluginInputs[field.id] = input;
- }
- });
-
- const modifiedData = { inputs: nonPluginInputs, ...remainingValues };
- // can send only one of org, user, team
- if (organization?.id) {
- modifiedData.organization = organization.id;
- } else if (me?.id) {
- modifiedData.user = me.id;
- }
- const {
- data: { id: newCredentialId },
- } = await CredentialsAPI.create(modifiedData);
-
- await Promise.all(
- Object.entries(pluginInputs).map(([key, value]) =>
- CredentialInputSourcesAPI.create({
- input_field_name: key,
- metadata: value.inputs,
- source_credential: value.credential.id,
- target_credential: newCredentialId,
- })
- )
- );
-
- return newCredentialId;
- },
- [me]
- )
- );
-
- useEffect(() => {
- if (credentialId) {
- history.push(`/credentials/${credentialId}/details`);
- }
- }, [credentialId, history]);
-
- const {
- isLoading,
- error,
- request: loadData,
- result,
- } = useRequest(
- useCallback(async () => {
- const credTypes = await fetchCredentialTypes();
- const creds = credTypes.reduce((credentialTypesMap, credentialType) => {
- credentialTypesMap[credentialType.id] = credentialType;
- return credentialTypesMap;
- }, {});
- return creds;
- }, []),
- {}
- );
- useEffect(() => {
- loadData();
- }, [loadData]);
-
- const handleCancel = () => {
- history.push('/credentials');
- };
-
- const handleSubmit = async (values) => {
- await submitRequest(values, result);
- };
-
- if (error) {
- return (
-
-
-
-
-
-
-
- );
- }
- if (isLoading) {
- return (
-
-
-
-
-
-
-
- );
- }
- return (
-
-
-
-
-
-
-
- );
-}
-
-export { CredentialAdd as _CredentialAdd };
-export default CredentialAdd;
diff --git a/awx/ui/src/screens/Credential/CredentialAdd/CredentialAdd.test.js b/awx/ui/src/screens/Credential/CredentialAdd/CredentialAdd.test.js
deleted file mode 100644
index 04ef94dfece1..000000000000
--- a/awx/ui/src/screens/Credential/CredentialAdd/CredentialAdd.test.js
+++ /dev/null
@@ -1,201 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import {
- CredentialsAPI,
- CredentialInputSourcesAPI,
- CredentialTypesAPI,
-} from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import CredentialAdd from './CredentialAdd';
-
-jest.mock('../../../api');
-
-const mockCredentialResults = {
- data: {
- results: [
- {
- id: 1,
- type: 'credential_type',
- url: '/api/v2/credential_types/1/',
- related: {
- credentials: '/api/v2/credential_types/1/credentials/',
- activity_stream: '/api/v2/credential_types/1/activity_stream/',
- },
- summary_fields: {
- user_capabilities: {
- edit: false,
- delete: false,
- },
- },
- created: '2020-02-12T19:42:43.539626Z',
- modified: '2020-02-12T19:43:03.159739Z',
- name: 'Machine',
- description: '',
- kind: 'ssh',
- namespace: 'ssh',
- managed: true,
- inputs: {
- fields: [
- {
- id: 'username',
- label: 'Username',
- type: 'string',
- },
- {
- id: 'password',
- label: 'Password',
- type: 'string',
- secret: true,
- ask_at_runtime: true,
- },
- {
- id: 'ssh_key_data',
- label: 'SSH Private Key',
- type: 'string',
- format: 'ssh_private_key',
- secret: true,
- multiline: true,
- },
- {
- id: 'ssh_public_key_data',
- label: 'Signed SSH Certificate',
- type: 'string',
- multiline: true,
- secret: true,
- },
- {
- id: 'ssh_key_unlock',
- label: 'Private Key Passphrase',
- type: 'string',
- secret: true,
- ask_at_runtime: true,
- },
- {
- id: 'become_method',
- label: 'Privilege Escalation Method',
- type: 'string',
- help_text:
- 'Specify a method for "become" operations. This is equivalent to specifying the --become-method Ansible parameter.',
- },
- {
- id: 'become_username',
- label: 'Privilege Escalation Username',
- type: 'string',
- },
- {
- id: 'become_password',
- label: 'Privilege Escalation Password',
- type: 'string',
- secret: true,
- ask_at_runtime: true,
- },
- ],
- },
- injectors: {},
- },
- ],
- },
-};
-
-describe('', () => {
- let wrapper;
- let history;
-
- describe('Initial GET request succeeds', () => {
- beforeEach(async () => {
- CredentialsAPI.read.mockResolvedValue(mockCredentialResults);
- CredentialsAPI.create.mockResolvedValue({ data: { id: 13 } });
- history = createMemoryHistory({ initialEntries: ['/credentials'] });
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
- });
-
- test('handleSubmit should call the api and redirect to details page', async () => {
- await waitForElement(wrapper, 'isLoading', (el) => el.length === 0);
- await act(async () => {
- wrapper.find('CredentialForm').prop('onSubmit')({
- user: 1,
- name: 'foo',
- description: 'bar',
- credential_type: '1',
- inputs: {
- username: {
- credential: {
- id: 1,
- name: 'Some cred',
- },
- inputs: {
- foo: 'bar',
- },
- },
- password: 'foo',
- ssh_key_data: 'bar',
- ssh_public_key_data: 'baz',
- ssh_key_unlock: 'foobar',
- become_method: '',
- become_username: '',
- become_password: '',
- },
- passwordPrompts: {
- become_password: true,
- },
- });
- });
- expect(CredentialsAPI.create).toHaveBeenCalledWith({
- user: 1,
- name: 'foo',
- description: 'bar',
- credential_type: '1',
- inputs: {
- password: 'foo',
- ssh_key_data: 'bar',
- ssh_public_key_data: 'baz',
- ssh_key_unlock: 'foobar',
- become_method: '',
- become_username: '',
- become_password: 'ASK',
- },
- });
- expect(CredentialInputSourcesAPI.create).toHaveBeenCalledWith({
- input_field_name: 'username',
- metadata: {
- foo: 'bar',
- },
- source_credential: 1,
- target_credential: 13,
- });
- expect(history.location.pathname).toBe('/credentials/13/details');
- });
-
- test('handleCancel should return the user back to the credentials list', async () => {
- await waitForElement(wrapper, 'isLoading', (el) => el.length === 0);
- await act(async () => {
- wrapper.find('Button[aria-label="Cancel"]').simulate('click');
- });
- wrapper.update();
- expect(history.location.pathname).toEqual('/credentials');
- });
- });
-
- describe('Initial GET request fails', () => {
- test('shows error when initial GET request fails', async () => {
- CredentialTypesAPI.read.mockRejectedValue(new Error());
- history = createMemoryHistory({ initialEntries: ['/credentials'] });
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
- wrapper.update();
- expect(wrapper.find('ContentError').length).toBe(1);
- });
- });
-});
diff --git a/awx/ui/src/screens/Credential/CredentialAdd/index.js b/awx/ui/src/screens/Credential/CredentialAdd/index.js
deleted file mode 100644
index 46ece18e1d64..000000000000
--- a/awx/ui/src/screens/Credential/CredentialAdd/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './CredentialAdd';
diff --git a/awx/ui/src/screens/Credential/CredentialDetail/CredentialDetail.js b/awx/ui/src/screens/Credential/CredentialDetail/CredentialDetail.js
deleted file mode 100644
index a5ec427ad82f..000000000000
--- a/awx/ui/src/screens/Credential/CredentialDetail/CredentialDetail.js
+++ /dev/null
@@ -1,329 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-import { Link, useHistory } from 'react-router-dom';
-import { t } from '@lingui/macro';
-import styled from 'styled-components';
-import {
- Button,
- TextList,
- TextListItem,
- TextListVariants,
- TextListItemVariants,
-} from '@patternfly/react-core';
-import AlertModal from 'components/AlertModal';
-import { CardBody, CardActionsRow } from 'components/Card';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import DeleteButton from 'components/DeleteButton';
-import { Detail, DetailList, UserDateDetail } from 'components/DetailList';
-import ChipGroup from 'components/ChipGroup';
-import CodeEditor from 'components/CodeEditor';
-import CredentialChip from 'components/CredentialChip';
-import ErrorDetail from 'components/ErrorDetail';
-import { CredentialsAPI, CredentialTypesAPI } from 'api';
-import { Credential } from 'types';
-import useRequest, { useDismissableError } from 'hooks/useRequest';
-import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
-
-const PluginInputMetadata = styled.div`
- grid-column: 1 / -1;
-`;
-
-const PluginFieldText = styled.p`
- margin-top: 10px;
-`;
-
-function CredentialDetail({ credential }) {
- const {
- id: credentialId,
- name,
- description,
- inputs,
- created,
- modified,
- summary_fields: {
- credential_type,
- organization,
- created_by,
- modified_by,
- user_capabilities,
- },
- } = credential;
- const history = useHistory();
-
- const {
- result: { fields, managedByTower, inputSources },
- request: fetchDetails,
- isLoading: hasContentLoading,
- error: contentError,
- } = useRequest(
- useCallback(async () => {
- const [
- {
- data: { inputs: credentialTypeInputs, managed },
- },
- {
- data: { results: loadedInputSources },
- },
- ] = await Promise.all([
- CredentialTypesAPI.readDetail(credential_type.id),
- CredentialsAPI.readInputSources(credentialId),
- ]);
- return {
- fields: credentialTypeInputs.fields || [],
- managedByTower: managed,
- inputSources: loadedInputSources.reduce(
- (inputSourcesMap, inputSource) => {
- inputSourcesMap[inputSource.input_field_name] = inputSource;
- return inputSourcesMap;
- },
- {}
- ),
- };
- }, [credentialId, credential_type.id]),
- {
- fields: [],
- managedByTower: true,
- inputSources: {},
- }
- );
-
- const {
- request: deleteCredential,
- isLoading,
- error: deleteError,
- } = useRequest(
- useCallback(async () => {
- await CredentialsAPI.destroy(credentialId);
- history.push('/credentials');
- }, [credentialId, history])
- );
-
- const { error, dismissError } = useDismissableError(deleteError);
-
- const renderDetail = ({
- id,
- label,
- type,
- ask_at_runtime,
- help_text = '',
- }) => {
- if (inputSources[id]) {
- return (
-
- {label} *}
- value={
-
-
-
- }
- />
-
- {}}
- rows={5}
- hasErrors={false}
- />
-
-
- );
- }
-
- if (type === 'boolean') {
- return null;
- }
-
- if (inputs[id] === '$encrypted$') {
- return (
-
- );
- }
-
- if (ask_at_runtime && inputs[id] === 'ASK') {
- return (
-
- );
- }
-
- return (
-
- );
- };
-
- useEffect(() => {
- fetchDetails();
- }, [fetchDetails]);
-
- const deleteDetailsRequests =
- relatedResourceDeleteRequests.credential(credential);
-
- const enabledBooleanFields = fields.filter(
- ({ id, type }) => type === 'boolean' && inputs[id]
- );
-
- if (hasContentLoading) {
- return ;
- }
-
- if (contentError) {
- return ;
- }
-
- return (
-
-
-
-
- {organization && (
-
- {organization.name}
-
- }
- />
- )}
-
- {credential_type.name}
-
- )
- }
- />
-
- {fields.map((field) => renderDetail(field))}
-
-
-
-
- {enabledBooleanFields.map(({ id, label }) => (
-
- {label}
-
- ))}
-
- }
- isEmpty={enabledBooleanFields.length === 0}
- />
-
- {Object.keys(inputSources).length > 0 && (
-
- {t`* This field will be retrieved from an external secret management system using the specified credential.`}
-
- )}
-
- {user_capabilities.edit && (
-
- )}
- {user_capabilities.delete && (
-
- {t`Delete`}
-
- )}
-
- {error && (
-
- {t`Failed to delete credential.`}
-
-
- )}
-
- );
-}
-
-CredentialDetail.propTypes = {
- credential: Credential.isRequired,
-};
-
-export default CredentialDetail;
diff --git a/awx/ui/src/screens/Credential/CredentialDetail/CredentialDetail.test.js b/awx/ui/src/screens/Credential/CredentialDetail/CredentialDetail.test.js
deleted file mode 100644
index 911e2e70564b..000000000000
--- a/awx/ui/src/screens/Credential/CredentialDetail/CredentialDetail.test.js
+++ /dev/null
@@ -1,171 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { CredentialsAPI, CredentialTypesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import CredentialDetail from './CredentialDetail';
-import { mockCredentials, mockCredentialType } from '../shared';
-
-jest.mock('../../../api');
-
-const mockCredential = mockCredentials.results[0];
-
-const mockInputSource = {
- id: 33,
- type: 'credential_input_source',
- url: '/api/v2/credential_input_sources/33/',
- summary_fields: {
- source_credential: {
- id: 424,
- name: 'External Credential',
- description: '',
- kind: 'conjur',
- cloud: false,
- credential_type_id: 20,
- },
- },
- input_field_name: 'ssh_key_unlock',
- metadata: {
- secret_path: '/foo/bar/baz',
- secret_version: '17',
- },
-};
-
-function expectDetailToMatch(wrapper, label, value) {
- const detail = wrapper.find(`Detail[label="${label}"]`);
- expect(detail).toHaveLength(1);
- expect(detail.find('dd').text()).toEqual(value);
-}
-
-describe('', () => {
- let wrapper;
-
- beforeEach(async () => {
- CredentialTypesAPI.readDetail.mockResolvedValue({
- data: mockCredentialType,
- });
-
- CredentialsAPI.readInputSources.mockResolvedValue({
- data: {
- results: [mockInputSource],
- },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- test('should render successfully', () => {
- expect(wrapper.find('CredentialDetail').length).toBe(1);
- });
-
- test('should have proper number of delete detail requests', () => {
- expect(
- wrapper.find('DeleteButton').prop('deleteDetailsRequests')
- ).toHaveLength(5);
- });
-
- test('should render details', () => {
- expectDetailToMatch(wrapper, 'Name', mockCredential.name);
- expectDetailToMatch(wrapper, 'Description', mockCredential.description);
- expectDetailToMatch(
- wrapper,
- 'Organization',
- mockCredential.summary_fields.organization.name
- );
- expectDetailToMatch(
- wrapper,
- 'Credential Type',
- mockCredential.summary_fields.credential_type.name
- );
- expectDetailToMatch(wrapper, 'Username', mockCredential.inputs.username);
- expectDetailToMatch(wrapper, 'Password', 'Encrypted');
- expectDetailToMatch(wrapper, 'SSH Private Key', 'Encrypted');
- expectDetailToMatch(wrapper, 'Signed SSH Certificate', 'Encrypted');
- const sshKeyUnlockDetail = wrapper.find(
- 'Detail#credential-ssh_key_unlock-detail'
- );
- expect(sshKeyUnlockDetail.length).toBe(1);
- expect(sshKeyUnlockDetail.find('CredentialChip').length).toBe(1);
- expect(
- wrapper.find('CodeEditor#credential-ssh_key_unlock-metadata').props()
- .value
- ).toBe(JSON.stringify(mockInputSource.metadata, null, 2));
- expectDetailToMatch(
- wrapper,
- 'Privilege Escalation Method',
- mockCredential.inputs.become_method
- );
- expectDetailToMatch(
- wrapper,
- 'Privilege Escalation Username',
- mockCredential.inputs.become_username
- );
- expectDetailToMatch(
- wrapper,
- 'Privilege Escalation Password',
- 'Prompt on launch'
- );
- expect(wrapper.find(`Detail[label="Enabled Options"] li`).text()).toEqual(
- 'Authorize'
- );
- });
-
- test('should show content error on throw', async () => {
- CredentialTypesAPI.readDetail.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-
- test('handleDelete should call api', async () => {
- CredentialsAPI.destroy = jest.fn();
- await act(async () => {
- wrapper.find('DeleteButton').invoke('onConfirm')();
- });
- wrapper.update();
- expect(CredentialsAPI.destroy).toHaveBeenCalledTimes(1);
- });
-
- test('should show error modal when credential is not successfully deleted from api', async () => {
- CredentialsAPI.destroy.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- wrapper.find('DeleteButton').invoke('onConfirm')();
- });
- await waitForElement(wrapper, 'ErrorDetail', (el) => el.length === 1);
- await act(async () => {
- wrapper.find('ModalBoxCloseButton').invoke('onClose')();
- });
- });
-
- test('should not load enabled options', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- const enabled_options_detail = wrapper
- .find(`Detail[label="Enabled Options"]`)
- .at(0);
- expect(enabled_options_detail.prop('isEmpty')).toEqual(true);
- });
-});
diff --git a/awx/ui/src/screens/Credential/CredentialDetail/index.js b/awx/ui/src/screens/Credential/CredentialDetail/index.js
deleted file mode 100644
index 3e5e8527faa8..000000000000
--- a/awx/ui/src/screens/Credential/CredentialDetail/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './CredentialDetail';
diff --git a/awx/ui/src/screens/Credential/CredentialEdit/CredentialEdit.js b/awx/ui/src/screens/Credential/CredentialEdit/CredentialEdit.js
deleted file mode 100644
index 8f38277fd130..000000000000
--- a/awx/ui/src/screens/Credential/CredentialEdit/CredentialEdit.js
+++ /dev/null
@@ -1,204 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { useHistory, useParams } from 'react-router-dom';
-import { CardBody } from 'components/Card';
-import {
- CredentialsAPI,
- CredentialInputSourcesAPI,
- CredentialTypesAPI,
- OrganizationsAPI,
- UsersAPI,
-} from 'api';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import useRequest from 'hooks/useRequest';
-import { useConfig } from 'contexts/Config';
-import { Credential } from 'types';
-import CredentialForm from '../shared/CredentialForm';
-
-function CredentialEdit({ credential }) {
- const history = useHistory();
- const { id: credId } = useParams();
- const { me = {} } = useConfig();
- const [isOrgLookupDisabled, setIsOrgLookupDisabled] = useState(false);
-
- const {
- error: submitError,
- request: submitRequest,
- result,
- } = useRequest(
- useCallback(
- async (values, credentialTypesMap, inputSourceMap) => {
- const { inputs: credentialTypeInputs } =
- credentialTypesMap[values.credential_type];
-
- const { inputs, organization, passwordPrompts, ...remainingValues } =
- values;
-
- const nonPluginInputs = {};
- const pluginInputs = {};
- const possibleFields = credentialTypeInputs.fields || [];
-
- possibleFields.forEach((field) => {
- const input = inputs[field.id];
- if (input?.credential && input?.inputs) {
- pluginInputs[field.id] = input;
- } else if (passwordPrompts[field.id]) {
- nonPluginInputs[field.id] = 'ASK';
- } else {
- nonPluginInputs[field.id] = input;
- }
- });
-
- const createAndUpdateInputSources = () =>
- Object.entries(pluginInputs).map(([fieldName, fieldValue]) => {
- if (!inputSourceMap[fieldName]) {
- return CredentialInputSourcesAPI.create({
- input_field_name: fieldName,
- metadata: fieldValue.inputs,
- source_credential: fieldValue.credential.id,
- target_credential: credId,
- });
- }
- if (fieldValue.touched) {
- return CredentialInputSourcesAPI.update(
- inputSourceMap[fieldName].id,
- {
- metadata: fieldValue.inputs,
- source_credential: fieldValue.credential.id,
- }
- );
- }
-
- return null;
- });
-
- const destroyInputSources = () =>
- Object.values(inputSourceMap).map((inputSource) => {
- const { id, input_field_name } = inputSource;
- if (!inputs[input_field_name]?.credential) {
- return CredentialInputSourcesAPI.destroy(id);
- }
- return null;
- });
-
- const modifiedData = { inputs: nonPluginInputs, ...remainingValues };
- // can send only one of org, user, team
- if (organization?.id) {
- modifiedData.organization = organization.id;
- } else {
- modifiedData.organization = null;
- if (me?.id) {
- modifiedData.user = me.id;
- }
- }
- const [{ data }] = await Promise.all([
- CredentialsAPI.update(credId, modifiedData),
- ...destroyInputSources(),
- ]);
-
- await Promise.all(createAndUpdateInputSources());
-
- return data;
- },
- [me, credId]
- )
- );
-
- useEffect(() => {
- if (result) {
- history.push(`/credentials/${result.id}/details`);
- }
- }, [result, history]);
- const {
- isLoading,
- error,
- request: loadData,
- result: { credentialTypes, loadedInputSources },
- } = useRequest(
- useCallback(async () => {
- const [
- { data },
- {
- data: { results },
- },
- {
- data: { count: adminOrgCount },
- },
- {
- data: { count: credentialAdminCount },
- },
- ] = await Promise.all([
- CredentialTypesAPI.read({ page_size: 200 }),
- CredentialsAPI.readInputSources(credId),
- UsersAPI.readAdminOfOrganizations(me.id),
- OrganizationsAPI.read({
- page_size: 1,
- role_level: 'credential_admin_role',
- }),
- ]);
- setIsOrgLookupDisabled(!(adminOrgCount || credentialAdminCount));
- const credTypes = data.results;
- if (data.next && data.next.includes('page=2')) {
- const {
- data: { results: additionalCredTypes },
- } = await CredentialTypesAPI.read({
- page_size: 200,
- page: 2,
- });
- credTypes.concat([...additionalCredTypes]);
- }
- const creds = credTypes.reduce((credentialTypesMap, credentialType) => {
- credentialTypesMap[credentialType.id] = credentialType;
- return credentialTypesMap;
- }, {});
- const inputSources = results.reduce((inputSourcesMap, inputSource) => {
- inputSourcesMap[inputSource.input_field_name] = inputSource;
- return inputSourcesMap;
- }, {});
- return { credentialTypes: creds, loadedInputSources: inputSources };
- }, [credId, me.id]),
- {}
- );
-
- useEffect(() => {
- loadData();
- }, [loadData]);
-
- const handleCancel = () => {
- const url = `/credentials/${credId}/details`;
- history.push(`${url}`);
- };
-
- const handleSubmit = async (values) => {
- await submitRequest(values, credentialTypes, loadedInputSources);
- };
-
- if (error) {
- return ;
- }
-
- if (isLoading || !credentialTypes) {
- return ;
- }
-
- return (
-
-
-
- );
-}
-
-CredentialEdit.propTypes = {
- credential: Credential.isRequired,
-};
-
-export { CredentialEdit as _CredentialEdit };
-export default CredentialEdit;
diff --git a/awx/ui/src/screens/Credential/CredentialEdit/CredentialEdit.test.js b/awx/ui/src/screens/Credential/CredentialEdit/CredentialEdit.test.js
deleted file mode 100644
index 2ce88656474f..000000000000
--- a/awx/ui/src/screens/Credential/CredentialEdit/CredentialEdit.test.js
+++ /dev/null
@@ -1,490 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import {
- CredentialsAPI,
- CredentialInputSourcesAPI,
- CredentialTypesAPI,
- OrganizationsAPI,
- UsersAPI,
-} from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import CredentialEdit from './CredentialEdit';
-
-jest.mock('../../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useParams: () => ({
- id: 3,
- }),
-}));
-
-const mockCredential = {
- id: 3,
- type: 'credential',
- url: '/api/v2/credentials/3/',
- related: {
- named_url: '/api/v2/credentials/oersdgfasf++Machine+ssh++org/',
- created_by: '/api/v2/users/1/',
- modified_by: '/api/v2/users/1/',
- organization: '/api/v2/organizations/1/',
- activity_stream: '/api/v2/credentials/3/activity_stream/',
- access_list: '/api/v2/credentials/3/access_list/',
- object_roles: '/api/v2/credentials/3/object_roles/',
- owner_users: '/api/v2/credentials/3/owner_users/',
- owner_teams: '/api/v2/credentials/3/owner_teams/',
- copy: '/api/v2/credentials/3/copy/',
- input_sources: '/api/v2/credentials/3/input_sources/',
- credential_type: '/api/v2/credential_types/1/',
- },
- summary_fields: {
- organization: {
- id: 1,
- name: 'org',
- description: '',
- },
- credential_type: {
- id: 1,
- name: 'Machine',
- description: '',
- },
- created_by: {
- id: 1,
- username: 'admin',
- first_name: '',
- last_name: '',
- },
- modified_by: {
- id: 1,
- username: 'admin',
- first_name: '',
- last_name: '',
- },
- object_roles: {
- admin_role: {
- description: 'Can manage all aspects of the credential',
- name: 'Admin',
- id: 36,
- },
- use_role: {
- description: 'Can use the credential in a job template',
- name: 'Use',
- id: 37,
- },
- read_role: {
- description: 'May view settings for the credential',
- name: 'Read',
- id: 38,
- },
- },
- user_capabilities: {
- edit: true,
- delete: true,
- copy: true,
- use: true,
- },
- owners: [
- {
- id: 1,
- type: 'user',
- name: 'admin',
- description: ' ',
- url: '/api/v2/users/1/',
- },
- {
- id: 1,
- type: 'organization',
- name: 'org',
- description: '',
- url: '/api/v2/organizations/1/',
- },
- ],
- },
- created: '2020-02-18T15:35:04.563928Z',
- modified: '2020-02-18T15:35:04.563957Z',
- name: 'oersdgfasf',
- description: '',
- organization: 1,
- credential_type: 1,
- inputs: {},
- kind: 'ssh',
- cloud: false,
- kubernetes: false,
-};
-
-const mockOrgAdmins = {
- data: {
- count: 1,
- results: [
- {
- id: 1,
- name: 'org',
- },
- ],
- },
-};
-
-const mockOrganizations = {
- data: {
- results: [{ id: 1 }],
- count: 1,
- },
-};
-
-const mockCredentialResults = {
- data: {
- results: [
- {
- id: 1,
- type: 'credential_type',
- url: '/api/v2/credential_types/1/',
- related: {
- credentials: '/api/v2/credential_types/1/credentials/',
- activity_stream: '/api/v2/credential_types/1/activity_stream/',
- },
- summary_fields: {
- user_capabilities: {
- edit: false,
- delete: false,
- },
- },
- created: '2020-02-12T19:42:43.539626Z',
- modified: '2020-02-12T19:43:03.159739Z',
- name: 'Machine',
- description: '',
- kind: 'ssh',
- namespace: 'ssh',
- managed: true,
- inputs: {
- fields: [
- {
- id: 'username',
- label: 'Username',
- type: 'string',
- },
- {
- id: 'password',
- label: 'Password',
- type: 'string',
- secret: true,
- ask_at_runtime: true,
- },
- {
- id: 'ssh_key_data',
- label: 'SSH Private Key',
- type: 'string',
- format: 'ssh_private_key',
- secret: true,
- multiline: true,
- },
- {
- id: 'ssh_public_key_data',
- label: 'Signed SSH Certificate',
- type: 'string',
- multiline: true,
- secret: true,
- },
- {
- id: 'ssh_key_unlock',
- label: 'Private Key Passphrase',
- type: 'string',
- secret: true,
- ask_at_runtime: true,
- },
- {
- id: 'become_method',
- label: 'Privilege Escalation Method',
- type: 'string',
- help_text:
- 'Specify a method for "become" operations. This is equivalent to specifying the --become-method Ansible parameter.',
- },
- {
- id: 'become_username',
- label: 'Privilege Escalation Username',
- type: 'string',
- },
- {
- id: 'become_password',
- label: 'Privilege Escalation Password',
- type: 'string',
- secret: true,
- ask_at_runtime: true,
- },
- ],
- },
- injectors: {},
- },
- {
- id: 9,
- type: 'credential_type',
- url: '/api/v2/credential_types/9/',
- related: {
- credentials: '/api/v2/credential_types/9/credentials/',
- activity_stream: '/api/v2/credential_types/9/activity_stream/',
- },
- summary_fields: {
- user_capabilities: {
- edit: true,
- delete: true,
- },
- },
- created: '2021-02-12T19:13:22.352791Z',
- modified: '2021-02-12T19:14:15.578773Z',
- name: 'Google Compute Engine',
- description: '',
- kind: 'cloud',
- namespace: 'gce',
- managed: true,
- inputs: {
- fields: [
- {
- id: 'username',
- label: 'Service Account Email Address',
- type: 'string',
- help_text:
- 'The email address assigned to the Google Compute Engine service account.',
- },
- {
- id: 'project',
- label: 'Project',
- type: 'string',
- help_text:
- 'The Project ID is the GCE assigned identification. It is often constructed as three words or two words followed by a three-digit number. Examples: project-id-000 and another-project-id',
- },
- {
- id: 'ssh_key_data',
- label: 'RSA Private Key',
- type: 'string',
- format: 'ssh_private_key',
- secret: true,
- multiline: true,
- help_text:
- 'Paste the contents of the PEM file associated with the service account email.',
- },
- ],
- required: ['username', 'ssh_key_data'],
- },
- injectors: {},
- },
- ],
- },
-};
-
-const mockInputSources = {
- data: {
- results: [
- {
- id: 34,
- summary_fields: {
- source_credential: {
- id: 20,
- name: 'CyberArk Conjur Secrets Manager Lookup',
- description: '',
- kind: 'conjur',
- cloud: false,
- credential_type_id: 20,
- },
- },
- input_field_name: 'password',
- metadata: {
- secret_path: 'a',
- secret_version: 'b',
- },
- source_credential: 20,
- },
- {
- id: 35,
- summary_fields: {
- source_credential: {
- id: 20,
- name: 'CyberArk Conjur Secrets Manager Lookup',
- description: '',
- kind: 'conjur',
- cloud: false,
- credential_type_id: 20,
- },
- },
- input_field_name: 'become_username',
- metadata: {
- secret_path: 'foo',
- secret_version: 'bar',
- },
- source_credential: 20,
- },
- ],
- },
-};
-
-describe('', () => {
- let wrapper;
- let history;
-
- describe('Initial GET request succeeds', () => {
- beforeEach(async () => {
- [
- [UsersAPI.readAdminOfOrganizations, mockOrgAdmins],
- [OrganizationsAPI.read, mockOrganizations],
- [CredentialsAPI.read, mockCredentialResults],
- [CredentialsAPI.update, { data: { id: 3 } }],
- [CredentialsAPI.readInputSources, mockInputSources],
- ].forEach(([apiMethod, mockData]) => {
- apiMethod.mockResolvedValue(mockData);
- });
- history = createMemoryHistory({ initialEntries: ['/credentials'] });
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: {
- router: { history },
- me: {
- id: 1,
- },
- },
- }
- );
- });
- });
-
- test('initially renders successfully', async () => {
- expect(wrapper.find('CredentialEdit').length).toBe(1);
- });
-
- test('handleCancel returns the user to credential detail', async () => {
- await waitForElement(wrapper, 'isLoading', (el) => el.length === 0);
- await act(async () => {
- wrapper.find('Button[aria-label="Cancel"]').simulate('click');
- });
- wrapper.update();
- expect(history.location.pathname).toEqual('/credentials/3/details');
- });
-
- test('handleSubmit should post to the api', async () => {
- await waitForElement(wrapper, 'isLoading', (el) => el.length === 0);
- await act(async () => {
- wrapper.find('CredentialForm').prop('onSubmit')({
- user: 1,
- name: 'foo',
- description: 'bar',
- credential_type: '1',
- inputs: {
- username: {
- credential: {
- id: 1,
- name: 'Some cred',
- },
- inputs: {
- foo: 'bar',
- },
- },
- password: 'foo',
- ssh_key_data: 'bar',
- ssh_public_key_data: 'baz',
- ssh_key_unlock: 'foobar',
- become_method: '',
- become_username: {
- credential: {
- id: 1,
- name: 'Some cred',
- },
- inputs: {
- secret_path: '/foo/bar',
- secret_version: '9000',
- },
- touched: true,
- },
- become_password: '',
- },
- passwordPrompts: {
- become_password: true,
- },
- });
- });
- expect(CredentialsAPI.update).toHaveBeenCalledWith(3, {
- user: 1,
- name: 'foo',
- organization: null,
- description: 'bar',
- credential_type: '1',
- inputs: {
- password: 'foo',
- ssh_key_data: 'bar',
- ssh_public_key_data: 'baz',
- ssh_key_unlock: 'foobar',
- become_method: '',
- become_password: 'ASK',
- },
- });
- expect(CredentialInputSourcesAPI.create).toHaveBeenCalledWith({
- input_field_name: 'username',
- metadata: {
- foo: 'bar',
- },
- source_credential: 1,
- target_credential: 3,
- });
- expect(CredentialInputSourcesAPI.update).toHaveBeenCalledWith(35, {
- metadata: {
- secret_path: '/foo/bar',
- secret_version: '9000',
- },
- source_credential: 1,
- });
- expect(CredentialInputSourcesAPI.destroy).toHaveBeenCalledWith(34);
- expect(history.location.pathname).toBe('/credentials/3/details');
- });
- test('inputs are properly rendered', async () => {
- history = createMemoryHistory({ initialEntries: ['/credentials'] });
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
- wrapper.update();
- expect(wrapper.find('input#credential-username').prop('value')).toBe(
- 'foo@ansible.com'
- );
- expect(wrapper.find('input#credential-project').prop('value')).toBe(
- 'foo'
- );
- expect(
- wrapper.find('textarea#credential-ssh_key_data').prop('value')
- ).toBe('$encrypted$');
- });
- });
-
- describe('Initial GET request fails', () => {
- test('shows error when initial GET request fails', async () => {
- CredentialTypesAPI.read.mockRejectedValue(new Error());
- history = createMemoryHistory({ initialEntries: ['/credentials'] });
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
- wrapper.update();
- expect(wrapper.find('ContentError').length).toBe(1);
- });
- });
-});
diff --git a/awx/ui/src/screens/Credential/CredentialEdit/index.js b/awx/ui/src/screens/Credential/CredentialEdit/index.js
deleted file mode 100644
index 98878743714a..000000000000
--- a/awx/ui/src/screens/Credential/CredentialEdit/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './CredentialEdit';
diff --git a/awx/ui/src/screens/Credential/CredentialList/CredentialList.js b/awx/ui/src/screens/Credential/CredentialList/CredentialList.js
deleted file mode 100644
index 30090f5d94a2..000000000000
--- a/awx/ui/src/screens/Credential/CredentialList/CredentialList.js
+++ /dev/null
@@ -1,226 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-import { useLocation } from 'react-router-dom';
-import { t, Plural } from '@lingui/macro';
-import { Card, PageSection } from '@patternfly/react-core';
-import { CredentialsAPI } from 'api';
-import useSelected from 'hooks/useSelected';
-import useToast, { AlertVariant } from 'hooks/useToast';
-import AlertModal from 'components/AlertModal';
-import ErrorDetail from 'components/ErrorDetail';
-import DataListToolbar from 'components/DataListToolbar';
-import PaginatedTable, {
- HeaderRow,
- HeaderCell,
- ToolbarAddButton,
- ToolbarDeleteButton,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import useRequest, { useDeleteItems } from 'hooks/useRequest';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
-import CredentialListItem from './CredentialListItem';
-
-const QS_CONFIG = getQSConfig('credential', {
- page: 1,
- page_size: 20,
- order_by: 'name',
-});
-
-function CredentialList() {
- const location = useLocation();
- const { addToast, Toast, toastProps } = useToast();
-
- const {
- result: {
- credentials,
- credentialCount,
- actions,
- relatedSearchableKeys,
- searchableKeys,
- },
- error: contentError,
- isLoading,
- request: fetchCredentials,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
- const [creds, credActions] = await Promise.all([
- CredentialsAPI.read(params),
- CredentialsAPI.readOptions(),
- ]);
- const searchKeys = getSearchableKeys(credActions.data.actions?.GET);
- if (credActions.data.actions?.GET.type) {
- searchKeys.push({ key: 'credential_type__kind', type: 'string' });
- }
- return {
- credentials: creds.data.results,
- credentialCount: creds.data.count,
- actions: credActions.data.actions,
- relatedSearchableKeys: (
- credActions?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: searchKeys,
- };
- }, [location]),
- {
- credentials: [],
- credentialCount: 0,
- actions: {},
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- useEffect(() => {
- fetchCredentials();
- }, [fetchCredentials]);
-
- const {
- selected,
- isAllSelected,
- handleSelect,
- setSelected,
- selectAll,
- clearSelected,
- } = useSelected(credentials);
-
- const {
- isLoading: isDeleteLoading,
- deleteItems: deleteCredentials,
- deletionError,
- clearDeletionError,
- } = useDeleteItems(
- useCallback(
- () => Promise.all(selected.map(({ id }) => CredentialsAPI.destroy(id))),
- [selected]
- ),
- {
- qsConfig: QS_CONFIG,
- allItemsSelected: isAllSelected,
- fetchItems: fetchCredentials,
- }
- );
-
- const handleDelete = async () => {
- await deleteCredentials();
- setSelected([]);
- };
-
- const handleCopy = useCallback(
- (newCredentialId) => {
- addToast({
- id: newCredentialId,
- title: t`Credential copied successfully`,
- variant: AlertVariant.success,
- hasTimeout: true,
- });
- },
- [addToast]
- );
-
- const canAdd =
- actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
- const deleteDetailsRequests = relatedResourceDeleteRequests.credential(
- selected[0]
- );
- return (
- <>
-
-
-
- {t`Name`}
- {t`Type`}
- {t`Actions`}
-
- }
- renderRow={(item, index) => (
- row.id === item.id)}
- onSelect={() => handleSelect(item)}
- onCopy={handleCopy}
- rowIndex={index}
- />
- )}
- renderToolbar={(props) => (
- ]
- : []),
-
- }
- />,
- ]}
- />
- )}
- />
-
-
- {t`Failed to delete one or more credentials.`}
-
-
-
-
- >
- );
-}
-
-export default CredentialList;
diff --git a/awx/ui/src/screens/Credential/CredentialList/CredentialList.test.js b/awx/ui/src/screens/Credential/CredentialList/CredentialList.test.js
deleted file mode 100644
index 08803a961ad5..000000000000
--- a/awx/ui/src/screens/Credential/CredentialList/CredentialList.test.js
+++ /dev/null
@@ -1,155 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { CredentialsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import { CredentialList } from '.';
-import { mockCredentials } from '../shared';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
-
- beforeEach(async () => {
- CredentialsAPI.read.mockResolvedValueOnce({ data: mockCredentials });
- CredentialsAPI.readOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- },
- });
-
- await act(async () => {
- wrapper = mountWithContexts();
- });
-
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('initially renders successfully', () => {
- expect(wrapper.find('CredentialList').length).toBe(1);
- });
-
- test('should have proper number of delete detail requests', () => {
- expect(
- wrapper.find('ToolbarDeleteButton').prop('deleteDetailsRequests')
- ).toHaveLength(5);
- });
-
- test('should fetch credentials from api and render the in the list', () => {
- expect(CredentialsAPI.read).toHaveBeenCalled();
- expect(wrapper.find('CredentialListItem').length).toBe(5);
- });
-
- test('should show content error if credentials are not successfully fetched from api', async () => {
- CredentialsAPI.readOptions.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-
- test('should check and uncheck the row item', async () => {
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').props().checked
- ).toBe(false);
- await act(async () => {
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .invoke('onChange')(true);
- });
- wrapper.update();
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').props().checked
- ).toBe(true);
- await act(async () => {
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .invoke('onChange')(false);
- });
- wrapper.update();
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').props().checked
- ).toBe(false);
- });
-
- test('should check all row items when select all is checked', async () => {
- wrapper.find('DataListCheck').forEach((el) => {
- expect(el.props().checked).toBe(false);
- });
- await act(async () => {
- wrapper.find('Checkbox#select-all').invoke('onChange')(true);
- });
- wrapper.update();
- wrapper.find('DataListCheck').forEach((el) => {
- expect(el.props().checked).toBe(true);
- });
- await act(async () => {
- wrapper.find('Checkbox#select-all').invoke('onChange')(false);
- });
- wrapper.update();
- wrapper.find('DataListCheck').forEach((el) => {
- expect(el.props().checked).toBe(false);
- });
- });
-
- test('should call api delete credentials for each selected credential', async () => {
- CredentialsAPI.read.mockResolvedValueOnce({ data: mockCredentials });
- CredentialsAPI.destroy = jest.fn();
-
- await act(async () => {
- wrapper
- .find('.pf-c-table__check')
- .at(2)
- .find('input')
- .invoke('onChange')();
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('ToolbarDeleteButton').invoke('onDelete')();
- });
- wrapper.update();
- expect(CredentialsAPI.destroy).toHaveBeenCalledTimes(1);
- });
-
- test('should show error modal when credential is not successfully deleted from api', async () => {
- CredentialsAPI.destroy.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- wrapper
- .find('.pf-c-table__check')
- .at(1)
- .find('input')
- .invoke('onChange')();
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('ToolbarDeleteButton').invoke('onDelete')();
- });
- await waitForElement(
- wrapper,
- 'Modal[aria-label="Deletion Error"]',
- (el) => el.props().isOpen === true && el.props().title === 'Error!'
- );
- await act(async () => {
- wrapper.find('ModalBoxCloseButton').invoke('onClose')();
- });
- await waitForElement(wrapper, 'Modal', (el) => el.props().isOpen === false);
- });
-});
diff --git a/awx/ui/src/screens/Credential/CredentialList/CredentialListItem.js b/awx/ui/src/screens/Credential/CredentialList/CredentialListItem.js
deleted file mode 100644
index 83470149aed7..000000000000
--- a/awx/ui/src/screens/Credential/CredentialList/CredentialListItem.js
+++ /dev/null
@@ -1,103 +0,0 @@
-import React, { useState, useCallback } from 'react';
-import { string, bool, func } from 'prop-types';
-
-import { t } from '@lingui/macro';
-import { Link } from 'react-router-dom';
-import { Button } from '@patternfly/react-core';
-import { Tr, Td } from '@patternfly/react-table';
-import { PencilAltIcon } from '@patternfly/react-icons';
-import { ActionsTd, ActionItem, TdBreakWord } from 'components/PaginatedTable';
-import { timeOfDay } from 'util/dates';
-
-import { Credential } from 'types';
-import { CredentialsAPI } from 'api';
-import CopyButton from 'components/CopyButton';
-
-function CredentialListItem({
- credential,
- detailUrl,
- isSelected,
- onSelect,
- onCopy,
- fetchCredentials,
- rowIndex,
-}) {
- const [isDisabled, setIsDisabled] = useState(false);
-
- const labelId = `check-action-${credential.id}`;
- const canEdit = credential.summary_fields.user_capabilities.edit;
-
- const copyCredential = useCallback(async () => {
- const response = await CredentialsAPI.copy(credential.id, {
- name: `${credential.name} @ ${timeOfDay()}`,
- });
- if (response.status === 201) {
- onCopy(response.data.id);
- }
- await fetchCredentials();
- }, [credential.id, credential.name, fetchCredentials, onCopy]);
-
- const handleCopyStart = useCallback(() => {
- setIsDisabled(true);
- }, []);
-
- const handleCopyFinish = useCallback(() => {
- setIsDisabled(false);
- }, []);
-
- return (
-
- |
-
-
- {credential.name}
-
-
-
- {credential.summary_fields.credential_type.name}
- |
-
-
-
-
-
-
-
-
-
- );
-}
-
-CredentialListItem.propTypes = {
- detailUrl: string.isRequired,
- credential: Credential.isRequired,
- isSelected: bool.isRequired,
- onSelect: func.isRequired,
-};
-
-export default CredentialListItem;
diff --git a/awx/ui/src/screens/Credential/CredentialList/CredentialListItem.test.js b/awx/ui/src/screens/Credential/CredentialList/CredentialListItem.test.js
deleted file mode 100644
index dce6692004c7..000000000000
--- a/awx/ui/src/screens/Credential/CredentialList/CredentialListItem.test.js
+++ /dev/null
@@ -1,105 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { CredentialsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import { CredentialListItem } from '.';
-import { mockCredentials } from '../shared';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
-
- test('edit button shown to users with edit capabilities', () => {
- wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
- });
-
- test('edit button hidden from users without edit capabilities', () => {
- wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
- });
- test('should call api to copy template', async () => {
- CredentialsAPI.copy.mockResolvedValue();
-
- wrapper = mountWithContexts(
-
- );
-
- await act(async () =>
- wrapper.find('Button[aria-label="Copy"]').prop('onClick')()
- );
- expect(CredentialsAPI.copy).toHaveBeenCalled();
- jest.clearAllMocks();
- });
-
- test('should render proper alert modal on copy error', async () => {
- CredentialsAPI.copy.mockRejectedValue(new Error());
-
- wrapper = mountWithContexts(
-
-
- {}}
- credential={mockCredentials.results[0]}
- />
-
-
- );
- await act(async () =>
- wrapper.find('Button[aria-label="Copy"]').prop('onClick')()
- );
- wrapper.update();
- expect(wrapper.find('Modal').prop('isOpen')).toBe(true);
- jest.clearAllMocks();
- });
-
- test('should not render copy button', async () => {
- wrapper = mountWithContexts(
-
-
- {}}
- credential={mockCredentials.results[1]}
- />
-
-
- );
- expect(wrapper.find('CopyButton').length).toBe(0);
- });
-});
diff --git a/awx/ui/src/screens/Credential/CredentialList/index.js b/awx/ui/src/screens/Credential/CredentialList/index.js
deleted file mode 100644
index 0e8ca914a37a..000000000000
--- a/awx/ui/src/screens/Credential/CredentialList/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as CredentialList } from './CredentialList';
-export { default as CredentialListItem } from './CredentialListItem';
diff --git a/awx/ui/src/screens/Credential/Credentials.js b/awx/ui/src/screens/Credential/Credentials.js
deleted file mode 100644
index f7f38e817912..000000000000
--- a/awx/ui/src/screens/Credential/Credentials.js
+++ /dev/null
@@ -1,57 +0,0 @@
-import React, { useState, useCallback } from 'react';
-import { Route, Switch } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { Config } from 'contexts/Config';
-import ScreenHeader from 'components/ScreenHeader';
-import PersistentFilters from 'components/PersistentFilters';
-import Credential from './Credential';
-import CredentialAdd from './CredentialAdd';
-import { CredentialList } from './CredentialList';
-
-function Credentials() {
- const [breadcrumbConfig, setBreadcrumbConfig] = useState({
- '/credentials': t`Credentials`,
- '/credentials/add': t`Create New Credential`,
- });
-
- const buildBreadcrumbConfig = useCallback((credential) => {
- if (!credential) {
- return;
- }
-
- setBreadcrumbConfig({
- '/credentials': t`Credentials`,
- '/credentials/add': t`Create New Credential`,
- [`/credentials/${credential.id}`]: `${credential.name}`,
- [`/credentials/${credential.id}/edit`]: t`Edit Details`,
- [`/credentials/${credential.id}/details`]: t`Details`,
- [`/credentials/${credential.id}/access`]: t`Access`,
- [`/credentials/${credential.id}/job_templates`]: t`Job Templates`,
- });
- }, []);
-
- return (
- <>
-
-
-
- {({ me }) => }
-
-
-
-
-
-
-
-
-
-
- >
- );
-}
-
-export default Credentials;
diff --git a/awx/ui/src/screens/Credential/Credentials.test.js b/awx/ui/src/screens/Credential/Credentials.test.js
deleted file mode 100644
index f31143106e69..000000000000
--- a/awx/ui/src/screens/Credential/Credentials.test.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import Credentials from './Credentials';
-
-describe('', () => {
- test('should set breadcrumb config', () => {
- const wrapper = shallow();
-
- const header = wrapper.find('ScreenHeader');
- expect(header.prop('streamType')).toEqual('credential');
- expect(header.prop('breadcrumbConfig')).toEqual({
- '/credentials': 'Credentials',
- '/credentials/add': 'Create New Credential',
- });
- });
-});
diff --git a/awx/ui/src/screens/Credential/index.js b/awx/ui/src/screens/Credential/index.js
deleted file mode 100644
index 618d2eaf9cdd..000000000000
--- a/awx/ui/src/screens/Credential/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './Credentials';
diff --git a/awx/ui/src/screens/Credential/shared/CredentialForm.js b/awx/ui/src/screens/Credential/shared/CredentialForm.js
deleted file mode 100644
index 9768b2a00abd..000000000000
--- a/awx/ui/src/screens/Credential/shared/CredentialForm.js
+++ /dev/null
@@ -1,381 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { useLocation } from 'react-router-dom';
-import { shape } from 'prop-types';
-import { Formik, useField, useFormikContext } from 'formik';
-
-import { t } from '@lingui/macro';
-import {
- ActionGroup,
- Button,
- Form,
- FormGroup,
- Select as PFSelect,
- SelectOption as PFSelectOption,
- SelectVariant,
- Tooltip,
-} from '@patternfly/react-core';
-import styled from 'styled-components';
-import FormField, { FormSubmitError } from 'components/FormField';
-import { FormColumnLayout, FormFullWidthLayout } from 'components/FormLayout';
-import { required } from 'util/validators';
-import OrganizationLookup from 'components/Lookup/OrganizationLookup';
-import TypeInputsSubForm from './TypeInputsSubForm';
-import ExternalTestModal from './ExternalTestModal';
-
-const Select = styled(PFSelect)`
- ul {
- max-width: 495px;
- }
- ${(props) => (props.isDisabled ? `cursor: not-allowed` : null)}
-`;
-
-const SelectOption = styled(PFSelectOption)`
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
-`;
-
-function CredentialFormFields({ initialTypeId, credentialTypes }) {
- const { pathname } = useLocation();
- const { setFieldValue, initialValues, setFieldTouched } = useFormikContext();
- const [isSelectOpen, setIsSelectOpen] = useState(false);
- const [credTypeField, credTypeMeta, credTypeHelpers] = useField({
- name: 'credential_type',
- validate: required(t`Select a value for this field`),
- });
-
- const [credentialTypeId, setCredentialTypeId] = useState(initialTypeId);
-
- const isGalaxyCredential =
- !!credentialTypeId && credentialTypes[credentialTypeId]?.kind === 'galaxy';
-
- const [orgField, orgMeta, orgHelpers] = useField('organization');
-
- const credentialTypeOptions = Object.keys(credentialTypes)
- .map((key) => ({
- value: credentialTypes[key].id,
- key: credentialTypes[key].id,
- label: credentialTypes[key].name,
- }))
- .sort((a, b) => (a.label.toLowerCase() > b.label.toLowerCase() ? 1 : -1));
-
- const resetSubFormFields = useCallback(
- (newCredentialTypeId) => {
- const fields = credentialTypes[newCredentialTypeId].inputs.fields || [];
- fields.forEach(
- ({ ask_at_runtime, type, id, choices, default: defaultValue }) => {
- if (parseInt(newCredentialTypeId, 10) === initialTypeId) {
- setFieldValue(`inputs.${id}`, initialValues.inputs[id]);
- if (ask_at_runtime) {
- setFieldValue(
- `passwordPrompts.${id}`,
- initialValues.passwordPrompts[id]
- );
- }
- } else {
- switch (type) {
- case 'string':
- setFieldValue(`inputs.${id}`, defaultValue || '');
- break;
- case 'boolean':
- setFieldValue(`inputs.${id}`, defaultValue || false);
- break;
- default:
- break;
- }
-
- if (choices) {
- setFieldValue(`inputs.${id}`, defaultValue);
- }
-
- if (ask_at_runtime) {
- setFieldValue(`passwordPrompts.${id}`, false);
- }
- }
- setFieldTouched(`inputs.${id}`, false);
- }
- );
- },
- [
- credentialTypes,
- initialTypeId,
- initialValues.inputs,
- initialValues.passwordPrompts,
- setFieldTouched,
- setFieldValue,
- ]
- );
-
- useEffect(() => {
- if (credentialTypeId) {
- resetSubFormFields(credentialTypeId);
- }
- }, [resetSubFormFields, credentialTypeId]);
-
- const handleOrganizationUpdate = useCallback(
- (value) => {
- setFieldValue('organization', value);
- setFieldTouched('organization', true, false);
- },
- [setFieldValue, setFieldTouched]
- );
-
- const isCredentialTypeDisabled = pathname.includes('edit');
- const credentialTypeSelect = (
-
- );
-
- return (
- <>
-
-
- orgHelpers.setTouched()}
- onChange={handleOrganizationUpdate}
- value={orgField.value}
- touched={orgMeta.touched}
- error={orgMeta.error}
- required={isGalaxyCredential}
- isDisabled={initialValues.isOrgLookupDisabled}
- validate={
- isGalaxyCredential
- ? required(t`Galaxy credentials must be owned by an Organization.`)
- : undefined
- }
- />
-
- {isCredentialTypeDisabled ? (
-
- {credentialTypeSelect}
-
- ) : (
- credentialTypeSelect
- )}
-
- {credentialTypeId !== undefined &&
- credentialTypeId !== '' &&
- credentialTypes[credentialTypeId]?.inputs?.fields && (
-
- )}
- >
- );
-}
-
-function CredentialForm({
- credential = {},
- credentialTypes,
- inputSources,
- onSubmit,
- onCancel,
- submitError,
- isOrgLookupDisabled,
- ...rest
-}) {
- const initialTypeId = credential?.credential_type;
-
- const [showExternalTestModal, setShowExternalTestModal] = useState(false);
- const initialValues = {
- name: credential.name || '',
- description: credential.description || '',
- organization: credential?.summary_fields?.organization || null,
- credential_type: credentialTypes[initialTypeId]?.id || '',
- inputs: { ...credential?.inputs },
- passwordPrompts: {},
- isOrgLookupDisabled: isOrgLookupDisabled || false,
- };
-
- Object.values(credentialTypes).forEach((credentialType) => {
- if (!credential.id || credential.credential_type === credentialType.id) {
- const fields = credentialType.inputs.fields || [];
- fields.forEach(
- ({ ask_at_runtime, type, id, choices, default: defaultValue }) => {
- if (credential?.inputs && id in credential.inputs) {
- if (ask_at_runtime) {
- initialValues.passwordPrompts[id] =
- credential.inputs[id] === 'ASK' || false;
- initialValues.inputs[id] =
- credential.inputs[id] === 'ASK' ? '' : credential.inputs[id];
- } else {
- initialValues.inputs[id] = credential.inputs[id];
- }
- } else {
- switch (type) {
- case 'string':
- initialValues.inputs[id] = defaultValue || '';
- break;
- case 'boolean':
- initialValues.inputs[id] = defaultValue || false;
- break;
- default:
- break;
- }
-
- if (choices) {
- initialValues.inputs[id] = defaultValue;
- }
-
- if (ask_at_runtime) {
- initialValues.passwordPrompts[id] = false;
- }
- }
- }
- );
- }
- });
-
- Object.values(inputSources).forEach((inputSource) => {
- initialValues.inputs[inputSource.input_field_name] = {
- credential: inputSource.summary_fields.source_credential,
- inputs: inputSource.metadata,
- };
- });
-
- return (
- {
- const { credential_type, ...actualValues } = values;
- // credential_type could be the raw id or the displayed name value.
- // If it's the name, replace it with the id before making the request.
- actualValues.credential_type =
- Object.keys(credentialTypes).find(
- (key) => credentialTypes[key].name === credential_type
- ) || credential_type;
- onSubmit(actualValues);
- }}
- >
- {(formik) => (
- <>
-
- {showExternalTestModal && (
- setShowExternalTestModal(false)}
- />
- )}
- >
- )}
-
- );
-}
-
-CredentialForm.propTypes = {
- credentialTypes: shape({}).isRequired,
- credential: shape({}),
- inputSources: shape({}),
- submitError: shape({}),
-};
-
-CredentialForm.defaultProps = {
- credential: {},
- inputSources: {},
- submitError: null,
-};
-
-export default CredentialForm;
diff --git a/awx/ui/src/screens/Credential/shared/CredentialForm.test.js b/awx/ui/src/screens/Credential/shared/CredentialForm.test.js
deleted file mode 100644
index ddacec5d30c7..000000000000
--- a/awx/ui/src/screens/Credential/shared/CredentialForm.test.js
+++ /dev/null
@@ -1,431 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import machineCredential from './data.machineCredential.json';
-import gceCredential from './data.gceCredential.json';
-import scmCredential from './data.scmCredential.json';
-import galaxyCredential from './data.galaxyCredential.json';
-import towerCredential from './data.towerCredential.json';
-import credentialTypesArr from './data.credentialTypes.json';
-import CredentialForm from './CredentialForm';
-
-jest.mock('../../../api');
-
-const credentialTypes = credentialTypesArr.reduce(
- (credentialTypesMap, credentialType) => {
- credentialTypesMap[credentialType.id] = credentialType;
- return credentialTypesMap;
- },
- {}
-);
-
-describe('', () => {
- let wrapper;
- const onCancel = jest.fn();
- const onSubmit = jest.fn();
-
- const addFieldExpects = () => {
- expect(wrapper.find('FormGroup').length).toBe(4);
- expect(wrapper.find('FormGroup[label="Name"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Description"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Organization"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Credential Type"]').length).toBe(1);
- };
-
- const machineFieldExpects = () => {
- expect(wrapper.find('FormGroup[label="Name"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Description"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Organization"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Credential Type"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Username"]').length).toBe(1);
- expect(wrapper.find('input#credential-password').length).toBe(1);
- expect(wrapper.find('FormGroup[label="SSH Private Key"]').length).toBe(1);
- expect(
- wrapper.find('FormGroup[label="Signed SSH Certificate"]').length
- ).toBe(1);
- expect(wrapper.find('input#credential-ssh_key_unlock').length).toBe(1);
- expect(
- wrapper.find('FormGroup[label="Privilege Escalation Method"]').length
- ).toBe(1);
- expect(
- wrapper.find('FormGroup[label="Privilege Escalation Username"]').length
- ).toBe(1);
- expect(wrapper.find('input#credential-become_password').length).toBe(1);
- };
-
- const sourceFieldExpects = () => {
- expect(wrapper.find('FormGroup').length).toBe(8);
- expect(wrapper.find('FormGroup[label="Name"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Description"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Organization"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Credential Type"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Username"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Password"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="SCM Private Key"]').length).toBe(1);
- expect(
- wrapper.find('FormGroup[label="Private Key Passphrase"]').length
- ).toBe(1);
- };
-
- const gceFieldExpects = () => {
- expect(wrapper.find('FormGroup').length).toBe(8);
- expect(wrapper.find('FormGroup[label="Name"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Description"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Organization"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Credential Type"]').length).toBe(1);
- expect(
- wrapper.find('FormGroup[label="Service account JSON file"]').length
- ).toBe(1);
- expect(
- wrapper.find('FormGroup[label="Service Account Email Address"]').length
- ).toBe(1);
- expect(wrapper.find('FormGroup[label="Project"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="RSA Private Key"]').length).toBe(1);
- };
-
- describe('Add', () => {
- beforeAll(async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- });
-
- test('should display form fields on add properly', async () => {
- addFieldExpects();
- });
-
- test('should hide Test button initially', () => {
- expect(wrapper.find('Button[children="Test"]').length).toBe(0);
- });
-
- test('should update form values', async () => {
- // name and description change
- await act(async () => {
- wrapper.find('input#credential-name').simulate('change', {
- target: { value: 'new Foo', name: 'name' },
- });
- wrapper.find('input#credential-description').simulate('change', {
- target: { value: 'new Bar', name: 'description' },
- });
- });
- wrapper.update();
- expect(wrapper.find('input#credential-name').prop('value')).toEqual(
- 'new Foo'
- );
- expect(
- wrapper.find('input#credential-description').prop('value')
- ).toEqual('new Bar');
- // organization change
- await act(async () => {
- wrapper.find('OrganizationLookup').invoke('onBlur')();
- wrapper.find('OrganizationLookup').invoke('onChange')({
- id: 3,
- name: 'organization',
- });
- });
- wrapper.update();
- expect(wrapper.find('OrganizationLookup').prop('value')).toEqual({
- id: 3,
- name: 'organization',
- });
- });
-
- test('should display cred type subform when scm type select has a value', async () => {
- await act(async () => {
- await wrapper
- .find('Select[aria-label="Credential Type"]')
- .invoke('onToggle')();
- });
- wrapper.update();
- await act(async () => {
- await wrapper
- .find('Select[aria-label="Credential Type"]')
- .invoke('onSelect')(null, 1);
- });
- wrapper.update();
-
- machineFieldExpects();
- await act(async () => {
- await wrapper
- .find('Select[aria-label="Credential Type"]')
- .invoke('onToggle')();
- });
- wrapper.update();
- await act(async () => {
- await wrapper
- .find('Select[aria-label="Credential Type"]')
- .invoke('onSelect')(null, 2);
- });
- wrapper.update();
- sourceFieldExpects();
- });
-
- test('should update expected fields when gce service account json file uploaded', async () => {
- await act(async () => {
- await wrapper
- .find('Select[aria-label="Credential Type"]')
- .invoke('onToggle')();
- });
- wrapper.update();
- await act(async () => {
- await wrapper
- .find('Select[aria-label="Credential Type"]')
- .invoke('onSelect')(null, 10);
- });
- wrapper.update();
- gceFieldExpects();
- expect(wrapper.find('input#credential-username').prop('value')).toBe('');
- expect(wrapper.find('input#credential-project').prop('value')).toBe('');
- expect(
- wrapper.find('textarea#credential-ssh_key_data').prop('value')
- ).toBe('');
- await act(async () => {
- wrapper.find('FileUpload#credential-gce-file').invoke('onChange')({
- name: 'foo.json',
- text: () =>
- '{"client_email":"testemail@ansible.com","project_id":"test123","private_key":"-----BEGIN PRIVATE KEY-----\\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\\n-----END PRIVATE KEY-----\\n"}',
- });
- });
- wrapper.update();
- expect(wrapper.find('input#credential-username').prop('value')).toBe(
- 'testemail@ansible.com'
- );
- expect(wrapper.find('input#credential-project').prop('value')).toBe(
- 'test123'
- );
- expect(
- wrapper.find('textarea#credential-ssh_key_data').prop('value')
- ).toBe(
- '-----BEGIN PRIVATE KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n-----END PRIVATE KEY-----\n'
- );
- });
-
- test('should clear expected fields when file clear button clicked', async () => {
- await act(async () => {
- wrapper
- .find('FileUploadField#credential-gce-file')
- .invoke('onClearButtonClick')();
- });
- wrapper.update();
- expect(wrapper.find('input#credential-username').prop('value')).toBe('');
- expect(wrapper.find('input#credential-project').prop('value')).toBe('');
- expect(
- wrapper.find('textarea#credential-ssh_key_data').prop('value')
- ).toBe('');
- });
-
- test('should update field when RSA Private Key file uploaded', async () => {
- await act(async () => {
- wrapper.find('FileUpload#credential-ssh_key_data').invoke('onChange')(
- '-----BEGIN PRIVATE KEY-----\\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\\n-----END PRIVATE KEY-----\\n',
- 'foo.key'
- );
- });
- wrapper.update();
- expect(
- wrapper.find('textarea#credential-ssh_key_data').prop('value')
- ).toBe(
- '-----BEGIN PRIVATE KEY-----\\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\\n-----END PRIVATE KEY-----\\n'
- );
- });
-
- test('should show error when error thrown parsing JSON', async () => {
- await act(async () => {
- await wrapper
- .find('Select[aria-label="Credential Type"]')
- .invoke('onToggle')();
- });
- wrapper.update();
- await act(async () => {
- await wrapper
- .find('Select[aria-label="Credential Type"]')
- .invoke('onSelect')(null, 10);
- });
- wrapper.update();
- expect(wrapper.find('#credential-gce-file-helper').text()).toBe(
- 'Select a JSON formatted service account key to autopopulate the following fields.'
- );
- await act(async () => {
- wrapper.find('FileUpload#credential-gce-file').invoke('onChange')({
- name: 'foo.json',
- text: () => '{not good json}',
- });
- });
- wrapper.update();
- expect(
- wrapper
- .find('FormGroup[fieldId="credential-gce-file"]')
- .prop('validated')
- ).toBe('error');
-
- expect(
- wrapper
- .find('FormGroup[fieldId="credential-gce-file"]')
- .prop('helperTextInvalid')
- ).toBe(
- 'There was an error parsing the file. Please check the file formatting and try again.'
- );
- });
- test('should show Test button when external credential type is selected', async () => {
- await act(async () => {
- await wrapper
- .find('Select[aria-label="Credential Type"]')
- .invoke('onToggle')();
- });
- wrapper.update();
- await act(async () => {
- await wrapper
- .find('Select[aria-label="Credential Type"]')
- .invoke('onSelect')(null, 21);
- });
- wrapper.update();
- expect(wrapper.find('Button[children="Test"]').length).toBe(1);
- expect(wrapper.find('Button[children="Test"]').props().isDisabled).toBe(
- true
- );
- });
- test('should call handleCancel when Cancel button is clicked', async () => {
- expect(onCancel).not.toHaveBeenCalled();
- wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
- expect(onCancel).toBeCalled();
- });
- });
-
- describe('Edit', () => {
- test('Initially renders successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
-
- expect(wrapper.length).toBe(1);
- });
-
- test('should display form fields for machine credential properly', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
-
- machineFieldExpects();
- });
-
- test('organization lookup should be disabled', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
-
- expect(
- wrapper
- .find('CredentialFormFields')
- .find('OrganizationLookup')
- .prop('isDisabled')
- ).toBe(true);
- });
-
- test('should display form fields for source control credential properly', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
-
- sourceFieldExpects();
- });
-
- test('should display form fields for gce credential properly', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
-
- gceFieldExpects();
- });
-
- test('should display form fields for galaxy/automation hub credentials', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('FormGroup[label="Name"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Description"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Organization"]').length).toBe(1);
- expect(
- wrapper.find('FormGroup[label="Organization"]').prop('isRequired')
- ).toBe(true);
- expect(wrapper.find('FormGroup[label="Credential Type"]').length).toBe(1);
- });
-
- test('should display form fields for tower credentials', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('FormGroup[label="Name"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Description"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Organization"]').length).toBe(1);
- expect(
- wrapper.find('FormGroup[label="Organization"]').prop('isRequired')
- ).toBe(false);
- expect(wrapper.find('FormGroup[label="Credential Type"]').length).toBe(1);
- expect(
- wrapper.find('FormGroup[label="Ansible Controller Hostname"]').length
- ).toBe(1);
- expect(wrapper.find('FormGroup[label="Username"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Password"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="OAuth Token"]').length).toBe(1);
- expect(
- wrapper.find('Checkbox#credential-verify_ssl').prop('isChecked')
- ).toBe(false);
- });
- });
-});
diff --git a/awx/ui/src/screens/Credential/shared/CredentialFormFields/BecomeMethodField.js b/awx/ui/src/screens/Credential/shared/CredentialFormFields/BecomeMethodField.js
deleted file mode 100644
index 974f52a900c9..000000000000
--- a/awx/ui/src/screens/Credential/shared/CredentialFormFields/BecomeMethodField.js
+++ /dev/null
@@ -1,86 +0,0 @@
-import React, { useState } from 'react';
-import { useField } from 'formik';
-import { bool, shape, string } from 'prop-types';
-import { t } from '@lingui/macro';
-import {
- FormGroup,
- Select,
- SelectOption,
- SelectVariant,
-} from '@patternfly/react-core';
-import Popover from 'components/Popover';
-
-function BecomeMethodField({ fieldOptions, isRequired }) {
- const [isOpen, setIsOpen] = useState(false);
- const [options, setOptions] = useState(
- [
- 'sudo',
- 'su',
- 'pbrun',
- 'pfexec',
- 'dzdo',
- 'pmrun',
- 'runas',
- 'enable',
- 'doas',
- 'ksu',
- 'machinectl',
- 'sesu',
- ].map((val) => ({ value: val }))
- );
- const [becomeMethodField, meta, helpers] = useField({
- name: `inputs.${fieldOptions.id}`,
- });
- return (
-
- }
- isRequired={isRequired}
- validated={!(meta.touched && meta.error) ? 'default' : 'error'}
- >
-
-
- );
-}
-BecomeMethodField.propTypes = {
- fieldOptions: shape({
- id: string.isRequired,
- label: string.isRequired,
- }).isRequired,
- isRequired: bool,
-};
-BecomeMethodField.defaultProps = {
- isRequired: false,
-};
-
-export default BecomeMethodField;
diff --git a/awx/ui/src/screens/Credential/shared/CredentialFormFields/BecomeMethodField.test.js b/awx/ui/src/screens/Credential/shared/CredentialFormFields/BecomeMethodField.test.js
deleted file mode 100644
index 429f63391d85..000000000000
--- a/awx/ui/src/screens/Credential/shared/CredentialFormFields/BecomeMethodField.test.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
-import BecomeMethodField from './BecomeMethodField';
-
-const fieldOptions = {
- help_text:
- "Specify a method for 'become' operations. This is equivalent to specifying the --become-method Ansible parameter.",
- id: 'become_method',
- label: 'Privilege Escalation Method',
- type: 'string',
-};
-describe('', () => {
- let wrapper;
-
- test('should mount properly', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- expect(wrapper.find('BecomeMethodField').length).toBe(1);
- });
-
- test('should open privilege escalation properly', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- act(() => wrapper.find('Select').prop('onToggle')(true));
- wrapper.update();
- expect(wrapper.find('SelectOption').length).toBe(12);
- });
-});
diff --git a/awx/ui/src/screens/Credential/shared/CredentialFormFields/CredentialField.js b/awx/ui/src/screens/Credential/shared/CredentialFormFields/CredentialField.js
deleted file mode 100644
index 0d4b1ed84077..000000000000
--- a/awx/ui/src/screens/Credential/shared/CredentialFormFields/CredentialField.js
+++ /dev/null
@@ -1,283 +0,0 @@
-/* eslint-disable react/jsx-no-useless-fragment */
-import React, { useState } from 'react';
-import { useLocation } from 'react-router-dom';
-import { useField, useFormikContext } from 'formik';
-import { shape, string } from 'prop-types';
-import styled from 'styled-components';
-
-import { t } from '@lingui/macro';
-import {
- Button,
- ButtonVariant,
- FileUpload as PFFileUpload,
- FormGroup,
- InputGroup,
- TextInput,
- Tooltip,
-} from '@patternfly/react-core';
-import { PficonHistoryIcon } from '@patternfly/react-icons';
-import { PasswordInput } from 'components/FormField';
-import AnsibleSelect from 'components/AnsibleSelect';
-import Popover from 'components/Popover';
-import { CredentialType } from 'types';
-import { required } from 'util/validators';
-import { CredentialPluginField } from '../CredentialPlugins';
-import BecomeMethodField from './BecomeMethodField';
-
-const FileUpload = styled(PFFileUpload)`
- flex-grow: 1;
-`;
-
-function CredentialInput({
- fieldOptions,
- isFieldGroupValid,
- credentialKind,
- isVaultIdDisabled,
- ...rest
-}) {
- const [fileName, setFileName] = useState('');
- const [fileIsUploading, setFileIsUploading] = useState(false);
- const [subFormField, meta, helpers] = useField(`inputs.${fieldOptions.id}`);
- const [passwordPromptsField] = useField(`passwordPrompts.${fieldOptions.id}`);
- const isValid = !(meta.touched && meta.error);
-
- const RevertReplaceButton = (
- <>
- {meta.initialValue &&
- meta.initialValue !== '' &&
- !meta.initialValue.credential &&
- !passwordPromptsField.value && (
-
-
-
- )}
- >
- );
-
- if (fieldOptions.multiline) {
- const handleFileChange = (value, filename) => {
- helpers.setValue(value);
- setFileName(filename);
- };
-
- if (fieldOptions.secret) {
- return (
-
- {RevertReplaceButton}
- setFileIsUploading(true)}
- onReadFinished={() => setFileIsUploading(false)}
- isLoading={fileIsUploading}
- allowEditingUploadedText
- validated={isValid ? 'default' : 'error'}
- />
-
- );
- }
-
- return (
- setFileIsUploading(true)}
- onReadFinished={() => setFileIsUploading(false)}
- isLoading={fileIsUploading}
- allowEditingUploadedText
- validated={isValid ? 'default' : 'error'}
- isDisabled={false}
- />
- );
- }
-
- if (fieldOptions.secret) {
- const passwordInput = () => (
- <>
- {RevertReplaceButton}
-
- >
- );
- return credentialKind === 'external' ? (
- {passwordInput()}
- ) : (
- passwordInput()
- );
- }
- return (
- {
- subFormField.onChange(event);
- }}
- isDisabled={isVaultIdDisabled}
- validated={isValid ? 'default' : 'error'}
- />
- );
-}
-
-CredentialInput.propTypes = {
- fieldOptions: shape({
- id: string.isRequired,
- label: string.isRequired,
- }).isRequired,
- credentialKind: string,
-};
-
-CredentialInput.defaultProps = {
- credentialKind: '',
-};
-
-function CredentialField({ credentialType, fieldOptions }) {
- const { values: formikValues } = useFormikContext();
- const location = useLocation();
- const requiredFields = credentialType?.inputs?.required || [];
- const isRequired = requiredFields.includes(fieldOptions.id);
- const validateField = () => {
- if (isRequired && !formikValues?.passwordPrompts[fieldOptions.id]) {
- const validationMsg = fieldOptions.ask_at_runtime
- ? t`Provide a value for this field or select the Prompt on launch option.`
- : null;
- return required(validationMsg);
- }
- return null;
- };
- const [subFormField, meta, helpers] = useField({
- name: `inputs.${fieldOptions.id}`,
- validate: validateField(),
- });
- const isValid =
- !(meta.touched && meta.error) ||
- formikValues.passwordPrompts[fieldOptions.id];
-
- if (fieldOptions.choices) {
- const selectOptions = fieldOptions.choices.map((choice) => ({
- value: choice,
- key: choice,
- label: choice,
- }));
- return (
-
- {
- helpers.setValue(value);
- }}
- />
-
- );
- }
- if (credentialType.kind === 'external') {
- return (
-
- }
- isRequired={isRequired}
- validated={isValid ? 'default' : 'error'}
- >
-
-
- );
- }
- if (credentialType.kind === 'ssh' && fieldOptions.id === 'become_method') {
- return (
-
- );
- }
-
- let disabled = false;
- if (
- credentialType.kind === 'vault' &&
- location.pathname.endsWith('edit') &&
- fieldOptions.id === 'vault_id'
- ) {
- disabled = true;
- }
- return (
-
-
-
- );
-}
-
-CredentialField.propTypes = {
- credentialType: CredentialType.isRequired,
- fieldOptions: shape({
- id: string.isRequired,
- label: string.isRequired,
- }).isRequired,
-};
-
-CredentialField.defaultProps = {};
-
-export default CredentialField;
diff --git a/awx/ui/src/screens/Credential/shared/CredentialFormFields/CredentialField.test.js b/awx/ui/src/screens/Credential/shared/CredentialFormFields/CredentialField.test.js
deleted file mode 100644
index 3902fb1d588d..000000000000
--- a/awx/ui/src/screens/Credential/shared/CredentialFormFields/CredentialField.test.js
+++ /dev/null
@@ -1,151 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
-import credentialTypes from '../data.credentialTypes.json';
-import CredentialField from './CredentialField';
-
-const credentialType = credentialTypes.find((type) => type.id === 5);
-const fieldOptions = {
- id: 'password',
- label: 'Secret Key',
- type: 'string',
- secret: true,
-};
-
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useLocation: () => ({
- pathname: '/credentials/3/edit',
- }),
-}));
-describe('', () => {
- let wrapper;
- test('renders correctly without initial value', () => {
- wrapper = mountWithContexts(
-
- {() => (
-
- )}
-
- );
- expect(wrapper.find('CredentialField').length).toBe(1);
- expect(wrapper.find('PasswordInput').length).toBe(1);
- expect(wrapper.find('TextInput').props().isDisabled).toBe(false);
- expect(wrapper.find('KeyIcon').length).toBe(1);
- expect(wrapper.find('PficonHistoryIcon').length).toBe(0);
- });
-
- test('renders correctly with initial value', () => {
- wrapper = mountWithContexts(
-
- {() => (
-
- )}
-
- );
- expect(wrapper.find('CredentialField').length).toBe(1);
- expect(wrapper.find('PasswordInput').length).toBe(1);
- expect(wrapper.find('TextInput').props().isDisabled).toBe(true);
- expect(wrapper.find('TextInput').props().value).toBe('');
- expect(wrapper.find('TextInput').props().placeholder).toBe('ENCRYPTED');
- expect(wrapper.find('KeyIcon').length).toBe(1);
- expect(wrapper.find('PficonHistoryIcon').length).toBe(1);
- });
-
- test('replace/revert button behaves as expected', async () => {
- wrapper = mountWithContexts(
-
- {() => (
-
- )}
-
- );
- expect(
- wrapper.find('Tooltip#credential-password-replace-tooltip').props()
- .content
- ).toBe('Replace');
- expect(wrapper.find('TextInput').props().isDisabled).toBe(true);
- wrapper.find('PficonHistoryIcon').simulate('click');
- await act(async () => {
- wrapper.update();
- });
- expect(
- wrapper.find('Tooltip#credential-password-replace-tooltip').props()
- .content
- ).toBe('Revert');
- expect(wrapper.find('TextInput').props().isDisabled).toBe(false);
- expect(wrapper.find('TextInput').props().value).toBe('');
- expect(wrapper.find('TextInput').props().placeholder).toBe(undefined);
- wrapper.find('PficonHistoryIcon').simulate('click');
- await act(async () => {
- wrapper.update();
- });
- expect(
- wrapper.find('Tooltip#credential-password-replace-tooltip').props()
- .content
- ).toBe('Replace');
- expect(wrapper.find('TextInput').props().isDisabled).toBe(true);
- expect(wrapper.find('TextInput').props().value).toBe('');
- expect(wrapper.find('TextInput').props().placeholder).toBe('ENCRYPTED');
- });
- test('Should check to see if the ability to edit vault ID is disabled after creation.', () => {
- const vaultCredential = credentialTypes.find((type) => type.id === 3);
- const vaultFieldOptions = {
- id: 'vault_id',
- label: 'Vault Identifier',
- type: 'string',
- secret: true,
- };
- wrapper = mountWithContexts(
-
- {() => (
-
- )}
-
- );
- expect(wrapper.find('CredentialInput').props().isDisabled).toBe(true);
- expect(wrapper.find('KeyIcon').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Credential/shared/CredentialFormFields/GceFileUploadField.js b/awx/ui/src/screens/Credential/shared/CredentialFormFields/GceFileUploadField.js
deleted file mode 100644
index e8d955c00b85..000000000000
--- a/awx/ui/src/screens/Credential/shared/CredentialFormFields/GceFileUploadField.js
+++ /dev/null
@@ -1,81 +0,0 @@
-import React, { useState } from 'react';
-
-import { t } from '@lingui/macro';
-import { useField } from 'formik';
-import { FileUpload, FormGroup } from '@patternfly/react-core';
-
-function GceFileUploadField() {
- const [fileError, setFileError] = useState(null);
- const [filename, setFilename] = useState('');
- const [file, setFile] = useState('');
- const [, , inputsUsernameHelpers] = useField({
- name: 'inputs.username',
- });
- const [, , inputsProjectHelpers] = useField({
- name: 'inputs.project',
- });
- const [, , inputsSSHKeyDataHelpers] = useField({
- name: 'inputs.ssh_key_data',
- });
- return (
-
- {
- if (value) {
- try {
- setFile(value);
- setFilename(value.name);
- const fileText = await value.text();
- const fileJSON = JSON.parse(fileText);
- if (
- !fileJSON.client_email &&
- !fileJSON.project_id &&
- !fileJSON.private_key
- ) {
- setFileError(
- t`Expected at least one of client_email, project_id or private_key to be present in the file.`
- );
- } else {
- inputsUsernameHelpers.setValue(fileJSON.client_email || '');
- inputsProjectHelpers.setValue(fileJSON.project_id || '');
- inputsSSHKeyDataHelpers.setValue(fileJSON.private_key || '');
- setFileError(null);
- }
- } catch {
- setFileError(
- t`There was an error parsing the file. Please check the file formatting and try again.`
- );
- }
- } else {
- setFile('');
- setFilename('');
- inputsUsernameHelpers.setValue('');
- inputsProjectHelpers.setValue('');
- inputsSSHKeyDataHelpers.setValue('');
- setFileError(null);
- }
- }}
- dropzoneProps={{
- accept: '.json',
- onDropRejected: () => {
- setFileError(
- t`File upload rejected. Please select a single .json file.`
- );
- },
- }}
- />
-
- );
-}
-
-export default GceFileUploadField;
diff --git a/awx/ui/src/screens/Credential/shared/CredentialFormFields/index.js b/awx/ui/src/screens/Credential/shared/CredentialFormFields/index.js
deleted file mode 100644
index d5d1e2f12a8b..000000000000
--- a/awx/ui/src/screens/Credential/shared/CredentialFormFields/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export { default as BecomeMethodField } from './BecomeMethodField';
-export { default as CredentialField } from './CredentialField';
-export { default as GceFileUploadField } from './GceFileUploadField';
diff --git a/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginField.js b/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginField.js
deleted file mode 100644
index 330cf58ddfe0..000000000000
--- a/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginField.js
+++ /dev/null
@@ -1,176 +0,0 @@
-/* eslint-disable react/jsx-no-useless-fragment */
-import React, { useEffect, useState } from 'react';
-import PropTypes from 'prop-types';
-import { useLocation, useHistory } from 'react-router-dom';
-import { t } from '@lingui/macro';
-import { useField } from 'formik';
-import {
- Button,
- ButtonVariant,
- FormGroup,
- InputGroup,
- Tooltip,
-} from '@patternfly/react-core';
-import { KeyIcon } from '@patternfly/react-icons';
-import styles from '@patternfly/react-styles/css/components/Form/form';
-import { css } from '@patternfly/react-styles';
-import FieldWithPrompt from 'components/FieldWithPrompt';
-import Popover from 'components/Popover';
-import { CredentialPluginPrompt } from './CredentialPluginPrompt';
-import CredentialPluginSelected from './CredentialPluginSelected';
-
-function CredentialPluginInput(props) {
- const { children, isDisabled, isRequired, validated, fieldOptions } = props;
- const [showPluginWizard, setShowPluginWizard] = useState(false);
- const [inputField, meta, helpers] = useField(`inputs.${fieldOptions.id}`);
- const [passwordPromptField] = useField(`passwordPrompts.${fieldOptions.id}`);
- const location = useLocation();
- const history = useHistory();
-
- const disableFieldAndButtons =
- !!passwordPromptField.value ||
- !!(
- meta.initialValue &&
- meta.initialValue !== '' &&
- meta.value === meta.initialValue
- );
-
- const handlePluginWizardClose = () => {
- setShowPluginWizard(false);
- history.push(location.pathname);
- };
-
- return (
- <>
- {inputField?.value?.credential ? (
- helpers.setValue('')}
- onEditPlugin={() => setShowPluginWizard(true)}
- fieldId={fieldOptions.id}
- />
- ) : (
-
- {React.cloneElement(children, {
- ...inputField,
- isRequired,
- validated: validated ? 'default' : 'error',
- isDisabled: disableFieldAndButtons,
- onChange: (_, event) => {
- inputField.onChange(event);
- },
- })}
-
-
-
-
- )}
- {showPluginWizard && (
- handlePluginWizardClose()}
- onSubmit={(val) => {
- val.touched = true;
- helpers.setValue(val);
- setShowPluginWizard(false);
- }}
- />
- )}
- >
- );
-}
-
-function CredentialPluginField(props) {
- const { fieldOptions, isRequired, validated } = props;
-
- const [, meta, helpers] = useField(`inputs.${fieldOptions.id}`);
- const [passwordPromptField] = useField(`passwordPrompts.${fieldOptions.id}`);
-
- let invalidHelperTextToDisplay;
-
- if (meta.error && meta.touched) {
- invalidHelperTextToDisplay = (
-
- {meta.error}
-
- );
- }
-
- if (fieldOptions.id === 'vault_password' && passwordPromptField.value) {
- invalidHelperTextToDisplay = null;
- }
-
- useEffect(() => {
- if (passwordPromptField.value) {
- helpers.setValue('');
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [passwordPromptField.value]);
-
- return (
- <>
- {fieldOptions.ask_at_runtime ? (
-
-
- {invalidHelperTextToDisplay}
-
- ) : (
-
- )
- }
- >
-
- {invalidHelperTextToDisplay}
-
- )}
- >
- );
-}
-
-CredentialPluginField.propTypes = {
- fieldOptions: PropTypes.shape({
- id: PropTypes.string.isRequired,
- label: PropTypes.string.isRequired,
- }).isRequired,
- isDisabled: PropTypes.bool,
- isRequired: PropTypes.bool,
-};
-
-CredentialPluginField.defaultProps = {
- isDisabled: false,
- isRequired: false,
-};
-
-export default CredentialPluginField;
diff --git a/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginField.test.js b/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginField.test.js
deleted file mode 100644
index 601613afac0d..000000000000
--- a/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginField.test.js
+++ /dev/null
@@ -1,95 +0,0 @@
-import React from 'react';
-import { Formik } from 'formik';
-import { TextInput } from '@patternfly/react-core';
-import { act } from 'react-dom/test-utils';
-import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
-import CredentialPluginField from './CredentialPluginField';
-
-const fieldOptions = {
- id: 'username',
- label: 'Username',
- type: 'string',
-};
-
-jest.mock('../../../../api');
-
-describe('', () => {
- let wrapper;
- describe('No plugin configured', () => {
- beforeAll(() => {
- wrapper = mountWithContexts(
-
- {() => (
-
-
-
- )}
-
- );
- });
-
- test('renders the expected content', () => {
- expect(wrapper.find('input').length).toBe(1);
- expect(wrapper.find('KeyIcon').length).toBe(1);
- expect(wrapper.find('CredentialPluginSelected').length).toBe(0);
- });
-
- test('clicking plugin button shows plugin prompt', async () => {
- expect(wrapper.find('CredentialPluginPrompt').length).toBe(0);
- await act(async () => {
- wrapper.find('KeyIcon').simulate('click');
- });
- wrapper.update();
- expect(wrapper.find('CredentialPluginPrompt').length).toBe(1);
- });
- });
-
- describe('Plugin already configured', () => {
- beforeAll(() => {
- wrapper = mountWithContexts(
-
- {() => (
-
-
-
- )}
-
- );
- });
-
- test('renders the expected content', () => {
- expect(wrapper.find('CredentialPluginPrompt').length).toBe(0);
- expect(wrapper.find('input').length).toBe(0);
- expect(wrapper.find('KeyIcon').length).toBe(1);
- expect(wrapper.find('CredentialPluginSelected').length).toBe(1);
- });
- });
-});
diff --git a/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js b/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js
deleted file mode 100644
index 8f6506498bba..000000000000
--- a/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.js
+++ /dev/null
@@ -1,155 +0,0 @@
-import React, { useCallback } from 'react';
-import { func, shape } from 'prop-types';
-import { Formik, useField } from 'formik';
-import { t } from '@lingui/macro';
-import {
- Button,
- Tooltip,
- Wizard,
- WizardContextConsumer,
- WizardFooter,
-} from '@patternfly/react-core';
-import { CredentialsAPI } from 'api';
-import useRequest from 'hooks/useRequest';
-import CredentialsStep from './CredentialsStep';
-import MetadataStep from './MetadataStep';
-import { CredentialPluginTestAlert } from '..';
-
-function CredentialPluginWizard({ handleSubmit, onClose }) {
- const [selectedCredential] = useField('credential');
- const [inputValues] = useField('inputs');
-
- const {
- result: testPluginSuccess,
- error: testPluginError,
- request: testPluginMetadata,
- } = useRequest(
- useCallback(
- async () =>
- CredentialsAPI.test(selectedCredential.value.id, {
- metadata: inputValues.value,
- }),
- [selectedCredential, inputValues]
- ),
- null
- );
-
- const steps = [
- {
- id: 1,
- name: t`Credential`,
- key: 'credential',
- component: ,
- enableNext: !!selectedCredential.value,
- },
- {
- id: 2,
- name: t`Metadata`,
- key: 'metadata',
- component: ,
- canJumpTo: !!selectedCredential.value,
- },
- ];
-
- const CustomFooter = (
-
-
- {({ activeStep, onNext, onBack }) => (
- <>
-
- {activeStep && activeStep.key === 'metadata' && (
- <>
-
-
-
-
-
- >
- )}
-
- >
- )}
-
-
- );
-
- return (
- <>
-
- {selectedCredential.value && (
-
- )}
- >
- );
-}
-
-function CredentialPluginPrompt({ onClose, onSubmit, initialValues }) {
- return (
-
- {({ handleSubmit }) => (
-
- )}
-
- );
-}
-
-CredentialPluginPrompt.propTypes = {
- onClose: func.isRequired,
- onSubmit: func.isRequired,
- initialValues: shape({}),
-};
-
-CredentialPluginPrompt.defaultProps = {
- initialValues: {},
-};
-
-export default CredentialPluginPrompt;
diff --git a/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.test.js b/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.test.js
deleted file mode 100644
index 9e587d9f6b3c..000000000000
--- a/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.test.js
+++ /dev/null
@@ -1,250 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { CredentialsAPI, CredentialTypesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../../../testUtils/enzymeHelpers';
-import selectedCredential from '../../data.cyberArkCredential.json';
-import azureVaultCredential from '../../data.azureVaultCredential.json';
-import hashiCorpCredential from '../../data.hashiCorpCredential.json';
-import CredentialPluginPrompt from './CredentialPluginPrompt';
-
-jest.mock('../../../../../api');
-
-const mockCredentialResults = {
- data: {
- count: 3,
- results: [selectedCredential, azureVaultCredential, hashiCorpCredential],
- },
-};
-
-const mockCredentialOptions = {
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- related_search_fields: [],
- },
-};
-
-const mockCredentialTypeDetail = {
- data: {
- id: 20,
- type: 'credential_type',
- url: '/api/v2/credential_types/20/',
- related: {
- named_url:
- '/api/v2/credential_types/CyberArk Conjur Secrets Manager Lookup+external/',
- credentials: '/api/v2/credential_types/20/credentials/',
- activity_stream: '/api/v2/credential_types/20/activity_stream/',
- },
- summary_fields: { user_capabilities: { edit: false, delete: false } },
- created: '2020-05-18T21:53:35.398260Z',
- modified: '2020-05-18T21:54:05.451444Z',
- name: 'CyberArk Conjur Secrets Manager Lookup',
- description: '',
- kind: 'external',
- namespace: 'conjur',
- managed: true,
- inputs: {
- fields: [
- { id: 'url', label: 'Conjur URL', type: 'string', format: 'url' },
- { id: 'api_key', label: 'API Key', type: 'string', secret: true },
- { id: 'account', label: 'Account', type: 'string' },
- { id: 'username', label: 'Username', type: 'string' },
- {
- id: 'cacert',
- label: 'Public Key Certificate',
- type: 'string',
- multiline: true,
- },
- ],
- metadata: [
- {
- id: 'secret_path',
- label: 'Secret Identifier',
- type: 'string',
- help_text: 'The identifier for the secret e.g., /some/identifier',
- },
- {
- id: 'secret_version',
- label: 'Secret Version',
- type: 'string',
- help_text:
- 'Used to specify a specific secret version (if left empty, the latest version will be used).',
- },
- ],
- required: ['url', 'api_key', 'account', 'username'],
- },
- injectors: {},
- },
-};
-
-describe('', () => {
- describe('Plugin not configured', () => {
- let wrapper;
- const onClose = jest.fn();
- const onSubmit = jest.fn();
- beforeAll(async () => {
- CredentialsAPI.test.mockResolvedValue({});
- CredentialsAPI.read.mockResolvedValue(mockCredentialResults);
- CredentialsAPI.readOptions.mockResolvedValue(mockCredentialOptions);
- CredentialTypesAPI.readDetail = async () => mockCredentialTypeDetail;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- });
-
- test('should render Wizard with all steps', async () => {
- const wizard = await waitForElement(wrapper, 'Wizard');
- const steps = wizard.prop('steps');
-
- expect(steps).toHaveLength(2);
- expect(steps[0].name).toEqual('Credential');
- expect(steps[1].name).toEqual('Metadata');
- });
-
- test('credentials step renders correctly', () => {
- expect(wrapper.find('CredentialsStep').length).toBe(1);
- expect(wrapper.find('CheckboxListItem').length).toBe(3);
- expect(
- wrapper.find('Radio').filterWhere((radio) => radio.isChecked).length
- ).toBe(0);
- });
-
- test('next button disabled until credential selected', () => {
- expect(wrapper.find('Button[children="Next"]').prop('isDisabled')).toBe(
- true
- );
- });
-
- test('clicking cancel button calls correct function', () => {
- wrapper.find('Button[children="Cancel"]').simulate('click');
- expect(onClose).toHaveBeenCalledTimes(1);
- });
-
- test('clicking credential row enables next button', async () => {
- await waitForElement(wrapper, 'CheckboxListItem', (el) => el.length > 0);
- await act(async () => {
- wrapper.find('td#check-action-item-1').find('input').simulate('click');
- });
- wrapper.update();
- expect(
- wrapper.find('td#check-action-item-1').find('input').prop('checked')
- ).toBe(true);
- expect(wrapper.find('Button[children="Next"]').prop('isDisabled')).toBe(
- false
- );
- });
- test('clicking next button shows metatdata step', async () => {
- await act(async () => {
- wrapper.find('Button[children="Next"]').simulate('click');
- });
- wrapper.update();
- expect(wrapper.find('MetadataStep').length).toBe(1);
- expect(wrapper.find('FormField').length).toBe(2);
- });
- test('submit button calls correct function with parameters', async () => {
- await act(async () => {
- wrapper.find('input#credential-secret_path').simulate('change', {
- target: { value: '/foo/bar', name: 'secret_path' },
- });
- });
- await act(async () => {
- wrapper.find('input#credential-secret_version').simulate('change', {
- target: { value: '9000', name: 'secret_version' },
- });
- });
- await act(async () => {
- wrapper.find('Button[children="OK"]').simulate('click');
- });
-
- expect(onSubmit).toHaveBeenCalledWith(
- expect.objectContaining({
- credential: selectedCredential,
- secret_path: '/foo/bar',
- secret_version: '9000',
- }),
- expect.anything()
- );
- });
- });
-
- describe('Plugin already configured', () => {
- let wrapper;
- const onClose = jest.fn();
- const onSubmit = jest.fn();
- beforeAll(async () => {
- jest.resetAllMocks();
- CredentialsAPI.test.mockResolvedValue({});
- CredentialsAPI.read.mockResolvedValue(mockCredentialResults);
- CredentialsAPI.readOptions.mockResolvedValue(mockCredentialOptions);
- CredentialTypesAPI.readDetail = async () => mockCredentialTypeDetail;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- });
-
- test('should render Wizard with all steps', async () => {
- const wizard = await waitForElement(wrapper, 'Wizard');
- const steps = wizard.prop('steps');
-
- expect(steps).toHaveLength(2);
- expect(steps[0].name).toEqual('Credential');
- expect(steps[1].name).toEqual('Metadata');
- });
-
- test('credentials step renders correctly', async () => {
- await waitForElement(wrapper, 'CheckboxListItem', (el) => el.length > 0);
-
- expect(wrapper.find('CredentialsStep').length).toBe(1);
- expect(wrapper.find('CheckboxListItem').length).toBe(3);
- expect(
- wrapper.find('td#check-action-item-1').find('input').prop('checked')
- ).toBe(true);
- expect(wrapper.find('Button[children="Next"]').prop('isDisabled')).toBe(
- false
- );
- });
-
- test('metadata step renders correctly', async () => {
- await act(async () => {
- wrapper.find('Button[children="Next"]').simulate('click');
- });
- wrapper.update();
- expect(wrapper.find('MetadataStep').length).toBe(1);
- expect(wrapper.find('FormField').length).toBe(2);
- expect(wrapper.find('input#credential-secret_path').prop('value')).toBe(
- '/foo/bar'
- );
- expect(
- wrapper.find('input#credential-secret_version').prop('value')
- ).toBe('9000');
- });
-
- test('clicking Test button makes correct call', async () => {
- await act(async () => {
- wrapper.find('Button[children="Test"]').simulate('click');
- });
- expect(CredentialsAPI.test).toHaveBeenCalledWith(1, {
- metadata: { secret_path: '/foo/bar', secret_version: '9000' },
- });
- });
- });
-});
diff --git a/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js b/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js
deleted file mode 100644
index 636e7751b8ca..000000000000
--- a/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.js
+++ /dev/null
@@ -1,110 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { useHistory } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { useField } from 'formik';
-import { CredentialsAPI } from 'api';
-import CheckboxListItem from 'components/CheckboxListItem';
-import ContentError from 'components/ContentError';
-import DataListToolbar from 'components/DataListToolbar';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import useRequest from 'hooks/useRequest';
-import PaginatedTable, {
- HeaderCell,
- HeaderRow,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-
-const QS_CONFIG = getQSConfig('credential', {
- page: 1,
- page_size: 5,
- order_by: 'name',
- credential_type__kind: 'external',
-});
-
-function CredentialsStep() {
- const [selectedCredential, , selectedCredentialHelper] =
- useField('credential');
- const history = useHistory();
-
- const {
- result: { credentials, count, relatedSearchableKeys, searchableKeys },
- error: credentialsError,
- isLoading: isCredentialsLoading,
- request: fetchCredentials,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, history.location.search);
- const [{ data }, actionsResponse] = await Promise.all([
- CredentialsAPI.read({ ...params }),
- CredentialsAPI.readOptions(),
- ]);
- return {
- credentials: data.results,
- count: data.count,
- relatedSearchableKeys: (
- actionsResponse?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
- };
- }, [history.location.search]),
- { credentials: [], count: 0, relatedSearchableKeys: [], searchableKeys: [] }
- );
-
- useEffect(() => {
- fetchCredentials();
- }, [fetchCredentials]);
-
- if (credentialsError) {
- return ;
- }
-
- return (
-
- {t`Name`}
-
- }
- renderRow={(credential, index) => (
- selectedCredentialHelper.setValue(credential)}
- onDeselect={() => selectedCredentialHelper.setValue(null)}
- isRadio
- />
- )}
- renderToolbar={(props) => }
- showPageSizeOptions={false}
- toolbarSearchColumns={[
- {
- name: t`Name`,
- key: 'name__icontains',
- isDefault: true,
- },
- {
- name: t`Created By (Username)`,
- key: 'created_by__username__icontains',
- },
- {
- name: t`Modified By (Username)`,
- key: 'modified_by__username__icontains',
- },
- ]}
- toolbarSearchableKeys={searchableKeys}
- toolbarRelatedSearchableKeys={relatedSearchableKeys}
- />
- );
-}
-
-export default CredentialsStep;
diff --git a/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/MetadataStep.js b/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/MetadataStep.js
deleted file mode 100644
index b64b60fa9b94..000000000000
--- a/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/MetadataStep.js
+++ /dev/null
@@ -1,126 +0,0 @@
-/* eslint-disable react/jsx-no-useless-fragment */
-import React, { useCallback, useEffect } from 'react';
-
-import { useField, useFormikContext } from 'formik';
-import { Form, FormGroup } from '@patternfly/react-core';
-import { CredentialTypesAPI } from 'api';
-import AnsibleSelect from 'components/AnsibleSelect';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import FormField from 'components/FormField';
-import { FormFullWidthLayout } from 'components/FormLayout';
-import Popover from 'components/Popover';
-import useRequest from 'hooks/useRequest';
-import { required } from 'util/validators';
-
-function MetadataStep() {
- const form = useFormikContext();
- const [selectedCredential] = useField('credential');
- const [inputValues] = useField('inputs');
-
- const {
- result: fields,
- error,
- isLoading,
- request: fetchMetadataOptions,
- } = useRequest(
- useCallback(async () => {
- const {
- data: {
- inputs: { required: requiredFields, metadata },
- },
- } = await CredentialTypesAPI.readDetail(
- selectedCredential.value.credential_type ||
- selectedCredential.value.credential_type_id
- );
- metadata.forEach((field) => {
- if (inputValues.value[field.id]) {
- form.initialValues.inputs[field.id] = inputValues.value[field.id];
- } else if (field.type === 'string' && field.choices) {
- form.initialValues.inputs[field.id] =
- field.default || field.choices[0];
- } else {
- form.initialValues.inputs[field.id] = '';
- }
- if (requiredFields && requiredFields.includes(field.id)) {
- field.required = true;
- }
- });
- return metadata;
- /* eslint-disable-next-line react-hooks/exhaustive-deps */
- }, []),
- []
- );
-
- useEffect(() => {
- fetchMetadataOptions();
- }, [fetchMetadataOptions]);
-
- if (isLoading) {
- return ;
- }
-
- if (error) {
- return ;
- }
-
- return (
- <>
- {fields.length > 0 && (
-
- )}
- >
- );
-}
-
-export default MetadataStep;
diff --git a/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/index.js b/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/index.js
deleted file mode 100644
index 467b3f39365a..000000000000
--- a/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export { default as CredentialPluginPrompt } from './CredentialPluginPrompt';
-export { default as CredentialsStep } from './CredentialsStep';
-export { default as MetadataStep } from './MetadataStep';
diff --git a/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js b/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js
deleted file mode 100644
index 11d8f11badb9..000000000000
--- a/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import React from 'react';
-import { func } from 'prop-types';
-
-import { t, Trans } from '@lingui/macro';
-import styled from 'styled-components';
-import { Button, ButtonVariant, Tooltip } from '@patternfly/react-core';
-import { KeyIcon } from '@patternfly/react-icons';
-import CredentialChip from 'components/CredentialChip';
-import { Credential } from 'types';
-
-const SelectedCredential = styled.div`
- display: flex;
- justify-content: space-between;
- background-color: white;
- border-bottom-color: var(--pf-global--BorderColor--200);
-`;
-
-const SpacedCredentialChip = styled(CredentialChip)`
- margin: 5px 8px;
-`;
-
-const PluginHelpText = styled.p`
- margin-top: 5px;
-`;
-
-function CredentialPluginSelected({
- credential,
- onEditPlugin,
- onClearPlugin,
- fieldId,
-}) {
- return (
- <>
-
-
-
-
-
-
-
-
- This field will be retrieved from an external secret management system
- using the specified credential.
-
-
- >
- );
-}
-
-CredentialPluginSelected.propTypes = {
- credential: Credential.isRequired,
- onEditPlugin: func,
- onClearPlugin: func,
-};
-
-CredentialPluginSelected.defaultProps = {
- onEditPlugin: () => {},
- onClearPlugin: () => {},
-};
-
-export default CredentialPluginSelected;
diff --git a/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.test.js b/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.test.js
deleted file mode 100644
index f11ba45242e7..000000000000
--- a/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.test.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
-import selectedCredential from '../data.cyberArkCredential.json';
-import CredentialPluginSelected from './CredentialPluginSelected';
-
-describe('', () => {
- let wrapper;
- const onClearPlugin = jest.fn();
- const onEditPlugin = jest.fn();
- beforeAll(() => {
- wrapper = mountWithContexts(
-
- );
- });
-
- test('renders the expected content', () => {
- expect(wrapper.find('CredentialChip').length).toBe(1);
- expect(wrapper.find('KeyIcon').length).toBe(1);
- });
-
- test('clearing plugin calls expected function', () => {
- wrapper.find('CredentialChip button').simulate('click');
- expect(onClearPlugin).toBeCalledTimes(1);
- });
-
- test('editing plugin calls expected function', () => {
- wrapper.find('KeyIcon').simulate('click');
- expect(onEditPlugin).toBeCalledTimes(1);
- });
-});
diff --git a/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.js b/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.js
deleted file mode 100644
index 902981a1b67b..000000000000
--- a/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.js
+++ /dev/null
@@ -1,86 +0,0 @@
-import React, { useEffect, useState } from 'react';
-
-import { t } from '@lingui/macro';
-import { string, shape } from 'prop-types';
-import {
- Alert,
- AlertActionCloseButton,
- AlertGroup,
-} from '@patternfly/react-core';
-
-function CredentialPluginTestAlert({
- credentialName,
- successResponse,
- errorResponse,
-}) {
- const [testMessage, setTestMessage] = useState('');
- const [testVariant, setTestVariant] = useState(false);
- useEffect(() => {
- if (errorResponse) {
- if (errorResponse?.response?.data?.inputs) {
- if (errorResponse.response.data.inputs.startsWith('HTTP')) {
- const [errorCode, errorStr] =
- errorResponse.response.data.inputs.split('\n');
- try {
- const errorJSON = JSON.parse(errorStr);
- setTestMessage(
- `${errorCode}${
- errorJSON?.errors[0] ? `: ${errorJSON.errors[0]}` : ''
- }`
- );
- } catch {
- setTestMessage(errorResponse.response.data.inputs);
- }
- } else {
- setTestMessage(errorResponse.response.data.inputs);
- }
- } else {
- setTestMessage(
- t`Something went wrong with the request to test this credential and metadata.`
- );
- }
- setTestVariant('danger');
- } else if (successResponse) {
- setTestMessage(t`Test passed`);
- setTestVariant('success');
- }
- }, [successResponse, errorResponse]);
-
- return (
-
- {testMessage && testVariant && (
- {
- setTestMessage(null);
- setTestVariant(null);
- }}
- />
- }
- title={
- <>
- {credentialName}
- {testMessage}
- >
- }
- variant={testVariant}
- ouiaId="credential-plugin-test-alert"
- />
- )}
-
- );
-}
-
-CredentialPluginTestAlert.propTypes = {
- credentialName: string.isRequired,
- successResponse: shape({}),
- errorResponse: shape({}),
-};
-
-CredentialPluginTestAlert.defaultProps = {
- successResponse: null,
- errorResponse: null,
-};
-
-export default CredentialPluginTestAlert;
diff --git a/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.test.js b/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.test.js
deleted file mode 100644
index 533e6623e46b..000000000000
--- a/awx/ui/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.test.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
-import CredentialPluginTestAlert from './CredentialPluginTestAlert';
-
-describe('', () => {
- let wrapper;
-
- test('renders expected content when test is successful', () => {
- wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('b#credential-plugin-test-name').text()).toBe('Foobar');
- expect(wrapper.find('p#credential-plugin-test-message').text()).toBe(
- 'Test passed'
- );
- });
-
- test('renders expected content when test fails with the expected return string formatting', () => {
- wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('b#credential-plugin-test-name').text()).toBe('Foobar');
- expect(wrapper.find('p#credential-plugin-test-message').text()).toBe(
- 'HTTP 404: not found'
- );
- });
-
- test('renders expected content when test fails without the expected return string formatting', () => {
- wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('b#credential-plugin-test-name').text()).toBe('Foobar');
- expect(wrapper.find('p#credential-plugin-test-message').text()).toBe(
- 'usernamee is not present at /secret/foo/bar/baz'
- );
- });
-});
diff --git a/awx/ui/src/screens/Credential/shared/CredentialPlugins/index.js b/awx/ui/src/screens/Credential/shared/CredentialPlugins/index.js
deleted file mode 100644
index 3799206eb4fc..000000000000
--- a/awx/ui/src/screens/Credential/shared/CredentialPlugins/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export { default as CredentialPluginSelected } from './CredentialPluginSelected';
-export { default as CredentialPluginField } from './CredentialPluginField';
-export { default as CredentialPluginTestAlert } from './CredentialPluginTestAlert';
diff --git a/awx/ui/src/screens/Credential/shared/ExternalTestModal.js b/awx/ui/src/screens/Credential/shared/ExternalTestModal.js
deleted file mode 100644
index 363492af2c4f..000000000000
--- a/awx/ui/src/screens/Credential/shared/ExternalTestModal.js
+++ /dev/null
@@ -1,181 +0,0 @@
-import React, { useCallback } from 'react';
-
-import { t } from '@lingui/macro';
-import { func, shape } from 'prop-types';
-import { Formik } from 'formik';
-import { Button, Form, FormGroup, Modal } from '@patternfly/react-core';
-import { CredentialsAPI, CredentialTypesAPI } from 'api';
-import AnsibleSelect from 'components/AnsibleSelect';
-import FormField from 'components/FormField';
-import { FormFullWidthLayout } from 'components/FormLayout';
-import Popover from 'components/Popover';
-import { required } from 'util/validators';
-import useRequest from 'hooks/useRequest';
-import { CredentialPluginTestAlert } from './CredentialPlugins';
-
-function ExternalTestModal({
- credential,
- credentialType,
- credentialFormValues,
- onClose,
-}) {
- const {
- result: testPluginSuccess,
- error: testPluginError,
- request: testPluginMetadata,
- } = useRequest(
- useCallback(
- async (values) => {
- const payload = {
- inputs: credentialType.inputs.fields.reduce(
- (filteredInputs, field) => {
- filteredInputs[field.id] = credentialFormValues.inputs[field.id];
- return filteredInputs;
- },
- {}
- ),
- metadata: values,
- };
-
- if (credential && credential.credential_type === credentialType.id) {
- return CredentialsAPI.test(credential.id, payload);
- }
- return CredentialTypesAPI.test(credentialType.id, payload);
- },
- [
- credential,
- credentialType.id,
- credentialType.inputs.fields,
- credentialFormValues.inputs,
- ]
- ),
- null
- );
-
- const handleTest = async (values) => {
- await testPluginMetadata(values);
- };
-
- return (
- <>
- {
- if (field.type === 'string' && field.choices) {
- initialValues[field.id] = field.default || field.choices[0];
- } else {
- initialValues[field.id] = '';
- }
- return initialValues;
- },
- {}
- )}
- onSubmit={(values) => handleTest(values)}
- >
- {({ handleSubmit, setFieldValue }) => (
- onClose()}
- variant="small"
- actions={[
- ,
- ,
- ]}
- >
-
-
- )}
-
-
- >
- );
-}
-
-ExternalTestModal.propType = {
- credential: shape({}),
- credentialType: shape({}).isRequired,
- credentialFormValues: shape({}).isRequired,
- onClose: func.isRequired,
-};
-
-ExternalTestModal.defaultProps = {
- credential: null,
-};
-
-export default ExternalTestModal;
diff --git a/awx/ui/src/screens/Credential/shared/ExternalTestModal.test.js b/awx/ui/src/screens/Credential/shared/ExternalTestModal.test.js
deleted file mode 100644
index 6efb255ecd42..000000000000
--- a/awx/ui/src/screens/Credential/shared/ExternalTestModal.test.js
+++ /dev/null
@@ -1,184 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { CredentialsAPI, CredentialTypesAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import ExternalTestModal from './ExternalTestModal';
-import credentialTypesArr from './data.credentialTypes.json';
-
-jest.mock('../../../api/models/Credentials');
-jest.mock('../../../api/models/CredentialTypes');
-
-const credentialType = credentialTypesArr.find(
- (credType) => credType.namespace === 'hashivault_kv'
-);
-
-const credentialFormValues = {
- name: 'Foobar',
- credential_type: credentialType.id,
- inputs: {
- api_version: 'v2',
- token: '$encrypted$',
- url: 'http://hashivault:8200',
- },
-};
-
-const credential = {
- id: 1,
- name: 'A credential',
- credential_type: credentialType.id,
-};
-
-describe('', () => {
- let wrapper;
-
- test('should display metadata fields correctly', async () => {
- wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('FormField').length).toBe(5);
- expect(wrapper.find('input#credential-secret_backend').length).toBe(1);
- expect(wrapper.find('input#credential-secret_path').length).toBe(1);
- expect(wrapper.find('input#credential-auth_path').length).toBe(1);
- expect(wrapper.find('input#credential-secret_key').length).toBe(1);
- expect(wrapper.find('input#credential-secret_version').length).toBe(1);
- });
-
- test('should make the test request correctly when testing an existing credential', async () => {
- wrapper = mountWithContexts(
-
- );
- await act(async () => {
- wrapper.find('input#credential-secret_path').simulate('change', {
- target: { value: '/secret/foo/bar/baz', name: 'secret_path' },
- });
- wrapper.find('input#credential-secret_key').simulate('change', {
- target: { value: 'password', name: 'secret_key' },
- });
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('Button[children="Run"]').simulate('click');
- });
- expect(CredentialsAPI.test).toHaveBeenCalledWith(1, {
- inputs: {
- api_version: 'v2',
- cacert: undefined,
- role_id: undefined,
- secret_id: undefined,
- token: '$encrypted$',
- url: 'http://hashivault:8200',
- },
- metadata: {
- auth_path: '',
- secret_backend: '',
- secret_key: 'password',
- secret_path: '/secret/foo/bar/baz',
- secret_version: '',
- },
- });
- });
-
- test('should make the test request correctly when testing a new credential', async () => {
- wrapper = mountWithContexts(
-
- );
- await act(async () => {
- wrapper.find('input#credential-secret_path').simulate('change', {
- target: { value: '/secret/foo/bar/baz', name: 'secret_path' },
- });
- wrapper.find('input#credential-secret_key').simulate('change', {
- target: { value: 'password', name: 'secret_key' },
- });
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('Button[children="Run"]').simulate('click');
- });
- expect(CredentialTypesAPI.test).toHaveBeenCalledWith(21, {
- inputs: {
- api_version: 'v2',
- cacert: undefined,
- role_id: undefined,
- secret_id: undefined,
- token: '$encrypted$',
- url: 'http://hashivault:8200',
- },
- metadata: {
- auth_path: '',
- secret_backend: '',
- secret_key: 'password',
- secret_path: '/secret/foo/bar/baz',
- secret_version: '',
- },
- });
- });
-
- test('should display the alert after a successful test', async () => {
- CredentialTypesAPI.test.mockResolvedValue({});
- wrapper = mountWithContexts(
-
- );
- await act(async () => {
- wrapper.find('input#credential-secret_path').simulate('change', {
- target: { value: '/secret/foo/bar/baz', name: 'secret_path' },
- });
- wrapper.find('input#credential-secret_key').simulate('change', {
- target: { value: 'password', name: 'secret_key' },
- });
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('Button[children="Run"]').simulate('click');
- });
- wrapper.update();
- expect(wrapper.find('Alert').length).toBe(1);
- expect(wrapper.find('Alert').props().variant).toBe('success');
- });
-
- test('should display the alert after a failed test', async () => {
- CredentialTypesAPI.test.mockRejectedValue({
- inputs: `HTTP 404
- {"errors":["no handler for route '/secret/foo/bar/baz'"]}
- `,
- });
- wrapper = mountWithContexts(
-
- );
- await act(async () => {
- wrapper.find('input#credential-secret_path').simulate('change', {
- target: { value: '/secret/foo/bar/baz', name: 'secret_path' },
- });
- wrapper.find('input#credential-secret_key').simulate('change', {
- target: { value: 'password', name: 'secret_key' },
- });
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('Button[children="Run"]').simulate('click');
- });
- wrapper.update();
- expect(wrapper.find('Alert').length).toBe(1);
- expect(wrapper.find('Alert').props().variant).toBe('danger');
- });
-});
diff --git a/awx/ui/src/screens/Credential/shared/TypeInputsSubForm.js b/awx/ui/src/screens/Credential/shared/TypeInputsSubForm.js
deleted file mode 100644
index 4703051c6fff..000000000000
--- a/awx/ui/src/screens/Credential/shared/TypeInputsSubForm.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import React from 'react';
-
-import { t } from '@lingui/macro';
-import { FormGroup, Title } from '@patternfly/react-core';
-import {
- FormCheckboxLayout,
- FormColumnLayout,
- FormFullWidthLayout,
- SubFormLayout,
-} from 'components/FormLayout';
-import { CheckboxField } from 'components/FormField';
-import { CredentialType } from 'types';
-import { CredentialField, GceFileUploadField } from './CredentialFormFields';
-
-function TypeInputsSubForm({ credentialType }) {
- const stringFields = credentialType.inputs.fields.filter(
- (fieldOptions) => fieldOptions.type === 'string' || fieldOptions.choices
- );
- const booleanFields = credentialType.inputs.fields.filter(
- (fieldOptions) => fieldOptions.type === 'boolean'
- );
- return (
-
-
- {t`Type Details`}
-
-
- {credentialType.namespace === 'gce' && }
- {stringFields.map((fieldOptions) =>
- fieldOptions.multiline ? (
-
-
-
- ) : (
-
- )
- )}
- {booleanFields.length > 0 && (
-
-
-
- {booleanFields.map((fieldOptions) => (
-
- ))}
-
-
-
- )}
-
-
- );
-}
-
-TypeInputsSubForm.propTypes = {
- credentialType: CredentialType.isRequired,
-};
-
-TypeInputsSubForm.defaultProps = {};
-
-export default TypeInputsSubForm;
diff --git a/awx/ui/src/screens/Credential/shared/data.azureVaultCredential.json b/awx/ui/src/screens/Credential/shared/data.azureVaultCredential.json
deleted file mode 100644
index efedfe045d5f..000000000000
--- a/awx/ui/src/screens/Credential/shared/data.azureVaultCredential.json
+++ /dev/null
@@ -1,85 +0,0 @@
-{
- "id": 12,
- "type": "credential",
- "url": "/api/v2/credentials/12/",
- "related": {
- "created_by": "/api/v2/users/1/",
- "modified_by": "/api/v2/users/1/",
- "activity_stream": "/api/v2/credentials/12/activity_stream/",
- "access_list": "/api/v2/credentials/12/access_list/",
- "object_roles": "/api/v2/credentials/12/object_roles/",
- "owner_users": "/api/v2/credentials/12/owner_users/",
- "owner_teams": "/api/v2/credentials/12/owner_teams/",
- "copy": "/api/v2/credentials/12/copy/",
- "input_sources": "/api/v2/credentials/12/input_sources/",
- "credential_type": "/api/v2/credential_types/19/",
- "user": "/api/v2/users/1/"
- },
- "summary_fields": {
- "credential_type": {
- "id": 19,
- "name": "Microsoft Azure Key Vault",
- "description": ""
- },
- "created_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "object_roles": {
- "admin_role": {
- "description": "Can manage all aspects of the credential",
- "name": "Admin",
- "id": 60
- },
- "use_role": {
- "description": "Can use the credential in a job template",
- "name": "Use",
- "id": 61
- },
- "read_role": {
- "description": "May view settings for the credential",
- "name": "Read",
- "id": 62
- }
- },
- "user_capabilities": {
- "edit": true,
- "delete": true,
- "copy": true,
- "use": true
- },
- "owners": [
- {
- "id": 1,
- "type": "user",
- "name": "admin",
- "description": " ",
- "url": "/api/v2/users/1/"
- }
- ]
- },
- "created": "2020-05-26T14:54:45.612847Z",
- "modified": "2020-05-26T14:54:45.612861Z",
- "name": "Microsoft Azure Key Vault",
- "description": "",
- "organization": null,
- "credential_type": 19,
- "inputs": {
- "url": "https://localhost",
- "client": "foo",
- "secret": "$encrypted$",
- "tenant": "9000",
- "cloud_name": "AzureCloud"
- },
- "kind": "azure_kv",
- "cloud": false,
- "kubernetes": false
-}
diff --git a/awx/ui/src/screens/Credential/shared/data.credentialTypes.json b/awx/ui/src/screens/Credential/shared/data.credentialTypes.json
deleted file mode 100644
index eb12c1506eab..000000000000
--- a/awx/ui/src/screens/Credential/shared/data.credentialTypes.json
+++ /dev/null
@@ -1,1332 +0,0 @@
-[
- {
- "id": 5,
- "type": "credential_type",
- "url": "/api/v2/credential_types/5/",
- "related": {
- "credentials": "/api/v2/credential_types/5/credentials/",
- "activity_stream": "/api/v2/credential_types/5/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": false,
- "delete": false
- }
- },
- "created": "2020-05-18T21:53:35.160896Z",
- "modified": "2020-05-18T21:54:05.348882Z",
- "name": "Amazon Web Services",
- "description": "",
- "kind": "cloud",
- "namespace": "aws",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "username",
- "label": "Access Key",
- "type": "string"
- },
- {
- "id": "password",
- "label": "Secret Key",
- "type": "string",
- "secret": true
- },
- {
- "id": "security_token",
- "label": "STS Token",
- "type": "string",
- "secret": true,
- "help_text": "Security Token Service (STS) is a web service that enables you to request temporary, limited-privilege credentials for AWS Identity and Access Management (IAM) users."
- }
- ],
- "required": ["username", "password"]
- },
- "injectors": {}
- },
- {
- "id": 16,
- "type": "credential_type",
- "url": "/api/v2/credential_types/16/",
- "related": {
- "credentials": "/api/v2/credential_types/16/credentials/",
- "activity_stream": "/api/v2/credential_types/16/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": false,
- "delete": false
- }
- },
- "created": "2020-05-18T21:53:35.334813Z",
- "modified": "2020-05-18T21:54:05.424087Z",
- "name": "Ansible Controller",
- "description": "",
- "kind": "cloud",
- "namespace": "tower",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "host",
- "label": "Ansible Controller Hostname",
- "type": "string",
- "help_text": "The Ansible Controller base URL to authenticate with."
- },
- {
- "id": "username",
- "label": "Username",
- "type": "string"
- },
- {
- "id": "password",
- "label": "Password",
- "type": "string",
- "secret": true
- },
- {
- "id": "oauth_token",
- "label": "OAuth Token",
- "type": "string",
- "secret": true,
- "help_text": "An OAuth token to use to authenticate with.This should not be set if username/password are being used."
- },
- {
- "id": "verify_ssl",
- "label": "Verify SSL",
- "type": "boolean",
- "secret": false
- }
- ],
- "required": ["host", "username", "password"]
- },
- "injectors": {
- "env": {
- "TOWER_HOST": "{{host}}",
- "TOWER_USERNAME": "{{username}}",
- "TOWER_PASSWORD": "{{password}}",
- "TOWER_VERIFY_SSL": "{{verify_ssl}}"
- }
- }
- },
- {
- "id": 10,
- "type": "credential_type",
- "url": "/api/v2/credential_types/10/",
- "related": {
- "credentials": "/api/v2/credential_types/10/credentials/",
- "activity_stream": "/api/v2/credential_types/10/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": false,
- "delete": false
- }
- },
- "created": "2020-05-18T21:53:35.251095Z",
- "modified": "2020-05-18T21:54:05.383305Z",
- "name": "Google Compute Engine",
- "description": "",
- "kind": "cloud",
- "namespace": "gce",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "username",
- "label": "Service Account Email Address",
- "type": "string",
- "help_text": "The email address assigned to the Google Compute Engine service account."
- },
- {
- "id": "project",
- "label": "Project",
- "type": "string",
- "help_text": "The Project ID is the GCE assigned identification. It is often constructed as three words or two words followed by a three-digit number. Examples: project-id-000 and another-project-id"
- },
- {
- "id": "ssh_key_data",
- "label": "RSA Private Key",
- "type": "string",
- "format": "ssh_private_key",
- "secret": true,
- "multiline": true,
- "help_text": "Paste the contents of the PEM file associated with the service account email."
- }
- ],
- "required": ["username", "ssh_key_data"]
- },
- "injectors": {}
- },
- {
- "id": 11,
- "type": "credential_type",
- "url": "/api/v2/credential_types/11/",
- "related": {
- "credentials": "/api/v2/credential_types/11/credentials/",
- "activity_stream": "/api/v2/credential_types/11/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": false,
- "delete": false
- }
- },
- "created": "2020-05-18T21:53:35.263250Z",
- "modified": "2020-05-18T21:54:05.389597Z",
- "name": "Microsoft Azure Resource Manager",
- "description": "",
- "kind": "cloud",
- "namespace": "azure_rm",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "subscription",
- "label": "Subscription ID",
- "type": "string",
- "help_text": "Subscription ID is an Azure construct, which is mapped to a username."
- },
- {
- "id": "username",
- "label": "Username",
- "type": "string"
- },
- {
- "id": "password",
- "label": "Password",
- "type": "string",
- "secret": true
- },
- {
- "id": "client",
- "label": "Client ID",
- "type": "string"
- },
- {
- "id": "secret",
- "label": "Client Secret",
- "type": "string",
- "secret": true
- },
- {
- "id": "tenant",
- "label": "Tenant ID",
- "type": "string"
- },
- {
- "id": "cloud_environment",
- "label": "Azure Cloud Environment",
- "type": "string",
- "help_text": "Environment variable AZURE_CLOUD_ENVIRONMENT when using Azure GovCloud or Azure stack."
- }
- ],
- "required": ["subscription"]
- },
- "injectors": {}
- },
- {
- "id": 6,
- "type": "credential_type",
- "url": "/api/v2/credential_types/6/",
- "related": {
- "credentials": "/api/v2/credential_types/6/credentials/",
- "activity_stream": "/api/v2/credential_types/6/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": false,
- "delete": false
- }
- },
- "created": "2020-05-18T21:53:35.178681Z",
- "modified": "2020-05-18T21:54:05.355877Z",
- "name": "OpenStack",
- "description": "",
- "kind": "cloud",
- "namespace": "openstack",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "username",
- "label": "Username",
- "type": "string"
- },
- {
- "id": "password",
- "label": "Password (API Key)",
- "type": "string",
- "secret": true
- },
- {
- "id": "host",
- "label": "Host (Authentication URL)",
- "type": "string",
- "help_text": "The host to authenticate with. For example, https://openstack.business.com/v2.0/"
- },
- {
- "id": "project",
- "label": "Project (Tenant Name)",
- "type": "string"
- },
- {
- "id": "project_domain_name",
- "label": "Project (Domain Name)",
- "type": "string"
- },
- {
- "id": "domain",
- "label": "Domain Name",
- "type": "string",
- "help_text": "OpenStack domains define administrative boundaries. It is only needed for Keystone v3 authentication URLs. Refer to the documentation for common scenarios."
- },
- {
- "id": "region",
- "label": "Region Name",
- "type": "string"
- },
- {
- "id": "verify_ssl",
- "label": "Verify SSL",
- "type": "boolean",
- "default": true
- }
- ],
- "required": ["username", "password", "host", "project"]
- },
- "injectors": {}
- },
- {
- "id": 8,
- "type": "credential_type",
- "url": "/api/v2/credential_types/8/",
- "related": {
- "credentials": "/api/v2/credential_types/8/credentials/",
- "activity_stream": "/api/v2/credential_types/8/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": false,
- "delete": false
- }
- },
- "created": "2020-05-18T21:53:35.209086Z",
- "modified": "2020-05-18T21:54:05.370135Z",
- "name": "Red Hat Satellite 6",
- "description": "",
- "kind": "cloud",
- "namespace": "satellite6",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "host",
- "label": "Satellite 6 URL",
- "type": "string",
- "help_text": "Enter the URL that corresponds to your Red Hat Satellite 6 server. For example, https://satellite.example.org"
- },
- {
- "id": "username",
- "label": "Username",
- "type": "string"
- },
- {
- "id": "password",
- "label": "Password",
- "type": "string",
- "secret": true
- }
- ],
- "required": ["host", "username", "password"]
- },
- "injectors": {}
- },
- {
- "id": 15,
- "type": "credential_type",
- "url": "/api/v2/credential_types/15/",
- "related": {
- "credentials": "/api/v2/credential_types/15/credentials/",
- "activity_stream": "/api/v2/credential_types/15/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": false,
- "delete": false
- }
- },
- "created": "2020-05-18T21:53:35.320780Z",
- "modified": "2020-05-18T21:54:05.415842Z",
- "name": "Red Hat Virtualization",
- "description": "",
- "kind": "cloud",
- "namespace": "rhv",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "host",
- "label": "Host (Authentication URL)",
- "type": "string",
- "help_text": "The host to authenticate with."
- },
- {
- "id": "username",
- "label": "Username",
- "type": "string"
- },
- {
- "id": "password",
- "label": "Password",
- "type": "string",
- "secret": true
- },
- {
- "id": "ca_file",
- "label": "CA File",
- "type": "string",
- "help_text": "Absolute file path to the CA file to use (optional)"
- }
- ],
- "required": ["host", "username", "password"]
- },
- "injectors": {
- "file": {
- "template": "[ovirt]\novirt_url={{host}}\novirt_username={{username}}\novirt_password={{password}}\n{% if ca_file %}ovirt_ca_file={{ca_file}}{% endif %}"
- },
- "env": {
- "OVIRT_INI_PATH": "{{tower.filename}}",
- "OVIRT_URL": "{{host}}",
- "OVIRT_USERNAME": "{{username}}",
- "OVIRT_PASSWORD": "{{password}}"
- }
- }
- },
- {
- "id": 7,
- "type": "credential_type",
- "url": "/api/v2/credential_types/7/",
- "related": {
- "credentials": "/api/v2/credential_types/7/credentials/",
- "activity_stream": "/api/v2/credential_types/7/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": false,
- "delete": false
- }
- },
- "created": "2020-05-18T21:53:35.194625Z",
- "modified": "2020-05-18T21:54:05.362610Z",
- "name": "VMware vCenter",
- "description": "",
- "kind": "cloud",
- "namespace": "vmware",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "host",
- "label": "VCenter Host",
- "type": "string",
- "help_text": "Enter the hostname or IP address that corresponds to your VMware vCenter."
- },
- {
- "id": "username",
- "label": "Username",
- "type": "string"
- },
- {
- "id": "password",
- "label": "Password",
- "type": "string",
- "secret": true
- }
- ],
- "required": ["host", "username", "password"]
- },
- "injectors": {}
- },
- {
- "id": 18,
- "type": "credential_type",
- "url": "/api/v2/credential_types/18/",
- "related": {
- "credentials": "/api/v2/credential_types/18/credentials/",
- "activity_stream": "/api/v2/credential_types/18/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": false,
- "delete": false
- }
- },
- "created": "2020-05-18T21:53:35.370730Z",
- "modified": "2020-05-18T21:54:05.436400Z",
- "name": "CyberArk Central Credential Provider Lookup",
- "description": "",
- "kind": "external",
- "namespace": "aim",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "url",
- "label": "CyberArk AIM URL",
- "type": "string",
- "format": "url"
- },
- {
- "id": "app_id",
- "label": "Application ID",
- "type": "string",
- "secret": true
- },
- {
- "id": "client_key",
- "label": "Client Key",
- "type": "string",
- "secret": true,
- "multiline": true
- },
- {
- "id": "client_cert",
- "label": "Client Certificate",
- "type": "string",
- "secret": true,
- "multiline": true
- },
- {
- "id": "verify",
- "label": "Verify SSL Certificates",
- "type": "boolean",
- "default": true
- }
- ],
- "metadata": [
- {
- "id": "object_query",
- "label": "Object Query",
- "type": "string",
- "help_text": "Lookup query for the object. Ex: Safe=TestSafe;Object=testAccountName123"
- },
- {
- "id": "object_query_format",
- "label": "Object Query Format",
- "type": "string",
- "default": "Exact",
- "choices": ["Exact", "Regexp"]
- },
- {
- "id": "reason",
- "label": "Reason",
- "type": "string",
- "help_text": "Object request reason. This is only needed if it is required by the object's policy."
- }
- ],
- "required": ["url", "app_id", "object_query"]
- },
- "injectors": {}
- },
- {
- "id": 20,
- "type": "credential_type",
- "url": "/api/v2/credential_types/20/",
- "related": {
- "credentials": "/api/v2/credential_types/20/credentials/",
- "activity_stream": "/api/v2/credential_types/20/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": false,
- "delete": false
- }
- },
- "created": "2020-05-18T21:53:35.398260Z",
- "modified": "2020-05-18T21:54:05.451444Z",
- "name": "CyberArk Conjur Secrets Manager Lookup",
- "description": "",
- "kind": "external",
- "namespace": "conjur",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "url",
- "label": "Conjur URL",
- "type": "string",
- "format": "url"
- },
- {
- "id": "api_key",
- "label": "API Key",
- "type": "string",
- "secret": true
- },
- {
- "id": "account",
- "label": "Account",
- "type": "string"
- },
- {
- "id": "username",
- "label": "Username",
- "type": "string"
- },
- {
- "id": "cacert",
- "label": "Public Key Certificate",
- "type": "string",
- "multiline": true
- }
- ],
- "metadata": [
- {
- "id": "secret_path",
- "label": "Secret Identifier",
- "type": "string",
- "help_text": "The identifier for the secret e.g., /some/identifier"
- },
- {
- "id": "secret_version",
- "label": "Secret Version",
- "type": "string",
- "help_text": "Used to specify a specific secret version (if left empty, the latest version will be used)."
- }
- ],
- "required": ["url", "api_key", "account", "username"]
- },
- "injectors": {}
- },
- {
- "id": 21,
- "type": "credential_type",
- "url": "/api/v2/credential_types/21/",
- "related": {
- "credentials": "/api/v2/credential_types/21/credentials/",
- "activity_stream": "/api/v2/credential_types/21/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": false,
- "delete": false
- }
- },
- "created": "2020-05-18T21:53:35.422968Z",
- "modified": "2020-05-18T21:54:05.459711Z",
- "name": "HashiCorp Vault Secret Lookup",
- "description": "",
- "kind": "external",
- "namespace": "hashivault_kv",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "url",
- "label": "Server URL",
- "type": "string",
- "format": "url",
- "help_text": "The URL to the HashiCorp Vault"
- },
- {
- "id": "token",
- "label": "Token",
- "type": "string",
- "secret": true,
- "help_text": "The access token used to authenticate to the Vault server"
- },
- {
- "id": "cacert",
- "label": "CA Certificate",
- "type": "string",
- "multiline": true,
- "help_text": "The CA certificate used to verify the SSL certificate of the Vault server"
- },
- {
- "id": "role_id",
- "label": "AppRole role_id",
- "type": "string",
- "multiline": false,
- "help_text": "The Role ID for AppRole Authentication"
- },
- {
- "id": "secret_id",
- "label": "AppRole secret_id",
- "type": "string",
- "multiline": false,
- "secret": true,
- "help_text": "The Secret ID for AppRole Authentication"
- },
- {
- "id": "api_version",
- "label": "API Version",
- "choices": ["v1", "v2"],
- "help_text": "API v1 is for static key/value lookups. API v2 is for versioned key/value lookups.",
- "default": "v1"
- }
- ],
- "metadata": [
- {
- "id": "secret_backend",
- "label": "Name of Secret Backend",
- "type": "string",
- "help_text": "The name of the kv secret backend (if left empty, the first segment of the secret path will be used)."
- },
- {
- "id": "secret_path",
- "label": "Path to Secret",
- "type": "string",
- "help_text": "The path to the secret stored in the secret backend e.g, /some/secret/"
- },
- {
- "id": "auth_path",
- "label": "Path to Auth",
- "type": "string",
- "help_text": "The path where the Authentication method is mounted e.g, approle"
- },
- {
- "id": "secret_key",
- "label": "Key Name",
- "type": "string",
- "help_text": "The name of the key to look up in the secret."
- },
- {
- "id": "secret_version",
- "label": "Secret Version (v2 only)",
- "type": "string",
- "help_text": "Used to specify a specific secret version (if left empty, the latest version will be used)."
- }
- ],
- "required": ["url", "secret_path", "api_version", "secret_key"]
- },
- "injectors": {}
- },
- {
- "id": 22,
- "type": "credential_type",
- "url": "/api/v2/credential_types/22/",
- "related": {
- "credentials": "/api/v2/credential_types/22/credentials/",
- "activity_stream": "/api/v2/credential_types/22/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": false,
- "delete": false
- }
- },
- "created": "2020-05-18T21:53:35.437779Z",
- "modified": "2020-05-18T21:54:05.472241Z",
- "name": "HashiCorp Vault Signed SSH",
- "description": "",
- "kind": "external",
- "namespace": "hashivault_ssh",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "url",
- "label": "Server URL",
- "type": "string",
- "format": "url",
- "help_text": "The URL to the HashiCorp Vault"
- },
- {
- "id": "token",
- "label": "Token",
- "type": "string",
- "secret": true,
- "help_text": "The access token used to authenticate to the Vault server"
- },
- {
- "id": "cacert",
- "label": "CA Certificate",
- "type": "string",
- "multiline": true,
- "help_text": "The CA certificate used to verify the SSL certificate of the Vault server"
- },
- {
- "id": "role_id",
- "label": "AppRole role_id",
- "type": "string",
- "multiline": false,
- "help_text": "The Role ID for AppRole Authentication"
- },
- {
- "id": "secret_id",
- "label": "AppRole secret_id",
- "type": "string",
- "multiline": false,
- "secret": true,
- "help_text": "The Secret ID for AppRole Authentication"
- }
- ],
- "metadata": [
- {
- "id": "public_key",
- "label": "Unsigned Public Key",
- "type": "string",
- "multiline": true
- },
- {
- "id": "secret_path",
- "label": "Path to Secret",
- "type": "string",
- "help_text": "The path to the secret stored in the secret backend e.g, /some/secret/"
- },
- {
- "id": "auth_path",
- "label": "Path to Auth",
- "type": "string",
- "help_text": "The path where the Authentication method is mounted e.g, approle"
- },
- {
- "id": "role",
- "label": "Role Name",
- "type": "string",
- "help_text": "The name of the role used to sign."
- },
- {
- "id": "valid_principals",
- "label": "Valid Principals",
- "type": "string",
- "help_text": "Valid principals (either usernames or hostnames) that the certificate should be signed for."
- }
- ],
- "required": ["url", "secret_path", "public_key", "role"]
- },
- "injectors": {}
- },
- {
- "id": 19,
- "type": "credential_type",
- "url": "/api/v2/credential_types/19/",
- "related": {
- "credentials": "/api/v2/credential_types/19/credentials/",
- "activity_stream": "/api/v2/credential_types/19/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": false,
- "delete": false
- }
- },
- "created": "2020-05-18T21:53:35.384608Z",
- "modified": "2020-05-18T21:54:05.443806Z",
- "name": "Microsoft Azure Key Vault",
- "description": "",
- "kind": "external",
- "namespace": "azure_kv",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "url",
- "label": "Vault URL (DNS Name)",
- "type": "string",
- "format": "url"
- },
- {
- "id": "client",
- "label": "Client ID",
- "type": "string"
- },
- {
- "id": "secret",
- "label": "Client Secret",
- "type": "string",
- "secret": true
- },
- {
- "id": "tenant",
- "label": "Tenant ID",
- "type": "string"
- },
- {
- "id": "cloud_name",
- "label": "Cloud Environment",
- "help_text": "Specify which azure cloud environment to use.",
- "choices": [
- "AzureUSGovernment",
- "AzureCloud",
- "AzureChinaCloud",
- "AzureGermanCloud"
- ],
- "default": "AzureCloud"
- }
- ],
- "metadata": [
- {
- "id": "secret_field",
- "label": "Secret Name",
- "type": "string",
- "help_text": "The name of the secret to look up."
- },
- {
- "id": "secret_version",
- "label": "Secret Version",
- "type": "string",
- "help_text": "Used to specify a specific secret version (if left empty, the latest version will be used)."
- }
- ],
- "required": ["url", "client", "secret", "tenant", "secret_field"]
- },
- "injectors": {}
- },
- {
- "id": 14,
- "type": "credential_type",
- "url": "/api/v2/credential_types/14/",
- "related": {
- "credentials": "/api/v2/credential_types/14/credentials/",
- "activity_stream": "/api/v2/credential_types/14/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": false,
- "delete": false
- }
- },
- "created": "2020-05-18T21:53:35.306541Z",
- "modified": "2020-05-18T21:54:05.409381Z",
- "name": "Insights",
- "description": "",
- "kind": "insights",
- "namespace": "insights",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "username",
- "label": "Username",
- "type": "string"
- },
- {
- "id": "password",
- "label": "Password",
- "type": "string",
- "secret": true
- }
- ],
- "required": ["username", "password"]
- },
- "injectors": {
- "extra_vars": {
- "scm_username": "{{username}}",
- "scm_password": "{{password}}"
- }
- }
- },
- {
- "id": 17,
- "type": "credential_type",
- "url": "/api/v2/credential_types/17/",
- "related": {
- "credentials": "/api/v2/credential_types/17/credentials/",
- "activity_stream": "/api/v2/credential_types/17/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": false,
- "delete": false
- }
- },
- "created": "2020-05-18T21:53:35.356431Z",
- "modified": "2020-05-18T21:54:05.430264Z",
- "name": "OpenShift or Kubernetes API Bearer Token",
- "description": "",
- "kind": "kubernetes",
- "namespace": "kubernetes_bearer_token",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "host",
- "label": "OpenShift or Kubernetes API Endpoint",
- "type": "string",
- "help_text": "The OpenShift or Kubernetes API Endpoint to authenticate with."
- },
- {
- "id": "bearer_token",
- "label": "API authentication bearer token",
- "type": "string",
- "secret": true
- },
- {
- "id": "verify_ssl",
- "label": "Verify SSL",
- "type": "boolean",
- "default": true
- },
- {
- "id": "ssl_ca_cert",
- "label": "Certificate Authority data",
- "type": "string",
- "secret": true,
- "multiline": true
- }
- ],
- "required": ["host", "bearer_token"]
- },
- "injectors": {}
- },
- {
- "id": 4,
- "type": "credential_type",
- "url": "/api/v2/credential_types/4/",
- "related": {
- "credentials": "/api/v2/credential_types/4/credentials/",
- "activity_stream": "/api/v2/credential_types/4/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": false,
- "delete": false
- }
- },
- "created": "2020-05-18T21:53:35.142889Z",
- "modified": "2020-05-18T21:54:05.341825Z",
- "name": "Network",
- "description": "",
- "kind": "net",
- "namespace": "net",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "username",
- "label": "Username",
- "type": "string"
- },
- {
- "id": "password",
- "label": "Password",
- "type": "string",
- "secret": true
- },
- {
- "id": "ssh_key_data",
- "label": "SSH Private Key",
- "type": "string",
- "format": "ssh_private_key",
- "secret": true,
- "multiline": true
- },
- {
- "id": "ssh_key_unlock",
- "label": "Private Key Passphrase",
- "type": "string",
- "secret": true
- },
- {
- "id": "authorize",
- "label": "Authorize",
- "type": "boolean"
- },
- {
- "id": "authorize_password",
- "label": "Authorize Password",
- "type": "string",
- "secret": true
- }
- ],
- "dependencies": {
- "authorize_password": ["authorize"]
- },
- "required": ["username"]
- },
- "injectors": {}
- },
- {
- "id": 2,
- "type": "credential_type",
- "url": "/api/v2/credential_types/2/",
- "related": {
- "credentials": "/api/v2/credential_types/2/credentials/",
- "activity_stream": "/api/v2/credential_types/2/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": false,
- "delete": false
- }
- },
- "created": "2020-05-18T21:53:35.112580Z",
- "modified": "2020-05-18T21:54:05.324947Z",
- "name": "Source Control",
- "description": "",
- "kind": "scm",
- "namespace": "scm",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "username",
- "label": "Username",
- "type": "string"
- },
- {
- "id": "password",
- "label": "Password",
- "type": "string",
- "secret": true
- },
- {
- "id": "ssh_key_data",
- "label": "SCM Private Key",
- "type": "string",
- "format": "ssh_private_key",
- "secret": true,
- "multiline": true
- },
- {
- "id": "ssh_key_unlock",
- "label": "Private Key Passphrase",
- "type": "string",
- "secret": true
- }
- ]
- },
- "injectors": {}
- },
- {
- "id": 1,
- "type": "credential_type",
- "url": "/api/v2/credential_types/1/",
- "related": {
- "credentials": "/api/v2/credential_types/1/credentials/",
- "activity_stream": "/api/v2/credential_types/1/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": false,
- "delete": false
- }
- },
- "created": "2020-05-18T21:53:35.092707Z",
- "modified": "2020-05-18T21:54:05.314415Z",
- "name": "Machine",
- "description": "",
- "kind": "ssh",
- "namespace": "ssh",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "username",
- "label": "Username",
- "type": "string"
- },
- {
- "id": "password",
- "label": "Password",
- "type": "string",
- "secret": true,
- "ask_at_runtime": true
- },
- {
- "id": "ssh_key_data",
- "label": "SSH Private Key",
- "type": "string",
- "format": "ssh_private_key",
- "secret": true,
- "multiline": true
- },
- {
- "id": "ssh_public_key_data",
- "label": "Signed SSH Certificate",
- "type": "string",
- "multiline": true,
- "secret": true
- },
- {
- "id": "ssh_key_unlock",
- "label": "Private Key Passphrase",
- "type": "string",
- "secret": true,
- "ask_at_runtime": true
- },
- {
- "id": "become_method",
- "label": "Privilege Escalation Method",
- "type": "string",
- "help_text": "Specify a method for \"become\" operations. This is equivalent to specifying the --become-method Ansible parameter."
- },
- {
- "id": "become_username",
- "label": "Privilege Escalation Username",
- "type": "string"
- },
- {
- "id": "become_password",
- "label": "Privilege Escalation Password",
- "type": "string",
- "secret": true,
- "ask_at_runtime": true
- }
- ]
- },
- "injectors": {}
- },
- {
- "id": 12,
- "type": "credential_type",
- "url": "/api/v2/credential_types/12/",
- "related": {
- "credentials": "/api/v2/credential_types/12/credentials/",
- "activity_stream": "/api/v2/credential_types/12/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": false,
- "delete": false
- }
- },
- "created": "2020-05-18T21:53:35.276032Z",
- "modified": "2020-05-18T21:54:05.395920Z",
- "name": "GitHub Personal Access Token",
- "description": "",
- "kind": "token",
- "namespace": "github_token",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "token",
- "label": "Token",
- "type": "string",
- "secret": true,
- "help_text": "This token needs to come from your profile settings in GitHub"
- }
- ],
- "required": ["token"]
- },
- "injectors": {}
- },
- {
- "id": 13,
- "type": "credential_type",
- "url": "/api/v2/credential_types/13/",
- "related": {
- "credentials": "/api/v2/credential_types/13/credentials/",
- "activity_stream": "/api/v2/credential_types/13/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": false,
- "delete": false
- }
- },
- "created": "2020-05-18T21:53:35.285972Z",
- "modified": "2020-05-18T21:54:05.402347Z",
- "name": "GitLab Personal Access Token",
- "description": "",
- "kind": "token",
- "namespace": "gitlab_token",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "token",
- "label": "Token",
- "type": "string",
- "secret": true,
- "help_text": "This token needs to come from your profile settings in GitLab"
- }
- ],
- "required": ["token"]
- },
- "injectors": {}
- },
- {
- "id": 3,
- "type": "credential_type",
- "url": "/api/v2/credential_types/3/",
- "related": {
- "credentials": "/api/v2/credential_types/3/credentials/",
- "activity_stream": "/api/v2/credential_types/3/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": false,
- "delete": false
- }
- },
- "created": "2020-05-18T21:53:35.126565Z",
- "modified": "2020-05-18T21:54:05.334941Z",
- "name": "Vault",
- "description": "",
- "kind": "vault",
- "namespace": "vault",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "vault_password",
- "label": "Vault Password",
- "type": "string",
- "secret": true,
- "ask_at_runtime": true
- },
- {
- "id": "vault_id",
- "label": "Vault Identifier",
- "type": "string",
- "format": "vault_id",
- "help_text": "Specify an (optional) Vault ID. This is equivalent to specifying the --vault-id Ansible parameter for providing multiple Vault passwords. Note: this feature only works in Ansible 2.4+."
- }
- ],
- "required": ["vault_password"]
- },
- "injectors": {}
- },
- {
- "id": 42,
- "type": "credential_type",
- "url": "/api/v2/credential_types/42/",
- "related": {
- "credentials": "/api/v2/credential_types/42/credentials/",
- "activity_stream": "/api/v2/credential_types/42/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": true,
- "delete": true
- }
- },
- "created": "2020-11-11T00:18:31.928286Z",
- "modified": "2020-11-11T00:19:00.851597Z",
- "name": "Ansible Galaxy/Automation Hub API Token",
- "description": "",
- "kind": "galaxy",
- "namespace": "galaxy_api_token",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "url",
- "label": "Galaxy Server URL",
- "type": "string",
- "help_text": "The URL of the Galaxy instance to connect to."
- },
- {
- "id": "auth_url",
- "label": "Auth Server URL",
- "type": "string",
- "help_text": "The URL of a Keycloak server token_endpoint, if using SSO auth."
- },
- {
- "id": "token",
- "label": "API Token",
- "type": "string",
- "secret": true,
- "help_text": "A token to use for authentication against the Galaxy instance."
- }
- ],
- "required": ["url"]
- },
- "injectors": {}
- }
-]
diff --git a/awx/ui/src/screens/Credential/shared/data.credential_type.json b/awx/ui/src/screens/Credential/shared/data.credential_type.json
deleted file mode 100644
index de6d0ac9ccb2..000000000000
--- a/awx/ui/src/screens/Credential/shared/data.credential_type.json
+++ /dev/null
@@ -1,85 +0,0 @@
-{
- "id": 1,
- "type": "credential_type",
- "url": "/api/v2/credential_types/1/",
- "related": {
- "named_url": "/api/v2/credential_types/Machine+ssh/",
- "credentials": "/api/v2/credential_types/1/credentials/",
- "activity_stream": "/api/v2/credential_types/1/activity_stream/"
- },
- "summary_fields": {
- "user_capabilities": {
- "edit": false,
- "delete": false
- }
- },
- "created": "2020-01-08T20:18:23.663144Z",
- "modified": "2020-01-08T20:18:46.056120Z",
- "name": "Machine",
- "description": "",
- "kind": "ssh",
- "namespace": "ssh",
- "managed": true,
- "inputs": {
- "fields": [
- {
- "id": "username",
- "label": "Username",
- "type": "string"
- },
- {
- "id": "password",
- "label": "Password",
- "type": "string",
- "secret": true,
- "ask_at_runtime": true
- },
- {
- "id": "ssh_key_data",
- "label": "SSH Private Key",
- "type": "string",
- "format": "ssh_private_key",
- "secret": true,
- "multiline": true
- },
- {
- "id": "ssh_public_key_data",
- "label": "Signed SSH Certificate",
- "type": "string",
- "multiline": true,
- "secret": true
- },
- {
- "id": "ssh_key_unlock",
- "label": "Private Key Passphrase",
- "type": "string",
- "secret": true,
- "ask_at_runtime": true
- },
- {
- "id": "become_method",
- "label": "Privilege Escalation Method",
- "type": "string",
- "help_text": "Specify a method for \"become\" operations. This is equivalent to specifying the --become-method Ansible parameter."
- },
- {
- "id": "become_username",
- "label": "Privilege Escalation Username",
- "type": "string"
- },
- {
- "id": "become_password",
- "label": "Privilege Escalation Password",
- "type": "string",
- "secret": true,
- "ask_at_runtime": true
- },
- {
- "id": "authorize",
- "label": "Authorize",
- "type": "boolean"
- }
- ]
- },
- "injectors": {}
-}
\ No newline at end of file
diff --git a/awx/ui/src/screens/Credential/shared/data.credentials.json b/awx/ui/src/screens/Credential/shared/data.credentials.json
deleted file mode 100644
index d2ff130af909..000000000000
--- a/awx/ui/src/screens/Credential/shared/data.credentials.json
+++ /dev/null
@@ -1,407 +0,0 @@
-{
- "count": 5,
- "next": "/api/v2/credentials/",
- "previous": null,
- "results": [
- {
- "id": 1,
- "type": "credential",
- "url": "/api/v2/credentials/1/",
- "related": {
- "created_by": "/api/v2/users/1/",
- "modified_by": "/api/v2/users/1/",
- "activity_stream": "/api/v2/credentials/1/activity_stream/",
- "access_list": "/api/v2/credentials/1/access_list/",
- "object_roles": "/api/v2/credentials/1/object_roles/",
- "owner_users": "/api/v2/credentials/1/owner_users/",
- "owner_teams": "/api/v2/credentials/1/owner_teams/",
- "copy": "/api/v2/credentials/1/copy/",
- "input_sources": "/api/v2/credentials/1/input_sources/",
- "credential_type": "/api/v2/credential_types/23/",
- "user": "/api/v2/users/7/"
- },
- "summary_fields": {
- "organization": {
- "id": 1,
- "name": "Org",
- "description": ""
- },
- "credential_type": {
- "id": 1,
- "name": "Machine",
- "description": ""
- },
- "created_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "object_roles": {
- "admin_role": {
- "description": "Can manage all aspects of the credential",
- "name": "Admin",
- "id": 284
- },
- "use_role": {
- "description": "Can use the credential in a job template",
- "name": "Use",
- "id": 285
- },
- "read_role": {
- "description": "May view settings for the credential",
- "name": "Read",
- "id": 286
- }
- },
- "user_capabilities": {
- "edit": true,
- "delete": true,
- "copy": true,
- "use": true
- }
- },
- "created": "2019-12-17T16:12:25.258897Z",
- "modified": "2019-12-17T16:12:25.258920Z",
- "name": "Foo",
- "description": "Foo Description",
- "organization": 1,
- "credential_type": 1,
- "inputs": {
- "password": "$encrypted$",
- "username": "foo",
- "ssh_key_data": "$encrypted$",
- "become_method": "sudo",
- "become_password": "ASK",
- "become_username": "bar",
- "ssh_public_key_data": "$encrypted$",
- "authorize": true
- },
- "kind": null,
- "cloud": true,
- "kubernetes": false
- },
- {
- "id": 2,
- "type": "credential",
- "url": "/api/v2/credentials/2/",
- "related": {
- "created_by": "/api/v2/users/8/",
- "modified_by": "/api/v2/users/12/",
- "activity_stream": "/api/v2/credentials/2/activity_stream/",
- "access_list": "/api/v2/credentials/2/access_list/",
- "object_roles": "/api/v2/credentials/2/object_roles/",
- "owner_users": "/api/v2/credentials/2/owner_users/",
- "owner_teams": "/api/v2/credentials/2/owner_teams/",
- "copy": "/api/v2/credentials/2/copy/",
- "input_sources": "/api/v2/credentials/2/input_sources/",
- "credential_type": "/api/v2/credential_types/1/",
- "user": "/api/v2/users/7/"
- },
- "summary_fields": {
- "credential_type": {
- "id": 1,
- "name": "Machine",
- "description": ""
- },
- "created_by": {
- "id": 8,
- "username": "user-2",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 12,
- "username": "user-6",
- "first_name": "",
- "last_name": ""
- },
- "object_roles": {
- "admin_role": {
- "description": "Can manage all aspects of the credential",
- "name": "Admin",
- "id": 81
- },
- "use_role": {
- "description": "Can use the credential in a job template",
- "name": "Use",
- "id": 82
- },
- "read_role": {
- "description": "May view settings for the credential",
- "name": "Read",
- "id": 83
- }
- },
- "user_capabilities": {
- "edit": false,
- "delete": false,
- "copy": false,
- "use": false
- },
- "owners": [
- {
- "id": 7,
- "type": "user",
- "name": "user-1",
- "description": " ",
- "url": "/api/v2/users/7/"
- }
- ]
- },
- "created": "2019-12-16T21:04:40.896097Z",
- "modified": "2019-12-16T21:04:40.896121Z",
- "name": "Bar",
- "description": "",
- "organization": null,
- "credential_type": 1,
- "inputs": {},
- "kind": "ssh",
- "cloud": false,
- "kubernetes": false
- },
- {
- "id": 3,
- "type": "credential",
- "url": "/api/v2/credentials/3/",
- "related": {
- "created_by": "/api/v2/users/9/",
- "modified_by": "/api/v2/users/13/",
- "activity_stream": "/api/v2/credentials/3/activity_stream/",
- "access_list": "/api/v2/credentials/3/access_list/",
- "object_roles": "/api/v2/credentials/3/object_roles/",
- "owner_users": "/api/v2/credentials/3/owner_users/",
- "owner_teams": "/api/v2/credentials/3/owner_teams/",
- "copy": "/api/v2/credentials/3/copy/",
- "input_sources": "/api/v2/credentials/3/input_sources/",
- "credential_type": "/api/v2/credential_types/1/",
- "user": "/api/v2/users/8/"
- },
- "summary_fields": {
- "credential_type": {
- "id": 1,
- "name": "Machine",
- "description": ""
- },
- "created_by": {
- "id": 9,
- "username": "user-3",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 13,
- "username": "user-7",
- "first_name": "",
- "last_name": ""
- },
- "object_roles": {
- "admin_role": {
- "description": "Can manage all aspects of the credential",
- "name": "Admin",
- "id": 84
- },
- "use_role": {
- "description": "Can use the credential in a job template",
- "name": "Use",
- "id": 85
- },
- "read_role": {
- "description": "May view settings for the credential",
- "name": "Read",
- "id": 86
- }
- },
- "user_capabilities": {
- "edit": true,
- "delete": true,
- "copy": true,
- "use": true
- },
- "owners": [
- {
- "id": 8,
- "type": "user",
- "name": "user-2",
- "description": " ",
- "url": "/api/v2/users/8/"
- }
- ]
- },
- "created": "2019-12-16T21:04:40.955954Z",
- "modified": "2019-12-16T21:04:40.955976Z",
- "name": "Baz",
- "description": "",
- "organization": null,
- "credential_type": 1,
- "inputs": {},
- "kind": "ssh",
- "cloud": false,
- "kubernetes": false
- },
- {
- "id": 4,
- "type": "credential",
- "url": "/api/v2/credentials/4/",
- "related": {
- "created_by": "/api/v2/users/1/",
- "modified_by": "/api/v2/users/1/",
- "activity_stream": "/api/v2/credentials/4/activity_stream/",
- "access_list": "/api/v2/credentials/4/access_list/",
- "object_roles": "/api/v2/credentials/4/object_roles/",
- "owner_users": "/api/v2/credentials/4/owner_users/",
- "owner_teams": "/api/v2/credentials/4/owner_teams/",
- "copy": "/api/v2/credentials/4/copy/",
- "input_sources": "/api/v2/credentials/4/input_sources/",
- "credential_type": "/api/v2/credential_types/25/",
- "user": "/api/v2/users/1/"
- },
- "summary_fields": {
- "credential_type": {
- "id": 2,
- "name": "Vault",
- "description": ""
- },
- "created_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "object_roles": {
- "admin_role": {
- "description": "Can manage all aspects of the credential",
- "name": "Admin",
- "id": 318
- },
- "use_role": {
- "description": "Can use the credential in a job template",
- "name": "Use",
- "id": 319
- },
- "read_role": {
- "description": "May view settings for the credential",
- "name": "Read",
- "id": 320
- }
- },
- "user_capabilities": {
- "edit": true,
- "delete": true,
- "copy": true,
- "use": true
- },
- "owners": [
- {
- "id": 1,
- "type": "user",
- "name": "admin",
- "description": " ",
- "url": "/api/v2/users/1/"
- }
- ]
- },
- "created": "2019-12-18T16:31:09.772005Z",
- "modified": "2019-12-18T16:31:09.832666Z",
- "name": "FooBar",
- "description": "",
- "organization": null,
- "credential_type": 2,
- "inputs": {},
- "kind": null,
- "cloud": true,
- "kubernetes": false
- },
- {
- "id": 5,
- "type": "credential",
- "url": "/api/v2/credentials/5/",
- "related": {
- "created_by": "/api/v2/users/1/",
- "modified_by": "/api/v2/users/1/",
- "activity_stream": "/api/v2/credentials/5/activity_stream/",
- "access_list": "/api/v2/credentials/5/access_list/",
- "object_roles": "/api/v2/credentials/5/object_roles/",
- "owner_users": "/api/v2/credentials/5/owner_users/",
- "owner_teams": "/api/v2/credentials/5/owner_teams/",
- "copy": "/api/v2/credentials/5/copy/",
- "input_sources": "/api/v2/credentials/5/input_sources/",
- "credential_type": "/api/v2/credential_types/25/",
- "user": "/api/v2/users/1/"
- },
- "summary_fields": {
- "credential_type": {
- "id": 3,
- "name": "Source Control",
- "description": ""
- },
- "created_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "object_roles": {
- "admin_role": {
- "description": "Can manage all aspects of the credential",
- "name": "Admin",
- "id": 290
- },
- "use_role": {
- "description": "Can use the credential in a job template",
- "name": "Use",
- "id": 291
- },
- "read_role": {
- "description": "May view settings for the credential",
- "name": "Read",
- "id": 292
- }
- },
- "user_capabilities": {
- "edit": true,
- "delete": true,
- "copy": true,
- "use": true
- },
- "owners": [
- {
- "id": 1,
- "type": "user",
- "name": "admin",
- "description": " ",
- "url": "/api/v2/users/1/"
- }
- ]
- },
- "created": "2019-12-17T16:12:44.923123Z",
- "modified": "2019-12-17T16:12:44.923151Z",
- "name": "Qux",
- "description": "",
- "organization": null,
- "credential_type": 3,
- "inputs": {},
- "kind": null,
- "cloud": true,
- "kubernetes": false
- }
- ]
-}
diff --git a/awx/ui/src/screens/Credential/shared/data.cyberArkCredential.json b/awx/ui/src/screens/Credential/shared/data.cyberArkCredential.json
deleted file mode 100644
index 77428a30836a..000000000000
--- a/awx/ui/src/screens/Credential/shared/data.cyberArkCredential.json
+++ /dev/null
@@ -1,85 +0,0 @@
-{
- "id": 1,
- "type": "credential",
- "url": "/api/v2/credentials/1/",
- "related": {
- "named_url": "/api/v2/credentials/CyberArk Conjur Secrets Manager Lookup+external++/",
- "created_by": "/api/v2/users/1/",
- "modified_by": "/api/v2/users/1/",
- "activity_stream": "/api/v2/credentials/1/activity_stream/",
- "access_list": "/api/v2/credentials/1/access_list/",
- "object_roles": "/api/v2/credentials/1/object_roles/",
- "owner_users": "/api/v2/credentials/1/owner_users/",
- "owner_teams": "/api/v2/credentials/1/owner_teams/",
- "copy": "/api/v2/credentials/1/copy/",
- "input_sources": "/api/v2/credentials/1/input_sources/",
- "credential_type": "/api/v2/credential_types/20/",
- "user": "/api/v2/users/1/"
- },
- "summary_fields": {
- "credential_type": {
- "id": 20,
- "name": "CyberArk Conjur Secrets Manager Lookup",
- "description": ""
- },
- "created_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "object_roles": {
- "admin_role": {
- "description": "Can manage all aspects of the credential",
- "name": "Admin",
- "id": 27
- },
- "use_role": {
- "description": "Can use the credential in a job template",
- "name": "Use",
- "id": 28
- },
- "read_role": {
- "description": "May view settings for the credential",
- "name": "Read",
- "id": 29
- }
- },
- "user_capabilities": {
- "edit": true,
- "delete": true,
- "copy": true,
- "use": true
- },
- "owners": [
- {
- "id": 1,
- "type": "user",
- "name": "admin",
- "description": " ",
- "url": "/api/v2/users/1/"
- }
- ]
- },
- "created": "2020-05-19T12:51:36.956029Z",
- "modified": "2020-05-19T12:51:36.956086Z",
- "name": "CyberArk Conjur Secrets Manager Lookup",
- "description": "",
- "organization": null,
- "credential_type": 20,
- "inputs": {
- "url": "https://localhost",
- "account": "adsf",
- "api_key": "$encrypted$",
- "username": "adsf"
- },
- "kind": "conjur",
- "cloud": false,
- "kubernetes": false
-}
diff --git a/awx/ui/src/screens/Credential/shared/data.galaxyCredential.json b/awx/ui/src/screens/Credential/shared/data.galaxyCredential.json
deleted file mode 100644
index 09c2d0c59a38..000000000000
--- a/awx/ui/src/screens/Credential/shared/data.galaxyCredential.json
+++ /dev/null
@@ -1,89 +0,0 @@
-{
- "id": 5,
- "type": "credential",
- "url": "/api/v2/credentials/5/",
- "related": {
- "named_url": "/api/v2/credentials/Foo++Ansible Galaxy%2FAutomation Hub API Token+galaxy++Default/",
- "created_by": "/api/v2/users/1/",
- "modified_by": "/api/v2/users/1/",
- "organization": "/api/v2/organizations/1/",
- "activity_stream": "/api/v2/credentials/5/activity_stream/",
- "access_list": "/api/v2/credentials/5/access_list/",
- "object_roles": "/api/v2/credentials/5/object_roles/",
- "owner_users": "/api/v2/credentials/5/owner_users/",
- "owner_teams": "/api/v2/credentials/5/owner_teams/",
- "copy": "/api/v2/credentials/5/copy/",
- "input_sources": "/api/v2/credentials/5/input_sources/",
- "credential_type": "/api/v2/credential_types/42/"
- },
- "summary_fields": {
- "organization": {
- "id": 1,
- "name": "Baz",
- "description": ""
- },
- "credential_type": {
- "id": 42,
- "name": "Ansible Galaxy/Automation Hub API Token",
- "description": ""
- },
- "created_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "object_roles": {
- "admin_role": {
- "description": "Can manage all aspects of the credential",
- "name": "Admin",
- "id": 109
- },
- "use_role": {
- "description": "Can use the credential in a job template",
- "name": "Use",
- "id": 110
- },
- "read_role": {
- "description": "May view settings for the credential",
- "name": "Read",
- "id": 111
- }
- },
- "user_capabilities": {
- "edit": true,
- "delete": true,
- "copy": true,
- "use": true
- },
- "owners": [
- {
- "id": 1,
- "type": "organization",
- "name": "Default",
- "description": "",
- "url": "/api/v2/organizations/1/"
- }
- ]
- },
- "created": "2020-11-16T21:33:39.385284Z",
- "modified": "2020-11-16T21:33:39.385311Z",
- "name": "Foo",
- "description": "Bar",
- "organization": 1,
- "credential_type": 42,
- "managed": false,
- "inputs": {
- "url": "https://localhost.com",
- "auth_url": ""
- },
- "kind": "galaxy_api_token",
- "cloud": false,
- "kubernetes": false
-}
\ No newline at end of file
diff --git a/awx/ui/src/screens/Credential/shared/data.gceCredential.json b/awx/ui/src/screens/Credential/shared/data.gceCredential.json
deleted file mode 100644
index 9359b09a06c4..000000000000
--- a/awx/ui/src/screens/Credential/shared/data.gceCredential.json
+++ /dev/null
@@ -1,96 +0,0 @@
-{
- "id": 9,
- "type": "credential",
- "url": "/api/v2/credentials/9/",
- "related": {
- "named_url": "/api/v2/credentials/a gce cred++Google Compute Engine+cloud++Default/",
- "created_by": "/api/v2/users/1/",
- "modified_by": "/api/v2/users/1/",
- "organization": "/api/v2/organizations/4/",
- "activity_stream": "/api/v2/credentials/9/activity_stream/",
- "access_list": "/api/v2/credentials/9/access_list/",
- "object_roles": "/api/v2/credentials/9/object_roles/",
- "owner_users": "/api/v2/credentials/9/owner_users/",
- "owner_teams": "/api/v2/credentials/9/owner_teams/",
- "copy": "/api/v2/credentials/9/copy/",
- "input_sources": "/api/v2/credentials/9/input_sources/",
- "credential_type": "/api/v2/credential_types/10/"
- },
- "summary_fields": {
- "organization": {
- "id": 4,
- "name": "Default",
- "description": ""
- },
- "credential_type": {
- "id": 10,
- "name": "Google Compute Engine",
- "description": ""
- },
- "created_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "object_roles": {
- "admin_role": {
- "description": "Can manage all aspects of the credential",
- "name": "Admin",
- "id": 287
- },
- "use_role": {
- "description": "Can use the credential in a job template",
- "name": "Use",
- "id": 288
- },
- "read_role": {
- "description": "May view settings for the credential",
- "name": "Read",
- "id": 289
- }
- },
- "user_capabilities": {
- "edit": true,
- "delete": true,
- "copy": true,
- "use": true
- },
- "owners": [
- {
- "id": 1,
- "type": "user",
- "name": "admin",
- "description": " ",
- "url": "/api/v2/users/1/"
- },
- {
- "id": 4,
- "type": "organization",
- "name": "Default",
- "description": "",
- "url": "/api/v2/organizations/4/"
- }
- ]
- },
- "created": "2020-04-13T17:33:27.625773Z",
- "modified": "2020-04-13T17:33:27.625882Z",
- "name": "a gce cred",
- "description": "",
- "organization": 4,
- "credential_type": 10,
- "inputs": {
- "project": "test123",
- "username": "test123.iam.gserviceaccount.com",
- "ssh_key_data": "$encrypted$"
- },
- "kind": "gce",
- "cloud": true,
- "kubernetes": false
-}
diff --git a/awx/ui/src/screens/Credential/shared/data.hashiCorpCredential.json b/awx/ui/src/screens/Credential/shared/data.hashiCorpCredential.json
deleted file mode 100644
index 426b622c5804..000000000000
--- a/awx/ui/src/screens/Credential/shared/data.hashiCorpCredential.json
+++ /dev/null
@@ -1,82 +0,0 @@
-{
- "id": 11,
- "type": "credential",
- "url": "/api/v2/credentials/11/",
- "related": {
- "created_by": "/api/v2/users/1/",
- "modified_by": "/api/v2/users/1/",
- "activity_stream": "/api/v2/credentials/11/activity_stream/",
- "access_list": "/api/v2/credentials/11/access_list/",
- "object_roles": "/api/v2/credentials/11/object_roles/",
- "owner_users": "/api/v2/credentials/11/owner_users/",
- "owner_teams": "/api/v2/credentials/11/owner_teams/",
- "copy": "/api/v2/credentials/11/copy/",
- "input_sources": "/api/v2/credentials/11/input_sources/",
- "credential_type": "/api/v2/credential_types/21/",
- "user": "/api/v2/users/1/"
- },
- "summary_fields": {
- "credential_type": {
- "id": 21,
- "name": "HashiCorp Vault Secret Lookup",
- "description": ""
- },
- "created_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "object_roles": {
- "admin_role": {
- "description": "Can manage all aspects of the credential",
- "name": "Admin",
- "id": 57
- },
- "use_role": {
- "description": "Can use the credential in a job template",
- "name": "Use",
- "id": 58
- },
- "read_role": {
- "description": "May view settings for the credential",
- "name": "Read",
- "id": 59
- }
- },
- "user_capabilities": {
- "edit": true,
- "delete": true,
- "copy": true,
- "use": true
- },
- "owners": [
- {
- "id": 1,
- "type": "user",
- "name": "admin",
- "description": " ",
- "url": "/api/v2/users/1/"
- }
- ]
- },
- "created": "2020-05-26T14:54:00.674404Z",
- "modified": "2020-05-26T14:54:00.674418Z",
- "name": "HashiCorp Vault Secret Lookup",
- "description": "",
- "organization": null,
- "credential_type": 21,
- "inputs": {
- "url": "https://localhost",
- "api_version": "v1"
- },
- "kind": "hashivault_kv",
- "cloud": false,
- "kubernetes": false
-}
diff --git a/awx/ui/src/screens/Credential/shared/data.machineCredential.json b/awx/ui/src/screens/Credential/shared/data.machineCredential.json
deleted file mode 100644
index e2576ca4879a..000000000000
--- a/awx/ui/src/screens/Credential/shared/data.machineCredential.json
+++ /dev/null
@@ -1,92 +0,0 @@
-{
- "id": 3,
- "type": "credential",
- "url": "/api/v2/credentials/3/",
- "related": {
- "named_url": "/api/v2/credentials/oersdgfasf++Machine+ssh++org/",
- "created_by": "/api/v2/users/1/",
- "modified_by": "/api/v2/users/1/",
- "organization": "/api/v2/organizations/1/",
- "activity_stream": "/api/v2/credentials/3/activity_stream/",
- "access_list": "/api/v2/credentials/3/access_list/",
- "object_roles": "/api/v2/credentials/3/object_roles/",
- "owner_users": "/api/v2/credentials/3/owner_users/",
- "owner_teams": "/api/v2/credentials/3/owner_teams/",
- "copy": "/api/v2/credentials/3/copy/",
- "input_sources": "/api/v2/credentials/3/input_sources/",
- "credential_type": "/api/v2/credential_types/1/"
- },
- "summary_fields": {
- "organization": {
- "id": 1,
- "name": "org",
- "description": ""
- },
- "credential_type": {
- "id": 1,
- "name": "Machine",
- "description": ""
- },
- "created_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "object_roles": {
- "admin_role": {
- "description": "Can manage all aspects of the credential",
- "name": "Admin",
- "id": 36
- },
- "use_role": {
- "description": "Can use the credential in a job template",
- "name": "Use",
- "id": 37
- },
- "read_role": {
- "description": "May view settings for the credential",
- "name": "Read",
- "id": 38
- }
- },
- "user_capabilities": {
- "edit": true,
- "delete": true,
- "copy": true,
- "use": true
- },
- "owners": [
- {
- "id": 1,
- "type": "user",
- "name": "admin",
- "description": " ",
- "url": "/api/v2/users/1/"
- },
- {
- "id": 1,
- "type": "organization",
- "name": "org",
- "description": "",
- "url": "/api/v2/organizations/1/"
- }
- ]
- },
- "created": "2020-02-18T15:35:04.563928Z",
- "modified": "2020-02-18T15:35:04.563957Z",
- "name": "oersdgfasf",
- "description": "",
- "organization": 1,
- "credential_type": 1,
- "inputs": {},
- "kind": "ssh",
- "cloud": false,
- "kubernetes": false
-}
diff --git a/awx/ui/src/screens/Credential/shared/data.orgCredential.json b/awx/ui/src/screens/Credential/shared/data.orgCredential.json
deleted file mode 100644
index 621c3a43ad62..000000000000
--- a/awx/ui/src/screens/Credential/shared/data.orgCredential.json
+++ /dev/null
@@ -1,92 +0,0 @@
-{
- "id": 3,
- "type": "credential",
- "url": "/api/v2/credentials/3/",
- "related": {
- "named_url": "/api/v2/credentials/oersdgfasf++Machine+ssh++org/",
- "created_by": "/api/v2/users/1/",
- "modified_by": "/api/v2/users/1/",
- "organization": "/api/v2/organizations/1/",
- "activity_stream": "/api/v2/credentials/3/activity_stream/",
- "access_list": "/api/v2/credentials/3/access_list/",
- "object_roles": "/api/v2/credentials/3/object_roles/",
- "owner_users": "/api/v2/credentials/3/owner_users/",
- "owner_teams": "/api/v2/credentials/3/owner_teams/",
- "copy": "/api/v2/credentials/3/copy/",
- "input_sources": "/api/v2/credentials/3/input_sources/",
- "credential_type": "/api/v2/credential_types/1/"
- },
- "summary_fields": {
- "organization": {
- "id": 1,
- "name": "org",
- "description": ""
- },
- "credential_type": {
- "id": 1,
- "name": "Machine",
- "description": ""
- },
- "created_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "object_roles": {
- "admin_role": {
- "description": "Can manage all aspects of the credential",
- "name": "Admin",
- "id": 36
- },
- "use_role": {
- "description": "Can use the credential in a job template",
- "name": "Use",
- "id": 37
- },
- "read_role": {
- "description": "May view settings for the credential",
- "name": "Read",
- "id": 38
- }
- },
- "user_capabilities": {
- "edit": true,
- "delete": true,
- "copy": true,
- "use": true
- },
- "owners": [
- {
- "id": 1,
- "type": "user",
- "name": "admin",
- "description": " ",
- "url": "/api/v2/users/1/"
- },
- {
- "id": 1,
- "type": "organization",
- "name": "org",
- "description": "",
- "url": "/api/v2/organizations/1/"
- }
- ]
- },
- "created": "2020-02-18T15:35:04.563928Z",
- "modified": "2020-02-18T15:35:04.563957Z",
- "name": "oersdgfasf",
- "description": "",
- "organization": 1,
- "credential_type": 1,
- "inputs": {},
- "kind": "ssh",
- "cloud": false,
- "kubernetes": false
-}
diff --git a/awx/ui/src/screens/Credential/shared/data.scmCredential.json b/awx/ui/src/screens/Credential/shared/data.scmCredential.json
deleted file mode 100644
index c57ee690e580..000000000000
--- a/awx/ui/src/screens/Credential/shared/data.scmCredential.json
+++ /dev/null
@@ -1,84 +0,0 @@
-{
- "id": 2,
- "type": "credential",
- "url": "/api/v2/credentials/2/",
- "related": {
- "named_url": "/api/v2/credentials/jojoijoij++Source Control+scm++/",
- "created_by": "/api/v2/users/1/",
- "modified_by": "/api/v2/users/1/",
- "activity_stream": "/api/v2/credentials/2/activity_stream/",
- "access_list": "/api/v2/credentials/2/access_list/",
- "object_roles": "/api/v2/credentials/2/object_roles/",
- "owner_users": "/api/v2/credentials/2/owner_users/",
- "owner_teams": "/api/v2/credentials/2/owner_teams/",
- "copy": "/api/v2/credentials/2/copy/",
- "input_sources": "/api/v2/credentials/2/input_sources/",
- "credential_type": "/api/v2/credential_types/2/",
- "user": "/api/v2/users/1/"
- },
- "summary_fields": {
- "credential_type": {
- "id": 2,
- "name": "Source Control",
- "description": ""
- },
- "created_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "object_roles": {
- "admin_role": {
- "description": "Can manage all aspects of the credential",
- "name": "Admin",
- "id": 6
- },
- "use_role": {
- "description": "Can use the credential in a job template",
- "name": "Use",
- "id": 7
- },
- "read_role": {
- "description": "May view settings for the credential",
- "name": "Read",
- "id": 8
- }
- },
- "user_capabilities": {
- "edit": true,
- "delete": true,
- "copy": true,
- "use": true
- },
- "owners": [
- {
- "id": 1,
- "type": "user",
- "name": "admin",
- "description": " ",
- "url": "/api/v2/users/1/"
- }
- ]
- },
- "created": "2020-02-12T19:59:11.508933Z",
- "modified": "2020-02-12T19:59:11.508958Z",
- "name": "jojoijoij",
- "description": "",
- "organization": null,
- "credential_type": 2,
- "inputs": {
- "password": "$encrypted$",
- "username": "uujoij",
- "ssh_key_unlock": "$encrypted$"
- },
- "kind": "scm",
- "cloud": false,
- "kubernetes": false
-}
diff --git a/awx/ui/src/screens/Credential/shared/data.towerCredential.json b/awx/ui/src/screens/Credential/shared/data.towerCredential.json
deleted file mode 100644
index c3e7efb139f5..000000000000
--- a/awx/ui/src/screens/Credential/shared/data.towerCredential.json
+++ /dev/null
@@ -1,85 +0,0 @@
-{
- "id": 4,
- "type": "credential",
- "url": "/api/v2/credentials/4/",
- "related": {
- "named_url": "/api/v2/credentials/Tower cred++Ansible Controller+cloud++/",
- "created_by": "/api/v2/users/2/",
- "modified_by": "/api/v2/users/2/",
- "activity_stream": "/api/v2/credentials/4/activity_stream/",
- "access_list": "/api/v2/credentials/4/access_list/",
- "object_roles": "/api/v2/credentials/4/object_roles/",
- "owner_users": "/api/v2/credentials/4/owner_users/",
- "owner_teams": "/api/v2/credentials/4/owner_teams/",
- "copy": "/api/v2/credentials/4/copy/",
- "input_sources": "/api/v2/credentials/4/input_sources/",
- "credential_type": "/api/v2/credential_types/15/",
- "user": "/api/v2/users/2/"
- },
- "summary_fields": {
- "credential_type": {
- "id": 16,
- "name": "Ansible Controller",
- "description": ""
- },
- "created_by": {
- "id": 2,
- "username": "test",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 2,
- "username": "test",
- "first_name": "",
- "last_name": ""
- },
- "object_roles": {
- "admin_role": {
- "description": "Can manage all aspects of the credential",
- "name": "Admin",
- "id": 37
- },
- "use_role": {
- "description": "Can use the credential in a job template",
- "name": "Use",
- "id": 38
- },
- "read_role": {
- "description": "May view settings for the credential",
- "name": "Read",
- "id": 39
- }
- },
- "user_capabilities": {
- "edit": true,
- "delete": true,
- "copy": true,
- "use": true
- },
- "owners": [
- {
- "id": 2,
- "type": "user",
- "name": "test",
- "description": " ",
- "url": "/api/v2/users/2/"
- }
- ]
- },
- "created": "2021-04-20T14:05:16.538312Z",
- "modified": "2021-04-20T14:05:26.177919Z",
- "name": "Tower cred",
- "description": "",
- "organization": null,
- "credential_type": 16,
- "managed": false,
- "inputs": {
- "host": "https://localhost",
- "username": "",
- "verify_ssl": false
- },
- "kind": "tower",
- "cloud": true,
- "kubernetes": false
-}
diff --git a/awx/ui/src/screens/Credential/shared/index.js b/awx/ui/src/screens/Credential/shared/index.js
deleted file mode 100644
index 28dda5128aa7..000000000000
--- a/awx/ui/src/screens/Credential/shared/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export { default as mockCredentials } from './data.credentials.json';
-export { default as mockCredentialType } from './data.credential_type.json';
-export { default as ExternalTestModal } from './ExternalTestModal';
diff --git a/awx/ui/src/screens/CredentialType/CredentialType.js b/awx/ui/src/screens/CredentialType/CredentialType.js
deleted file mode 100644
index 452662909768..000000000000
--- a/awx/ui/src/screens/CredentialType/CredentialType.js
+++ /dev/null
@@ -1,122 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-import {
- Link,
- Redirect,
- Route,
- Switch,
- useLocation,
- useParams,
-} from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { Card, PageSection } from '@patternfly/react-core';
-import { CaretLeftIcon } from '@patternfly/react-icons';
-
-import useRequest from 'hooks/useRequest';
-import { CredentialTypesAPI } from 'api';
-import RoutedTabs from 'components/RoutedTabs';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-
-import CredentialTypeDetails from './CredentialTypeDetails';
-import CredentialTypeEdit from './CredentialTypeEdit';
-
-function CredentialType({ setBreadcrumb }) {
- const { id } = useParams();
- const { pathname } = useLocation();
-
- const {
- isLoading,
- error: contentError,
- request: fetchCredentialTypes,
- result: credentialType,
- } = useRequest(
- useCallback(async () => {
- const { data } = await CredentialTypesAPI.readDetail(id);
- return data;
- }, [id])
- );
-
- useEffect(() => {
- fetchCredentialTypes();
- }, [fetchCredentialTypes, pathname]);
-
- useEffect(() => {
- if (credentialType) {
- setBreadcrumb(credentialType);
- }
- }, [credentialType, setBreadcrumb]);
-
- const tabsArray = [
- {
- name: (
- <>
-
- {t`Back to credential types`}
- >
- ),
- link: '/credential_types',
- id: 99,
- isBackButton: true,
- },
- {
- name: t`Details`,
- link: `/credential_types/${id}/details`,
- id: 0,
- },
- ];
-
- if (!isLoading && contentError) {
- return (
-
-
-
- {contentError.response?.status === 404 && (
-
- {t`Credential type not found.`}{' '}
-
- {t`View all credential types`}
-
-
- )}
-
-
-
- );
- }
-
- let cardHeader = ;
- if (pathname.endsWith('edit')) {
- cardHeader = null;
- }
-
- return (
-
-
- {cardHeader}
- {isLoading && }
- {!isLoading && credentialType && (
-
-
- {credentialType && (
- <>
-
-
-
-
-
-
- >
- )}
-
- )}
-
-
- );
-}
-
-export default CredentialType;
diff --git a/awx/ui/src/screens/CredentialType/CredentialType.test.js b/awx/ui/src/screens/CredentialType/CredentialType.test.js
deleted file mode 100644
index d003706dd4f3..000000000000
--- a/awx/ui/src/screens/CredentialType/CredentialType.test.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-
-import { CredentialTypesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../testUtils/enzymeHelpers';
-
-import CredentialType from './CredentialType';
-
-jest.mock('../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useRouteMatch: () => ({
- url: '/credential_types',
- }),
- useParams: () => ({ id: 42 }),
-}));
-
-describe('', () => {
- let wrapper;
- test('should render details properly', async () => {
- await act(async () => {
- wrapper = mountWithContexts( {}} />);
- });
- wrapper.update();
- expect(wrapper.find('CredentialType').length).toBe(1);
- expect(CredentialTypesAPI.readDetail).toBeCalledWith(42);
- });
-
- test('should render expected tabs', async () => {
- const expectedTabs = ['Back to credential types', 'Details'];
- await act(async () => {
- wrapper = mountWithContexts( {}} />);
- });
- wrapper.find('RoutedTabs li').forEach((tab, index) => {
- expect(tab.text()).toEqual(expectedTabs[index]);
- });
- });
-
- test('should show content error when user attempts to navigate to erroneous route', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/credential_types/42/foobar'],
- });
- await act(async () => {
- wrapper = mountWithContexts( {}} />, {
- context: {
- router: {
- history,
- },
- },
- });
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-});
diff --git a/awx/ui/src/screens/CredentialType/CredentialTypeAdd/CredentialTypeAdd.js b/awx/ui/src/screens/CredentialType/CredentialTypeAdd/CredentialTypeAdd.js
deleted file mode 100644
index f65148b5a79a..000000000000
--- a/awx/ui/src/screens/CredentialType/CredentialTypeAdd/CredentialTypeAdd.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import React, { useState } from 'react';
-import { Card, PageSection } from '@patternfly/react-core';
-import { useHistory } from 'react-router-dom';
-
-import { CardBody } from 'components/Card';
-import { CredentialTypesAPI } from 'api';
-import { parseVariableField } from 'util/yaml';
-import CredentialTypeForm from '../shared/CredentialTypeForm';
-
-function CredentialTypeAdd() {
- const history = useHistory();
- const [submitError, setSubmitError] = useState(null);
-
- const handleSubmit = async (values) => {
- try {
- const { data: response } = await CredentialTypesAPI.create({
- ...values,
- injectors: parseVariableField(values.injectors),
- inputs: parseVariableField(values.inputs),
- kind: 'cloud',
- });
- history.push(`/credential_types/${response.id}/details`);
- } catch (error) {
- setSubmitError(error);
- }
- };
-
- const handleCancel = () => {
- history.push(`/credential_types`);
- };
-
- return (
-
-
-
-
-
-
-
- );
-}
-
-export default CredentialTypeAdd;
diff --git a/awx/ui/src/screens/CredentialType/CredentialTypeAdd/CredentialTypeAdd.test.js b/awx/ui/src/screens/CredentialType/CredentialTypeAdd/CredentialTypeAdd.test.js
deleted file mode 100644
index b542dcb5765c..000000000000
--- a/awx/ui/src/screens/CredentialType/CredentialTypeAdd/CredentialTypeAdd.test.js
+++ /dev/null
@@ -1,96 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-
-import { CredentialTypesAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import CredentialTypeAdd from './CredentialTypeAdd';
-
-jest.mock('../../../api');
-
-const credentialTypeData = {
- name: 'Foo',
- description: 'Bar',
- kind: 'cloud',
- inputs: JSON.stringify({
- fields: [
- {
- id: 'username',
- type: 'string',
- label: 'Jenkins username',
- },
- {
- id: 'password',
- type: 'string',
- label: 'Jenkins password',
- secret: true,
- },
- ],
- required: ['username', 'password'],
- }),
- injectors: JSON.stringify({
- extra_vars: {
- Jenkins_password: '{{ password }}',
- Jenkins_username: '{{ username }}',
- },
- }),
-};
-
-describe('', () => {
- let wrapper;
- let history;
-
- beforeEach(async () => {
- history = createMemoryHistory({
- initialEntries: ['/credential_types'],
- });
- CredentialTypesAPI.create.mockResolvedValue({
- data: {
- id: 42,
- },
- });
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('handleSubmit should call the api and redirect to details page', async () => {
- await act(async () => {
- wrapper.find('CredentialTypeForm').prop('onSubmit')(credentialTypeData);
- });
- wrapper.update();
- expect(CredentialTypesAPI.create).toHaveBeenCalledWith({
- ...credentialTypeData,
- inputs: JSON.parse(credentialTypeData.inputs),
- injectors: JSON.parse(credentialTypeData.injectors),
- });
- expect(history.location.pathname).toBe('/credential_types/42/details');
- });
-
- test('handleCancel should return the user back to the credential types list', async () => {
- wrapper.find('Button[aria-label="Cancel"]').simulate('click');
- expect(history.location.pathname).toEqual('/credential_types');
- });
-
- test('failed form submission should show an error message', async () => {
- const error = {
- response: {
- data: { detail: 'An error occurred' },
- },
- };
- CredentialTypesAPI.create.mockImplementationOnce(() =>
- Promise.reject(error)
- );
- await act(async () => {
- wrapper.find('CredentialTypeForm').invoke('onSubmit')(credentialTypeData);
- });
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/CredentialType/CredentialTypeAdd/index.js b/awx/ui/src/screens/CredentialType/CredentialTypeAdd/index.js
deleted file mode 100644
index 47ad6341d0ee..000000000000
--- a/awx/ui/src/screens/CredentialType/CredentialTypeAdd/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './CredentialTypeAdd';
diff --git a/awx/ui/src/screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js b/awx/ui/src/screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js
deleted file mode 100644
index 623120ba2da7..000000000000
--- a/awx/ui/src/screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js
+++ /dev/null
@@ -1,143 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-
-import { t } from '@lingui/macro';
-import { Link, useHistory } from 'react-router-dom';
-import { Button } from '@patternfly/react-core';
-
-import { VariablesDetail } from 'components/CodeEditor';
-import AlertModal from 'components/AlertModal';
-import { CardBody, CardActionsRow } from 'components/Card';
-import DeleteButton from 'components/DeleteButton';
-import { Detail, DetailList, UserDateDetail } from 'components/DetailList';
-import useRequest, { useDismissableError } from 'hooks/useRequest';
-import { CredentialTypesAPI } from 'api';
-import { jsonToYaml } from 'util/yaml';
-import {
- relatedResourceDeleteRequests,
- getRelatedResourceDeleteCounts,
-} from 'util/getRelatedResourceDeleteDetails';
-import ErrorDetail from 'components/ErrorDetail';
-
-function CredentialTypeDetails({ credentialType }) {
- const { id, name, description, injectors, inputs } = credentialType;
- const history = useHistory();
-
- const {
- request: deleteCredentialType,
- isLoading,
- error: deleteError,
- } = useRequest(
- useCallback(async () => {
- await CredentialTypesAPI.destroy(id);
- history.push(`/credential_types`);
- }, [id, history])
- );
-
- const {
- result: { isDeleteDisabled },
- error: deleteDetailsError,
- request: fetchDeleteDetails,
- } = useRequest(
- useCallback(async () => {
- const { results: deleteDetails, error } =
- await getRelatedResourceDeleteCounts(
- relatedResourceDeleteRequests.credentialType(credentialType)
- );
- if (error) {
- throw new Error(error);
- }
- if (deleteDetails) {
- return { isDeleteDisabled: true };
- }
- return { isDeleteDisabled: false };
- }, [credentialType]),
- { isDeleteDisabled: false }
- );
-
- useEffect(() => {
- fetchDeleteDetails();
- }, [fetchDeleteDetails]);
- const { error, dismissError } = useDismissableError(
- deleteError || deleteDetailsError
- );
-
- return (
-
-
-
-
-
-
-
-
-
-
- {credentialType.summary_fields.user_capabilities &&
- credentialType.summary_fields.user_capabilities.edit && (
-
- )}
- {credentialType.summary_fields.user_capabilities &&
- credentialType.summary_fields.user_capabilities.delete && (
-
- {t`Delete`}
-
- )}
-
-
- {error && (
-
-
-
- )}
-
- );
-}
-
-export default CredentialTypeDetails;
diff --git a/awx/ui/src/screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.test.js b/awx/ui/src/screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.test.js
deleted file mode 100644
index 2deb699f8fe9..000000000000
--- a/awx/ui/src/screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.test.js
+++ /dev/null
@@ -1,171 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-
-import { CredentialTypesAPI, CredentialsAPI } from 'api';
-import { jsonToYaml } from 'util/yaml';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import CredentialTypeDetails from './CredentialTypeDetails';
-
-jest.mock('../../../api');
-
-const credentialTypeData = {
- name: 'Foo',
- description: 'Bar',
- kind: 'cloud',
- inputs: {
- fields: [
- {
- id: 'username',
- type: 'string',
- label: 'Jenkins username',
- },
- {
- id: 'password',
- type: 'string',
- label: 'Jenkins password',
- secret: true,
- },
- ],
- required: ['username', 'password'],
- },
- injectors: {
- extra_vars: {
- Jenkins_password: '{{ password }}',
- Jenkins_username: '{{ username }}',
- },
- },
- summary_fields: {
- created_by: {
- id: 1,
- username: 'admin',
- first_name: '',
- last_name: '',
- },
- modified_by: {
- id: 1,
- username: 'admin',
- first_name: '',
- last_name: '',
- },
- user_capabilities: {
- edit: true,
- delete: true,
- },
- },
- created: '2020-06-25T16:52:36.127008Z',
- modified: '2020-06-25T16:52:36.127022Z',
-};
-
-function expectDetailToMatch(wrapper, label, value) {
- const detail = wrapper.find(`Detail[label="${label}"]`);
- expect(detail).toHaveLength(1);
- expect(detail.prop('value')).toEqual(value);
-}
-
-describe('', () => {
- let wrapper;
- afterEach(() => {
- jest.clearAllMocks();
- });
- test('should render details properly', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
- expectDetailToMatch(wrapper, 'Name', credentialTypeData.name);
- expectDetailToMatch(wrapper, 'Description', credentialTypeData.description);
- const dates = wrapper.find('UserDateDetail');
- expect(dates).toHaveLength(2);
- expect(dates.at(0).prop('date')).toEqual(credentialTypeData.created);
- expect(dates.at(1).prop('date')).toEqual(credentialTypeData.modified);
- const vars = wrapper.find('VariablesDetail');
- expect(vars).toHaveLength(2);
-
- expect(vars.at(0).prop('label')).toEqual('Input configuration');
- expect(vars.at(0).prop('value')).toEqual(
- jsonToYaml(JSON.stringify(credentialTypeData.inputs))
- );
- expect(vars.at(1).prop('label')).toEqual('Injector configuration');
- expect(vars.at(1).prop('value')).toEqual(
- jsonToYaml(JSON.stringify(credentialTypeData.injectors))
- );
- });
-
- test('should disabled delete and show proper tooltip requests', async () => {
- CredentialsAPI.read.mockResolvedValue({ data: { count: 15 } });
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
-
- expect(wrapper.find('DeleteButton').prop('disabledTooltip')).toBe(
- 'This credential type is currently being used by some credentials and cannot be deleted'
- );
- expect(wrapper.find('Button[aria-label="Delete"]').prop('isDisabled')).toBe(
- true
- );
- expect(wrapper.find('Tooltip').length).toBe(1);
- expect(wrapper.find('Tooltip').prop('content')).toBe(
- 'This credential type is currently being used by some credentials and cannot be deleted'
- );
- });
-
- test('should throw error', async () => {
- CredentialsAPI.read.mockRejectedValue(new Error('error'));
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
- expect(wrapper.find('ErrorDetail').length).toBe(1);
- });
-
- test('expected api call is made for delete', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/credential_types/42/details'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
- await act(async () => {
- wrapper.find('DeleteButton').invoke('onConfirm')();
- });
- expect(CredentialTypesAPI.destroy).toHaveBeenCalledTimes(1);
- expect(history.location.pathname).toBe('/credential_types');
- });
-
- test('should not render delete button', async () => {
- credentialTypeData.summary_fields.user_capabilities.delete = false;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
-
- expect(wrapper.find('Button[aria-label="Delete"]').length).toBe(0);
- });
- test('should not render edit button', async () => {
- credentialTypeData.summary_fields.user_capabilities.edit = false;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
-
- expect(wrapper.find('Button[aria-label="Edit"]').length).toBe(0);
- });
-});
diff --git a/awx/ui/src/screens/CredentialType/CredentialTypeDetails/index.js b/awx/ui/src/screens/CredentialType/CredentialTypeDetails/index.js
deleted file mode 100644
index d2f8e7c13d51..000000000000
--- a/awx/ui/src/screens/CredentialType/CredentialTypeDetails/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './CredentialTypeDetails';
diff --git a/awx/ui/src/screens/CredentialType/CredentialTypeEdit/CredentialTypeEdit.js b/awx/ui/src/screens/CredentialType/CredentialTypeEdit/CredentialTypeEdit.js
deleted file mode 100644
index 5bb35cb76825..000000000000
--- a/awx/ui/src/screens/CredentialType/CredentialTypeEdit/CredentialTypeEdit.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import React, { useState } from 'react';
-import { useHistory } from 'react-router-dom';
-
-import { CardBody } from 'components/Card';
-import { CredentialTypesAPI } from 'api';
-import { parseVariableField } from 'util/yaml';
-import CredentialTypeForm from '../shared/CredentialTypeForm';
-
-function CredentialTypeEdit({ credentialType }) {
- const history = useHistory();
- const [submitError, setSubmitError] = useState(null);
- const detailsUrl = `/credential_types/${credentialType.id}/details`;
-
- const handleSubmit = async (values) => {
- try {
- await CredentialTypesAPI.update(credentialType.id, {
- ...values,
- injectors: parseVariableField(values.injectors),
- inputs: parseVariableField(values.inputs),
- });
- history.push(detailsUrl);
- } catch (error) {
- setSubmitError(error);
- }
- };
-
- const handleCancel = () => {
- history.push(detailsUrl);
- };
- return (
-
-
-
- );
-}
-
-export default CredentialTypeEdit;
diff --git a/awx/ui/src/screens/CredentialType/CredentialTypeEdit/CredentialTypeEdit.test.js b/awx/ui/src/screens/CredentialType/CredentialTypeEdit/CredentialTypeEdit.test.js
deleted file mode 100644
index 936bc0c198ec..000000000000
--- a/awx/ui/src/screens/CredentialType/CredentialTypeEdit/CredentialTypeEdit.test.js
+++ /dev/null
@@ -1,139 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-
-import { CredentialTypesAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import CredentialTypeEdit from './CredentialTypeEdit';
-
-jest.mock('../../../api');
-
-const credentialTypeData = {
- id: 42,
- name: 'Foo',
- description: 'New credential',
- kind: 'cloud',
- inputs: JSON.stringify({
- fields: [
- {
- id: 'username',
- type: 'string',
- label: 'Jenkins username',
- },
- {
- id: 'password',
- type: 'string',
- label: 'Jenkins password',
- secret: true,
- },
- ],
- required: ['username', 'password'],
- }),
- injectors: JSON.stringify({
- extra_vars: {
- Jenkins_password: '{{ password }}',
- Jenkins_username: '{{ username }}',
- },
- }),
- summary_fields: {
- created_by: {
- id: 1,
- username: 'admin',
- first_name: '',
- last_name: '',
- },
- modified_by: {
- id: 1,
- username: 'admin',
- first_name: '',
- last_name: '',
- },
- user_capabilities: {
- edit: true,
- delete: true,
- },
- },
- created: '2020-06-25T16:52:36.127008Z',
- modified: '2020-06-25T16:52:36.127022Z',
-};
-
-const updateCredentialTypeData = {
- name: 'Bar',
- description: 'Updated new Credential Type',
- injectors: credentialTypeData.injectors,
- inputs: credentialTypeData.inputs,
-};
-
-describe('', () => {
- let wrapper;
- let history;
-
- beforeAll(async () => {
- history = createMemoryHistory();
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('handleSubmit should call the api and redirect to details page', async () => {
- await act(async () => {
- wrapper.find('CredentialTypeForm').invoke('onSubmit')(
- updateCredentialTypeData
- );
- wrapper.update();
- expect(CredentialTypesAPI.update).toHaveBeenCalledWith(42, {
- ...updateCredentialTypeData,
- injectors: JSON.parse(credentialTypeData.injectors),
- inputs: JSON.parse(credentialTypeData.inputs),
- });
- });
- });
-
- test('should navigate to credential types detail when cancel is clicked', async () => {
- await act(async () => {
- wrapper.find('button[aria-label="Cancel"]').prop('onClick')();
- });
- expect(history.location.pathname).toEqual('/credential_types/42/details');
- });
-
- test('should navigate to credential type detail after successful submission', async () => {
- await act(async () => {
- wrapper.find('CredentialTypeForm').invoke('onSubmit')({
- ...updateCredentialTypeData,
- injectors: JSON.parse(credentialTypeData.injectors),
- inputs: JSON.parse(credentialTypeData.inputs),
- });
- });
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(0);
- expect(history.location.pathname).toEqual('/credential_types/42/details');
- });
-
- test('failed form submission should show an error message', async () => {
- const error = {
- response: {
- data: { detail: 'An error occurred' },
- },
- };
- CredentialTypesAPI.update.mockImplementationOnce(() =>
- Promise.reject(error)
- );
- await act(async () => {
- wrapper.find('CredentialTypeForm').invoke('onSubmit')(
- updateCredentialTypeData
- );
- });
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/CredentialType/CredentialTypeEdit/index.js b/awx/ui/src/screens/CredentialType/CredentialTypeEdit/index.js
deleted file mode 100644
index 8a35d6b6fdfe..000000000000
--- a/awx/ui/src/screens/CredentialType/CredentialTypeEdit/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './CredentialTypeEdit';
diff --git a/awx/ui/src/screens/CredentialType/CredentialTypeList/CredentialTypeList.js b/awx/ui/src/screens/CredentialType/CredentialTypeList/CredentialTypeList.js
deleted file mode 100644
index d70dddb85200..000000000000
--- a/awx/ui/src/screens/CredentialType/CredentialTypeList/CredentialTypeList.js
+++ /dev/null
@@ -1,215 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-import { useLocation, useRouteMatch } from 'react-router-dom';
-
-import { t, Plural } from '@lingui/macro';
-import { Card, PageSection } from '@patternfly/react-core';
-
-import { CredentialTypesAPI } from 'api';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import useRequest, { useDeleteItems } from 'hooks/useRequest';
-import useSelected from 'hooks/useSelected';
-import PaginatedTable, {
- HeaderRow,
- HeaderCell,
- ToolbarDeleteButton,
- ToolbarAddButton,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import ErrorDetail from 'components/ErrorDetail';
-import AlertModal from 'components/AlertModal';
-import DatalistToolbar from 'components/DataListToolbar';
-import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
-import CredentialTypeListItem from './CredentialTypeListItem';
-
-const QS_CONFIG = getQSConfig('credential-type', {
- page: 1,
- page_size: 20,
- managed: false,
-});
-
-function CredentialTypeList() {
- const location = useLocation();
- const match = useRouteMatch();
-
- const {
- error: contentError,
- isLoading,
- request: fetchCredentialTypes,
- result: {
- credentialTypes,
- credentialTypesCount,
- actions,
- relatedSearchableKeys,
- searchableKeys,
- },
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
-
- const [response, responseActions] = await Promise.all([
- CredentialTypesAPI.read(params),
- CredentialTypesAPI.readOptions(),
- ]);
-
- return {
- credentialTypes: response.data.results,
- credentialTypesCount: response.data.count,
- actions: responseActions.data.actions,
- relatedSearchableKeys: (
- responseActions?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(responseActions.data.actions?.GET),
- };
- }, [location]),
- {
- credentialTypes: [],
- credentialTypesCount: 0,
- actions: {},
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- useEffect(() => {
- fetchCredentialTypes();
- }, [fetchCredentialTypes]);
-
- const { selected, isAllSelected, handleSelect, clearSelected, selectAll } =
- useSelected(credentialTypes);
-
- const {
- isLoading: deleteLoading,
- deletionError,
- deleteItems: deleteCredentialTypes,
- clearDeletionError,
- } = useDeleteItems(
- useCallback(
- () =>
- Promise.all(selected.map(({ id }) => CredentialTypesAPI.destroy(id))),
- [selected]
- ),
- {
- qsConfig: QS_CONFIG,
- allItemsSelected: isAllSelected,
- fetchItems: fetchCredentialTypes,
- }
- );
-
- const handleDelete = async () => {
- await deleteCredentialTypes();
- clearSelected();
- };
-
- const canAdd = actions && actions.POST;
-
- const deleteDetailsRequests = relatedResourceDeleteRequests.credentialType(
- selected[0]
- );
-
- return (
- <>
-
-
- (
- ,
- ]
- : []),
-
- }
- />,
- ]}
- />
- )}
- headerRow={
-
- {t`Name`}
- {t`Actions`}
-
- }
- renderRow={(credentialType, index) => (
- handleSelect(credentialType)}
- isSelected={selected.some(
- (row) => row.id === credentialType.id
- )}
- rowIndex={index}
- />
- )}
- emptyStateControls={
- canAdd && (
-
- )
- }
- />
-
-
-
- {t`Failed to delete one or more credential types.`}
-
-
- >
- );
-}
-
-export default CredentialTypeList;
diff --git a/awx/ui/src/screens/CredentialType/CredentialTypeList/CredentialTypeList.test.js b/awx/ui/src/screens/CredentialType/CredentialTypeList/CredentialTypeList.test.js
deleted file mode 100644
index 22e11b1167cb..000000000000
--- a/awx/ui/src/screens/CredentialType/CredentialTypeList/CredentialTypeList.test.js
+++ /dev/null
@@ -1,177 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { CredentialTypesAPI, CredentialsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import CredentialTypeList from './CredentialTypeList';
-
-jest.mock('../../../api/models/CredentialTypes');
-jest.mock('../../../api/models/Credentials');
-
-const credentialTypes = {
- data: {
- results: [
- {
- id: 1,
- name: 'Foo',
- kind: 'cloud',
- summary_fields: {
- user_capabilities: { edit: true, delete: true },
- },
- url: '',
- },
- {
- id: 2,
- name: 'Bar',
- kind: 'cloud',
- summary_fields: {
- user_capabilities: { edit: false, delete: true },
- },
- url: '',
- },
- ],
- count: 2,
- },
-};
-
-const options = { data: { actions: { POST: true } } };
-
-describe(' {
- let wrapper;
-
- beforeEach(() => {
- CredentialsAPI.read.mockResolvedValue({ data: { count: 0 } });
- CredentialTypesAPI.read.mockResolvedValue(credentialTypes);
- CredentialTypesAPI.readOptions.mockResolvedValue(options);
- });
-
- test('should mount successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'CredentialTypeList', (el) => el.length > 0);
- });
-
- test('should have proper number of delete detail requests', () => {
- expect(
- wrapper.find('ToolbarDeleteButton').prop('deleteDetailsRequests')
- ).toHaveLength(1);
- });
-
- test('should have data fetched and render 2 rows', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'CredentialTypeList', (el) => el.length > 0);
- expect(wrapper.find('CredentialTypeListItem').length).toBe(2);
- expect(CredentialTypesAPI.read).toBeCalled();
- expect(CredentialTypesAPI.readOptions).toBeCalled();
- });
-
- test('should delete item successfully', async () => {
- CredentialTypesAPI.read.mockResolvedValue(credentialTypes);
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'CredentialTypeList', (el) => el.length > 0);
-
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .simulate('change', credentialTypes.data.results[0]);
- wrapper.update();
-
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').prop('checked')
- ).toBe(true);
-
- await act(async () => {
- wrapper.find('Button[aria-label="Delete"]').prop('onClick')();
- });
- wrapper.update();
-
- await act(async () =>
- wrapper.find('Button[aria-label="confirm delete"]').prop('onClick')()
- );
-
- expect(CredentialTypesAPI.destroy).toBeCalledWith(
- credentialTypes.data.results[0].id
- );
- });
-
- test('should not render add button', async () => {
- CredentialTypesAPI.readOptions.mockResolvedValue({
- data: { actions: { POST: false } },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- waitForElement(wrapper, 'CredentialTypeList', (el) => el.length > 0);
- expect(wrapper.find('ToolbarAddButton').length).toBe(0);
- });
-
- test('should thrown content error', async () => {
- CredentialTypesAPI.destroy = jest.fn();
- CredentialTypesAPI.read.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'GET',
- url: '/api/v2/credential_types',
- },
- data: 'An error occurred',
- },
- })
- );
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'CredentialTypeList', (el) => el.length > 0);
- expect(wrapper.find('ContentError').length).toBe(1);
- });
-
- test('should render deletion error modal', async () => {
- CredentialTypesAPI.destroy = jest.fn();
- CredentialTypesAPI.destroy.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'DELETE',
- url: '/api/v2/credential_types',
- },
- data: 'An error occurred',
- },
- })
- );
- await act(async () => {
- wrapper = mountWithContexts();
- });
- waitForElement(wrapper, 'CredentialTypeList', (el) => el.length > 0);
-
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .simulate('change', 'a');
- wrapper.update();
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').prop('checked')
- ).toBe(true);
-
- await act(async () =>
- wrapper.find('Button[aria-label="Delete"]').prop('onClick')()
- );
- wrapper.update();
-
- await act(async () =>
- wrapper.find('Button[aria-label="confirm delete"]').prop('onClick')()
- );
- wrapper.update();
- expect(wrapper.find('ErrorDetail').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js b/awx/ui/src/screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js
deleted file mode 100644
index 44404ee54d9f..000000000000
--- a/awx/ui/src/screens/CredentialType/CredentialTypeList/CredentialTypeListItem.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import React from 'react';
-import { string, bool, func } from 'prop-types';
-import { t } from '@lingui/macro';
-import { Link } from 'react-router-dom';
-import { Button } from '@patternfly/react-core';
-import { Tr, Td } from '@patternfly/react-table';
-import { PencilAltIcon } from '@patternfly/react-icons';
-import { ActionsTd, ActionItem, TdBreakWord } from 'components/PaginatedTable';
-import { CredentialType } from 'types';
-
-function CredentialTypeListItem({
- credentialType,
- detailUrl,
- isSelected,
- onSelect,
- rowIndex,
-}) {
- const labelId = `check-action-${credentialType.id}`;
-
- return (
-
- |
-
-
- {credentialType.name}
-
-
-
-
-
-
-
-
- );
-}
-
-CredentialTypeListItem.prototype = {
- credentialType: CredentialType.isRequired,
- detailUrl: string.isRequired,
- isSelected: bool.isRequired,
- onSelect: func.isRequired,
-};
-
-export default CredentialTypeListItem;
diff --git a/awx/ui/src/screens/CredentialType/CredentialTypeList/CredentialTypeListItem.test.js b/awx/ui/src/screens/CredentialType/CredentialTypeList/CredentialTypeListItem.test.js
deleted file mode 100644
index 45c78d918e59..000000000000
--- a/awx/ui/src/screens/CredentialType/CredentialTypeList/CredentialTypeListItem.test.js
+++ /dev/null
@@ -1,97 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import CredentialTypeListItem from './CredentialTypeListItem';
-
-describe('', () => {
- let wrapper;
- const credential_type = {
- id: 1,
- name: 'Foo',
- summary_fields: { user_capabilities: { edit: true, delete: true } },
- kind: 'cloud',
- };
-
- test('should mount successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('CredentialTypeListItem').length).toBe(1);
- });
-
- test('should render the proper data', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('Td[dataLabel="Name"]').text()).toBe('Foo');
- expect(wrapper.find('PencilAltIcon').length).toBe(1);
- expect(wrapper.find('.pf-c-table__check input').prop('checked')).toBe(
- undefined
- );
- });
-
- test('edit button shown to users with edit capabilities', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
-
- expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
- });
-
- test('edit button hidden from users without edit capabilities', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
-
- expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
- });
-});
diff --git a/awx/ui/src/screens/CredentialType/CredentialTypeList/index.js b/awx/ui/src/screens/CredentialType/CredentialTypeList/index.js
deleted file mode 100644
index ed47b3615c0a..000000000000
--- a/awx/ui/src/screens/CredentialType/CredentialTypeList/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './CredentialTypeList';
diff --git a/awx/ui/src/screens/CredentialType/CredentialTypes.js b/awx/ui/src/screens/CredentialType/CredentialTypes.js
deleted file mode 100644
index 6178091e833a..000000000000
--- a/awx/ui/src/screens/CredentialType/CredentialTypes.js
+++ /dev/null
@@ -1,52 +0,0 @@
-import React, { useState, useCallback } from 'react';
-
-import { t } from '@lingui/macro';
-import { Route, Switch } from 'react-router-dom';
-import PersistentFilters from 'components/PersistentFilters';
-import ScreenHeader from 'components/ScreenHeader';
-import CredentialTypeAdd from './CredentialTypeAdd';
-import CredentialTypeList from './CredentialTypeList';
-import CredentialType from './CredentialType';
-
-function CredentialTypes() {
- const [breadcrumbConfig, setBreadcrumbConfig] = useState({
- '/credential_types': t`Credential Types`,
- '/credential_types/add': t`Create new credential type`,
- });
-
- const buildBreadcrumbConfig = useCallback((credentialTypes) => {
- if (!credentialTypes) {
- return;
- }
- setBreadcrumbConfig({
- '/credential_types': t`Credential Types`,
- '/credential_types/add': t`Create new credential Type`,
- [`/credential_types/${credentialTypes.id}`]: `${credentialTypes.name}`,
- [`/credential_types/${credentialTypes.id}/edit`]: t`Edit details`,
- [`/credential_types/${credentialTypes.id}/details`]: t`Details`,
- });
- }, []);
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-}
-
-export default CredentialTypes;
diff --git a/awx/ui/src/screens/CredentialType/CredentialTypes.test.js b/awx/ui/src/screens/CredentialType/CredentialTypes.test.js
deleted file mode 100644
index e6321a28760b..000000000000
--- a/awx/ui/src/screens/CredentialType/CredentialTypes.test.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import React from 'react';
-
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-
-import CredentialTypes from './CredentialTypes';
-
-describe('', () => {
- let pageWrapper;
- let pageSections;
-
- beforeEach(() => {
- pageWrapper = mountWithContexts();
- pageSections = pageWrapper.find('PageSection');
- });
-
- test('initially renders without crashing', () => {
- expect(pageWrapper.length).toBe(1);
- expect(pageSections.length).toBe(1);
- expect(pageSections.first().props().variant).toBe('light');
- });
-});
diff --git a/awx/ui/src/screens/CredentialType/index.js b/awx/ui/src/screens/CredentialType/index.js
deleted file mode 100644
index e58eca46f6ac..000000000000
--- a/awx/ui/src/screens/CredentialType/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './CredentialTypes';
diff --git a/awx/ui/src/screens/CredentialType/shared/CredentialTypeForm.js b/awx/ui/src/screens/CredentialType/shared/CredentialTypeForm.js
deleted file mode 100644
index 2fecb06b9e8f..000000000000
--- a/awx/ui/src/screens/CredentialType/shared/CredentialTypeForm.js
+++ /dev/null
@@ -1,103 +0,0 @@
-import React from 'react';
-import { func, shape } from 'prop-types';
-import { Formik } from 'formik';
-
-import { t } from '@lingui/macro';
-
-import { Form } from '@patternfly/react-core';
-import { VariablesField } from 'components/CodeEditor';
-import FormField, { FormSubmitError } from 'components/FormField';
-import FormActionGroup from 'components/FormActionGroup';
-import { required } from 'util/validators';
-import { FormColumnLayout, FormFullWidthLayout } from 'components/FormLayout';
-
-import { jsonToYaml } from 'util/yaml';
-
-function CredentialTypeFormFields() {
- return (
- <>
-
-
-
-
-
-
-
-
- >
- );
-}
-
-function CredentialTypeForm({
- credentialType = {},
- onSubmit,
- onCancel,
- submitError,
- ...rest
-}) {
- const initialValues = {
- name: credentialType.name || '',
- description: credentialType.description || '',
- inputs: credentialType.inputs
- ? jsonToYaml(JSON.stringify(credentialType.inputs))
- : '---',
- injectors: credentialType.injectors
- ? jsonToYaml(JSON.stringify(credentialType.injectors))
- : '---',
- };
- return (
- onSubmit(values)}
- >
- {(formik) => (
-
- )}
-
- );
-}
-
-CredentialTypeForm.propTypes = {
- credentialType: shape({}),
- onCancel: func.isRequired,
- onSubmit: func.isRequired,
- submitError: shape({}),
-};
-
-CredentialTypeForm.defaultProps = {
- credentialType: {},
- submitError: null,
-};
-
-export default CredentialTypeForm;
diff --git a/awx/ui/src/screens/CredentialType/shared/CredentialTypeForm.test.js b/awx/ui/src/screens/CredentialType/shared/CredentialTypeForm.test.js
deleted file mode 100644
index 4adb2f691935..000000000000
--- a/awx/ui/src/screens/CredentialType/shared/CredentialTypeForm.test.js
+++ /dev/null
@@ -1,131 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import CredentialTypeForm from './CredentialTypeForm';
-
-jest.mock('../../../api');
-
-const credentialType = {
- id: 28,
- type: 'credential_type',
- url: '/api/v2/credential_types/28/',
- summary_fields: {
- created_by: {
- id: 1,
- username: 'admin',
- first_name: '',
- last_name: '',
- },
- modified_by: {
- id: 1,
- username: 'admin',
- first_name: '',
- last_name: '',
- },
- user_capabilities: {
- edit: true,
- delete: true,
- },
- },
- created: '2020-06-18T14:48:47.869002Z',
- modified: '2020 - 06 - 18T14: 48: 47.869017Z',
- name: 'Jenkins Credential',
- description: 'Jenkins Credential',
- kind: 'cloud',
- namespace: null,
- managed: false,
- inputs: JSON.stringify({
- fields: [
- {
- id: 'username',
- type: 'string',
- label: 'Jenkins username',
- },
- {
- id: 'password',
- type: 'string',
- label: 'Jenkins password',
- secret: true,
- },
- ],
- required: ['username', 'password'],
- }),
- injectors: JSON.stringify({
- extra_vars: {
- Jenkins_password: '{{ password }}',
- Jenkins_username: '{{ username }}',
- },
- }),
-};
-
-describe('', () => {
- let wrapper;
- let onCancel;
- let onSubmit;
-
- beforeEach(async () => {
- onCancel = jest.fn();
- onSubmit = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- });
-
- afterEach(() => {
- jest.resetAllMocks();
- });
-
- test('Initially renders successfully', () => {
- expect(wrapper.length).toBe(1);
- });
-
- test('should display form fields properly', () => {
- expect(wrapper.find('FormGroup[label="Name"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Description"]').length).toBe(1);
- expect(
- wrapper.find('VariablesField[label="Input configuration"]').length
- ).toBe(1);
- expect(
- wrapper.find('VariablesField[label="Injector configuration"]').length
- ).toBe(1);
- });
-
- test('should call onSubmit when form submitted', async () => {
- expect(onSubmit).not.toHaveBeenCalled();
- await act(async () => {
- wrapper.find('button[aria-label="Save"]').simulate('click');
- });
- expect(onSubmit).toHaveBeenCalledTimes(1);
- });
-
- test('should update form values', async () => {
- act(() => {
- wrapper.find('input#credential-type-name').simulate('change', {
- target: { value: 'Foo', name: 'name' },
- });
- wrapper.find('input#credential-type-description').simulate('change', {
- target: { value: 'New description', name: 'description' },
- });
- });
- await act(async () => wrapper.update());
- expect(wrapper.find('input#credential-type-name').prop('value')).toEqual(
- 'Foo'
- );
- expect(
- wrapper.find('input#credential-type-description').prop('value')
- ).toEqual('New description');
- });
-
- test('should call handleCancel when Cancel button is clicked', async () => {
- expect(onCancel).not.toHaveBeenCalled();
- wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
- expect(onCancel).toBeCalled();
- });
-});
diff --git a/awx/ui/src/screens/CredentialType/shared/data.json b/awx/ui/src/screens/CredentialType/shared/data.json
deleted file mode 100644
index 54f5c78e6c7c..000000000000
--- a/awx/ui/src/screens/CredentialType/shared/data.json
+++ /dev/null
@@ -1,62 +0,0 @@
-{
- "id": 28,
- "type": "credential_type",
- "url": "/api/v2/credential_types/28/",
- "related": {
- "named_url": "/api/v2/credential_types/Jenkins Credential+cloud/",
- "created_by": "/api/v2/users/1/",
- "modified_by": "/api/v2/users/1/",
- "credentials": "/api/v2/credential_types/28/credentials/",
- "activity_stream": "/api/v2/credential_types/28/activity_stream/"
- },
- "summary_fields": {
- "created_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "user_capabilities": {
- "edit": true,
- "delete": true
- }
- },
- "created": "2020-06-18T14:48:47.869002Z",
- "modified": "2020-06-18T14:48:47.869017Z",
- "name": "Jenkins Credential",
- "description": "Jenkins Credential",
- "kind": "cloud",
- "namespace": null,
- "managed": false,
- "inputs": {
- "fields": [
- {
- "id": "username",
- "type": "string",
- "label": "Jenkins username"
- },
- {
- "id": "password",
- "type": "string",
- "label": "Jenkins password",
- "secret": true
- }
- ],
- "required": [
- "username",
- "password"
- ]
- },
- "injectors": {
- "extra_vars": {
- "Jenkins_password": "{{ password }}",
- "Jenkins_username": "{{ username }}"
- }
- }
-}
diff --git a/awx/ui/src/screens/CredentialType/shared/index.js b/awx/ui/src/screens/CredentialType/shared/index.js
deleted file mode 100644
index 6084152bbf38..000000000000
--- a/awx/ui/src/screens/CredentialType/shared/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './CredentialTypeForm';
diff --git a/awx/ui/src/screens/Dashboard/Dashboard.js b/awx/ui/src/screens/Dashboard/Dashboard.js
deleted file mode 100644
index 1481b9ac5f1a..000000000000
--- a/awx/ui/src/screens/Dashboard/Dashboard.js
+++ /dev/null
@@ -1,162 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import styled from 'styled-components';
-
-import { t } from '@lingui/macro';
-import {
- Card,
- PageSection,
- Tabs,
- Tab,
- TabTitleText,
-} from '@patternfly/react-core';
-
-import useRequest from 'hooks/useRequest';
-import { DashboardAPI } from 'api';
-import ScreenHeader from 'components/ScreenHeader';
-import JobList from 'components/JobList';
-import ContentLoading from 'components/ContentLoading';
-import TemplateList from 'components/TemplateList';
-import Count from './shared/Count';
-import DashboardGraph from './DashboardGraph';
-
-const Counts = styled.div`
- display: grid;
- grid-template-columns: repeat(6, 1fr);
- grid-gap: var(--pf-global--spacer--lg);
-
- @media (max-width: 900px) {
- grid-template-columns: repeat(3, 1fr);
- grid-auto-rows: 1fr;
- }
-`;
-
-const MainPageSection = styled(PageSection)`
- padding-top: 0;
- padding-bottom: 0;
-
- & .spacer {
- margin-bottom: var(--pf-global--spacer--lg);
- }
-`;
-
-function Dashboard() {
- const [activeTabId, setActiveTabId] = useState(0);
-
- const {
- isLoading,
- result: countData,
- request: fetchDashboardGraph,
- } = useRequest(
- useCallback(async () => {
- const { data: dataFromCount } = await DashboardAPI.read();
-
- return dataFromCount;
- }, []),
- {}
- );
-
- useEffect(() => {
- fetchDashboardGraph();
- }, [fetchDashboardGraph]);
- if (isLoading) {
- return (
-
-
-
-
-
- );
- }
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- setActiveTabId(eventKey)}
- ouiaId="dashboard-tabs"
- >
- {t`Job status`}}
- ouiaId="job-status-graph-tab"
- >
-
-
- {t`Recent Jobs`}}
- ouiaId="recent-jobs-list-tab"
- >
-
- {activeTabId === 1 && (
-
- )}
-
-
- {t`Recent Templates`}}
- ouiaId="recent-templates-list-tab"
- >
-
- {activeTabId === 2 && (
-
- )}
-
-
-
-
-
-
- >
- );
-}
-
-export default Dashboard;
diff --git a/awx/ui/src/screens/Dashboard/Dashboard.test.js b/awx/ui/src/screens/Dashboard/Dashboard.test.js
deleted file mode 100644
index 24fd37f74413..000000000000
--- a/awx/ui/src/screens/Dashboard/Dashboard.test.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { DashboardAPI } from 'api';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-
-import Dashboard from './Dashboard';
-
-jest.mock('../../api');
-
-describe('', () => {
- let pageWrapper;
- let graphRequest;
-
- beforeEach(async () => {
- await act(async () => {
- DashboardAPI.read.mockResolvedValue({});
- graphRequest = DashboardAPI.readJobGraph;
- graphRequest.mockResolvedValue({});
- pageWrapper = mountWithContexts();
- });
- });
-
- test('initially renders without crashing', () => {
- expect(pageWrapper.length).toBe(1);
- });
-
- test('renders dashboard graph by default', () => {
- expect(pageWrapper.find('LineChart').length).toBe(1);
- });
-
- test('renders template list when the active tab is changed', async () => {
- expect(pageWrapper.find('DashboardTemplateList').length).toBe(0);
- await act(async () => {
- pageWrapper
- .find('button[aria-label="Recent Templates list tab"]')
- .simulate('click');
- });
- pageWrapper.update();
- expect(pageWrapper.find('TemplateList').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Dashboard/DashboardGraph.js b/awx/ui/src/screens/Dashboard/DashboardGraph.js
deleted file mode 100644
index 53c5cd4fca47..000000000000
--- a/awx/ui/src/screens/Dashboard/DashboardGraph.js
+++ /dev/null
@@ -1,185 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import styled from 'styled-components';
-
-import { t } from '@lingui/macro';
-import {
- Card,
- CardHeader,
- CardActions,
- CardBody,
- PageSection,
- Select,
- SelectVariant,
- SelectOption,
-} from '@patternfly/react-core';
-
-import useRequest from 'hooks/useRequest';
-import { DashboardAPI } from 'api';
-import ContentLoading from 'components/ContentLoading';
-import LineChart from './shared/LineChart';
-
-const StatusSelect = styled(Select)`
- && {
- --pf-c-select__toggle--MinWidth: 165px;
- }
-`;
-const GraphCardHeader = styled(CardHeader)`
- margin-top: var(--pf-global--spacer--lg);
-`;
-
-const GraphCardActions = styled(CardActions)`
- margin-left: initial;
- padding-left: 0;
-`;
-
-function DashboardGraph() {
- const [isPeriodDropdownOpen, setIsPeriodDropdownOpen] = useState(false);
- const [isJobTypeDropdownOpen, setIsJobTypeDropdownOpen] = useState(false);
- const [isJobStatusDropdownOpen, setIsJobStatusDropdownOpen] = useState(false);
- const [periodSelection, setPeriodSelection] = useState('month');
- const [jobTypeSelection, setJobTypeSelection] = useState('all');
- const [jobStatusSelection, setJobStatusSelection] = useState('all');
-
- const {
- isLoading,
- result: jobGraphData,
- request: fetchDashboardGraph,
- } = useRequest(
- useCallback(async () => {
- const { data } = await DashboardAPI.readJobGraph({
- period: periodSelection,
- job_type: jobTypeSelection,
- });
- const newData = {};
- data.jobs.successful.forEach(([dateSecs, count]) => {
- if (!newData[dateSecs]) {
- newData[dateSecs] = {};
- }
- newData[dateSecs].successful = count;
- });
- data.jobs.failed.forEach(([dateSecs, count]) => {
- if (!newData[dateSecs]) {
- newData[dateSecs] = {};
- }
- newData[dateSecs].failed = count;
- });
- const jobData = Object.keys(newData).map((dateSecs) => {
- const [created] = new Date(dateSecs * 1000).toISOString().split('T');
- newData[dateSecs].created = created;
- return newData[dateSecs];
- });
- return jobData;
- }, [periodSelection, jobTypeSelection]),
- []
- );
-
- useEffect(() => {
- fetchDashboardGraph();
- }, [fetchDashboardGraph, periodSelection, jobTypeSelection]);
- if (isLoading) {
- return (
-
-
-
-
-
- );
- }
-
- return (
- <>
-
-
-
-
- {
- setIsJobStatusDropdownOpen(false);
- setJobStatusSelection(selection);
- }}
- selections={jobStatusSelection}
- isOpen={isJobStatusDropdownOpen}
- >
- {t`All jobs`}
- {t`Successful jobs`}
- {t`Failed jobs`}
-
-
-
-
-
-
- >
- );
-}
-export default DashboardGraph;
diff --git a/awx/ui/src/screens/Dashboard/DashboardGraph.test.js b/awx/ui/src/screens/Dashboard/DashboardGraph.test.js
deleted file mode 100644
index 606b068da82e..000000000000
--- a/awx/ui/src/screens/Dashboard/DashboardGraph.test.js
+++ /dev/null
@@ -1,61 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { DashboardAPI } from 'api';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-
-import DashboardGraph from './DashboardGraph';
-
-jest.mock('../../api');
-
-describe('', () => {
- let pageWrapper;
- let graphRequest;
-
- beforeEach(async () => {
- await act(async () => {
- DashboardAPI.read.mockResolvedValue({});
- graphRequest = DashboardAPI.readJobGraph;
- graphRequest.mockResolvedValue({});
- pageWrapper = mountWithContexts();
- });
- });
-
- test('renders month-based/all job type chart by default', () => {
- expect(graphRequest).toHaveBeenCalledWith({
- job_type: 'all',
- period: 'month',
- });
- });
-
- test('should render all three line chart filters with correct number of options', async () => {
- expect(pageWrapper.find('Select[variant="single"]')).toHaveLength(3);
- await act(async () => {
- pageWrapper
- .find('Select[placeholderText="Select job type"]')
- .prop('onToggle')(true);
- });
- pageWrapper.update();
- expect(pageWrapper.find('SelectOption')).toHaveLength(4);
- await act(async () => {
- pageWrapper
- .find('Select[placeholderText="Select job type"]')
- .prop('onToggle')(false);
- pageWrapper
- .find('Select[placeholderText="Select period"]')
- .prop('onToggle')(true);
- });
- pageWrapper.update();
- expect(pageWrapper.find('SelectOption')).toHaveLength(4);
- await act(async () => {
- pageWrapper
- .find('Select[placeholderText="Select period"]')
- .prop('onToggle')(false);
- pageWrapper
- .find('Select[placeholderText="Select status"]')
- .prop('onToggle')(true);
- });
- pageWrapper.update();
- expect(pageWrapper.find('SelectOption')).toHaveLength(3);
- });
-});
diff --git a/awx/ui/src/screens/Dashboard/index.js b/awx/ui/src/screens/Dashboard/index.js
deleted file mode 100644
index 449ae5672dbc..000000000000
--- a/awx/ui/src/screens/Dashboard/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './Dashboard';
diff --git a/awx/ui/src/screens/Dashboard/shared/ChartTooltip.js b/awx/ui/src/screens/Dashboard/shared/ChartTooltip.js
deleted file mode 100644
index d9205fe46e5b..000000000000
--- a/awx/ui/src/screens/Dashboard/shared/ChartTooltip.js
+++ /dev/null
@@ -1,200 +0,0 @@
-import * as d3 from 'd3';
-import { t } from '@lingui/macro';
-
-class Tooltip {
- constructor(opts) {
- this.label = opts.label;
- this.svg = opts.svg;
- this.colors = opts.colors;
-
- this.draw();
- }
-
- draw() {
- this.toolTipBase = d3.select(`${this.svg} > svg`).append('g');
- this.toolTipBase.attr('id', 'chart-tooltip');
- this.toolTipBase.attr('overflow', 'visible');
- this.toolTipBase.style('opacity', 0);
- this.toolTipBase.style('pointer-events', 'none');
- this.toolTipBase.attr('transform', 'translate(100, 100)');
- this.boxWidth = 145;
- this.textWidthThreshold = 20;
-
- this.toolTipPoint = this.toolTipBase
- .append('rect')
- .attr('transform', 'translate(10, -10) rotate(45)')
- .attr('x', 0)
- .attr('y', 0)
- .attr('height', 20)
- .attr('width', 20)
- .attr('fill', '#393f44');
- this.boundingBox = this.toolTipBase
- .append('rect')
- .attr('x', 10)
- .attr('y', -41)
- .attr('rx', 2)
- .attr('height', 82)
- .attr('width', this.boxWidth)
- .attr('fill', '#393f44');
- this.circleGreen = this.toolTipBase
- .append('circle')
- .attr('cx', 26)
- .attr('cy', 0)
- .attr('r', 7)
- .attr('stroke', 'white')
- .attr('fill', this.colors(1));
- this.circleRed = this.toolTipBase
- .append('circle')
- .attr('cx', 26)
- .attr('cy', 26)
- .attr('r', 7)
- .attr('stroke', 'white')
- .attr('fill', this.colors(0));
- this.successText = this.toolTipBase
- .append('text')
- .attr('x', 43)
- .attr('y', 4)
- .attr('font-size', 12)
- .attr('fill', 'white')
- .text(t`Successful`);
- this.failText = this.toolTipBase
- .append('text')
- .attr('x', 43)
- .attr('y', 28)
- .attr('font-size', 12)
- .attr('fill', 'white')
- .text(t`Failed`);
- this.icon = this.toolTipBase
- .append('text')
- .attr('fill', 'white')
- .attr('stroke', 'white')
- .attr('x', 24)
- .attr('y', 30)
- .attr('font-size', 12)
- .text('!');
- this.jobs = this.toolTipBase
- .append('text')
- .attr('fill', 'white')
- .attr('x', 137)
- .attr('y', -21)
- .attr('font-size', 12)
- .attr('text-anchor', 'end')
- .text(t`No Jobs`);
- this.successful = this.toolTipBase
- .append('text')
- .attr('fill', 'white')
- .attr('font-size', 12)
- .attr('x', 122)
- .attr('y', 4)
- .attr('id', 'successful-count')
- .text('0');
- this.failed = this.toolTipBase
- .append('text')
- .attr('fill', 'white')
- .attr('font-size', 12)
- .attr('x', 122)
- .attr('y', 28)
- .attr('id', 'failed-count')
- .text('0');
- this.date = this.toolTipBase
- .append('text')
- .attr('fill', 'white')
- .attr('stroke', 'white')
- .attr('x', 20)
- .attr('y', -21)
- .attr('font-size', 12)
- .text(t`Never`);
- }
-
- handleMouseOver = (event, data) => {
- let success = 0;
- let fail = 0;
- let total = 0;
- const [x, y] = d3.pointer(event);
- const tooltipPointerX = x + 75;
-
- const formatTooltipDate = d3.timeFormat('%m/%d');
- if (!event) {
- return;
- }
-
- const toolTipWidth = this.toolTipBase.node().getBoundingClientRect().width;
- const chartWidth = d3
- .select(`${this.svg}> svg`)
- .node()
- .getBoundingClientRect().width;
- const overflow = 100 - (toolTipWidth / chartWidth) * 100;
- const flipped = overflow < (tooltipPointerX / chartWidth) * 100;
- if (data) {
- success = data.RAN || 0;
- fail = data.FAIL || 0;
- total = data.TOTAL || 0;
- this.date.text(formatTooltipDate(data.DATE || null));
- }
-
- if (data) {
- success = data.RAN || 0;
- fail = data.FAIL || 0;
- total = data.TOTAL || 0;
- this.date.text(formatTooltipDate(data.DATE || null));
- }
-
- this.jobs.text(`${total} ${this.label}`);
- this.jobsWidth = this.jobs.node().getComputedTextLength();
- this.failed.text(`${fail}`);
- this.successful.text(`${success}`);
- this.successTextWidth = this.successful.node().getComputedTextLength();
- this.failTextWidth = this.failed.node().getComputedTextLength();
-
- const maxTextPerc = (this.jobsWidth / this.boxWidth) * 100;
- const threshold = 40;
- const overage = maxTextPerc / threshold;
- let adjustedWidth;
- if (maxTextPerc > threshold) {
- adjustedWidth = this.boxWidth * overage;
- } else {
- adjustedWidth = this.boxWidth;
- }
-
- this.boundingBox.attr('width', adjustedWidth);
- this.toolTipBase.attr('transform', `translate(${tooltipPointerX}, ${y})`);
- if (flipped) {
- this.toolTipPoint.attr('transform', 'translate(-20, -10) rotate(45)');
- this.boundingBox.attr('x', -adjustedWidth - 20);
- this.circleGreen.attr('cx', -adjustedWidth);
- this.circleRed.attr('cx', -adjustedWidth);
- this.icon.attr('x', -adjustedWidth - 2);
- this.successText.attr('x', -adjustedWidth + 17);
- this.failText.attr('x', -adjustedWidth + 17);
- this.successful.attr('x', -this.successTextWidth - 20 - 12);
- this.failed.attr('x', -this.failTextWidth - 20 - 12);
- this.date.attr('x', -adjustedWidth - 5);
- this.jobs.attr('x', -this.jobsWidth / 2 - 14);
- } else {
- this.toolTipPoint.attr('transform', 'translate(10, -10) rotate(45)');
- this.boundingBox.attr('x', 10);
- this.circleGreen.attr('cx', 26);
- this.circleRed.attr('cx', 26);
- this.icon.attr('x', 24);
- this.successText.attr('x', 43);
- this.failText.attr('x', 43);
- this.successful.attr('x', adjustedWidth - this.successTextWidth);
- this.failed.attr('x', adjustedWidth - this.failTextWidth);
- this.date.attr('x', 20);
- this.jobs.attr('x', adjustedWidth);
- }
-
- this.toolTipBase.style('opacity', 1);
- this.toolTipBase.interrupt();
- };
-
- handleMouseOut = () => {
- this.toolTipBase
- .transition()
- .delay(15)
- .style('opacity', 0)
- .style('pointer-events', 'none');
- };
-}
-
-export default Tooltip;
diff --git a/awx/ui/src/screens/Dashboard/shared/Count.js b/awx/ui/src/screens/Dashboard/shared/Count.js
deleted file mode 100644
index eb6c692550a3..000000000000
--- a/awx/ui/src/screens/Dashboard/shared/Count.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import React from 'react';
-import styled from 'styled-components';
-import { Link } from 'react-router-dom';
-import { Card } from '@patternfly/react-core';
-
-const CountCard = styled(Card)`
- padding: var(--pf-global--spacer--md);
- display: flex;
- align-items: center;
- padding-top: var(--pf-global--spacer--sm);
- cursor: pointer;
- text-align: center;
- color: var(--pf-global--palette--black-1000);
- text-decoration: none;
-
- & h2 {
- font-size: var(--pf-global--FontSize--4xl);
- color: var(--pf-global--palette--blue-400);
- text-decoration: none;
- }
-
- & h2.failed {
- color: var(--pf-global--palette--red-200);
- }
-`;
-
-const CountLink = styled(Link)`
- display: contents;
- &:hover {
- text-decoration: none;
- }
-`;
-
-function Count({ failed, link, data, label }) {
- return (
-
-
- {data || 0}
- {label}
-
-
- );
-}
-
-export default Count;
diff --git a/awx/ui/src/screens/Dashboard/shared/Count.test.js b/awx/ui/src/screens/Dashboard/shared/Count.test.js
deleted file mode 100644
index ded7ad951ba7..000000000000
--- a/awx/ui/src/screens/Dashboard/shared/Count.test.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from 'react';
-
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import Count from './Count';
-
-describe('', () => {
- let pageWrapper;
-
- test('initially renders without crashing', () => {
- pageWrapper = mountWithContexts();
- expect(pageWrapper.length).toBe(1);
- });
-
- test('renders non-failed version of count without prop', () => {
- pageWrapper = mountWithContexts();
- expect(pageWrapper.find('h2').hasClass('failed')).toBe(false);
- });
-
- test('renders failed version of count with appropriate prop', () => {
- pageWrapper = mountWithContexts();
- expect(pageWrapper.find('h2').hasClass('failed')).toBe(true);
- });
-});
diff --git a/awx/ui/src/screens/Dashboard/shared/LineChart.js b/awx/ui/src/screens/Dashboard/shared/LineChart.js
deleted file mode 100644
index f68034ca106c..000000000000
--- a/awx/ui/src/screens/Dashboard/shared/LineChart.js
+++ /dev/null
@@ -1,291 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-import { string, number, shape, arrayOf } from 'prop-types';
-import * as d3 from 'd3';
-import { t } from '@lingui/macro';
-import { PageContextConsumer } from '@patternfly/react-core';
-import ChartTooltip from './ChartTooltip';
-
-function LineChart({ id, data, height, pageContext, jobStatus }) {
- const { isNavOpen } = pageContext;
-
- // Methods
- const draw = useCallback(() => {
- const margin = { top: 15, right: 15, bottom: 35, left: 70 };
-
- const getWidth = () => {
- let width;
- // This is in an a try/catch due to an error from jest.
- // Even though the d3.select returns a valid selector with
- // style function, it says it is null in the test
- try {
- width =
- parseInt(d3.select(`#${id}`).style('width'), 10) -
- margin.left -
- margin.right || 700;
- } catch (error) {
- width = 700;
- }
- return width;
- };
-
- // Clear our chart container element first
- d3.selectAll(`#${id} > *`).remove();
- const width = getWidth();
-
- function transition(path) {
- path.transition().duration(1000).attrTween('stroke-dasharray', tweenDash);
- }
-
- function tweenDash(...params) {
- const l = params[2][params[1]].getTotalLength();
- const i = d3.interpolateString(`0,${l}`, `${l},${l}`);
- return (val) => i(val);
- }
-
- const x = d3.scaleTime().rangeRound([0, width]);
- const y = d3.scaleLinear().range([height, 0]);
-
- // [success, fail, total]
- const colors = d3.scaleOrdinal(['#6EC664', '#A30000', '#06C']);
- const svg = d3
- .select(`#${id}`)
- .append('svg')
- .attr('width', width + margin.left + margin.right)
- .attr('height', height + margin.top + margin.bottom)
- // .attr('id', 'foo')
- .attr('z', 100)
- .append('g')
- .attr('id', 'chart-container')
- .attr('transform', `translate(${margin.left}, ${margin.top})`);
- // Tooltip
- const tooltip = new ChartTooltip({
- svg: `#${id}`,
- colors,
- label: t`Jobs`,
- });
- const parseTime = d3.timeParse('%Y-%m-%d');
-
- const formattedData = data.reduce(
- (formatted, { created, successful, failed }) => {
- const DATE = parseTime(created) || new Date();
- const RAN = +successful || 0;
- const FAIL = +failed || 0;
- const TOTAL = +successful + failed || 0;
- return formatted.concat({ DATE, RAN, FAIL, TOTAL });
- },
- []
- );
- // Scale the range of the data
- const largestY = formattedData.reduce((a_max, b) => {
- const b_max = Math.max(b.RAN > b.FAIL ? b.RAN : b.FAIL);
- return a_max > b_max ? a_max : b_max;
- }, 0);
- x.domain(d3.extent(formattedData, (d) => d.DATE));
- y.domain([
- 0,
- largestY > 4 ? largestY + Math.max(largestY / 10, 1) : 5,
- ]).nice();
-
- const successLine = d3
- .line()
- .curve(d3.curveMonotoneX)
- .x((d) => x(d.DATE))
- .y((d) => y(d.RAN));
-
- const failLine = d3
- .line()
- .defined((d) => typeof d.FAIL === 'number')
- .curve(d3.curveMonotoneX)
- .x((d) => x(d.DATE))
- .y((d) => y(d.FAIL));
- // Add the Y Axis
- svg
- .append('g')
- .attr('class', 'y-axis')
- .call(
- d3
- .axisLeft(y)
- .ticks(
- largestY > 3
- ? Math.min(largestY + Math.max(largestY / 10, 1), 10)
- : 5
- )
- .tickSize(-width)
- .tickFormat(d3.format('d'))
- )
- .selectAll('line')
- .attr('stroke', '#d7d7d7');
- svg.selectAll('.y-axis .tick text').attr('x', -5);
-
- // text label for the y axis
- svg
- .append('text')
- .attr('transform', 'rotate(-90)')
- .attr('y', 0 - margin.left)
- .attr('x', 0 - height / 2)
- .attr('dy', '1em')
- .style('text-anchor', 'middle')
- .text(t`Job Runs`);
-
- // Add the X Axis
- let ticks;
- const maxTicks = Math.round(
- formattedData.length / (formattedData.length / 2)
- );
- ticks = formattedData.map((d) => d.DATE);
- if (formattedData.length === 31) {
- ticks = formattedData
- .map((d, i) => (i % maxTicks === 0 ? d.DATE : undefined))
- .filter((item) => item);
- }
-
- svg.select('.domain').attr('stroke', '#d7d7d7');
-
- svg
- .append('g')
- .attr('class', 'x-axis')
- .attr('transform', `translate(0, ${height})`)
- .call(
- d3
- .axisBottom(x)
- .tickValues(ticks)
- .tickSize(-height)
- .tickFormat(d3.timeFormat('%-m/%-d')) // "1/19"
- ) // "Jan-01"
- .selectAll('line')
- .attr('stroke', '#d7d7d7');
-
- svg.selectAll('.x-axis .tick text').attr('y', 10);
-
- // text label for the x axis
- svg
- .append('text')
- .attr(
- 'transform',
- `translate(${width / 2} , ${height + margin.top + 20})`
- )
- .style('text-anchor', 'middle')
- .text(t`Date`);
- const vertical = svg
- .append('path')
- .attr('class', 'mouse-line')
- .style('stroke', 'black')
- .style('stroke-width', '3px')
- .style('stroke-dasharray', '3, 3')
- .style('opacity', '0');
-
- const handleMouseOver = (event, d) => {
- tooltip.handleMouseOver(event, d);
- // show vertical line
- vertical.transition().style('opacity', '1');
- };
- const handleMouseMove = function mouseMove(event) {
- const [pointerX] = d3.pointer(event);
- vertical.attr('d', () => `M${pointerX},${height} ${pointerX},${0}`);
- };
-
- const handleMouseOut = () => {
- // hide tooltip
- tooltip.handleMouseOut();
- // hide vertical line
- vertical.transition().style('opacity', 0);
- };
-
- const dateFormat = d3.timeFormat('%-m-%-d');
-
- if (jobStatus !== 'failed') {
- // Add the success line path.
- svg
- .append('path')
- .data([formattedData])
- .attr('class', 'line')
- .style('fill', 'none')
- .style('stroke', () => colors(1))
- .attr('stroke-width', 2)
- .attr('d', successLine)
- .call(transition);
-
- // create our success line circles
-
- svg
- .selectAll('dot')
- .data(formattedData)
- .enter()
- .append('circle')
- .attr('r', 3)
- .style('stroke', () => colors(1))
- .style('fill', () => colors(1))
- .attr('cx', (d) => x(d.DATE))
- .attr('cy', (d) => y(d.RAN))
- .attr('id', (d) => `success-dot-${dateFormat(d.DATE)}`)
- .on('mouseover', (event, d) => handleMouseOver(event, d))
- .on('mousemove', handleMouseMove)
- .on('mouseout', handleMouseOut);
- }
-
- if (jobStatus !== 'successful') {
- // Add the failed line path.
- svg
- .append('path')
- .data([formattedData])
- .attr('class', 'line')
- .style('fill', 'none')
- .style('stroke', () => colors(0))
- .attr('stroke-width', 2)
- .attr('d', failLine)
- .call(transition);
-
- // create our failed line circles
-
- svg
- .selectAll('dot')
- .data(formattedData)
- .enter()
- .append('circle')
- .attr('r', 3)
- .style('stroke', () => colors(0))
- .style('fill', () => colors(0))
- .attr('cx', (d) => x(d.DATE))
- .attr('cy', (d) => y(d.FAIL))
- .attr('id', (d) => `fail-dot-${dateFormat(d.DATE)}`)
- .on('mouseover', handleMouseOver)
- .on('mousemove', handleMouseMove)
- .on('mouseout', handleMouseOut);
- }
- }, [data, height, id, jobStatus]);
-
- useEffect(() => {
- draw();
- }, [draw, isNavOpen]);
-
- useEffect(() => {
- function handleResize() {
- draw();
- }
-
- window.addEventListener('resize', handleResize);
-
- handleResize();
-
- return () => window.removeEventListener('resize', handleResize);
- }, [draw]);
-
- return ;
-}
-
-LineChart.propTypes = {
- id: string.isRequired,
- data: arrayOf(shape({})).isRequired,
- height: number.isRequired,
-};
-
-const withPageContext = (Component) =>
- function contextComponent(props) {
- return (
-
- {(pageContext) => }
-
- );
- };
-
-export default withPageContext(LineChart);
diff --git a/awx/ui/src/screens/Dashboard/shared/data.job_template.json b/awx/ui/src/screens/Dashboard/shared/data.job_template.json
deleted file mode 100644
index e28410769112..000000000000
--- a/awx/ui/src/screens/Dashboard/shared/data.job_template.json
+++ /dev/null
@@ -1,181 +0,0 @@
-{
- "id": 7,
- "type": "job_template",
- "url": "/api/v2/job_templates/7/",
- "related": {
- "named_url": "/api/v2/job_templates/Mike's JT/",
- "created_by": "/api/v2/users/1/",
- "modified_by": "/api/v2/users/1/",
- "labels": "/api/v2/job_templates/7/labels/",
- "inventory": "/api/v2/inventories/1/",
- "project": "/api/v2/projects/6/",
- "credentials": "/api/v2/job_templates/7/credentials/",
- "last_job": "/api/v2/jobs/12/",
- "jobs": "/api/v2/job_templates/7/jobs/",
- "schedules": "/api/v2/job_templates/7/schedules/",
- "activity_stream": "/api/v2/job_templates/7/activity_stream/",
- "launch": "/api/v2/job_templates/7/launch/",
- "notification_templates_started": "/api/v2/job_templates/7/notification_templates_started/",
- "notification_templates_success": "/api/v2/job_templates/7/notification_templates_success/",
- "notification_templates_error": "/api/v2/job_templates/7/notification_templates_error/",
- "access_list": "/api/v2/job_templates/7/access_list/",
- "survey_spec": "/api/v2/job_templates/7/survey_spec/",
- "object_roles": "/api/v2/job_templates/7/object_roles/",
- "instance_groups": "/api/v2/job_templates/7/instance_groups/",
- "slice_workflow_jobs": "/api/v2/job_templates/7/slice_workflow_jobs/",
- "copy": "/api/v2/job_templates/7/copy/",
- "webhook_receiver": "/api/v2/job_templates/7/github/",
- "webhook_key": "/api/v2/job_templates/7/webhook_key/"
- },
- "summary_fields": {
- "inventory": {
- "id": 1,
- "name": "Mike's Inventory",
- "description": "",
- "has_active_failures": false,
- "total_hosts": 1,
- "hosts_with_active_failures": 0,
- "total_groups": 0,
- "groups_with_active_failures": 0,
- "has_inventory_sources": false,
- "total_inventory_sources": 0,
- "inventory_sources_with_failures": 0,
- "organization_id": 1,
- "kind": ""
- },
- "project": {
- "id": 6,
- "name": "Mike's Project",
- "description": "",
- "status": "successful",
- "scm_type": "git"
- },
- "last_job": {
- "id": 12,
- "name": "Mike's JT",
- "description": "",
- "finished": "2019-10-01T14:34:35.142483Z",
- "status": "successful",
- "failed": false
- },
- "last_update": {
- "id": 12,
- "name": "Mike's JT",
- "description": "",
- "status": "successful",
- "failed": false
- },
- "created_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "object_roles": {
- "admin_role": {
- "description": "Can manage all aspects of the job template",
- "name": "Admin",
- "id": 24
- },
- "execute_role": {
- "description": "May run the job template",
- "name": "Execute",
- "id": 25
- },
- "read_role": {
- "description": "May view settings for the job template",
- "name": "Read",
- "id": 26
- }
- },
- "user_capabilities": {
- "edit": true,
- "delete": true,
- "start": true,
- "schedule": true,
- "copy": true
- },
- "labels": {
- "count": 1,
- "results": [{
- "id": 91,
- "name": "L_91o2"
- }]
- },
- "survey": {
- "title": "",
- "description": ""
- },
- "recent_jobs": [{
- "id": 12,
- "status": "successful",
- "finished": "2019-10-01T14:34:35.142483Z",
- "type": "job"
- }],
- "credentials": [{
- "id": 1,
- "kind": "ssh",
- "name": "Credential 1"
- },
- {
- "id": 2,
- "kind": "awx",
- "name": "Credential 2"
- }
- ],
- "webhook_credential": {
- "id": "1",
- "name": "Webhook Credential"
-
- }
- },
- "created": "2019-09-30T16:18:34.564820Z",
- "modified": "2019-10-01T14:47:31.818431Z",
- "name": "Mike's JT",
- "description": "",
- "job_type": "run",
- "inventory": 1,
- "project": 6,
- "playbook": "ping.yml",
- "scm_branch": "Foo branch",
- "forks": 0,
- "limit": "",
- "verbosity": 0,
- "extra_vars": "",
- "job_tags": "T_100,T_200",
- "force_handlers": false,
- "skip_tags": "S_100,S_200",
- "start_at_task": "",
- "timeout": 0,
- "use_fact_cache": true,
- "last_job_run": "2019-10-01T14:34:35.142483Z",
- "last_job_failed": false,
- "next_job_run": null,
- "status": "successful",
- "host_config_key": "",
- "ask_scm_branch_on_launch": false,
- "ask_diff_mode_on_launch": false,
- "ask_variables_on_launch": false,
- "ask_limit_on_launch": false,
- "ask_tags_on_launch": false,
- "ask_skip_tags_on_launch": false,
- "ask_job_type_on_launch": false,
- "ask_verbosity_on_launch": false,
- "ask_inventory_on_launch": false,
- "ask_credential_on_launch": false,
- "survey_enabled": true,
- "become_enabled": false,
- "diff_mode": false,
- "allow_simultaneous": false,
- "custom_virtualenv": null,
- "job_slice_count": 1,
- "webhook_credential": 1,
- "webhook_key": "asertdyuhjkhgfd234567kjgfds",
- "webhook_service": "github"
-}
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironment.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironment.js
deleted file mode 100644
index 8896025bb65c..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironment.js
+++ /dev/null
@@ -1,138 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-import {
- Link,
- Redirect,
- Route,
- Switch,
- useLocation,
- useParams,
-} from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { Card, PageSection } from '@patternfly/react-core';
-import { CaretLeftIcon } from '@patternfly/react-icons';
-
-import useRequest from 'hooks/useRequest';
-import { ExecutionEnvironmentsAPI } from 'api';
-import RoutedTabs from 'components/RoutedTabs';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-
-import ExecutionEnvironmentDetails from './ExecutionEnvironmentDetails';
-import ExecutionEnvironmentEdit from './ExecutionEnvironmentEdit';
-import ExecutionEnvironmentTemplateList from './ExecutionEnvironmentTemplate';
-
-function ExecutionEnvironment({ setBreadcrumb }) {
- const { id } = useParams();
- const { pathname } = useLocation();
-
- const {
- isLoading,
- error: contentError,
- request: fetchExecutionEnvironments,
- result: executionEnvironment,
- } = useRequest(
- useCallback(async () => {
- const { data } = await ExecutionEnvironmentsAPI.readDetail(id);
- return data;
- }, [id]),
- null
- );
-
- useEffect(() => {
- fetchExecutionEnvironments();
- }, [fetchExecutionEnvironments, pathname]);
-
- useEffect(() => {
- if (executionEnvironment) {
- setBreadcrumb(executionEnvironment);
- }
- }, [executionEnvironment, setBreadcrumb]);
-
- const tabsArray = [
- {
- name: (
- <>
-
- {t`Back to execution environments`}
- >
- ),
- link: '/execution_environments',
- id: 99,
- isBackButton: true,
- },
- {
- name: t`Details`,
- link: `/execution_environments/${id}/details`,
- id: 0,
- },
- {
- name: t`Templates`,
- link: `/execution_environments/${id}/templates`,
- id: 1,
- },
- ];
-
- if (!isLoading && contentError) {
- return (
-
-
-
- {contentError.response?.status === 404 && (
-
- {t`Execution environment not found.`}{' '}
-
- {t`View all execution environments`}
-
-
- )}
-
-
-
- );
- }
-
- let cardHeader = ;
- if (pathname.endsWith('edit')) {
- cardHeader = null;
- }
-
- return (
-
-
- {cardHeader}
- {isLoading && }
- {!isLoading && executionEnvironment && (
-
-
- {executionEnvironment && (
- <>
-
-
-
-
-
-
-
-
-
- >
- )}
-
- )}
-
-
- );
-}
-
-export default ExecutionEnvironment;
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentAdd/ExecutionEnvironmentAdd.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentAdd/ExecutionEnvironmentAdd.js
deleted file mode 100644
index 098fb5110963..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentAdd/ExecutionEnvironmentAdd.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import React, { useState } from 'react';
-import { Card, PageSection } from '@patternfly/react-core';
-import { useHistory } from 'react-router-dom';
-
-import { ExecutionEnvironmentsAPI } from 'api';
-import { Config } from 'contexts/Config';
-import { CardBody } from 'components/Card';
-import ExecutionEnvironmentForm from '../shared/ExecutionEnvironmentForm';
-
-function ExecutionEnvironmentAdd() {
- const history = useHistory();
- const [submitError, setSubmitError] = useState(null);
-
- const handleSubmit = async (values) => {
- try {
- const { data: response } = await ExecutionEnvironmentsAPI.create({
- ...values,
- credential: values.credential?.id,
- organization: values.organization?.id,
- });
- history.push(`/execution_environments/${response.id}/details`);
- } catch (error) {
- setSubmitError(error);
- }
- };
-
- const handleCancel = () => {
- history.push(`/execution_environments`);
- };
-
- const hubParams = {
- description: '',
- image: '',
- name: '',
- };
-
- history.location.search
- .replace(/^\?/, '')
- .split('&')
- .map((s) => s.split('='))
- .forEach(([key, val]) => {
- if (!(key in hubParams)) {
- return;
- }
- hubParams[key] = decodeURIComponent(val);
- });
-
- return (
-
-
-
-
- {({ me }) => (
-
- )}
-
-
-
-
- );
-}
-
-export default ExecutionEnvironmentAdd;
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentAdd/ExecutionEnvironmentAdd.test.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentAdd/ExecutionEnvironmentAdd.test.js
deleted file mode 100644
index 22b712e24b2e..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentAdd/ExecutionEnvironmentAdd.test.js
+++ /dev/null
@@ -1,153 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-
-import { ExecutionEnvironmentsAPI, CredentialTypesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import ExecutionEnvironmentAdd from './ExecutionEnvironmentAdd';
-
-jest.mock('../../../api');
-
-const mockMe = {
- is_superuser: true,
- is_system_auditor: false,
-};
-
-const executionEnvironmentData = {
- name: 'Test EE',
- credential: 4,
- description: 'A simple EE',
- image: 'https://registry.com/image/container',
- pull: 'one',
- summary_fields: {
- credential: {
- id: 4,
- name: 'Container Registry',
- description: '',
- kind: 'registry',
- cloud: false,
- kubernetes: false,
- credential_type_id: 17,
- },
- },
-};
-
-const mockOptions = {
- data: {
- actions: {
- POST: {
- pull: {
- choices: [
- ['one', 'One'],
- ['two', 'Two'],
- ['three', 'Three'],
- ],
- },
- },
- },
- },
-};
-
-const containerRegistryCredentialResolve = {
- data: {
- results: [
- {
- id: 4,
- name: 'Container Registry',
- kind: 'registry',
- },
- ],
- count: 1,
- },
-};
-
-describe('', () => {
- let wrapper;
- let history;
-
- beforeEach(async () => {
- CredentialTypesAPI.read.mockResolvedValue(
- containerRegistryCredentialResolve
- );
- history = createMemoryHistory({
- initialEntries: ['/execution_environments'],
- });
- ExecutionEnvironmentsAPI.readOptions.mockResolvedValue(mockOptions);
- ExecutionEnvironmentsAPI.create.mockResolvedValue({
- data: {
- id: 42,
- },
- });
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
-
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('handleSubmit should call the api and redirect to details page', async () => {
- await act(async () => {
- wrapper.find('ExecutionEnvironmentForm').prop('onSubmit')({
- executionEnvironmentData,
- });
- });
- wrapper.update();
- expect(ExecutionEnvironmentsAPI.create).toHaveBeenCalledWith({
- executionEnvironmentData,
- });
- expect(history.location.pathname).toBe(
- '/execution_environments/42/details'
- );
- });
-
- test('handleCancel should return the user back to the execution environments list', async () => {
- wrapper.find('Button[aria-label="Cancel"]').simulate('click');
- expect(history.location.pathname).toEqual('/execution_environments');
- });
-
- test('failed form submission should show an error message', async () => {
- const error = {
- response: {
- data: { detail: 'An error occurred' },
- },
- };
- ExecutionEnvironmentsAPI.create.mockImplementationOnce(() =>
- Promise.reject(error)
- );
- await act(async () => {
- wrapper.find('ExecutionEnvironmentForm').invoke('onSubmit')(
- executionEnvironmentData
- );
- });
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(1);
- });
-
- test('should parse and prefill select form fields from query params', async () => {
- history = createMemoryHistory({
- initialEntries: [
- '/execution_environments/add?image=https://myhub.io/repo:2.0',
- ],
- });
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
-
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
-
- expect(
- wrapper.find('input#execution-environment-image').prop('value')
- ).toEqual('https://myhub.io/repo:2.0');
- });
-});
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentAdd/index.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentAdd/index.js
deleted file mode 100644
index 69765fcf3b7c..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentAdd/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ExecutionEnvironmentAdd';
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js
deleted file mode 100644
index 6634c93bff23..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.js
+++ /dev/null
@@ -1,151 +0,0 @@
-import React, { useCallback } from 'react';
-
-import { t } from '@lingui/macro';
-import { Link, useHistory } from 'react-router-dom';
-import { Button, Label } from '@patternfly/react-core';
-
-import AlertModal from 'components/AlertModal';
-import { CardBody, CardActionsRow } from 'components/Card';
-import DeleteButton from 'components/DeleteButton';
-import { Detail, DetailList, UserDateDetail } from 'components/DetailList';
-import useRequest, { useDismissableError } from 'hooks/useRequest';
-import { toTitleCase } from 'util/strings';
-import { ExecutionEnvironmentsAPI } from 'api';
-import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
-import getHelpText from '../shared/ExecutionEnvironment.helptext';
-
-function ExecutionEnvironmentDetails({ executionEnvironment }) {
- const helpText = getHelpText();
- const history = useHistory();
- const {
- id,
- name,
- image,
- description,
- pull,
- organization,
- summary_fields,
- managed: managedByTower,
- } = executionEnvironment;
-
- const {
- request: deleteExecutionEnvironment,
- isLoading,
- error: deleteError,
- } = useRequest(
- useCallback(async () => {
- await ExecutionEnvironmentsAPI.destroy(id);
- history.push(`/execution_environments`);
- }, [id, history])
- );
-
- const { error, dismissError } = useDismissableError(deleteError);
- const deleteDetailsRequests =
- relatedResourceDeleteRequests.executionEnvironment(executionEnvironment);
- return (
-
-
-
-
-
-
-
- {summary_fields.organization.name}
-
- ) : (
- t`Globally Available`
- )
- }
- dataCy="execution-environment-detail-organization"
- />
-
-
- {executionEnvironment.summary_fields.credential && (
-
- {executionEnvironment.summary_fields.credential.name}
-
- }
- dataCy="execution-environment-credential"
- helpText={helpText.registryCredential}
- />
- )}
-
-
-
-
- {summary_fields.user_capabilities?.edit && (
-
- )}
- {summary_fields.user_capabilities?.delete && (
-
- {t`Delete`}
-
- )}
-
-
- {error && (
-
- )}
-
- );
-}
-
-export default ExecutionEnvironmentDetails;
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.test.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.test.js
deleted file mode 100644
index 73d96708e98e..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentDetails/ExecutionEnvironmentDetails.test.js
+++ /dev/null
@@ -1,281 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-
-import { ExecutionEnvironmentsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import ExecutionEnvironmentDetails from './ExecutionEnvironmentDetails';
-
-jest.mock('../../../api');
-
-const executionEnvironment = {
- id: 17,
- type: 'execution_environment',
- url: '/api/v2/execution_environments/17/',
- related: {
- created_by: '/api/v2/users/1/',
- modified_by: '/api/v2/users/1/',
- activity_stream: '/api/v2/execution_environments/17/activity_stream/',
- unified_job_templates:
- '/api/v2/execution_environments/17/unified_job_templates/',
- credential: '/api/v2/credentials/4/',
- },
- summary_fields: {
- user_capabilities: {
- edit: true,
- delete: true,
- copy: true,
- },
- credential: {
- id: 4,
- name: 'Container Registry',
- },
- created_by: {
- id: 1,
- username: 'admin',
- first_name: '',
- last_name: '',
- },
- modified_by: {
- id: 1,
- username: 'admin',
- first_name: '',
- last_name: '',
- },
- },
- name: 'Default EE',
- created: '2020-09-17T20:14:15.408782Z',
- modified: '2020-09-17T20:14:15.408802Z',
- description: 'Foo',
- organization: null,
- image: 'https://localhost:90/12345/ma',
- managed: false,
- credential: 4,
-};
-
-describe('', () => {
- let wrapper;
- test('should render details properly', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
-
- expect(wrapper.find('Detail[label="Image"]').prop('value')).toEqual(
- executionEnvironment.image
- );
- expect(wrapper.find('Detail[label="Description"]').prop('value')).toEqual(
- 'Foo'
- );
- expect(wrapper.find('Detail[label="Organization"]').prop('value')).toEqual(
- 'Globally Available'
- );
- expect(
- wrapper.find('Detail[label="Registry credential"]').prop('value').props
- .children
- ).toEqual(executionEnvironment.summary_fields.credential.name);
- expect(wrapper.find('Detail[label="Managed"]').prop('value')).toEqual(
- 'False'
- );
- const dates = wrapper.find('UserDateDetail');
- expect(dates).toHaveLength(2);
- expect(dates.at(0).prop('date')).toEqual(executionEnvironment.created);
- expect(dates.at(1).prop('date')).toEqual(executionEnvironment.modified);
- const editButton = wrapper.find('Button[aria-label="edit"]');
- expect(editButton.text()).toEqual('Edit');
- expect(editButton.prop('to')).toBe('/execution_environments/17/edit');
-
- const deleteButton = wrapper.find('Button[aria-label="Delete"]');
- expect(deleteButton.text()).toEqual('Delete');
- });
-
- test('should render organization detail', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
-
- expect(wrapper.find('Detail[label="Image"]').prop('value')).toEqual(
- executionEnvironment.image
- );
- expect(wrapper.find('Detail[label="Description"]').prop('value')).toEqual(
- 'Foo'
- );
- expect(wrapper.find(`Detail[label="Organization"] dd`).text()).toBe('Bar');
- expect(
- wrapper.find('Detail[label="Registry credential"]').prop('value').props
- .children
- ).toEqual(executionEnvironment.summary_fields.credential.name);
- const dates = wrapper.find('UserDateDetail');
- expect(dates).toHaveLength(2);
- expect(dates.at(0).prop('date')).toEqual(executionEnvironment.created);
- expect(dates.at(1).prop('date')).toEqual(executionEnvironment.modified);
- });
-
- test('expected api call is made for delete', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/execution_environments/42/details'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
- await act(async () => {
- wrapper.find('DeleteButton').invoke('onConfirm')();
- });
- expect(ExecutionEnvironmentsAPI.destroy).toHaveBeenCalledTimes(1);
- expect(history.location.pathname).toBe('/execution_environments');
- });
-
- test('should render action buttons to managed ee', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
-
- expect(wrapper.find('Detail[label="Image"]').prop('value')).toEqual(
- executionEnvironment.image
- );
- expect(wrapper.find('Detail[label="Description"]').prop('value')).toEqual(
- 'Foo'
- );
- expect(wrapper.find('Detail[label="Organization"]').prop('value')).toEqual(
- 'Globally Available'
- );
- expect(
- wrapper.find('Detail[label="Registry credential"]').prop('value').props
- .children
- ).toEqual(executionEnvironment.summary_fields.credential.name);
- expect(wrapper.find('Detail[label="Managed"]').prop('value')).toEqual(
- 'True'
- );
- const dates = wrapper.find('UserDateDetail');
- expect(dates).toHaveLength(2);
- expect(dates.at(0).prop('date')).toEqual(executionEnvironment.created);
- expect(dates.at(1).prop('date')).toEqual(executionEnvironment.modified);
- expect(wrapper.find('Button[aria-label="edit"]')).toHaveLength(1);
-
- expect(wrapper.find('Button[aria-label="Delete"]')).toHaveLength(1);
- });
-
- test('should have proper number of delete detail requests', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/execution_environments/42/details'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
- expect(
- wrapper.find('DeleteButton').prop('deleteDetailsRequests')
- ).toHaveLength(4);
- });
-
- test('should show edit button for users with edit permission', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- const editButton = await waitForElement(
- wrapper,
- 'ExecutionEnvironmentDetails Button[aria-label="edit"]'
- );
- expect(editButton.text()).toEqual('Edit');
- expect(editButton.prop('to')).toBe('/execution_environments/17/edit');
- });
-
- test('should hide edit button for users without edit permission', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ExecutionEnvironmentDetails');
- expect(
- wrapper.find('ExecutionEnvironmentDetails Button[aria-label="edit"]')
- .length
- ).toBe(0);
- });
-
- test('should show delete button for users with delete permission', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- const deleteButton = await waitForElement(
- wrapper,
- 'ExecutionEnvironmentDetails Button[aria-label="Delete"]'
- );
- expect(deleteButton.text()).toEqual('Delete');
- });
-
- test('should hide delete button for users without delete permission', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ExecutionEnvironmentDetails');
- expect(
- wrapper.find('ExecutionEnvironmentDetails Button[aria-label="Delete"]')
- .length
- ).toBe(0);
- });
-});
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentDetails/index.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentDetails/index.js
deleted file mode 100644
index 36121ea0d9ce..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentDetails/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ExecutionEnvironmentDetails';
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentEdit/ExecutionEnvironmentEdit.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentEdit/ExecutionEnvironmentEdit.js
deleted file mode 100644
index ab6b34f282b6..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentEdit/ExecutionEnvironmentEdit.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import React, { useState } from 'react';
-import { useHistory } from 'react-router-dom';
-
-import { CardBody } from 'components/Card';
-import { ExecutionEnvironmentsAPI } from 'api';
-import { Config } from 'contexts/Config';
-import ExecutionEnvironmentForm from '../shared/ExecutionEnvironmentForm';
-
-function ExecutionEnvironmentEdit({ executionEnvironment }) {
- const history = useHistory();
- const [submitError, setSubmitError] = useState(null);
- const detailsUrl = `/execution_environments/${executionEnvironment.id}/details`;
-
- const handleSubmit = async (values) => {
- try {
- await ExecutionEnvironmentsAPI.update(executionEnvironment.id, {
- ...values,
- credential: values.credential ? values.credential.id : null,
- organization: values.organization ? values.organization.id : null,
- });
- history.push(detailsUrl);
- } catch (error) {
- setSubmitError(error);
- }
- };
-
- const handleCancel = () => {
- history.push(detailsUrl);
- };
- return (
-
-
- {({ me }) => (
-
- )}
-
-
- );
-}
-
-export default ExecutionEnvironmentEdit;
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentEdit/ExecutionEnvironmentEdit.test.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentEdit/ExecutionEnvironmentEdit.test.js
deleted file mode 100644
index a5a266298b95..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentEdit/ExecutionEnvironmentEdit.test.js
+++ /dev/null
@@ -1,144 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-
-import { ExecutionEnvironmentsAPI, CredentialTypesAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import ExecutionEnvironmentEdit from './ExecutionEnvironmentEdit';
-
-jest.mock('../../../api');
-
-const mockMe = {
- is_superuser: true,
- is_system_auditor: false,
-};
-
-const executionEnvironmentData = {
- id: 42,
- credential: { id: 4 },
- description: 'A simple EE',
- image: 'https://registry.com/image/container',
- pull: 'one',
- name: 'Test EE',
-};
-
-const updateExecutionEnvironmentData = {
- image: 'https://registry.com/image/container2',
- description: 'Updated new description',
-};
-
-const mockOptions = {
- data: {
- actions: {
- POST: {
- pull: {
- choices: [
- ['one', 'One'],
- ['two', 'Two'],
- ['three', 'Three'],
- ],
- },
- },
- },
- },
-};
-
-const containerRegistryCredentialResolve = {
- data: {
- results: [
- {
- id: 4,
- name: 'Container Registry',
- kind: 'registry',
- },
- ],
- count: 1,
- },
-};
-
-describe('', () => {
- let wrapper;
- let history;
-
- beforeAll(async () => {
- ExecutionEnvironmentsAPI.readOptions.mockResolvedValue(mockOptions);
- CredentialTypesAPI.read.mockResolvedValue(
- containerRegistryCredentialResolve
- );
- history = createMemoryHistory();
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('handleSubmit should call the api and redirect to details page', async () => {
- await act(async () => {
- wrapper.find('ExecutionEnvironmentForm').invoke('onSubmit')(
- updateExecutionEnvironmentData
- );
- wrapper.update();
- expect(ExecutionEnvironmentsAPI.update).toHaveBeenCalledWith(42, {
- ...updateExecutionEnvironmentData,
- credential: null,
- organization: null,
- });
- });
-
- expect(history.location.pathname).toEqual(
- '/execution_environments/42/details'
- );
- });
-
- test('should navigate to execution environments details when cancel is clicked', async () => {
- await act(async () => {
- wrapper.find('button[aria-label="Cancel"]').prop('onClick')();
- });
- expect(history.location.pathname).toEqual(
- '/execution_environments/42/details'
- );
- });
-
- test('should navigate to execution environments detail after successful submission', async () => {
- await act(async () => {
- wrapper.find('ExecutionEnvironmentForm').invoke('onSubmit')({
- updateExecutionEnvironmentData,
- });
- });
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(0);
- expect(history.location.pathname).toEqual(
- '/execution_environments/42/details'
- );
- });
-
- test('failed form submission should show an error message', async () => {
- const error = {
- response: {
- data: { detail: 'An error occurred' },
- },
- };
- ExecutionEnvironmentsAPI.update.mockImplementationOnce(() =>
- Promise.reject(error)
- );
- await act(async () => {
- wrapper.find('ExecutionEnvironmentForm').invoke('onSubmit')(
- updateExecutionEnvironmentData
- );
- });
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentEdit/index.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentEdit/index.js
deleted file mode 100644
index 6ab135ca0579..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentEdit/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ExecutionEnvironmentEdit';
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js
deleted file mode 100644
index 6153f3217c88..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.js
+++ /dev/null
@@ -1,241 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-import { useLocation, useRouteMatch } from 'react-router-dom';
-import { t, Plural } from '@lingui/macro';
-import { Card, PageSection } from '@patternfly/react-core';
-
-import { ExecutionEnvironmentsAPI } from 'api';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import useRequest, { useDeleteItems } from 'hooks/useRequest';
-import useSelected from 'hooks/useSelected';
-import useToast, { AlertVariant } from 'hooks/useToast';
-import PaginatedTable, {
- HeaderRow,
- HeaderCell,
- ToolbarDeleteButton,
- ToolbarAddButton,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import ErrorDetail from 'components/ErrorDetail';
-import AlertModal from 'components/AlertModal';
-import DatalistToolbar from 'components/DataListToolbar';
-import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
-import ExecutionEnvironmentsListItem from './ExecutionEnvironmentListItem';
-
-const QS_CONFIG = getQSConfig('execution_environments', {
- page: 1,
- page_size: 20,
- order_by: 'name',
-});
-
-function ExecutionEnvironmentList() {
- const location = useLocation();
- const match = useRouteMatch();
- const { addToast, Toast, toastProps } = useToast();
-
- const {
- error: contentError,
- isLoading,
- request: fetchExecutionEnvironments,
- result: {
- executionEnvironments,
- executionEnvironmentsCount,
- actions,
- relatedSearchableKeys,
- searchableKeys,
- },
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
-
- const [response, responseActions] = await Promise.all([
- ExecutionEnvironmentsAPI.read(params),
- ExecutionEnvironmentsAPI.readOptions(),
- ]);
-
- return {
- executionEnvironments: response.data.results,
- executionEnvironmentsCount: response.data.count,
- actions: responseActions.data.actions,
- relatedSearchableKeys: (
- responseActions?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(responseActions.data.actions?.GET),
- };
- }, [location]),
- {
- executionEnvironments: [],
- executionEnvironmentsCount: 0,
- actions: {},
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- useEffect(() => {
- fetchExecutionEnvironments();
- }, [fetchExecutionEnvironments]);
-
- const { selected, isAllSelected, handleSelect, clearSelected, selectAll } =
- useSelected(executionEnvironments);
-
- const {
- isLoading: deleteLoading,
- deletionError,
- deleteItems: deleteExecutionEnvironments,
- clearDeletionError,
- } = useDeleteItems(
- useCallback(async () => {
- await Promise.all(
- selected.map(({ id }) => ExecutionEnvironmentsAPI.destroy(id))
- );
- }, [selected]),
- {
- qsConfig: QS_CONFIG,
- allItemsSelected: isAllSelected,
- fetchItems: fetchExecutionEnvironments,
- }
- );
-
- const handleCopy = useCallback(
- (newId) => {
- addToast({
- id: newId,
- title: t`Execution environment copied successfully`,
- variant: AlertVariant.success,
- hasTimeout: true,
- });
- },
- [addToast]
- );
-
- const handleDelete = async () => {
- await deleteExecutionEnvironments();
- clearSelected();
- };
-
- const canAdd = actions && actions.POST;
- const deleteDetailsRequests =
- relatedResourceDeleteRequests.executionEnvironment(selected[0]);
- return (
- <>
-
-
-
- {t`Name`}
- {t`Image`}
- {t`Organization`}
- {t`Actions`}
-
- }
- renderToolbar={(props) => (
- ,
- ]
- : []),
-
- }
- />,
- ]}
- />
- )}
- renderRow={(executionEnvironment, index) => (
- handleSelect(executionEnvironment)}
- onCopy={handleCopy}
- isSelected={selected.some(
- (row) => row.id === executionEnvironment.id
- )}
- fetchExecutionEnvironments={fetchExecutionEnvironments}
- />
- )}
- emptyStateControls={
- canAdd && (
-
- )
- }
- />
-
-
-
- {t`Failed to delete one or more execution environments`}
-
-
-
- >
- );
-}
-
-export default ExecutionEnvironmentList;
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.test.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.test.js
deleted file mode 100644
index a373b37e58cb..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentList.test.js
+++ /dev/null
@@ -1,222 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import {
- ExecutionEnvironmentsAPI,
- InventorySourcesAPI,
- WorkflowJobTemplateNodesAPI,
- OrganizationsAPI,
- ProjectsAPI,
- UnifiedJobTemplatesAPI,
-} from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import ExecutionEnvironmentList from './ExecutionEnvironmentList';
-
-jest.mock('../../../api/models/ExecutionEnvironments');
-jest.mock('../../../api/models/UnifiedJobTemplates');
-jest.mock('../../../api/models/Projects');
-jest.mock('../../../api/models/Organizations');
-jest.mock('../../../api/models/InventorySources');
-jest.mock('../../../api/models/WorkflowJobTemplateNodes');
-
-const executionEnvironments = {
- data: {
- results: [
- {
- name: 'Foo',
- id: 1,
- image: 'https://registry.com/r/image/manifest',
- organization: null,
- credential: null,
- url: '/api/v2/execution_environments/1/',
- summary_fields: { user_capabilities: { edit: true, delete: true } },
- },
- {
- name: 'Bar',
- id: 2,
- image: 'https://registry.com/r/image2/manifest',
- organization: null,
- credential: null,
- url: '/api/v2/execution_environments/2/',
- summary_fields: { user_capabilities: { edit: false, delete: true } },
- },
- ],
- count: 2,
- },
-};
-
-const options = { data: { actions: { POST: true } } };
-
-describe('', () => {
- beforeEach(() => {
- ExecutionEnvironmentsAPI.read.mockResolvedValue(executionEnvironments);
- ExecutionEnvironmentsAPI.readOptions.mockResolvedValue(options);
- InventorySourcesAPI.read.mockResolvedValue({
- data: { results: [{ id: 10000000 }] },
- });
- WorkflowJobTemplateNodesAPI.read.mockResolvedValue({ data: { count: 0 } });
-
- OrganizationsAPI.read.mockResolvedValue({ data: { count: 0 } });
-
- UnifiedJobTemplatesAPI.read.mockResolvedValue({ data: { count: 0 } });
-
- ProjectsAPI.read.mockResolvedValue({ data: { count: 0 } });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
- let wrapper;
-
- test('should mount successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(
- wrapper,
- 'ExecutionEnvironmentList',
- (el) => el.length > 0
- );
- });
-
- test('should have data fetched and render 2 rows', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(
- wrapper,
- 'ExecutionEnvironmentList',
- (el) => el.length > 0
- );
-
- expect(wrapper.find('ExecutionEnvironmentListItem').length).toBe(2);
- expect(ExecutionEnvironmentsAPI.read).toBeCalled();
- expect(ExecutionEnvironmentsAPI.readOptions).toBeCalled();
- });
-
- test('should delete items successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(
- wrapper,
- 'ExecutionEnvironmentList',
- (el) => el.length > 0
- );
-
- await act(async () => {
- wrapper.find('ExecutionEnvironmentListItem').at(0).invoke('onSelect')();
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('ExecutionEnvironmentListItem').at(1).invoke('onSelect')();
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('ToolbarDeleteButton').invoke('onDelete')();
- });
-
- expect(ExecutionEnvironmentsAPI.destroy).toHaveBeenCalledTimes(2);
- });
-
- test('should render deletion error modal', async () => {
- ExecutionEnvironmentsAPI.destroy.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'DELETE',
- url: '/api/v2/execution_environments',
- },
- data: 'An error occurred',
- },
- })
- );
- await act(async () => {
- wrapper = mountWithContexts();
- });
- waitForElement(wrapper, 'ExecutionEnvironmentList', (el) => el.length > 0);
-
- wrapper
- .find('ExecutionEnvironmentListItem')
- .at(0)
- .find('input')
- .simulate('change', 'a');
- wrapper.update();
-
- expect(
- wrapper
- .find('ExecutionEnvironmentListItem')
- .at(0)
- .find('input')
- .prop('checked')
- ).toBe(true);
-
- await act(async () =>
- wrapper.find('Button[aria-label="Delete"]').prop('onClick')()
- );
- wrapper.update();
-
- await waitForElement(
- wrapper,
- 'Button[aria-label="confirm delete"]',
- (el) => el.length > 0
- );
- await act(async () =>
- wrapper.find('Button[aria-label="confirm delete"]').prop('onClick')()
- );
- wrapper.update();
- expect(wrapper.find('ErrorDetail').length).toBe(1);
- });
-
- test('should thrown content error', async () => {
- ExecutionEnvironmentsAPI.read.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'GET',
- url: '/api/v2/execution_environments',
- },
- data: 'An error occurred',
- },
- })
- );
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(
- wrapper,
- 'ExecutionEnvironmentList',
- (el) => el.length > 0
- );
- expect(wrapper.find('ContentError').length).toBe(1);
- });
-
- test('should not render add button', async () => {
- ExecutionEnvironmentsAPI.read.mockResolvedValue(executionEnvironments);
- ExecutionEnvironmentsAPI.readOptions.mockResolvedValue({
- data: { actions: { POST: false } },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- waitForElement(wrapper, 'ExecutionEnvironmentList', (el) => el.length > 0);
- expect(wrapper.find('ToolbarAddButton').length).toBe(0);
- });
-
- test('should have proper number of delete detail requests', async () => {
- ExecutionEnvironmentsAPI.read.mockResolvedValue(executionEnvironments);
- ExecutionEnvironmentsAPI.readOptions.mockResolvedValue({
- data: { actions: { POST: false } },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- expect(
- wrapper.find('ToolbarDeleteButton').prop('deleteDetailsRequests')
- ).toHaveLength(4);
- });
-});
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js
deleted file mode 100644
index 8281c55a6899..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.js
+++ /dev/null
@@ -1,128 +0,0 @@
-import React, { useState, useCallback } from 'react';
-import { string, bool, func } from 'prop-types';
-
-import { t } from '@lingui/macro';
-import { Link } from 'react-router-dom';
-import { Button } from '@patternfly/react-core';
-import { Tr, Td } from '@patternfly/react-table';
-import { PencilAltIcon } from '@patternfly/react-icons';
-
-import { ActionsTd, ActionItem, TdBreakWord } from 'components/PaginatedTable';
-import CopyButton from 'components/CopyButton';
-import { ExecutionEnvironment } from 'types';
-import { ExecutionEnvironmentsAPI } from 'api';
-import { timeOfDay } from 'util/dates';
-
-function ExecutionEnvironmentListItem({
- executionEnvironment,
- detailUrl,
- isSelected,
- onSelect,
- onCopy,
- rowIndex,
- fetchExecutionEnvironments,
-}) {
- const [isDisabled, setIsDisabled] = useState(false);
-
- const copyExecutionEnvironment = useCallback(async () => {
- const response = await ExecutionEnvironmentsAPI.copy(
- executionEnvironment.id,
- {
- name: `${executionEnvironment.name} @ ${timeOfDay()}`,
- }
- );
- if (response.status === 201) {
- onCopy(response.data.id);
- }
- await fetchExecutionEnvironments();
- }, [
- executionEnvironment.id,
- executionEnvironment.name,
- fetchExecutionEnvironments,
- onCopy,
- ]);
-
- const handleCopyStart = useCallback(() => {
- setIsDisabled(true);
- }, []);
-
- const handleCopyFinish = useCallback(() => {
- setIsDisabled(false);
- }, []);
-
- return (
-
- |
-
-
- {executionEnvironment.name}
-
-
- {executionEnvironment.image} |
-
- {executionEnvironment.organization ? (
-
- {executionEnvironment?.summary_fields?.organization?.name}
-
- ) : (
- t`Globally Available`
- )}
- |
-
-
-
-
-
-
-
-
-
- );
-}
-
-ExecutionEnvironmentListItem.prototype = {
- executionEnvironment: ExecutionEnvironment.isRequired,
- detailUrl: string.isRequired,
- isSelected: bool.isRequired,
- onSelect: func.isRequired,
- onCopy: func.isRequired,
-};
-
-export default ExecutionEnvironmentListItem;
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.test.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.test.js
deleted file mode 100644
index 6c50e2ffb9e0..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/ExecutionEnvironmentListItem.test.js
+++ /dev/null
@@ -1,160 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { ExecutionEnvironmentsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import ExecutionEnvironmentListItem from './ExecutionEnvironmentListItem';
-
-jest.mock('../../../api');
-
-describe('', () => {
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- let wrapper;
- const executionEnvironment = {
- name: 'Foo',
- id: 1,
- image: 'https://registry.com/r/image/manifest',
- organization: null,
- credential: null,
- summary_fields: {
- user_capabilities: { edit: true, copy: true, delete: true },
- },
- managed: false,
- };
-
- test('should mount successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('ExecutionEnvironmentListItem').length).toBe(1);
- });
-
- test('should render the proper data', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('Td').at(1).text()).toBe(executionEnvironment.name);
- expect(wrapper.find('Td').at(2).text()).toBe(executionEnvironment.image);
-
- expect(wrapper.find('Td').at(3).text()).toBe('Globally Available');
-
- expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
- });
-
- test('should call api to copy execution environment', async () => {
- ExecutionEnvironmentsAPI.copy.mockResolvedValue();
-
- wrapper = mountWithContexts(
-
- );
-
- await act(async () =>
- wrapper.find('Button[aria-label="Copy"]').prop('onClick')()
- );
- expect(ExecutionEnvironmentsAPI.copy).toHaveBeenCalled();
- });
-
- test('should render proper alert modal on copy error', async () => {
- ExecutionEnvironmentsAPI.copy.mockRejectedValue(new Error());
-
- wrapper = mountWithContexts(
-
- );
-
- await act(async () =>
- wrapper.find('Button[aria-label="Copy"]').prop('onClick')()
- );
- wrapper.update();
- expect(wrapper.find('Modal').prop('isOpen')).toBe(true);
- });
-
- test('should not render copy button', async () => {
- wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('CopyButton').length).toBe(0);
- });
-
- test('should not render the pencil action for managed ee', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('Td').at(1).text()).toBe(executionEnvironment.name);
- expect(wrapper.find('Td').at(2).text()).toBe(executionEnvironment.image);
-
- expect(wrapper.find('Td').at(3).text()).toBe('Globally Available');
-
- expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
- });
-});
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/index.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/index.js
deleted file mode 100644
index a8aa4263d786..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentList/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ExecutionEnvironmentList';
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js
deleted file mode 100644
index b5343bd4b031..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.js
+++ /dev/null
@@ -1,131 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-import { useLocation } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { Card } from '@patternfly/react-core';
-
-import { ExecutionEnvironmentsAPI } from 'api';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import useRequest from 'hooks/useRequest';
-import DatalistToolbar from 'components/DataListToolbar';
-import PaginatedTable, {
- HeaderCell,
- HeaderRow,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-
-import ExecutionEnvironmentTemplateListItem from './ExecutionEnvironmentTemplateListItem';
-
-const QS_CONFIG = getQSConfig(
- 'execution_environments',
- {
- page: 1,
- page_size: 20,
- order_by: 'name',
- type: 'job_template,workflow_job_template',
- },
- ['id', 'page', 'page_size']
-);
-
-function ExecutionEnvironmentTemplateList({ executionEnvironment }) {
- const { id } = executionEnvironment;
- const location = useLocation();
-
- const {
- error: contentError,
- isLoading,
- request: fetchTemplates,
- result: {
- templates,
- templatesCount,
- relatedSearchableKeys,
- searchableKeys,
- },
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
-
- const [response, responseActions] = await Promise.all([
- ExecutionEnvironmentsAPI.readUnifiedJobTemplates(id, params),
- ExecutionEnvironmentsAPI.readUnifiedJobTemplateOptions(id),
- ]);
-
- return {
- templates: response.data.results,
- templatesCount: response.data.count,
- actions: responseActions.data.actions,
- relatedSearchableKeys: (
- responseActions?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(responseActions.data.actions?.GET),
- };
- }, [location, id]),
- {
- templates: [],
- templatesCount: 0,
- actions: {},
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- useEffect(() => {
- fetchTemplates();
- }, [fetchTemplates]);
-
- return (
-
- (
-
- )}
- headerRow={
-
- {t`Name`}
- {t`Type`}
-
- }
- renderRow={(template) => (
-
- )}
- />
-
- );
-}
-
-export default ExecutionEnvironmentTemplateList;
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.test.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.test.js
deleted file mode 100644
index 4f56b6979cde..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateList.test.js
+++ /dev/null
@@ -1,116 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { ExecutionEnvironmentsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import ExecutionEnvironmentTemplateList from './ExecutionEnvironmentTemplateList';
-
-jest.mock('../../../api/');
-
-const templates = {
- data: {
- count: 3,
- results: [
- {
- id: 1,
- type: 'job_template',
- name: 'Foo',
- url: '/api/v2/job_templates/1/',
- related: {
- execution_environment: '/api/v2/execution_environments/1/',
- },
- },
- {
- id: 2,
- type: 'workflow_job_template',
- name: 'Bar',
- url: '/api/v2/workflow_job_templates/2/',
- related: {
- execution_environment: '/api/v2/execution_environments/1/',
- },
- },
- {
- id: 3,
- type: 'job_template',
- name: 'Fuzz',
- url: '/api/v2/job_templates/3/',
- related: {
- execution_environment: '/api/v2/execution_environments/1/',
- },
- },
- ],
- },
-};
-
-const mockExecutionEnvironment = {
- id: 1,
- name: 'Default EE',
-};
-
-const options = { data: { actions: { GET: {} } } };
-
-describe('', () => {
- let wrapper;
-
- test('should mount successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(
- wrapper,
- 'ExecutionEnvironmentTemplateList',
- (el) => el.length > 0
- );
- });
-
- test('should have data fetched and render 3 rows', async () => {
- ExecutionEnvironmentsAPI.readUnifiedJobTemplates.mockResolvedValue(
- templates
- );
-
- ExecutionEnvironmentsAPI.readUnifiedJobTemplateOptions.mockResolvedValue(
- options
- );
-
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(
- wrapper,
- 'ExecutionEnvironmentTemplateList',
- (el) => el.length > 0
- );
-
- expect(wrapper.find('ExecutionEnvironmentTemplateListItem').length).toBe(3);
- expect(ExecutionEnvironmentsAPI.readUnifiedJobTemplates).toBeCalled();
- expect(ExecutionEnvironmentsAPI.readUnifiedJobTemplateOptions).toBeCalled();
- });
-
- test('should not render add button', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- waitForElement(
- wrapper,
- 'ExecutionEnvironmentTemplateList',
- (el) => el.length > 0
- );
- expect(wrapper.find('ToolbarAddButton').length).toBe(0);
- });
-});
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js
deleted file mode 100644
index 86f4c843f79a..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import React from 'react';
-import { t } from '@lingui/macro';
-import { Link } from 'react-router-dom';
-import { Tr, Td } from '@patternfly/react-table';
-
-function ExecutionEnvironmentTemplateListItem({ template, detailUrl }) {
- return (
-
- |
-
- {template.name}
-
- |
-
- {template.type === 'job_template'
- ? t`Job Template`
- : t`Workflow Job Template`}
- |
-
- );
-}
-
-export default ExecutionEnvironmentTemplateListItem;
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.test.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.test.js
deleted file mode 100644
index e51c3e26ada1..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/ExecutionEnvironmentTemplateListItem.test.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import ExecutionEnvironmentTemplateListItem from './ExecutionEnvironmentTemplateListItem';
-
-describe('', () => {
- let wrapper;
- const template = {
- id: 1,
- name: 'Foo',
- type: 'job_template',
- };
-
- test('should mount successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('ExecutionEnvironmentTemplateListItem').length).toBe(1);
- expect(wrapper.find('Td').at(0).text()).toBe(template.name);
- expect(wrapper.find('Td').at(1).text()).toBe('Job Template');
- });
-
- test('should distinguish template types', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('ExecutionEnvironmentTemplateListItem').length).toBe(1);
- expect(wrapper.find('Td').at(1).text()).toBe('Workflow Job Template');
- });
-});
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/index.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/index.js
deleted file mode 100644
index 3bdea254eeb6..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironmentTemplate/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ExecutionEnvironmentTemplateList';
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironments.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironments.js
deleted file mode 100644
index e2a9a26cf60b..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironments.js
+++ /dev/null
@@ -1,51 +0,0 @@
-import React, { useState, useCallback } from 'react';
-
-import { t } from '@lingui/macro';
-import { Route, Switch } from 'react-router-dom';
-import PersistentFilters from 'components/PersistentFilters';
-import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
-import ExecutionEnvironment from './ExecutionEnvironment';
-import ExecutionEnvironmentAdd from './ExecutionEnvironmentAdd';
-import ExecutionEnvironmentList from './ExecutionEnvironmentList';
-
-function ExecutionEnvironments() {
- const [breadcrumbConfig, setBreadcrumbConfig] = useState({
- '/execution_environments': t`Execution Environments`,
- '/execution_environments/add': t`Create new execution environment`,
- });
-
- const buildBreadcrumbConfig = useCallback((executionEnvironments) => {
- if (!executionEnvironments) {
- return;
- }
- setBreadcrumbConfig({
- '/execution_environments': t`Execution Environments`,
- '/execution_environments/add': t`Create new execution environment`,
- [`/execution_environments/${executionEnvironments.id}`]: `${executionEnvironments.name}`,
- [`/execution_environments/${executionEnvironments.id}/edit`]: t`Edit details`,
- [`/execution_environments/${executionEnvironments.id}/details`]: t`Details`,
- });
- }, []);
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-}
-export default ExecutionEnvironments;
diff --git a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironments.test.js b/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironments.test.js
deleted file mode 100644
index e036174dc947..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironments.test.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import React from 'react';
-
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-
-import ExecutionEnvironments from './ExecutionEnvironments';
-
-describe('', () => {
- let pageWrapper;
- let pageSections;
-
- beforeEach(() => {
- pageWrapper = mountWithContexts();
- pageSections = pageWrapper.find('PageSection');
- });
-
- test('initially renders without crashing', () => {
- expect(pageWrapper.length).toBe(1);
- expect(pageSections.length).toBe(1);
- expect(pageSections.first().props().variant).toBe('light');
- });
-});
diff --git a/awx/ui/src/screens/ExecutionEnvironment/index.js b/awx/ui/src/screens/ExecutionEnvironment/index.js
deleted file mode 100644
index f66a2b3cf3ae..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ExecutionEnvironments';
diff --git a/awx/ui/src/screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js b/awx/ui/src/screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js
deleted file mode 100644
index 539bd6cae437..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from 'react';
-import { t } from '@lingui/macro';
-
-const executionEnvironmentHelpTextStrings = () => ({
- image: (
-
- {t`The full image location, including the container registry, image name, and version tag.`}
-
-
- {t`Examples:`}
-
- -
-
quay.io/ansible/awx-ee:latest
-
- -
-
repo/project/image-name:tag
-
-
-
- ),
- registryCredential: t`Credential to authenticate with a protected container registry.`,
-});
-
-export default executionEnvironmentHelpTextStrings;
diff --git a/awx/ui/src/screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js b/awx/ui/src/screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js
deleted file mode 100644
index 459421db723d..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.js
+++ /dev/null
@@ -1,242 +0,0 @@
-import React, { useCallback, useEffect, useRef } from 'react';
-import { func, shape, bool } from 'prop-types';
-import { Formik, useField, useFormikContext } from 'formik';
-import { t } from '@lingui/macro';
-import { Form, FormGroup, Tooltip } from '@patternfly/react-core';
-import { ExecutionEnvironmentsAPI } from 'api';
-import CredentialLookup from 'components/Lookup/CredentialLookup';
-import FormActionGroup from 'components/FormActionGroup';
-import FormField, { FormSubmitError } from 'components/FormField';
-import AnsibleSelect from 'components/AnsibleSelect';
-import { FormColumnLayout } from 'components/FormLayout';
-import { OrganizationLookup } from 'components/Lookup';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import { required } from 'util/validators';
-import useRequest from 'hooks/useRequest';
-import getHelpText from './ExecutionEnvironment.helptext';
-
-function ExecutionEnvironmentFormFields({
- me,
- options,
- executionEnvironment,
- isOrgLookupDisabled,
-}) {
- const helpText = getHelpText();
- const [credentialField, credentialMeta, credentialHelpers] =
- useField('credential');
- const [organizationField, organizationMeta, organizationHelpers] =
- useField('organization');
-
- const isGloballyAvailable = useRef(!organizationField.value);
-
- const { setFieldValue, setFieldTouched } = useFormikContext();
-
- const onCredentialChange = useCallback(
- (value) => {
- setFieldValue('credential', value);
- },
- [setFieldValue]
- );
-
- const handleOrganizationUpdate = useCallback(
- (value) => {
- setFieldValue('organization', value);
- setFieldTouched('organization', true, false);
- },
- [setFieldValue, setFieldTouched]
- );
-
- const [containerOptionsField, containerOptionsMeta, containerOptionsHelpers] =
- useField('pull');
-
- const containerPullChoices = options?.actions?.POST?.pull?.choices.map(
- ([value, label]) => ({ value, label, key: value })
- );
-
- const renderOrganizationLookup = () => (
- organizationHelpers.setTouched()}
- onChange={handleOrganizationUpdate}
- value={organizationField.value}
- required={!me.is_superuser}
- helperText={
- me?.is_superuser &&
- ((!isOrgLookupDisabled && isGloballyAvailable) ||
- organizationField.value === null)
- ? t`Leave this field blank to make the execution environment globally available.`
- : null
- }
- autoPopulate={!me?.is_superuser ? !executionEnvironment?.id : null}
- isDisabled={
- (!!isOrgLookupDisabled && isGloballyAvailable.current) ||
- executionEnvironment?.managed
- }
- validate={
- !me?.is_superuser
- ? required(t`Select a value for this field`)
- : undefined
- }
- />
- );
-
- return (
- <>
-
-
-
- {
- containerOptionsHelpers.setValue(value);
- }}
- />
-
-
- {isOrgLookupDisabled && isGloballyAvailable.current ? (
-
- {renderOrganizationLookup()}
-
- ) : (
- renderOrganizationLookup()
- )}
-
- credentialHelpers.setTouched()}
- onChange={onCredentialChange}
- value={credentialField.value}
- tooltip={helpText.registryCredential}
- isDisabled={executionEnvironment?.managed || false}
- />
- >
- );
-}
-
-function ExecutionEnvironmentForm({
- executionEnvironment = {},
- onSubmit,
- onCancel,
- submitError,
- me,
- isOrgLookupDisabled,
- ...rest
-}) {
- const {
- isLoading,
- error,
- request: fetchOptions,
- result: options,
- } = useRequest(
- useCallback(async () => {
- const res = await ExecutionEnvironmentsAPI.readOptions();
- const { data } = res;
- return data;
- }, []),
- null
- );
-
- useEffect(() => {
- fetchOptions();
- }, [fetchOptions]);
-
- if (isLoading || !options) {
- return ;
- }
-
- if (error) {
- return ;
- }
-
- const initialValues = {
- name: executionEnvironment.name || '',
- image: executionEnvironment.image || '',
- pull: executionEnvironment?.pull || '',
- description: executionEnvironment.description || '',
- credential: executionEnvironment.summary_fields?.credential || null,
- organization: executionEnvironment.summary_fields?.organization || null,
- };
- return (
- onSubmit(values)}
- >
- {(formik) => (
-
- )}
-
- );
-}
-
-ExecutionEnvironmentForm.propTypes = {
- executionEnvironment: shape({}),
- onCancel: func.isRequired,
- onSubmit: func.isRequired,
- submitError: shape({}),
- isOrgLookupDisabled: bool,
-};
-
-ExecutionEnvironmentForm.defaultProps = {
- executionEnvironment: {},
- submitError: null,
- isOrgLookupDisabled: false,
-};
-
-export default ExecutionEnvironmentForm;
diff --git a/awx/ui/src/screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.test.js b/awx/ui/src/screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.test.js
deleted file mode 100644
index 7db85d6214a4..000000000000
--- a/awx/ui/src/screens/ExecutionEnvironment/shared/ExecutionEnvironmentForm.test.js
+++ /dev/null
@@ -1,315 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { ExecutionEnvironmentsAPI, CredentialTypesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import ExecutionEnvironmentForm from './ExecutionEnvironmentForm';
-
-jest.mock('../../../api');
-
-const mockMe = {
- is_superuser: true,
- is_super_auditor: false,
-};
-
-const executionEnvironment = {
- id: 16,
- name: 'Test EE',
- type: 'execution_environment',
- pull: 'one',
- url: '/api/v2/execution_environments/16/',
- related: {
- created_by: '/api/v2/users/1/',
- modified_by: '/api/v2/users/1/',
- activity_stream: '/api/v2/execution_environments/16/activity_stream/',
- unified_job_templates:
- '/api/v2/execution_environments/16/unified_job_templates/',
- credential: '/api/v2/credentials/4/',
- },
- summary_fields: {
- organization: {
- id: 1,
- name: 'Default',
- description: '',
- },
- credential: {
- id: 4,
- name: 'Container Registry',
- description: '',
- kind: 'registry',
- cloud: false,
- kubernetes: false,
- credential_type_id: 17,
- },
- },
- created: '2020-09-17T16:06:57.346128Z',
- modified: '2020-09-17T16:06:57.346147Z',
- description: 'A simple EE',
- organization: 1,
- image: 'https://registry.com/image/container',
- managed: false,
- credential: 4,
-};
-
-const globallyAvailableEE = {
- id: 17,
- name: 'GEE',
- type: 'execution_environment',
- pull: 'one',
- url: '/api/v2/execution_environments/17/',
- related: {
- created_by: '/api/v2/users/1/',
- modified_by: '/api/v2/users/1/',
- activity_stream: '/api/v2/execution_environments/16/activity_stream/',
- unified_job_templates:
- '/api/v2/execution_environments/16/unified_job_templates/',
- credential: '/api/v2/credentials/4/',
- },
- summary_fields: {
- credential: {
- id: 4,
- name: 'Container Registry',
- description: '',
- kind: 'registry',
- cloud: false,
- kubernetes: false,
- credential_type_id: 17,
- },
- },
- created: '2020-09-17T16:06:57.346128Z',
- modified: '2020-09-17T16:06:57.346147Z',
- description: 'A simple EE',
- organization: null,
- image: 'https://registry.com/image/container',
- managed: false,
- credential: 4,
-};
-
-const mockOptions = {
- data: {
- actions: {
- POST: {
- pull: {
- choices: [
- ['one', 'One'],
- ['two', 'Two'],
- ['three', 'Three'],
- ],
- },
- },
- },
- },
-};
-
-const containerRegistryCredentialResolve = {
- data: {
- results: [
- {
- id: 4,
- name: 'Container Registry',
- kind: 'registry',
- },
- ],
- count: 1,
- },
-};
-
-describe('', () => {
- let wrapper;
- let onCancel;
- let onSubmit;
-
- beforeEach(async () => {
- onCancel = jest.fn();
- onSubmit = jest.fn();
- ExecutionEnvironmentsAPI.readOptions.mockResolvedValue(mockOptions);
- CredentialTypesAPI.read.mockResolvedValue(
- containerRegistryCredentialResolve
- );
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('Initially renders successfully', () => {
- expect(wrapper.length).toBe(1);
- });
-
- test('should display form fields properly', () => {
- expect(wrapper.find('FormGroup[label="Image"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Description"]').length).toBe(1);
- expect(wrapper.find('CredentialLookup').length).toBe(1);
- });
-
- test('should call onSubmit when form submitted', async () => {
- expect(onSubmit).not.toHaveBeenCalled();
- await act(async () => {
- wrapper.find('button[aria-label="Save"]').simulate('click');
- });
- expect(onSubmit).toHaveBeenCalledTimes(1);
- });
-
- test('should update form values', async () => {
- await act(async () => {
- wrapper.find('input#execution-environment-image').simulate('change', {
- target: {
- value: 'Updated EE Name',
- name: 'name',
- },
- });
- wrapper.find('input#execution-environment-image').simulate('change', {
- target: {
- value: 'https://registry.com/image/container2',
- name: 'image',
- },
- });
- wrapper
- .find('input#execution-environment-description')
- .simulate('change', {
- target: { value: 'New description', name: 'description' },
- });
- wrapper.find('CredentialLookup').invoke('onBlur')();
- wrapper.find('CredentialLookup').invoke('onChange')({
- id: 99,
- name: 'credential',
- });
-
- wrapper.find('OrganizationLookup').invoke('onBlur')();
- wrapper.find('OrganizationLookup').invoke('onChange')({
- id: 3,
- name: 'organization',
- });
- });
-
- wrapper.update();
- expect(wrapper.find('OrganizationLookup').prop('value')).toEqual({
- id: 3,
- name: 'organization',
- });
- expect(
- wrapper.find('input#execution-environment-image').prop('value')
- ).toEqual('https://registry.com/image/container2');
- expect(
- wrapper.find('input#execution-environment-description').prop('value')
- ).toEqual('New description');
- expect(wrapper.find('CredentialLookup').prop('value')).toEqual({
- id: 99,
- name: 'credential',
- });
- });
-
- test('should call handleCancel when Cancel button is clicked', async () => {
- expect(onCancel).not.toHaveBeenCalled();
- wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
- expect(onCancel).toBeCalled();
- });
-
- test('globally available EE can not have organization reassigned', async () => {
- let newWrapper;
- await act(async () => {
- newWrapper = mountWithContexts(
-
- );
- });
- await waitForElement(newWrapper, 'ContentLoading', (el) => el.length === 0);
- expect(newWrapper.find('OrganizationLookup').prop('isDisabled')).toEqual(
- true
- );
- expect(newWrapper.find('Tooltip').prop('content')).toEqual(
- 'Globally available execution environment can not be reassigned to a specific Organization'
- );
- });
-
- test('should allow an organization to be re-assigned as globally available EE', async () => {
- let newWrapper;
- await act(async () => {
- newWrapper = mountWithContexts(
-
- );
- });
- await waitForElement(newWrapper, 'ContentLoading', (el) => el.length === 0);
- expect(newWrapper.find('OrganizationLookup').prop('isDisabled')).toEqual(
- false
- );
- expect(newWrapper.find('Tooltip').length).toEqual(0);
-
- await act(async () => {
- newWrapper.find('OrganizationLookup').invoke('onBlur')();
- newWrapper.find('OrganizationLookup').invoke('onChange')(null);
- });
- newWrapper.update();
- expect(newWrapper.find('OrganizationLookup').prop('value')).toEqual(null);
- });
-
- test('should disable edition for managed EEs, except pull option', async () => {
- let newWrapper;
- await act(async () => {
- newWrapper = mountWithContexts(
-
- );
- });
- await waitForElement(newWrapper, 'ContentLoading', (el) => el.length === 0);
- expect(newWrapper.find('OrganizationLookup').prop('isDisabled')).toEqual(
- true
- );
- expect(newWrapper.find('CredentialLookup').prop('isDisabled')).toEqual(
- true
- );
- expect(
- newWrapper
- .find('TextInputBase[id="execution-environment-name"]')
- .prop('isDisabled')
- ).toEqual(true);
- expect(
- newWrapper
- .find('TextInputBase[id="execution-environment-description"]')
- .prop('isDisabled')
- ).toEqual(true);
- expect(
- newWrapper
- .find('TextInputBase[id="execution-environment-image"]')
- .prop('isDisabled')
- ).toEqual(true);
- expect(
- newWrapper
- .find('FormSelect[id="container-pull-options"]')
- .prop('isDisabled')
- ).toEqual(false);
- });
-});
diff --git a/awx/ui/src/screens/Host/Host.js b/awx/ui/src/screens/Host/Host.js
deleted file mode 100644
index d62e34c69395..000000000000
--- a/awx/ui/src/screens/Host/Host.js
+++ /dev/null
@@ -1,147 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-
-import { t } from '@lingui/macro';
-import {
- Switch,
- Route,
- Redirect,
- Link,
- useRouteMatch,
- useLocation,
-} from 'react-router-dom';
-import { CaretLeftIcon } from '@patternfly/react-icons';
-import { Card, PageSection } from '@patternfly/react-core';
-import RoutedTabs from 'components/RoutedTabs';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import JobList from 'components/JobList';
-import { HostsAPI } from 'api';
-import useRequest from 'hooks/useRequest';
-import HostFacts from './HostFacts';
-import HostDetail from './HostDetail';
-import HostEdit from './HostEdit';
-import HostGroups from './HostGroups';
-
-function Host({ setBreadcrumb }) {
- const location = useLocation();
- const match = useRouteMatch('/hosts/:id');
- const {
- error,
- isLoading,
- result: host,
- request: fetchHost,
- } = useRequest(
- useCallback(async () => {
- const { data } = await HostsAPI.readDetail(match.params.id);
- setBreadcrumb(data);
- return data;
- }, [match.params.id, setBreadcrumb])
- );
-
- useEffect(() => {
- fetchHost();
- }, [fetchHost, location.pathname]);
-
- const tabsArray = [
- {
- name: (
- <>
-
- {t`Back to Hosts`}
- >
- ),
- link: `/hosts`,
- id: 99,
- isBackButton: true,
- },
- {
- name: t`Details`,
- link: `${match.url}/details`,
- id: 0,
- },
- {
- name: t`Facts`,
- link: `${match.url}/facts`,
- id: 1,
- },
- {
- name: t`Groups`,
- link: `${match.url}/groups`,
- id: 2,
- },
- {
- name: t`Jobs`,
- link: `${match.url}/jobs`,
- id: 3,
- },
- ];
-
- if (isLoading) {
- return (
-
-
-
-
-
- );
- }
-
- if (error) {
- return (
-
-
-
- {error?.response?.status === 404 && (
-
- {t`Host not found.`}{' '}
- {t`View all Hosts.`}
-
- )}
-
-
-
- );
- }
-
- let showCardHeader = true;
-
- if (location.pathname.endsWith('edit')) {
- showCardHeader = false;
- }
-
- return (
-
-
- {showCardHeader && }
-
-
- {host && [
-
-
- ,
-
-
- ,
-
-
- ,
-
-
- ,
-
-
- ,
- ]}
-
-
- {t`View Host Details`}
-
-
-
-
-
- );
-}
-
-export default Host;
-export { Host as _Host };
diff --git a/awx/ui/src/screens/Host/Host.test.js b/awx/ui/src/screens/Host/Host.test.js
deleted file mode 100644
index cb013ab1419f..000000000000
--- a/awx/ui/src/screens/Host/Host.test.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import React from 'react';
-import { Route } from 'react-router-dom';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { HostsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../testUtils/enzymeHelpers';
-import mockHost from './data.host.json';
-import Host from './Host';
-
-jest.mock('../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useRouteMatch: () => ({
- url: '/hosts/1',
- params: { id: 1 },
- }),
-}));
-
-HostsAPI.readDetail.mockResolvedValue({
- data: { ...mockHost },
-});
-
-describe('', () => {
- let wrapper;
- let history;
-
- beforeEach(async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}} />
-
- );
- });
- });
-
- test('should render expected tabs', async () => {
- const expectedTabs = ['Details', 'Facts', 'Groups', 'Completed Jobs'];
- wrapper.find('RoutedTabs li').forEach((tab, index) => {
- expect(tab.text()).toEqual(expectedTabs[index]);
- });
- });
-
- test('should show content error when api throws error on initial render', async () => {
- HostsAPI.readDetail.mockRejectedValueOnce(new Error());
- await act(async () => {
- wrapper = mountWithContexts( {}} />, {
- context: { router: { history } },
- });
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-
- test('should show content error when user attempts to navigate to erroneous route', async () => {
- history = createMemoryHistory({
- initialEntries: ['/hosts/1/foobar'],
- });
- await act(async () => {
- wrapper = mountWithContexts( {}} />, {
- context: { router: { history } },
- });
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-});
diff --git a/awx/ui/src/screens/Host/HostAdd/HostAdd.js b/awx/ui/src/screens/Host/HostAdd/HostAdd.js
deleted file mode 100644
index 56fd0c5ca594..000000000000
--- a/awx/ui/src/screens/Host/HostAdd/HostAdd.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import React, { useState } from 'react';
-import { useHistory } from 'react-router-dom';
-import { PageSection, Card } from '@patternfly/react-core';
-import HostForm from 'components/HostForm';
-import { CardBody } from 'components/Card';
-import { HostsAPI } from 'api';
-
-function HostAdd() {
- const [formError, setFormError] = useState(null);
- const history = useHistory();
-
- const handleSubmit = async (formData) => {
- try {
- const dataToSend = { ...formData };
- if (dataToSend.inventory) {
- dataToSend.inventory = dataToSend.inventory.id;
- }
- const { data: response } = await HostsAPI.create(dataToSend);
- history.push(`/hosts/${response.id}/details`);
- } catch (error) {
- setFormError(error);
- }
- };
-
- const handleCancel = () => {
- history.push(`/hosts`);
- };
-
- return (
-
-
-
-
-
-
-
- );
-}
-
-export default HostAdd;
diff --git a/awx/ui/src/screens/Host/HostAdd/HostAdd.test.js b/awx/ui/src/screens/Host/HostAdd/HostAdd.test.js
deleted file mode 100644
index 6f19c79d9f6f..000000000000
--- a/awx/ui/src/screens/Host/HostAdd/HostAdd.test.js
+++ /dev/null
@@ -1,81 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { HostsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import HostAdd from './HostAdd';
-
-jest.mock('../../../api');
-
-const hostData = {
- name: 'new name',
- description: 'new description',
- inventory: {
- id: 1,
- name: 'Demo Inventory',
- },
- variables: '---\nfoo: bar',
-};
-
-describe('', () => {
- let wrapper;
- let history;
-
- beforeEach(async () => {
- history = createMemoryHistory({
- initialEntries: ['/templates/job_templates/1/survey/edit/foo'],
- state: { some: 'state' },
- });
- HostsAPI.create.mockResolvedValue({
- data: {
- ...hostData,
- id: 5,
- },
- });
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('handleSubmit should post to api', async () => {
- await act(async () => {
- wrapper.find('HostForm').prop('handleSubmit')(hostData);
- });
- expect(HostsAPI.create).toHaveBeenCalledWith({ ...hostData, inventory: 1 });
- });
-
- test('should navigate to hosts list when cancel is clicked', async () => {
- await act(async () => {
- wrapper.find('button[aria-label="Cancel"]').prop('onClick')();
- });
- expect(history.location.pathname).toEqual('/hosts');
- });
-
- test('successful form submission should trigger redirect', async () => {
- await act(async () => {
- wrapper.find('HostForm').invoke('handleSubmit')(hostData);
- });
- expect(wrapper.find('FormSubmitError').length).toBe(0);
- expect(history.location.pathname).toEqual('/hosts/5/details');
- });
-
- test('failed form submission should show an error message', async () => {
- const error = {
- response: {
- data: { detail: 'An error occurred' },
- },
- };
- HostsAPI.create.mockImplementationOnce(() => Promise.reject(error));
- await act(async () => {
- wrapper.find('HostForm').invoke('handleSubmit')(hostData);
- });
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Host/HostAdd/index.js b/awx/ui/src/screens/Host/HostAdd/index.js
deleted file mode 100644
index 98cccae21ce7..000000000000
--- a/awx/ui/src/screens/Host/HostAdd/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './HostAdd';
diff --git a/awx/ui/src/screens/Host/HostDetail/HostDetail.js b/awx/ui/src/screens/Host/HostDetail/HostDetail.js
deleted file mode 100644
index 61bdbdd8eefa..000000000000
--- a/awx/ui/src/screens/Host/HostDetail/HostDetail.js
+++ /dev/null
@@ -1,138 +0,0 @@
-import 'styled-components/macro';
-import React, { useState } from 'react';
-import { Link, useHistory } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { Button } from '@patternfly/react-core';
-import { Host } from 'types';
-import { CardBody, CardActionsRow } from 'components/Card';
-import AlertModal from 'components/AlertModal';
-import ErrorDetail from 'components/ErrorDetail';
-import { DetailList, Detail, UserDateDetail } from 'components/DetailList';
-import { VariablesDetail } from 'components/CodeEditor';
-import Sparkline from 'components/Sparkline';
-import DeleteButton from 'components/DeleteButton';
-import { HostsAPI } from 'api';
-import HostToggle from 'components/HostToggle';
-
-function HostDetail({ host }) {
- const {
- created,
- description,
- id,
- modified,
- name,
- variables,
- summary_fields: {
- inventory,
- recent_jobs: recentJobs,
- created_by,
- modified_by,
- user_capabilities,
- },
- } = host;
-
- const [isLoading, setIsloading] = useState(false);
- const [deletionError, setDeletionError] = useState(false);
- const history = useHistory();
-
- const handleHostDelete = async () => {
- setIsloading(true);
- try {
- await HostsAPI.destroy(id);
- history.push('/hosts');
- } catch (err) {
- setDeletionError(err);
- } finally {
- setIsloading(false);
- }
- };
-
- if (!isLoading && deletionError) {
- return (
- setDeletionError(false)}
- >
- {t`Failed to delete ${name}.`}
-
-
- );
- }
-
- return (
-
-
-
-
- }
- isEmpty={recentJobs?.length === 0}
- />
-
-
- {inventory.name}
-
- }
- />
-
-
-
-
-
- {user_capabilities?.edit && (
-
- )}
- {user_capabilities?.delete && (
- handleHostDelete()}
- modalTitle={t`Delete Host`}
- name={name}
- />
- )}
-
- {deletionError && (
- setDeletionError(null)}
- >
- {t`Failed to delete host.`}
-
-
- )}
-
- );
-}
-
-HostDetail.propTypes = {
- host: Host.isRequired,
-};
-
-export default HostDetail;
diff --git a/awx/ui/src/screens/Host/HostDetail/HostDetail.test.js b/awx/ui/src/screens/Host/HostDetail/HostDetail.test.js
deleted file mode 100644
index 7f5c6ffb9ab0..000000000000
--- a/awx/ui/src/screens/Host/HostDetail/HostDetail.test.js
+++ /dev/null
@@ -1,92 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { HostsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import HostDetail from './HostDetail';
-
-import mockHost from '../data.host.json';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
-
- describe('User has edit permissions', () => {
- beforeAll(() => {
- wrapper = mountWithContexts();
- });
-
- test('should render Details', async () => {
- function assertDetail(label, value) {
- expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
- expect(wrapper.find(`Detail[label="${label}"] dd`).text()).toBe(value);
- }
-
- assertDetail('Name', 'localhost');
- assertDetail('Description', 'a good description');
- assertDetail('Inventory', 'Mikes Inventory');
- assertDetail('Created', '10/28/2019, 9:26:54 PM');
- assertDetail('Last Modified', '10/29/2019, 8:18:41 PM');
- });
-
- test('should show edit button for users with edit permission', () => {
- const editButton = wrapper.find('Button[aria-label="edit"]');
- expect(editButton.text()).toEqual('Edit');
- expect(editButton.prop('to')).toBe('/hosts/2/edit');
- });
-
- test('expected api call is made for delete', async () => {
- await act(async () => {
- wrapper.find('DeleteButton').invoke('onConfirm')();
- });
- expect(HostsAPI.destroy).toHaveBeenCalledTimes(1);
- });
-
- test('Error dialog shown for failed deletion', async () => {
- HostsAPI.destroy.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- wrapper.find('DeleteButton').invoke('onConfirm')();
- });
- await waitForElement(
- wrapper,
- 'Modal[title="Error!"]',
- (el) => el.length === 1
- );
- await act(async () => {
- wrapper.find('Modal[title="Error!"]').invoke('onClose')();
- });
- await waitForElement(
- wrapper,
- 'Modal[title="Error!"]',
- (el) => el.length === 0
- );
- });
- });
-
- describe('User has read-only permissions', () => {
- beforeAll(() => {
- const readOnlyHost = { ...mockHost };
- readOnlyHost.summary_fields.user_capabilities.edit = false;
- readOnlyHost.summary_fields.recent_jobs = [];
-
- wrapper = mountWithContexts();
- });
-
- test('should hide activity stream when there are no recent jobs', async () => {
- expect(wrapper.find(`Detail[label="Activity"] Sparkline`)).toHaveLength(
- 0
- );
- const activity_detail = wrapper.find(`Detail[label="Activity"]`).at(0);
- expect(activity_detail.prop('isEmpty')).toEqual(true);
- });
-
- test('should hide edit button for users without edit permission', async () => {
- expect(wrapper.find('Button[aria-label="edit"]').length).toBe(0);
- });
- });
-});
diff --git a/awx/ui/src/screens/Host/HostDetail/index.js b/awx/ui/src/screens/Host/HostDetail/index.js
deleted file mode 100644
index 1ee5ffed1138..000000000000
--- a/awx/ui/src/screens/Host/HostDetail/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './HostDetail';
diff --git a/awx/ui/src/screens/Host/HostEdit/HostEdit.js b/awx/ui/src/screens/Host/HostEdit/HostEdit.js
deleted file mode 100644
index 4e54a73082aa..000000000000
--- a/awx/ui/src/screens/Host/HostEdit/HostEdit.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import React, { useState } from 'react';
-import PropTypes from 'prop-types';
-import { useHistory } from 'react-router-dom';
-import { CardBody } from 'components/Card';
-import HostForm from 'components/HostForm';
-import { HostsAPI } from 'api';
-
-function HostEdit({ host }) {
- const [formError, setFormError] = useState(null);
- const detailsUrl = `/hosts/${host.id}/details`;
- const history = useHistory();
-
- const handleSubmit = async (values) => {
- try {
- const dataToSend = { ...values };
- if (dataToSend.inventory) {
- dataToSend.inventory = dataToSend.inventory.id;
- }
- await HostsAPI.update(host.id, dataToSend);
- history.push(detailsUrl);
- } catch (error) {
- setFormError(error);
- }
- };
-
- const handleCancel = () => {
- history.push(detailsUrl);
- };
-
- return (
-
-
-
- );
-}
-
-HostEdit.propTypes = {
- host: PropTypes.shape().isRequired,
-};
-
-export default HostEdit;
diff --git a/awx/ui/src/screens/Host/HostEdit/HostEdit.test.js b/awx/ui/src/screens/Host/HostEdit/HostEdit.test.js
deleted file mode 100644
index 53745c25b3bb..000000000000
--- a/awx/ui/src/screens/Host/HostEdit/HostEdit.test.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { HostsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import mockHost from '../data.host.json';
-import HostEdit from './HostEdit';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
- let history;
-
- const updatedHostData = {
- name: 'new name',
- description: 'new description',
- variables: '---\nfoo: bar',
- };
-
- beforeAll(async () => {
- history = createMemoryHistory();
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('handleSubmit should call api update', async () => {
- await act(async () => {
- wrapper.find('HostForm').prop('handleSubmit')(updatedHostData);
- });
- expect(HostsAPI.update).toHaveBeenCalledWith(2, updatedHostData);
- });
-
- test('should navigate to host detail when cancel is clicked', async () => {
- await act(async () => {
- wrapper.find('button[aria-label="Cancel"]').prop('onClick')();
- });
- expect(history.location.pathname).toEqual('/hosts/2/details');
- });
-
- test('should navigate to host detail after successful submission', async () => {
- await act(async () => {
- wrapper.find('HostForm').invoke('handleSubmit')(updatedHostData);
- });
- expect(wrapper.find('FormSubmitError').length).toBe(0);
- expect(history.location.pathname).toEqual('/hosts/2/details');
- });
-
- test('failed form submission should show an error message', async () => {
- const error = {
- response: {
- data: { detail: 'An error occurred' },
- },
- };
- HostsAPI.update.mockImplementationOnce(() => Promise.reject(error));
- await act(async () => {
- wrapper.find('HostForm').invoke('handleSubmit')(mockHost);
- });
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Host/HostEdit/index.js b/awx/ui/src/screens/Host/HostEdit/index.js
deleted file mode 100644
index 3c0140c9c472..000000000000
--- a/awx/ui/src/screens/Host/HostEdit/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './HostEdit';
diff --git a/awx/ui/src/screens/Host/HostFacts/HostFacts.js b/awx/ui/src/screens/Host/HostFacts/HostFacts.js
deleted file mode 100644
index 40aa2bc5752e..000000000000
--- a/awx/ui/src/screens/Host/HostFacts/HostFacts.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-
-import { t } from '@lingui/macro';
-import { Host } from 'types';
-import { CardBody } from 'components/Card';
-import { DetailList } from 'components/DetailList';
-import { VariablesDetail } from 'components/CodeEditor';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import useRequest from 'hooks/useRequest';
-import { HostsAPI } from 'api';
-
-function HostFacts({ host }) {
- const {
- result: facts,
- isLoading,
- error,
- request: fetchFacts,
- } = useRequest(
- useCallback(async () => {
- const [{ data: factsObj }] = await Promise.all([
- HostsAPI.readFacts(host.id),
- ]);
- return JSON.stringify(factsObj, null, 4);
- }, [host]),
- '{}'
- );
-
- useEffect(() => {
- fetchFacts();
- }, [fetchFacts]);
-
- if (isLoading) {
- return ;
- }
-
- if (error) {
- return ;
- }
-
- return (
-
-
-
-
-
- );
-}
-
-HostFacts.propTypes = {
- host: Host.isRequired,
-};
-
-export default HostFacts;
diff --git a/awx/ui/src/screens/Host/HostFacts/HostFacts.test.js b/awx/ui/src/screens/Host/HostFacts/HostFacts.test.js
deleted file mode 100644
index 5d4b5f82e3fb..000000000000
--- a/awx/ui/src/screens/Host/HostFacts/HostFacts.test.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { HostsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import HostFacts from './HostFacts';
-import mockHost from '../data.host.json';
-import mockHostFacts from '../data.hostFacts.json';
-
-jest.mock('../../../api/models/Hosts');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useParams: () => ({
- id: 1,
- hostId: 1,
- }),
-}));
-
-describe('', () => {
- let wrapper;
-
- beforeEach(async () => {
- HostsAPI.readFacts.mockResolvedValue({ data: mockHostFacts });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('initially renders successfully ', () => {
- expect(wrapper.find('HostFacts').length).toBe(1);
- });
-
- test('renders ContentError when facts GET fails', async () => {
- HostsAPI.readFacts.mockRejectedValueOnce(
- new Error({
- response: {
- config: {
- method: 'get',
- url: '/api/v2/hosts/1/ansible_facts',
- },
- data: 'An error occurred',
- status: 500,
- },
- })
- );
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-});
diff --git a/awx/ui/src/screens/Host/HostFacts/index.js b/awx/ui/src/screens/Host/HostFacts/index.js
deleted file mode 100644
index 59f799bd86b2..000000000000
--- a/awx/ui/src/screens/Host/HostFacts/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './HostFacts';
diff --git a/awx/ui/src/screens/Host/HostGroups/HostGroupItem.js b/awx/ui/src/screens/Host/HostGroups/HostGroupItem.js
deleted file mode 100644
index 3cad209c1a99..000000000000
--- a/awx/ui/src/screens/Host/HostGroups/HostGroupItem.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import React from 'react';
-import { bool, func, number, oneOfType, string } from 'prop-types';
-
-import { t } from '@lingui/macro';
-
-import { Button } from '@patternfly/react-core';
-import { Tr, Td } from '@patternfly/react-table';
-import { Link } from 'react-router-dom';
-import { PencilAltIcon } from '@patternfly/react-icons';
-import { ActionsTd, ActionItem } from 'components/PaginatedTable';
-import { Group } from 'types';
-
-function HostGroupItem({ group, inventoryId, isSelected, onSelect, rowIndex }) {
- const labelId = `check-action-${group.id}`;
- const detailUrl = `/inventories/inventory/${inventoryId}/groups/${group.id}/details`;
- const editUrl = `/inventories/inventory/${inventoryId}/groups/${group.id}/edit`;
-
- return (
-
- |
-
- {' '}
-
- {group.name}
-
- |
-
-
-
-
-
-
- );
-}
-
-HostGroupItem.propTypes = {
- group: Group.isRequired,
- inventoryId: oneOfType([number, string]).isRequired,
- isSelected: bool.isRequired,
- onSelect: func.isRequired,
-};
-
-export default HostGroupItem;
diff --git a/awx/ui/src/screens/Host/HostGroups/HostGroupItem.test.js b/awx/ui/src/screens/Host/HostGroups/HostGroupItem.test.js
deleted file mode 100644
index e0fbe4893924..000000000000
--- a/awx/ui/src/screens/Host/HostGroups/HostGroupItem.test.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import HostGroupItem from './HostGroupItem';
-
-describe('', () => {
- let wrapper;
- const mockGroup = {
- id: 2,
- type: 'group',
- name: 'foo',
- inventory: 1,
- summary_fields: {
- user_capabilities: {
- edit: true,
- },
- },
- };
-
- beforeEach(() => {
- wrapper = mountWithContexts(
-
- );
- });
-
- test('initially renders successfully', () => {
- expect(wrapper.find('HostGroupItem').length).toBe(1);
- });
-
- test('edit button should be shown to users with edit capabilities', () => {
- expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
- });
-
- test('edit button should be hidden from users without edit capabilities', () => {
- const copyMockGroup = { ...mockGroup };
- copyMockGroup.summary_fields.user_capabilities.edit = false;
-
- wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
- });
-});
diff --git a/awx/ui/src/screens/Host/HostGroups/HostGroups.js b/awx/ui/src/screens/Host/HostGroups/HostGroups.js
deleted file mode 100644
index eb8bf05d6636..000000000000
--- a/awx/ui/src/screens/Host/HostGroups/HostGroups.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react';
-
-import { Switch, Route } from 'react-router-dom';
-import HostGroupsList from './HostGroupsList';
-
-function HostGroups({ host }) {
- return (
-
-
-
-
-
- );
-}
-
-export { HostGroups as _HostGroups };
-export default HostGroups;
diff --git a/awx/ui/src/screens/Host/HostGroups/HostGroups.test.js b/awx/ui/src/screens/Host/HostGroups/HostGroups.test.js
deleted file mode 100644
index 850a4965dac7..000000000000
--- a/awx/ui/src/screens/Host/HostGroups/HostGroups.test.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import HostGroups from './HostGroups';
-
-jest.mock('../../../api');
-
-describe('', () => {
- test('initially renders successfully', async () => {
- let wrapper;
- const history = createMemoryHistory({
- initialEntries: ['/hosts/1/groups'],
- });
- const host = {
- id: 1,
- name: 'Foo',
- summary_fields: { inventory: { id: 1 } },
- };
-
- await act(async () => {
- wrapper = mountWithContexts(
- {}} host={host} />,
-
- {
- context: {
- router: { history, route: { location: history.location } },
- },
- }
- );
- });
- expect(wrapper.length).toBe(1);
- expect(wrapper.find('HostGroupsList').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Host/HostGroups/HostGroupsList.js b/awx/ui/src/screens/Host/HostGroups/HostGroupsList.js
deleted file mode 100644
index 26f214e941a5..000000000000
--- a/awx/ui/src/screens/Host/HostGroups/HostGroupsList.js
+++ /dev/null
@@ -1,256 +0,0 @@
-import React, { useState, useEffect, useCallback } from 'react';
-import { useParams, useLocation } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { getQSConfig, parseQueryString, mergeParams } from 'util/qs';
-import useRequest, {
- useDismissableError,
- useDeleteItems,
-} from 'hooks/useRequest';
-import useSelected from 'hooks/useSelected';
-import { HostsAPI, InventoriesAPI } from 'api';
-import AlertModal from 'components/AlertModal';
-import ErrorDetail from 'components/ErrorDetail';
-import PaginatedTable, {
- HeaderCell,
- HeaderRow,
- ToolbarAddButton,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import AssociateModal from 'components/AssociateModal';
-import DisassociateButton from 'components/DisassociateButton';
-import DataListToolbar from 'components/DataListToolbar';
-import HostGroupItem from './HostGroupItem';
-
-const QS_CONFIG = getQSConfig('group', {
- page: 1,
- page_size: 20,
- order_by: 'name',
-});
-
-function HostGroupsList({ host }) {
- const [isModalOpen, setIsModalOpen] = useState(false);
-
- const { id: hostId } = useParams();
- const { search } = useLocation();
- const invId = host.summary_fields.inventory.id;
-
- const {
- result: {
- groups,
- itemCount,
- actions,
- relatedSearchableKeys,
- searchableKeys,
- },
- error: contentError,
- isLoading,
- request: fetchGroups,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, search);
-
- const [
- {
- data: { count, results },
- },
- actionsResponse,
- ] = await Promise.all([
- HostsAPI.readAllGroups(hostId, params),
- HostsAPI.readGroupsOptions(hostId),
- ]);
-
- return {
- groups: results,
- itemCount: count,
- actions: actionsResponse.data.actions,
- relatedSearchableKeys: (
- actionsResponse?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
- };
- }, [hostId, search]),
- {
- groups: [],
- itemCount: 0,
- actions: {},
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- useEffect(() => {
- fetchGroups();
- }, [fetchGroups]);
-
- const { selected, isAllSelected, handleSelect, clearSelected, selectAll } =
- useSelected(groups);
-
- const {
- isLoading: isDisassociateLoading,
- deleteItems: disassociateHosts,
- deletionError: disassociateError,
- } = useDeleteItems(
- useCallback(
- () =>
- Promise.all(
- selected.map((group) => HostsAPI.disassociateGroup(hostId, group))
- ),
- [hostId, selected]
- ),
- {
- qsConfig: QS_CONFIG,
- allItemsSelected: isAllSelected,
- fetchItems: fetchGroups,
- }
- );
-
- const handleDisassociate = async () => {
- await disassociateHosts();
- clearSelected();
- };
-
- const fetchGroupsToAssociate = useCallback(
- (params) =>
- InventoriesAPI.readGroups(
- invId,
- mergeParams(params, { not__hosts: hostId })
- ),
- [invId, hostId]
- );
-
- const fetchGroupsOptions = useCallback(
- () => InventoriesAPI.readGroupsOptions(invId),
- [invId]
- );
-
- const { request: handleAssociate, error: associateError } = useRequest(
- useCallback(
- async (groupsToAssociate) => {
- await Promise.all(
- groupsToAssociate.map((group) =>
- HostsAPI.associateGroup(hostId, group.id)
- )
- );
- fetchGroups();
- },
- [hostId, fetchGroups]
- )
- );
-
- const { error, dismissError } = useDismissableError(
- associateError || disassociateError
- );
-
- const canAdd =
- actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
-
- return (
- <>
-
- {t`Name`}
- {t`Actions`}
-
- }
- renderRow={(item, index) => (
- row.id === item.id)}
- onSelect={() => handleSelect(item)}
- rowIndex={index}
- />
- )}
- renderToolbar={(props) => (
- setIsModalOpen(true)}
- />,
- ]
- : []),
- ,
- ]}
- />
- )}
- emptyStateControls={
- canAdd ? (
- setIsModalOpen(true)} />
- ) : null
- }
- />
- {isModalOpen && (
- setIsModalOpen(false)}
- title={t`Select Groups`}
- />
- )}
- {error && (
-
- {associateError
- ? t`Failed to associate.`
- : t`Failed to disassociate one or more groups.`}
-
-
- )}
- >
- );
-}
-export default HostGroupsList;
diff --git a/awx/ui/src/screens/Host/HostGroups/HostGroupsList.test.js b/awx/ui/src/screens/Host/HostGroups/HostGroupsList.test.js
deleted file mode 100644
index 1f1b5f25f328..000000000000
--- a/awx/ui/src/screens/Host/HostGroups/HostGroupsList.test.js
+++ /dev/null
@@ -1,285 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Route } from 'react-router-dom';
-import { createMemoryHistory } from 'history';
-import { HostsAPI, InventoriesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import HostGroupsList from './HostGroupsList';
-
-jest.mock('../../../api');
-
-const host = {
- summary_fields: {
- inventory: {
- id: 1,
- },
- },
-};
-
-const mockGroups = [
- {
- id: 1,
- type: 'group',
- name: 'foo',
- inventory: 1,
- url: '/api/v2/groups/1',
- summary_fields: {
- inventory: {
- id: 1,
- },
- user_capabilities: {
- delete: true,
- edit: true,
- },
- },
- },
- {
- id: 2,
- type: 'group',
- name: 'bar',
- inventory: 1,
- url: '/api/v2/groups/2',
- summary_fields: {
- inventory: {
- id: 1,
- },
- user_capabilities: {
- delete: true,
- edit: true,
- },
- },
- },
- {
- id: 3,
- type: 'group',
- name: 'baz',
- inventory: 1,
- url: '/api/v2/groups/3',
- summary_fields: {
- inventory: {
- id: 1,
- },
- user_capabilities: {
- delete: true,
- edit: false,
- },
- },
- },
-];
-
-describe('', () => {
- let wrapper;
-
- beforeEach(async () => {
- HostsAPI.readAllGroups.mockResolvedValue({
- data: {
- count: mockGroups.length,
- results: mockGroups,
- },
- });
- HostsAPI.readGroupsOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- },
- });
- const history = createMemoryHistory({
- initialEntries: ['/hosts/3/groups'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- ,
- {
- context: {
- router: { history, route: { location: history.location } },
- },
- }
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('initially renders successfully', () => {
- expect(wrapper.find('HostGroupsList').length).toBe(1);
- });
-
- test('should fetch groups from api and render them in the list', async () => {
- expect(HostsAPI.readAllGroups).toHaveBeenCalled();
- expect(wrapper.find('HostGroupItem').length).toBe(3);
- });
-
- test('should check and uncheck the row item', async () => {
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').props().checked
- ).toBe(false);
-
- await act(async () => {
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .invoke('onChange')(true);
- });
- wrapper.update();
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').props().checked
- ).toBe(true);
-
- await act(async () => {
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .invoke('onChange')(false);
- });
- wrapper.update();
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').props().checked
- ).toBe(false);
- });
-
- test('should check all row items when select all is checked', async () => {
- expect.assertions(9);
- wrapper.find('.pf-c-table__check input').forEach((el) => {
- expect(el.props().checked).toBe(false);
- });
- await act(async () => {
- wrapper.find('Checkbox#select-all').invoke('onChange')(true);
- });
- wrapper.update();
- wrapper.find('.pf-c-table__check input').forEach((el) => {
- expect(el.props().checked).toBe(true);
- });
- await act(async () => {
- wrapper.find('Checkbox#select-all').invoke('onChange')(false);
- });
- wrapper.update();
- wrapper.find('.pf-c-table__check input').forEach((el) => {
- expect(el.props().checked).toBe(false);
- });
- });
-
- test('should show content error when api throws error on initial render', async () => {
- HostsAPI.readAllGroups.mockImplementation(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-
- test('should show add button according to permissions', async () => {
- expect(wrapper.find('ToolbarAddButton').length).toBe(1);
- HostsAPI.readGroupsOptions.mockResolvedValueOnce({
- data: {
- actions: {
- GET: {},
- },
- },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('ToolbarAddButton').length).toBe(0);
- });
-
- test('should show associate group modal when adding an existing group', () => {
- wrapper.find('ToolbarAddButton').simulate('click');
- expect(wrapper.find('AssociateModal').length).toBe(1);
- wrapper.find('ModalBoxCloseButton').simulate('click');
- expect(wrapper.find('AssociateModal').length).toBe(0);
- });
-
- test('should make expected api request when associating groups', async () => {
- HostsAPI.associateGroup.mockResolvedValue();
- InventoriesAPI.readGroups.mockResolvedValue({
- data: {
- count: 1,
- results: [{ id: 123, name: 'foo', url: '/api/v2/groups/123/' }],
- },
- });
- InventoriesAPI.readGroupsOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- related_search_fields: [],
- },
- });
- await act(async () => {
- wrapper.find('ToolbarAddButton').simulate('click');
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- wrapper.update();
- await act(async () => {
- wrapper.find('CheckboxListItem').first().invoke('onSelect')();
- });
- await act(async () => {
- wrapper.find('button[aria-label="Save"]').simulate('click');
- });
- await waitForElement(wrapper, 'AssociateModal', (el) => el.length === 0);
- expect(InventoriesAPI.readGroups).toHaveBeenCalledTimes(1);
- expect(HostsAPI.associateGroup).toHaveBeenCalledTimes(1);
- });
-
- test('expected api calls are made for multi-disassociation', async () => {
- expect.assertions(11);
- expect(HostsAPI.disassociateGroup).toHaveBeenCalledTimes(0);
- expect(HostsAPI.readAllGroups).toHaveBeenCalledTimes(1);
-
- wrapper.find('.pf-c-table__check input').forEach((el) => {
- expect(el.props().checked).toBe(false);
- });
- await act(async () => {
- wrapper.find('Checkbox#select-all').invoke('onChange')(true);
- });
- wrapper.update();
- wrapper.find('.pf-c-table__check input').forEach((el) => {
- expect(el.props().checked).toBe(true);
- });
- wrapper.find('button[aria-label="Disassociate"]').simulate('click');
- expect(wrapper.find('AlertModal Title').text()).toEqual(
- 'Disassociate group from host?'
- );
- await act(async () => {
- wrapper
- .find('button[aria-label="confirm disassociate"]')
- .simulate('click');
- });
- expect(HostsAPI.disassociateGroup).toHaveBeenCalledTimes(3);
- expect(HostsAPI.readAllGroups).toHaveBeenCalledTimes(2);
- });
-
- test('should show error modal for failed disassociation', async () => {
- HostsAPI.disassociateGroup.mockRejectedValue(new Error());
- await act(async () => {
- wrapper.find('Checkbox#select-all').invoke('onChange')(true);
- });
- wrapper.update();
- wrapper.find('button[aria-label="Disassociate"]').simulate('click');
- expect(wrapper.find('AlertModal Title').text()).toEqual(
- 'Disassociate group from host?'
- );
- await act(async () => {
- wrapper
- .find('button[aria-label="confirm disassociate"]')
- .simulate('click');
- });
- wrapper.update();
- expect(wrapper.find('AlertModal ErrorDetail').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Host/HostGroups/index.js b/awx/ui/src/screens/Host/HostGroups/index.js
deleted file mode 100644
index d37c1c9cb198..000000000000
--- a/awx/ui/src/screens/Host/HostGroups/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './HostGroups';
diff --git a/awx/ui/src/screens/Host/HostList/HostList.js b/awx/ui/src/screens/Host/HostList/HostList.js
deleted file mode 100644
index 0d4b0bdafd42..000000000000
--- a/awx/ui/src/screens/Host/HostList/HostList.js
+++ /dev/null
@@ -1,244 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-import { useHistory, useLocation, useRouteMatch } from 'react-router-dom';
-import { t } from '@lingui/macro';
-import { Card, PageSection } from '@patternfly/react-core';
-import { HostsAPI } from 'api';
-import AlertModal from 'components/AlertModal';
-import DataListToolbar from 'components/DataListToolbar';
-import ErrorDetail from 'components/ErrorDetail';
-import PaginatedTable, {
- HeaderRow,
- HeaderCell,
- ToolbarAddButton,
- ToolbarDeleteButton,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import useRequest, { useDeleteItems } from 'hooks/useRequest';
-import useSelected from 'hooks/useSelected';
-import useExpanded from 'hooks/useExpanded';
-import { encodeQueryString, getQSConfig, parseQueryString } from 'util/qs';
-
-import HostListItem from './HostListItem';
-import SmartInventoryButton from './SmartInventoryButton';
-
-const QS_CONFIG = getQSConfig('host', {
- page: 1,
- page_size: 20,
- order_by: 'name',
-});
-
-function HostList() {
- const history = useHistory();
- const location = useLocation();
- const match = useRouteMatch();
- const parsedQueryStrings = parseQueryString(QS_CONFIG, location.search);
- const nonDefaultSearchParams = {};
-
- Object.keys(parsedQueryStrings).forEach((key) => {
- if (!QS_CONFIG.defaultParams[key]) {
- nonDefaultSearchParams[key] = parsedQueryStrings[key];
- }
- });
-
- const hasAnsibleFactsKeys = () => {
- const nonDefaultSearchValues = Object.values(nonDefaultSearchParams);
- return (
- nonDefaultSearchValues.filter((value) => value.includes('ansible_facts'))
- .length > 0
- );
- };
-
- const hasInvalidHostFilterKeys = () => {
- const nonDefaultSearchKeys = Object.keys(nonDefaultSearchParams);
- return (
- nonDefaultSearchKeys.filter((searchKey) => searchKey.startsWith('not__'))
- .length > 0 ||
- nonDefaultSearchKeys.filter((searchKey) => searchKey.endsWith('__search'))
- .length > 0
- );
- };
-
- const {
- result: { hosts, count, actions, relatedSearchableKeys, searchableKeys },
- error: contentError,
- isLoading,
- request: fetchHosts,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
- const results = await Promise.all([
- HostsAPI.read(params),
- HostsAPI.readOptions(),
- ]);
- return {
- hosts: results[0].data.results,
- count: results[0].data.count,
- actions: results[1].data.actions,
- relatedSearchableKeys: (
- results[1]?.data?.related_search_fields || []
- ).map((val) => (val.endsWith('search') ? val.slice(0, -8) : val)),
- searchableKeys: getSearchableKeys(results[1].data.actions?.GET),
- };
- }, [location]),
- {
- hosts: [],
- count: 0,
- actions: {},
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- useEffect(() => {
- fetchHosts();
- }, [fetchHosts]);
-
- const { selected, isAllSelected, handleSelect, selectAll, clearSelected } =
- useSelected(hosts);
-
- const { expanded, isAllExpanded, handleExpand, expandAll } =
- useExpanded(hosts);
-
- const {
- isLoading: isDeleteLoading,
- deleteItems: deleteHosts,
- deletionError,
- clearDeletionError,
- } = useDeleteItems(
- useCallback(
- () => Promise.all(selected.map((host) => HostsAPI.destroy(host.id))),
- [selected]
- ),
- {
- qsConfig: QS_CONFIG,
- allItemsSelected: isAllSelected,
- fetchItems: fetchHosts,
- }
- );
-
- const handleHostDelete = async () => {
- await deleteHosts();
- clearSelected();
- };
-
- const handleSmartInventoryClick = () => {
- history.push(
- `/inventories/smart_inventory/add?host_filter=${encodeURIComponent(
- encodeQueryString(nonDefaultSearchParams)
- )}`
- );
- };
-
- const canAdd =
- actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
-
- return (
-
-
-
- {t`Name`}
- {t`Description`}
- {t`Inventory`}
- {t`Actions`}
-
- }
- renderToolbar={(props) => (
- ]
- : []),
- ,
- ...(canAdd
- ? [
- handleSmartInventoryClick()}
- />,
- ]
- : []),
- ]}
- />
- )}
- renderRow={(host, index) => (
- row.id === host.id)}
- onExpand={() => handleExpand(host)}
- detailUrl={`${match.url}/${host.id}/details`}
- isSelected={selected.some((row) => row.id === host.id)}
- onSelect={() => handleSelect(host)}
- rowIndex={index}
- />
- )}
- emptyStateControls={
- canAdd ? (
-
- ) : null
- }
- />
-
- {deletionError && (
-
- {t`Failed to delete one or more hosts.`}
-
-
- )}
-
- );
-}
-
-export default HostList;
diff --git a/awx/ui/src/screens/Host/HostList/HostList.test.js b/awx/ui/src/screens/Host/HostList/HostList.test.js
deleted file mode 100644
index a301aaac6414..000000000000
--- a/awx/ui/src/screens/Host/HostList/HostList.test.js
+++ /dev/null
@@ -1,335 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { HostsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import HostList from './HostList';
-
-jest.mock('../../../api');
-
-const mockHosts = [
- {
- id: 1,
- name: 'Host 1',
- url: '/api/v2/hosts/1',
- inventory: 1,
- summary_fields: {
- inventory: {
- id: 1,
- name: 'inv 1',
- },
- user_capabilities: {
- delete: true,
- update: true,
- },
- recent_jobs: [],
- },
- },
- {
- id: 2,
- name: 'Host 2',
- url: '/api/v2/hosts/2',
- inventory: 1,
- summary_fields: {
- inventory: {
- id: 1,
- name: 'inv 1',
- },
- user_capabilities: {
- delete: true,
- update: true,
- },
- recent_jobs: [],
- },
- },
- {
- id: 3,
- name: 'Host 3',
- url: '/api/v2/hosts/3',
- inventory: 1,
- summary_fields: {
- inventory: {
- id: 1,
- name: 'inv 1',
- },
- recent_jobs: [
- {
- id: 123,
- name: 'Bibbity Bop',
- status: 'success',
- finished: '2020-01-27T19:40:36.208728Z',
- },
- ],
- user_capabilities: {
- delete: false,
- update: false,
- },
- },
- },
-];
-
-function waitForLoaded(wrapper) {
- return waitForElement(
- wrapper,
- 'HostList',
- (el) => el.find('ContentLoading').length === 0
- );
-}
-
-describe('', () => {
- beforeEach(() => {
- HostsAPI.read.mockResolvedValue({
- data: {
- count: mockHosts.length,
- results: mockHosts,
- },
- });
-
- HostsAPI.readOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- related_search_fields: ['first_key__search', 'ansible_facts'],
- },
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('initially renders successfully', async () => {
- await act(async () => {
- mountWithContexts(
-
- );
- });
- });
-
- test('Hosts are retrieved from the api and the components finishes loading', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForLoaded(wrapper);
-
- expect(
- wrapper.find('PaginatedTable').props().toolbarRelatedSearchableKeys
- ).toStrictEqual(['first_key', 'ansible_facts']);
-
- expect(HostsAPI.read).toHaveBeenCalled();
- expect(wrapper.find('HostListItem')).toHaveLength(3);
- });
-
- test('should select and deselect a single item', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForLoaded(wrapper);
-
- act(() => {
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .invoke('onChange')();
- });
- wrapper.update();
- expect(wrapper.find('HostListItem').first().prop('isSelected')).toEqual(
- true
- );
- act(() => {
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .invoke('onChange')();
- });
- wrapper.update();
- expect(wrapper.find('HostListItem').first().prop('isSelected')).toEqual(
- false
- );
- });
-
- test('should select all items', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForLoaded(wrapper);
-
- act(() => {
- wrapper.find('DataListToolbar').invoke('onSelectAll')(true);
- });
- wrapper.update();
-
- wrapper.find('HostListItem').forEach((item) => {
- expect(item.prop('isSelected')).toEqual(true);
- });
- });
-
- test('delete button is disabled if user does not have delete capabilities on a selected host', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForLoaded(wrapper);
-
- act(() => {
- wrapper.find('HostListItem').at(2).invoke('onSelect')();
- });
- expect(wrapper.find('ToolbarDeleteButton button').prop('disabled')).toEqual(
- true
- );
- });
-
- test('api is called to delete hosts for each selected host.', async () => {
- HostsAPI.destroy = jest.fn();
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForLoaded(wrapper);
-
- await act(async () => {
- wrapper.find('HostListItem').at(0).invoke('onSelect')();
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('HostListItem').at(1).invoke('onSelect')();
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('ToolbarDeleteButton').invoke('onDelete')();
- });
- expect(HostsAPI.destroy).toHaveBeenCalledTimes(2);
- });
-
- test('error is shown when host not successfully deleted from api', async () => {
- HostsAPI.destroy.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'delete',
- url: '/api/v2/hosts/1',
- },
- data: 'An error occurred',
- },
- })
- );
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForLoaded(wrapper);
-
- await act(async () => {
- wrapper.find('HostListItem').at(0).invoke('onSelect')();
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('ToolbarDeleteButton').invoke('onDelete')();
- });
- wrapper.update();
-
- const modal = wrapper.find('Modal');
- expect(modal).toHaveLength(1);
- expect(modal.prop('title')).toEqual('Error!');
- });
-
- test('should show Add and Smart Inventory buttons according to permissions', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForLoaded(wrapper);
-
- expect(wrapper.find('ToolbarAddButton').length).toBe(1);
- expect(wrapper.find('Button[aria-label="Smart Inventory"]').length).toBe(1);
- });
-
- test('should hide Add and Smart Inventory buttons according to permissions', async () => {
- HostsAPI.readOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {},
- },
- },
- });
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForLoaded(wrapper);
-
- expect(wrapper.find('ToolbarAddButton').length).toBe(0);
- expect(wrapper.find('Button[aria-label="Smart Inventory"]').length).toBe(0);
- });
-
- test('Smart Inventory button should be disabled when no search params are present', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForLoaded(wrapper);
- expect(
- wrapper.find('Button[aria-label="Smart Inventory"]').props().isDisabled
- ).toBe(true);
- });
-
- test('Smart Inventory button should be disable to ansible facts search', async () => {
- let wrapper;
- const history = createMemoryHistory({
- initialEntries: [
- '/hosts?host.host_filter=ansible_facts__ansible_date_time__weekday_number%3D"3"',
- ],
- });
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
-
- await waitForLoaded(wrapper);
- expect(
- wrapper.find('Button[aria-label="Smart Inventory"]').props().isDisabled
- ).toBe(true);
- });
-
- test('Clicking Smart Inventory button should navigate to smart inventory form with correct query param', async () => {
- let wrapper;
- const history = createMemoryHistory({
- initialEntries: ['/hosts?host.name__icontains=foo'],
- });
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
-
- await waitForLoaded(wrapper);
- expect(
- wrapper.find('Button[aria-label="Smart Inventory"]').props().isDisabled
- ).toBe(false);
- await act(async () => {
- wrapper.find('Button[aria-label="Smart Inventory"]').simulate('click');
- });
- wrapper.update();
- expect(history.location.pathname).toEqual(
- '/inventories/smart_inventory/add'
- );
- expect(history.location.search).toEqual(
- '?host_filter=name__icontains%3Dfoo'
- );
- });
-});
diff --git a/awx/ui/src/screens/Host/HostList/HostListItem.js b/awx/ui/src/screens/Host/HostList/HostListItem.js
deleted file mode 100644
index 991ab47442e9..000000000000
--- a/awx/ui/src/screens/Host/HostList/HostListItem.js
+++ /dev/null
@@ -1,119 +0,0 @@
-import 'styled-components/macro';
-import React from 'react';
-import { string, bool, func } from 'prop-types';
-
-import { t } from '@lingui/macro';
-import { Button } from '@patternfly/react-core';
-import { Tr, Td, ExpandableRowContent } from '@patternfly/react-table';
-import { Link } from 'react-router-dom';
-import { PencilAltIcon } from '@patternfly/react-icons';
-import { ActionsTd, ActionItem, TdBreakWord } from 'components/PaginatedTable';
-import { Host } from 'types';
-import HostToggle from 'components/HostToggle';
-import { DetailList, Detail } from 'components/DetailList';
-import Sparkline from 'components/Sparkline';
-
-function HostListItem({
- host,
- isSelected,
- onSelect,
- detailUrl,
- rowIndex,
- isExpanded,
- onExpand,
-}) {
- const labelId = `check-action-${host.id}`;
-
- const {
- summary_fields: { recent_jobs: recentJobs = [] },
- } = host;
-
- return (
- <>
-
- |
- |
-
-
- {host.name}
-
-
-
- {host.description}
-
-
- {host.summary_fields.inventory && (
-
- {host.summary_fields.inventory.name}
-
- )}
-
-
-
-
-
-
-
-
-
- |
-
-
-
- 0 ? (
-
- ) : (
- t`No job data available`
- )
- }
- />
-
-
- |
-
- >
- );
-}
-
-HostListItem.propTypes = {
- host: Host.isRequired,
- detailUrl: string.isRequired,
- isSelected: bool.isRequired,
- onSelect: func.isRequired,
-};
-
-export default HostListItem;
diff --git a/awx/ui/src/screens/Host/HostList/HostListItem.test.js b/awx/ui/src/screens/Host/HostList/HostListItem.test.js
deleted file mode 100644
index dbf44184ac04..000000000000
--- a/awx/ui/src/screens/Host/HostList/HostListItem.test.js
+++ /dev/null
@@ -1,75 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import HostsListItem from './HostListItem';
-
-const mockHost = {
- id: 1,
- name: 'Host 1',
- url: '/api/v2/hosts/1',
- description: 'Buzz',
- inventory: 1,
- summary_fields: {
- inventory: {
- id: 1,
- name: 'Inv 1',
- },
- user_capabilities: {
- edit: true,
- },
- recent_jobs: [],
- },
-};
-
-describe('', () => {
- let wrapper;
-
- beforeEach(() => {
- wrapper = mountWithContexts(
-
-
- {}}
- host={mockHost}
- />
-
-
- );
- });
-
- test('should display expected details', () => {
- expect(wrapper.find('HostListItem').length).toBe(1);
- expect(wrapper.find('Td[dataLabel="Name"]').find('Link').prop('to')).toBe(
- '/host/1'
- );
- expect(wrapper.find('Td[dataLabel="Description"]').text()).toBe('Buzz');
- });
-
- test('edit button shown to users with edit capabilities', () => {
- expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
- });
-
- test('edit button hidden from users without edit capabilities', () => {
- const copyMockHost = { ...mockHost };
- copyMockHost.summary_fields.user_capabilities.edit = false;
- wrapper = mountWithContexts(
-
-
- {}}
- host={copyMockHost}
- />
-
-
- );
- expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
- });
-
- test('should display host toggle', () => {
- expect(wrapper.find('HostToggle').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Host/HostList/SmartInventoryButton.js b/awx/ui/src/screens/Host/HostList/SmartInventoryButton.js
deleted file mode 100644
index dd82986b5b06..000000000000
--- a/awx/ui/src/screens/Host/HostList/SmartInventoryButton.js
+++ /dev/null
@@ -1,80 +0,0 @@
-import React from 'react';
-import { bool, func } from 'prop-types';
-import { Button, DropdownItem, Tooltip } from '@patternfly/react-core';
-import { t } from '@lingui/macro';
-import { useKebabifiedMenu } from 'contexts/Kebabified';
-
-function SmartInventoryButton({
- onClick,
- isDisabled,
- hasInvalidKeys,
- hasAnsibleFactsKeys,
-}) {
- const { isKebabified } = useKebabifiedMenu();
-
- const renderTooltipContent = () => {
- if (hasInvalidKeys) {
- return t`Some search modifiers like not__ and __search are not supported in Smart Inventory host filters. Remove these to create a new Smart Inventory with this filter.`;
- }
- if (hasAnsibleFactsKeys) {
- return t`To create a smart inventory using ansible facts, go to the smart inventory screen.`;
- }
- if (isDisabled) {
- return t`Enter at least one search filter to create a new Smart Inventory`;
- }
-
- return t`Create a new Smart Inventory with the applied filter`;
- };
-
- const renderContent = () => {
- if (isKebabified) {
- return (
-
- {t`Smart Inventory`}
-
- );
- }
-
- return (
-
- );
- };
-
- return (
-
- {renderContent()}
-
- );
-}
-SmartInventoryButton.propTypes = {
- hasInvalidKeys: bool,
- isDisabled: bool,
- onClick: func.isRequired,
- hasAnsibleFactsKeys: bool,
-};
-
-SmartInventoryButton.defaultProps = {
- hasInvalidKeys: false,
- isDisabled: false,
- hasAnsibleFactsKeys: false,
-};
-
-export default SmartInventoryButton;
diff --git a/awx/ui/src/screens/Host/HostList/SmartInventoryButton.test.js b/awx/ui/src/screens/Host/HostList/SmartInventoryButton.test.js
deleted file mode 100644
index b8f47725f5d4..000000000000
--- a/awx/ui/src/screens/Host/HostList/SmartInventoryButton.test.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import SmartInventoryButton from './SmartInventoryButton';
-
-describe('', () => {
- test('should render button', () => {
- const onClick = jest.fn();
- const wrapper = mountWithContexts(
-
- );
- const button = wrapper.find('button');
- expect(button).toHaveLength(1);
- button.simulate('click');
- expect(onClick).toHaveBeenCalled();
- });
-});
diff --git a/awx/ui/src/screens/Host/HostList/index.js b/awx/ui/src/screens/Host/HostList/index.js
deleted file mode 100644
index 2e40cbabb77e..000000000000
--- a/awx/ui/src/screens/Host/HostList/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './HostList';
diff --git a/awx/ui/src/screens/Host/Hosts.js b/awx/ui/src/screens/Host/Hosts.js
deleted file mode 100644
index 522acd124e02..000000000000
--- a/awx/ui/src/screens/Host/Hosts.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import React, { useState, useCallback } from 'react';
-import { Route, Switch } from 'react-router-dom';
-import { t } from '@lingui/macro';
-
-import { Config } from 'contexts/Config';
-import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
-import PersistentFilters from 'components/PersistentFilters';
-import HostList from './HostList';
-import HostAdd from './HostAdd';
-import Host from './Host';
-
-function Hosts() {
- const [breadcrumbConfig, setBreadcrumbConfig] = useState({
- '/hosts': t`Hosts`,
- '/hosts/add': t`Create New Host`,
- });
-
- const buildBreadcrumbConfig = useCallback((host) => {
- if (!host) {
- return;
- }
- setBreadcrumbConfig({
- '/hosts': t`Hosts`,
- '/hosts/add': t`Create New Host`,
- [`/hosts/${host.id}`]: `${host.name}`,
- [`/hosts/${host.id}/edit`]: t`Edit Details`,
- [`/hosts/${host.id}/details`]: t`Details`,
- [`/hosts/${host.id}/facts`]: t`Facts`,
- [`/hosts/${host.id}/groups`]: t`Groups`,
- [`/hosts/${host.id}/jobs`]: t`Jobs`,
- });
- }, []);
-
- return (
- <>
-
-
-
-
-
-
-
- {({ me }) => (
-
- )}
-
-
-
-
-
-
-
-
- >
- );
-}
-
-export { Hosts as _Hosts };
-export default Hosts;
diff --git a/awx/ui/src/screens/Host/Hosts.test.js b/awx/ui/src/screens/Host/Hosts.test.js
deleted file mode 100644
index 6ca584eda4f6..000000000000
--- a/awx/ui/src/screens/Host/Hosts.test.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import React from 'react';
-import { createMemoryHistory } from 'history';
-import { shallow } from 'enzyme';
-import { act } from 'react-dom/test-utils';
-
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-
-import Hosts from './Hosts';
-
-jest.mock('../../api');
-
-describe('', () => {
- test('should display a breadcrumb heading', () => {
- const wrapper = shallow();
-
- const header = wrapper.find('ScreenHeader');
- expect(header.prop('streamType')).toEqual('host');
- expect(header.prop('breadcrumbConfig')).toEqual({
- '/hosts': 'Hosts',
- '/hosts/add': 'Create New Host',
- });
- });
-
- test('should render Host component', async () => {
- let wrapper;
- const history = createMemoryHistory({
- initialEntries: ['/hosts/1'],
- });
-
- const match = {
- path: '/hosts/:id',
- url: '/hosts/1',
- isExact: true,
- };
-
- await act(async () => {
- wrapper = await mountWithContexts(, {
- context: { router: { history, route: { match } } },
- });
- });
-
- expect(wrapper.find('Host').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Host/data.host.json b/awx/ui/src/screens/Host/data.host.json
deleted file mode 100644
index aacc08f78769..000000000000
--- a/awx/ui/src/screens/Host/data.host.json
+++ /dev/null
@@ -1,86 +0,0 @@
-{
- "id": 2,
- "type": "host",
- "url": "/api/v2/hosts/2/",
- "related": {
- "created_by": "/api/v2/users/1/",
- "modified_by": "/api/v2/users/1/",
- "variable_data": "/api/v2/hosts/2/variable_data/",
- "groups": "/api/v2/hosts/2/groups/",
- "all_groups": "/api/v2/hosts/2/all_groups/",
- "job_events": "/api/v2/hosts/2/job_events/",
- "job_host_summaries": "/api/v2/hosts/2/job_host_summaries/",
- "activity_stream": "/api/v2/hosts/2/activity_stream/",
- "inventory_sources": "/api/v2/hosts/2/inventory_sources/",
- "smart_inventories": "/api/v2/hosts/2/smart_inventories/",
- "ad_hoc_commands": "/api/v2/hosts/2/ad_hoc_commands/",
- "ad_hoc_command_events": "/api/v2/hosts/2/ad_hoc_command_events/",
- "insights": "/api/v2/hosts/2/insights/",
- "ansible_facts": "/api/v2/hosts/2/ansible_facts/",
- "inventory": "/api/v2/inventories/3/",
- "last_job": "/api/v2/jobs/3/",
- "last_job_host_summary": "/api/v2/job_host_summaries/1/"
- },
- "summary_fields": {
- "inventory": {
- "id": 3,
- "name": "Mikes Inventory",
- "description": "",
- "has_active_failures": false,
- "total_hosts": 3,
- "hosts_with_active_failures": 0,
- "total_groups": 0,
- "groups_with_active_failures": 0,
- "has_inventory_sources": true,
- "total_inventory_sources": 1,
- "inventory_sources_with_failures": 0,
- "organization_id": 3,
- "kind": ""
- },
- "last_job": {
- "id": 3,
- "name": "Ping",
- "description": "",
- "finished": "2019-10-28T21:29:08.880572Z",
- "status": "successful",
- "failed": false,
- "job_template_id": 9,
- "job_template_name": "Ping"
- },
- "last_job_host_summary": {
- "id": 1,
- "failed": false
- },
- "user_capabilities": {
- "edit": true,
- "delete": true
- },
- "groups": {
- "count": 0,
- "results": []
- },
- "recent_jobs": [
- {
- "id": 3,
- "name": "Ping",
- "status": "successful",
- "finished": "2019-10-28T21:29:08.880572Z",
- "type": "job"
- }
- ]
- },
- "created": "2019-10-28T21:26:54.508081Z",
- "modified": "2019-10-29T20:18:41.915796Z",
- "name": "localhost",
- "description": "a good description",
- "inventory": 3,
- "enabled": true,
- "instance_id": "",
- "variables": "---\nansible_connection: local",
- "has_active_failures": false,
- "has_inventory_sources": false,
- "last_job": 3,
- "last_job_host_summary": 1,
- "insights_system_id": null,
- "ansible_facts_modified": null
-}
\ No newline at end of file
diff --git a/awx/ui/src/screens/Host/data.hostFacts.json b/awx/ui/src/screens/Host/data.hostFacts.json
deleted file mode 100644
index 0675ffb4ef93..000000000000
--- a/awx/ui/src/screens/Host/data.hostFacts.json
+++ /dev/null
@@ -1,1243 +0,0 @@
-{
- "ansible_lo": {
- "mtu": 65536,
- "ipv4": {
- "address": "127.0.0.1",
- "netmask": "255.0.0.0",
- "network": "127.0.0.0",
- "broadcast": "host"
- },
- "type": "loopback",
- "active": true,
- "device": "lo",
- "promisc": false,
- "features": {
- "rx_all": "off [fixed]",
- "rx_fcs": "off [fixed]",
- "highdma": "on [fixed]",
- "fcoe_mtu": "off [fixed]",
- "loopback": "on [fixed]",
- "busy_poll": "off [fixed]",
- "netns_local": "on [fixed]",
- "tx_lockless": "on [fixed]",
- "hw_tc_offload": "off [fixed]",
- "tx_gso_robust": "off [fixed]",
- "l2_fwd_offload": "off [fixed]",
- "ntuple_filters": "off [fixed]",
- "rx_vlan_filter": "off [fixed]",
- "scatter_gather": "on",
- "tx_gso_partial": "off [fixed]",
- "receive_hashing": "off [fixed]",
- "rx_checksumming": "on [fixed]",
- "rx_vlan_offload": "off [fixed]",
- "tx_checksumming": "on",
- "tx_nocache_copy": "off [fixed]",
- "tx_vlan_offload": "off [fixed]",
- "vlan_challenged": "on [fixed]",
- "tx_checksum_ipv4": "off [fixed]",
- "tx_checksum_ipv6": "off [fixed]",
- "tx_checksum_sctp": "on [fixed]",
- "tx_scatter_gather": "on [fixed]",
- "rx_vlan_stag_filter": "off [fixed]",
- "tx_gre_segmentation": "off [fixed]",
- "tx_tcp_segmentation": "on",
- "tx_checksum_fcoe_crc": "off [fixed]",
- "tx_fcoe_segmentation": "off [fixed]",
- "tx_sctp_segmentation": "on",
- "tx_tcp6_segmentation": "on",
- "large_receive_offload": "off [fixed]",
- "rx_vlan_stag_hw_parse": "off [fixed]",
- "tx_checksum_ip_generic": "on [fixed]",
- "tx_ipxip4_segmentation": "off [fixed]",
- "tx_ipxip6_segmentation": "off [fixed]",
- "tx_vlan_stag_hw_insert": "off [fixed]",
- "generic_receive_offload": "on",
- "tx_tcp_ecn_segmentation": "on",
- "tx_udp_tnl_segmentation": "off [fixed]",
- "tcp_segmentation_offload": "on",
- "tx_gre_csum_segmentation": "off [fixed]",
- "udp_fragmentation_offload": "on",
- "tx_scatter_gather_fraglist": "on [fixed]",
- "generic_segmentation_offload": "on",
- "tx_tcp_mangleid_segmentation": "on",
- "tx_udp_tnl_csum_segmentation": "off [fixed]"
- },
- "timestamping": [
- "rx_software",
- "software"
- ],
- "hw_timestamp_filters": []
- },
- "ansible_dns": {
- "options": {
- "ndots": "0"
- },
- "nameservers": [
- "127.0.0.11"
- ]
- },
- "ansible_env": {
- "_": "/usr/libexec/platform-python",
- "OS": " Operating System: Docker Desktop",
- "TZ": "UTC",
- "PWD": "/tmp/awx_13_r1ffeqze/project",
- "HOME": "/var/lib/awx",
- "LANG": "\"en-us\"",
- "PATH": "/var/lib/awx/venv/ansible/bin:/var/lib/awx/venv/awx/bin:/var/lib/awx/venv/awx/bin:/usr/local/n/versions/node/10.15.0/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
- "SHLVL": "4",
- "JOB_ID": "13",
- "LC_ALL": "en_US.UTF-8",
- "MFLAGS": "-w",
- "OLDPWD": "/awx_devel",
- "AWX_HOST": "https://towerhost",
- "HOSTNAME": "awx",
- "LANGUAGE": "en_US:en",
- "SDB_HOST": "0.0.0.0",
- "SDB_PORT": "7899",
- "MAKEFLAGS": "w",
- "MAKELEVEL": "2",
- "PYTHONPATH": "/var/lib/awx/venv/ansible/lib/python3.6/site-packages:/awx_devel/awx/lib:/var/lib/awx/venv/awx/lib/python3.6/site-packages/ansible_runner/callbacks",
- "CURRENT_UID": "501",
- "VIRTUAL_ENV": "/var/lib/awx/venv/ansible",
- "INVENTORY_ID": "1",
- "MAX_EVENT_RES": "700000",
- "ANSIBLE_LIBRARY": "/awx_devel/awx/plugins/library",
- "SDB_NOTIFY_HOST": "docker.for.mac.host.internal",
- "AWX_GROUP_QUEUES": "tower",
- "PROJECT_REVISION": "9e2cd25bfb26ba82f40cf31276e1942bf38b3a30",
- "ANSIBLE_VENV_PATH": "/var/lib/awx/venv/ansible",
- "ANSIBLE_ROLES_PATH": "/tmp/awx_13_r1ffeqze/requirements_roles:~/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles",
- "RUNNER_OMIT_EVENTS": "False",
- "SUPERVISOR_ENABLED": "1",
- "ANSIBLE_FORCE_COLOR": "True",
- "ANSIBLE_CACHE_PLUGIN": "jsonfile",
- "AWX_PRIVATE_DATA_DIR": "/tmp/awx_13_r1ffeqze",
- "SUPERVISOR_GROUP_NAME": "tower-processes",
- "SUPERVISOR_SERVER_URL": "unix:///tmp/supervisor.sock",
- "DJANGO_SETTINGS_MODULE": "awx.settings.development",
- "ANSIBLE_STDOUT_CALLBACK": "awx_display",
- "SUPERVISOR_PROCESS_NAME": "awx-dispatcher",
- "ANSIBLE_CALLBACK_PLUGINS": "/awx_devel/awx/plugins/callback:/var/lib/awx/venv/awx/lib/python3.6/site-packages/ansible_runner/callbacks",
- "ANSIBLE_COLLECTIONS_PATHS": "/tmp/awx_13_r1ffeqze/requirements_collections:~/.ansible/collections:/usr/share/ansible/collections",
- "ANSIBLE_HOST_KEY_CHECKING": "False",
- "RUNNER_ONLY_FAILED_EVENTS": "False",
- "ANSIBLE_RETRY_FILES_ENABLED": "False",
- "ANSIBLE_SSH_CONTROL_PATH_DIR": "/tmp/awx_13_r1ffeqze/cp",
- "ANSIBLE_CACHE_PLUGIN_CONNECTION": "/tmp/awx_13_r1ffeqze/artifacts/13/fact_cache",
- "DJANGO_LIVE_TEST_SERVER_ADDRESS": "localhost:9013-9199",
- "ANSIBLE_INVENTORY_UNPARSED_FAILED": "True",
- "ANSIBLE_PARAMIKO_RECORD_HOST_KEYS": "False"
- },
- "ansible_lsb": {},
- "ansible_eth0": {
- "mtu": 1500,
- "ipv4": {
- "address": "172.18.0.5",
- "netmask": "255.255.0.0",
- "network": "172.18.0.0",
- "broadcast": "172.18.255.255"
- },
- "type": "ether",
- "speed": 10000,
- "active": true,
- "device": "eth0",
- "promisc": false,
- "features": {
- "rx_all": "off [fixed]",
- "rx_fcs": "off [fixed]",
- "highdma": "on",
- "fcoe_mtu": "off [fixed]",
- "loopback": "off [fixed]",
- "busy_poll": "off [fixed]",
- "netns_local": "off [fixed]",
- "tx_lockless": "on [fixed]",
- "hw_tc_offload": "off [fixed]",
- "tx_gso_robust": "off [fixed]",
- "l2_fwd_offload": "off [fixed]",
- "ntuple_filters": "off [fixed]",
- "rx_vlan_filter": "off [fixed]",
- "scatter_gather": "on",
- "tx_gso_partial": "off [fixed]",
- "receive_hashing": "off [fixed]",
- "rx_checksumming": "on",
- "rx_vlan_offload": "on",
- "tx_checksumming": "on",
- "tx_nocache_copy": "off",
- "tx_vlan_offload": "on",
- "vlan_challenged": "off [fixed]",
- "tx_checksum_ipv4": "off [fixed]",
- "tx_checksum_ipv6": "off [fixed]",
- "tx_checksum_sctp": "on",
- "tx_scatter_gather": "on",
- "rx_vlan_stag_filter": "off [fixed]",
- "tx_gre_segmentation": "on",
- "tx_tcp_segmentation": "on",
- "tx_checksum_fcoe_crc": "off [fixed]",
- "tx_fcoe_segmentation": "off [fixed]",
- "tx_sctp_segmentation": "on",
- "tx_tcp6_segmentation": "on",
- "large_receive_offload": "off [fixed]",
- "rx_vlan_stag_hw_parse": "on",
- "tx_checksum_ip_generic": "on",
- "tx_ipxip4_segmentation": "on",
- "tx_ipxip6_segmentation": "on",
- "tx_vlan_stag_hw_insert": "on",
- "generic_receive_offload": "on",
- "tx_tcp_ecn_segmentation": "on",
- "tx_udp_tnl_segmentation": "on",
- "tcp_segmentation_offload": "on",
- "tx_gre_csum_segmentation": "on",
- "udp_fragmentation_offload": "on",
- "tx_scatter_gather_fraglist": "on",
- "generic_segmentation_offload": "on",
- "tx_tcp_mangleid_segmentation": "on",
- "tx_udp_tnl_csum_segmentation": "on"
- },
- "macaddress": "02:42:ac:12:00:05",
- "timestamping": [
- "rx_software",
- "software"
- ],
- "hw_timestamp_filters": []
- },
- "ansible_fips": false,
- "ansible_fqdn": "awx",
- "module_setup": true,
- "ansible_local": {},
- "ansible_tunl0": {
- "mtu": 1480,
- "type": "unknown",
- "active": false,
- "device": "tunl0",
- "promisc": false,
- "features": {
- "rx_all": "off [fixed]",
- "rx_fcs": "off [fixed]",
- "highdma": "on",
- "fcoe_mtu": "off [fixed]",
- "loopback": "off [fixed]",
- "busy_poll": "off [fixed]",
- "netns_local": "on [fixed]",
- "tx_lockless": "on [fixed]",
- "hw_tc_offload": "off [fixed]",
- "tx_gso_robust": "off [fixed]",
- "l2_fwd_offload": "off [fixed]",
- "ntuple_filters": "off [fixed]",
- "rx_vlan_filter": "off [fixed]",
- "scatter_gather": "on",
- "tx_gso_partial": "off [fixed]",
- "receive_hashing": "off [fixed]",
- "rx_checksumming": "off [fixed]",
- "rx_vlan_offload": "off [fixed]",
- "tx_checksumming": "on",
- "tx_nocache_copy": "off",
- "tx_vlan_offload": "off [fixed]",
- "vlan_challenged": "off [fixed]",
- "tx_checksum_ipv4": "off [fixed]",
- "tx_checksum_ipv6": "off [fixed]",
- "tx_checksum_sctp": "off [fixed]",
- "tx_scatter_gather": "on",
- "rx_vlan_stag_filter": "off [fixed]",
- "tx_gre_segmentation": "off [fixed]",
- "tx_tcp_segmentation": "on",
- "tx_checksum_fcoe_crc": "off [fixed]",
- "tx_fcoe_segmentation": "off [fixed]",
- "tx_sctp_segmentation": "on",
- "tx_tcp6_segmentation": "on",
- "large_receive_offload": "off [fixed]",
- "rx_vlan_stag_hw_parse": "off [fixed]",
- "tx_checksum_ip_generic": "on",
- "tx_ipxip4_segmentation": "off [fixed]",
- "tx_ipxip6_segmentation": "off [fixed]",
- "tx_vlan_stag_hw_insert": "off [fixed]",
- "generic_receive_offload": "on",
- "tx_tcp_ecn_segmentation": "on",
- "tx_udp_tnl_segmentation": "off [fixed]",
- "tcp_segmentation_offload": "on",
- "tx_gre_csum_segmentation": "off [fixed]",
- "udp_fragmentation_offload": "on",
- "tx_scatter_gather_fraglist": "on",
- "generic_segmentation_offload": "on",
- "tx_tcp_mangleid_segmentation": "on",
- "tx_udp_tnl_csum_segmentation": "off [fixed]"
- },
- "macaddress": "00:00:00:00",
- "timestamping": [
- "rx_software",
- "software"
- ],
- "hw_timestamp_filters": []
- },
- "gather_subset": [
- "all"
- ],
- "ansible_domain": "",
- "ansible_kernel": "4.9.184-linuxkit",
- "ansible_mounts": [
- {
- "uuid": "N/A",
- "mount": "/etc/resolv.conf",
- "device": "/dev/sda1",
- "fstype": "ext4",
- "options": "rw,nosuid,relatime,data=ordered,bind",
- "block_size": 4096,
- "block_used": 3938899,
- "inode_used": 479010,
- "size_total": 62725623808,
- "block_total": 15313873,
- "inode_total": 3907584,
- "size_available": 46591893504,
- "block_available": 11374974,
- "inode_available": 3428574
- },
- {
- "uuid": "N/A",
- "mount": "/etc/hostname",
- "device": "/dev/sda1",
- "fstype": "ext4",
- "options": "rw,nosuid,relatime,data=ordered,bind",
- "block_size": 4096,
- "block_used": 3938899,
- "inode_used": 479010,
- "size_total": 62725623808,
- "block_total": 15313873,
- "inode_total": 3907584,
- "size_available": 46591893504,
- "block_available": 11374974,
- "inode_available": 3428574
- },
- {
- "uuid": "N/A",
- "mount": "/etc/hosts",
- "device": "/dev/sda1",
- "fstype": "ext4",
- "options": "rw,nosuid,relatime,data=ordered,bind",
- "block_size": 4096,
- "block_used": 3938899,
- "inode_used": 479010,
- "size_total": 62725623808,
- "block_total": 15313873,
- "inode_total": 3907584,
- "size_available": 46591893504,
- "block_available": 11374974,
- "inode_available": 3428574
- }
- ],
- "ansible_python": {
- "type": "cpython",
- "version": {
- "major": 3,
- "micro": 8,
- "minor": 6,
- "serial": 0,
- "releaselevel": "final"
- },
- "executable": "/usr/libexec/platform-python",
- "version_info": [
- 3,
- 6,
- 8,
- "final",
- 0
- ],
- "has_sslcontext": true
- },
- "ansible_system": "Linux",
- "ansible_cmdline": {
- "root": "/dev/sr0",
- "text": true,
- "panic": "1",
- "console": "ttyS1",
- "vsyscall": "emulate",
- "BOOT_IMAGE": "/boot/kernel",
- "page_poison": "1"
- },
- "ansible_devices": {
- "sda": {
- "host": "",
- "size": "59.60 GB",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": "BHYVE SATA DISK",
- "vendor": "ATA",
- "holders": [],
- "sectors": "124999680",
- "virtual": 1,
- "removable": "0",
- "partitions": {
- "sda1": {
- "size": "59.60 GB",
- "uuid": null,
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "start": "2048",
- "holders": [],
- "sectors": "124997632",
- "sectorsize": 512
- }
- },
- "rotational": "1",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "deadline",
- "support_discard": "4096",
- "sas_device_handle": null
- },
- "sr0": {
- "host": "",
- "size": "453.38 MB",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": "BHYVE DVD-ROM",
- "vendor": "BHYVE",
- "holders": [],
- "sectors": "928528",
- "virtual": 1,
- "removable": "1",
- "partitions": {},
- "rotational": "1",
- "sectorsize": "2048",
- "sas_address": null,
- "scheduler_mode": "deadline",
- "support_discard": "0",
- "sas_device_handle": null
- },
- "sr1": {
- "host": "",
- "size": "86.00 KB",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": "BHYVE DVD-ROM",
- "vendor": "BHYVE",
- "holders": [],
- "sectors": "172",
- "virtual": 1,
- "removable": "1",
- "partitions": {},
- "rotational": "1",
- "sectorsize": "2048",
- "sas_address": null,
- "scheduler_mode": "deadline",
- "support_discard": "0",
- "sas_device_handle": null
- },
- "sr2": {
- "host": "",
- "size": "832.02 MB",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": "BHYVE DVD-ROM",
- "vendor": "BHYVE",
- "holders": [],
- "sectors": "1703968",
- "virtual": 1,
- "removable": "1",
- "partitions": {},
- "rotational": "1",
- "sectorsize": "2048",
- "sas_address": null,
- "scheduler_mode": "deadline",
- "support_discard": "0",
- "sas_device_handle": null
- },
- "nbd0": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd1": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd2": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd3": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd4": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd5": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd6": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd7": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd8": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd9": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "loop0": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "1",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "0",
- "sas_device_handle": null
- },
- "loop1": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "1",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "0",
- "sas_device_handle": null
- },
- "loop2": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "1",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "0",
- "sas_device_handle": null
- },
- "loop3": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "1",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "0",
- "sas_device_handle": null
- },
- "loop4": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "1",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "0",
- "sas_device_handle": null
- },
- "loop5": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "1",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "0",
- "sas_device_handle": null
- },
- "loop6": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "1",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "0",
- "sas_device_handle": null
- },
- "loop7": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "1",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "0",
- "sas_device_handle": null
- },
- "nbd10": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd11": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd12": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd13": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd14": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd15": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- }
- },
- "ansible_hostnqn": "",
- "ansible_ip6tnl0": {
- "mtu": 1452,
- "type": "unknown",
- "active": false,
- "device": "ip6tnl0",
- "promisc": false,
- "features": {
- "rx_all": "off [fixed]",
- "rx_fcs": "off [fixed]",
- "highdma": "on",
- "fcoe_mtu": "off [fixed]",
- "loopback": "off [fixed]",
- "busy_poll": "off [fixed]",
- "netns_local": "on [fixed]",
- "tx_lockless": "on [fixed]",
- "hw_tc_offload": "off [fixed]",
- "tx_gso_robust": "off [fixed]",
- "l2_fwd_offload": "off [fixed]",
- "ntuple_filters": "off [fixed]",
- "rx_vlan_filter": "off [fixed]",
- "scatter_gather": "on",
- "tx_gso_partial": "off [fixed]",
- "receive_hashing": "off [fixed]",
- "rx_checksumming": "off [fixed]",
- "rx_vlan_offload": "off [fixed]",
- "tx_checksumming": "on",
- "tx_nocache_copy": "off",
- "tx_vlan_offload": "off [fixed]",
- "vlan_challenged": "off [fixed]",
- "tx_checksum_ipv4": "off [fixed]",
- "tx_checksum_ipv6": "off [fixed]",
- "tx_checksum_sctp": "off [fixed]",
- "tx_scatter_gather": "on",
- "rx_vlan_stag_filter": "off [fixed]",
- "tx_gre_segmentation": "off [fixed]",
- "tx_tcp_segmentation": "on",
- "tx_checksum_fcoe_crc": "off [fixed]",
- "tx_fcoe_segmentation": "off [fixed]",
- "tx_sctp_segmentation": "on",
- "tx_tcp6_segmentation": "on",
- "large_receive_offload": "off [fixed]",
- "rx_vlan_stag_hw_parse": "off [fixed]",
- "tx_checksum_ip_generic": "on",
- "tx_ipxip4_segmentation": "off [fixed]",
- "tx_ipxip6_segmentation": "off [fixed]",
- "tx_vlan_stag_hw_insert": "off [fixed]",
- "generic_receive_offload": "on",
- "tx_tcp_ecn_segmentation": "on",
- "tx_udp_tnl_segmentation": "off [fixed]",
- "tcp_segmentation_offload": "on",
- "tx_gre_csum_segmentation": "off [fixed]",
- "udp_fragmentation_offload": "on",
- "tx_scatter_gather_fraglist": "on",
- "generic_segmentation_offload": "on",
- "tx_tcp_mangleid_segmentation": "on",
- "tx_udp_tnl_csum_segmentation": "off [fixed]"
- },
- "macaddress": "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00",
- "timestamping": [
- "rx_software",
- "software"
- ],
- "hw_timestamp_filters": []
- },
- "ansible_machine": "x86_64",
- "ansible_pkg_mgr": "dnf",
- "ansible_selinux": {
- "status": "disabled"
- },
- "ansible_user_id": "awx",
- "ansible_apparmor": {
- "status": "disabled"
- },
- "ansible_hostname": "awx",
- "ansible_nodename": "awx",
- "ansible_user_dir": "/tmp",
- "ansible_user_gid": 0,
- "ansible_user_uid": 501,
- "ansible_bios_date": "03/14/2014",
- "ansible_date_time": {
- "tz": "UTC",
- "day": "11",
- "date": "2020-03-11",
- "hour": "18",
- "time": "18:17:15",
- "year": "2020",
- "epoch": "1583950635",
- "month": "03",
- "minute": "17",
- "second": "15",
- "iso8601": "2020-03-11T18:17:15Z",
- "weekday": "Wednesday",
- "tz_offset": "+0000",
- "weeknumber": "10",
- "iso8601_basic": "20200311T181715335527",
- "iso8601_micro": "2020-03-11T18:17:15.335587Z",
- "weekday_number": "3",
- "iso8601_basic_short": "20200311T181715"
- },
- "ansible_is_chroot": false,
- "ansible_iscsi_iqn": "",
- "ansible_memory_mb": {
- "real": {
- "free": 268,
- "used": 7705,
- "total": 7973
- },
- "swap": {
- "free": 1023,
- "used": 0,
- "total": 1023,
- "cached": 0
- },
- "nocache": {
- "free": 4740,
- "used": 3233
- }
- },
- "ansible_os_family": "RedHat",
- "ansible_processor": [
- "0",
- "GenuineIntel",
- "Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz",
- "1",
- "GenuineIntel",
- "Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz",
- "2",
- "GenuineIntel",
- "Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz",
- "3",
- "GenuineIntel",
- "Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz",
- "4",
- "GenuineIntel",
- "Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz",
- "5",
- "GenuineIntel",
- "Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz"
- ],
- "ansible_interfaces": [
- "ip6tnl0",
- "eth0",
- "lo",
- "tunl0"
- ],
- "ansible_machine_id": "a76ef18c33e144f09ee5370f751184fa",
- "ansible_memfree_mb": 268,
- "ansible_user_gecos": ",,,",
- "ansible_user_shell": "/bin/bash",
- "ansible_form_factor": "Unknown",
- "ansible_memtotal_mb": 7973,
- "ansible_service_mgr": "bwrap",
- "ansible_swapfree_mb": 1023,
- "ansible_architecture": "x86_64",
- "ansible_bios_version": "1.00",
- "ansible_default_ipv4": {
- "mtu": 1500,
- "type": "ether",
- "alias": "eth0",
- "address": "172.18.0.5",
- "gateway": "172.18.0.1",
- "netmask": "255.255.0.0",
- "network": "172.18.0.0",
- "broadcast": "172.18.255.255",
- "interface": "eth0",
- "macaddress": "02:42:ac:12:00:05"
- },
- "ansible_default_ipv6": {},
- "ansible_device_links": {
- "ids": {},
- "uuids": {},
- "labels": {},
- "masters": {}
- },
- "ansible_distribution": "CentOS",
- "ansible_proc_cmdline": {
- "root": "/dev/sr0",
- "text": true,
- "panic": "1",
- "console": [
- "ttyS0",
- "ttyS1"
- ],
- "vsyscall": "emulate",
- "BOOT_IMAGE": "/boot/kernel",
- "page_poison": "1"
- },
- "ansible_product_name": "BHYVE",
- "ansible_product_uuid": "NA",
- "ansible_real_user_id": 501,
- "ansible_swaptotal_mb": 1023,
- "ansible_real_group_id": 0,
- "ansible_system_vendor": "NA",
- "ansible_kernel_version": "#1 SMP Tue Jul 2 22:58:16 UTC 2019",
- "ansible_product_serial": "NA",
- "ansible_python_version": "3.6.8",
- "ansible_uptime_seconds": 1867065,
- "ansible_userspace_bits": "64",
- "_ansible_facts_gathered": true,
- "ansible_processor_cores": 1,
- "ansible_processor_count": 6,
- "ansible_processor_vcpus": 6,
- "ansible_product_version": "1.0",
- "ansible_effective_user_id": 501,
- "ansible_fibre_channel_wwn": [],
- "ansible_all_ipv4_addresses": [
- "172.18.0.5"
- ],
- "ansible_all_ipv6_addresses": [],
- "ansible_effective_group_id": 0,
- "ansible_system_capabilities": [
- ""
- ],
- "ansible_virtualization_role": "guest",
- "ansible_virtualization_type": "docker",
- "ansible_distribution_release": "Core",
- "ansible_distribution_version": "8.1",
- "discovered_interpreter_python": "/usr/libexec/platform-python",
- "ansible_distribution_file_path": "/etc/redhat-release",
- "ansible_selinux_python_present": true,
- "ansible_userspace_architecture": "x86_64",
- "ansible_distribution_file_parsed": true,
- "ansible_distribution_file_variety": "RedHat",
- "ansible_distribution_major_version": "8",
- "ansible_processor_threads_per_core": 1,
- "ansible_system_capabilities_enforced": "True"
-}
diff --git a/awx/ui/src/screens/Host/index.js b/awx/ui/src/screens/Host/index.js
deleted file mode 100644
index f501bcbcb943..000000000000
--- a/awx/ui/src/screens/Host/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './Hosts';
diff --git a/awx/ui/src/screens/InstanceGroup/ContainerGroup.js b/awx/ui/src/screens/InstanceGroup/ContainerGroup.js
deleted file mode 100644
index 3533b1c97a33..000000000000
--- a/awx/ui/src/screens/InstanceGroup/ContainerGroup.js
+++ /dev/null
@@ -1,135 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-import {
- Link,
- Redirect,
- Route,
- Switch,
- useLocation,
- useParams,
-} from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { CaretLeftIcon } from '@patternfly/react-icons';
-import { Card, PageSection } from '@patternfly/react-core';
-
-import useRequest from 'hooks/useRequest';
-import { InstanceGroupsAPI } from 'api';
-import RoutedTabs from 'components/RoutedTabs';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import JobList from 'components/JobList';
-
-import ContainerGroupDetails from './ContainerGroupDetails';
-import ContainerGroupEdit from './ContainerGroupEdit';
-
-function ContainerGroup({ setBreadcrumb }) {
- const { id } = useParams();
- const { pathname } = useLocation();
-
- const {
- isLoading,
- error: contentError,
- request: fetchInstanceGroups,
- result: { instanceGroup },
- } = useRequest(
- useCallback(async () => {
- const { data } = await InstanceGroupsAPI.readDetail(id);
- return {
- instanceGroup: data,
- };
- }, [id]),
- { instanceGroup: null }
- );
-
- useEffect(() => {
- fetchInstanceGroups();
- }, [fetchInstanceGroups, pathname]);
-
- useEffect(() => {
- if (instanceGroup) {
- setBreadcrumb(instanceGroup);
- }
- }, [instanceGroup, setBreadcrumb]);
-
- const tabsArray = [
- {
- name: (
- <>
-
- {t`Back to instance groups`}
- >
- ),
- link: '/instance_groups',
- id: 99,
- },
- {
- name: t`Details`,
- link: `/instance_groups/container_group/${id}/details`,
- id: 0,
- },
- {
- name: t`Jobs`,
- link: `/instance_groups/container_group/${id}/jobs`,
- id: 1,
- },
- ];
-
- if (!isLoading && contentError) {
- return (
-
-
-
- {contentError.response?.status === 404 && (
-
- {t`Container group not found.`}
-
- {t`View all instance groups`}
-
- )}
-
-
-
- );
- }
-
- let cardHeader = ;
- if (pathname.endsWith('edit')) {
- cardHeader = null;
- }
-
- return (
-
-
- {cardHeader}
- {isLoading && }
- {!isLoading && instanceGroup && (
-
-
- {instanceGroup && (
- <>
-
-
-
-
-
-
-
-
-
- >
- )}
-
- )}
-
-
- );
-}
-
-export default ContainerGroup;
diff --git a/awx/ui/src/screens/InstanceGroup/ContainerGroup.test.js b/awx/ui/src/screens/InstanceGroup/ContainerGroup.test.js
deleted file mode 100644
index 0dc914ee713a..000000000000
--- a/awx/ui/src/screens/InstanceGroup/ContainerGroup.test.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-
-import { InstanceGroupsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../testUtils/enzymeHelpers';
-
-import ContainerGroup from './ContainerGroup';
-
-jest.mock('../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useRouteMatch: () => ({
- url: '/instance_groups/container_group',
- }),
- useParams: () => ({ id: 42 }),
-}));
-
-describe('', () => {
- let wrapper;
- test('should render details properly', async () => {
- await act(async () => {
- wrapper = mountWithContexts( {}} />);
- });
- wrapper.update();
- expect(wrapper.find('ContainerGroup').length).toBe(1);
- expect(InstanceGroupsAPI.readDetail).toBeCalledWith(42);
- });
-
- test('should render expected tabs', async () => {
- const expectedTabs = ['Back to instance groups', 'Details', 'Jobs'];
- await act(async () => {
- wrapper = mountWithContexts( {}} />);
- });
- wrapper.find('RoutedTabs li').forEach((tab, index) => {
- expect(tab.text()).toEqual(expectedTabs[index]);
- });
- });
-
- test('should show content error when user attempts to navigate to erroneous route', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/instance_groups/container_group/42/foobar'],
- });
- await act(async () => {
- wrapper = mountWithContexts( {}} />, {
- context: {
- router: {
- history,
- },
- },
- });
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-});
diff --git a/awx/ui/src/screens/InstanceGroup/ContainerGroupAdd/ContainerGroupAdd.js b/awx/ui/src/screens/InstanceGroup/ContainerGroupAdd/ContainerGroupAdd.js
deleted file mode 100644
index 7aaf5f80b060..000000000000
--- a/awx/ui/src/screens/InstanceGroup/ContainerGroupAdd/ContainerGroupAdd.js
+++ /dev/null
@@ -1,111 +0,0 @@
-import React, { useState, useEffect, useCallback } from 'react';
-import { Card, PageSection } from '@patternfly/react-core';
-import { useHistory } from 'react-router-dom';
-
-import { CardBody } from 'components/Card';
-import { InstanceGroupsAPI } from 'api';
-import useRequest from 'hooks/useRequest';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import { jsonToYaml, isJsonString } from 'util/yaml';
-
-import ContainerGroupForm from '../shared/ContainerGroupForm';
-
-function ContainerGroupAdd() {
- const history = useHistory();
- const [submitError, setSubmitError] = useState(null);
-
- const getPodSpecValue = (value) => {
- if (isJsonString(value)) {
- value = jsonToYaml(value);
- }
- if (value !== jsonToYaml(JSON.stringify(initialPodSpec))) {
- return value;
- }
- return null;
- };
-
- const handleSubmit = async (values) => {
- try {
- const { data: response } = await InstanceGroupsAPI.create({
- name: values.name,
- max_forks: values.max_forks ? values.max_forks : 0,
- max_concurrent_jobs: values.max_concurrent_jobs
- ? values.max_concurrent_jobs
- : 0,
- credential: values?.credential?.id,
- pod_spec_override: values.override
- ? getPodSpecValue(values.pod_spec_override)
- : null,
- is_container_group: true,
- });
- history.push(`/instance_groups/container_group/${response.id}/details`);
- } catch (error) {
- setSubmitError(error);
- }
- };
-
- const handleCancel = () => {
- history.push(`/instance_groups`);
- };
-
- const {
- error: fetchError,
- isLoading,
- request: fetchInitialPodSpec,
- result: initialPodSpec,
- } = useRequest(
- useCallback(async () => {
- const { data } = await InstanceGroupsAPI.readOptions();
- return data.actions.POST.pod_spec_override.default;
- }, []),
- {
- initialPodSpec: {},
- }
- );
-
- useEffect(() => {
- fetchInitialPodSpec();
- }, [fetchInitialPodSpec]);
-
- if (fetchError) {
- return (
-
-
-
-
-
-
-
- );
- }
-
- if (isLoading) {
- return (
-
-
-
-
-
-
-
- );
- }
-
- return (
-
-
-
-
-
-
-
- );
-}
-
-export default ContainerGroupAdd;
diff --git a/awx/ui/src/screens/InstanceGroup/ContainerGroupAdd/ContainerGroupAdd.test.js b/awx/ui/src/screens/InstanceGroup/ContainerGroupAdd/ContainerGroupAdd.test.js
deleted file mode 100644
index 223381f7a4ff..000000000000
--- a/awx/ui/src/screens/InstanceGroup/ContainerGroupAdd/ContainerGroupAdd.test.js
+++ /dev/null
@@ -1,97 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-
-import { InstanceGroupsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import ContainerGroupAdd from './ContainerGroupAdd';
-
-jest.mock('../../../api');
-
-const initialPodSpec = {
- default: {
- apiVersion: 'v1',
- kind: 'Pod',
- metadata: {
- namespace: 'default',
- },
- spec: {
- containers: [
- {
- image: 'ansible/ansible-runner',
- tty: true,
- stdin: true,
- imagePullPolicy: 'Always',
- args: ['sleep', 'infinity'],
- },
- ],
- },
- is_container_group: true,
- },
-};
-
-const instanceGroupCreateData = {
- name: 'Fuz',
- credential: { id: 71, name: 'CG' },
- max_concurrent_jobs: 0,
- max_forks: 0,
- pod_spec_override:
- 'apiVersion: v1\nkind: Pod\nmetadata:\n namespace: default\nspec:\n containers:\n - image: ansible/ansible-runner\n tty: true\n stdin: true\n imagePullPolicy: Always\n args:\n - sleep\n - infinity\n - test',
-};
-
-describe('', () => {
- let wrapper;
- let history;
-
- beforeEach(async () => {
- history = createMemoryHistory({
- initialEntries: ['/instance_groups'],
- });
-
- InstanceGroupsAPI.create.mockResolvedValue({
- data: {
- id: 123,
- },
- });
-
- InstanceGroupsAPI.readOptions.mockResolvedValue({
- data: {
- results: initialPodSpec,
- },
- });
-
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('handleSubmit should call the api and redirect to details page', async () => {
- await act(async () => {
- wrapper.find('ContainerGroupForm').prop('onSubmit')({
- ...instanceGroupCreateData,
- override: true,
- });
- });
- wrapper.update();
- expect(InstanceGroupsAPI.create).toHaveBeenCalledWith({
- ...instanceGroupCreateData,
- credential: 71,
- is_container_group: true,
- });
- expect(wrapper.find('FormSubmitError').length).toBe(0);
- expect(history.location.pathname).toBe(
- '/instance_groups/container_group/123/details'
- );
- });
-
- test('handleCancel should return the user back to the instance group list', async () => {
- wrapper.find('Button[aria-label="Cancel"]').simulate('click');
- expect(history.location.pathname).toEqual('/instance_groups');
- });
-});
diff --git a/awx/ui/src/screens/InstanceGroup/ContainerGroupAdd/index.js b/awx/ui/src/screens/InstanceGroup/ContainerGroupAdd/index.js
deleted file mode 100644
index d693720aacd5..000000000000
--- a/awx/ui/src/screens/InstanceGroup/ContainerGroupAdd/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ContainerGroupAdd';
diff --git a/awx/ui/src/screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js b/awx/ui/src/screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js
deleted file mode 100644
index 5ae579bc4581..000000000000
--- a/awx/ui/src/screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.js
+++ /dev/null
@@ -1,152 +0,0 @@
-import React, { useCallback } from 'react';
-
-import { t } from '@lingui/macro';
-import { Link, useHistory } from 'react-router-dom';
-import { Button, Label } from '@patternfly/react-core';
-
-import { VariablesDetail } from 'components/CodeEditor';
-import AlertModal from 'components/AlertModal';
-import ErrorDetail from 'components/ErrorDetail';
-import { CardBody, CardActionsRow } from 'components/Card';
-import DeleteButton from 'components/DeleteButton';
-import {
- Detail,
- DetailList,
- UserDateDetail,
- DetailBadge,
-} from 'components/DetailList';
-import useRequest, { useDismissableError } from 'hooks/useRequest';
-import { jsonToYaml, isJsonString } from 'util/yaml';
-import { InstanceGroupsAPI } from 'api';
-import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
-
-function ContainerGroupDetails({ instanceGroup }) {
- const { id, name } = instanceGroup;
-
- const history = useHistory();
-
- const {
- request: deleteInstanceGroup,
- isLoading,
- error: deleteError,
- } = useRequest(
- useCallback(async () => {
- await InstanceGroupsAPI.destroy(id);
- history.push(`/instance_groups`);
- }, [id, history])
- );
-
- const { error, dismissError } = useDismissableError(deleteError);
- const deleteDetailsRequests =
- relatedResourceDeleteRequests.instanceGroup(instanceGroup);
- return (
-
-
-
-
-
-
- {instanceGroup.summary_fields.credential && (
-
-
-
- }
- dataCy="container-group-credential"
- />
- )}
-
-
- {instanceGroup.pod_spec_override && (
-
- )}
-
-
-
- {instanceGroup.summary_fields.user_capabilities &&
- instanceGroup.summary_fields.user_capabilities.edit && (
-
- )}
- {instanceGroup.summary_fields.user_capabilities &&
- instanceGroup.summary_fields.user_capabilities.delete && (
-
- {t`Delete`}
-
- )}
-
- {error && (
-
-
-
- )}
-
- );
-}
-
-export default ContainerGroupDetails;
diff --git a/awx/ui/src/screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.test.js b/awx/ui/src/screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.test.js
deleted file mode 100644
index e0860c5aeb5e..000000000000
--- a/awx/ui/src/screens/InstanceGroup/ContainerGroupDetails/ContainerGroupDetails.test.js
+++ /dev/null
@@ -1,129 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-
-import { InstanceGroupsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import ContainerGroupDetails from './ContainerGroupDetails';
-
-jest.mock('../../../api');
-
-const instanceGroup = {
- id: 42,
- type: 'instance_group',
- url: '/api/v2/instance_groups/128/',
- related: {
- named_url: '/api/v2/instance_groups/A1/',
- jobs: '/api/v2/instance_groups/128/jobs/',
- instances: '/api/v2/instance_groups/128/instances/',
- credential: '/api/v2/credentials/71/',
- },
- name: 'Foo',
- created: '2020-09-03T18:26:47.113934Z',
- modified: '2020-09-03T19:34:23.244694Z',
- capacity: 0,
- max_concurrent_jobs: 0,
- max_forks: 0,
- committed_capacity: 0,
- consumed_capacity: 0,
- percent_capacity_remaining: 0.0,
- jobs_running: 0,
- jobs_total: 0,
- instances: 0,
- controller: null,
- is_container_group: true,
- credential: 71,
- policy_instance_percentage: 0,
- policy_instance_minimum: 0,
- policy_instance_list: [],
- pod_spec_override:
- 'apiVersion: v1\nkind: Pod\nmetadata:\n namespace: default\nspec:\n containers:\n - image: ansible/ansible-runner\n tty: true\n stdin: true\n imagePullPolicy: Always\n args:\n - sleep\n - infinity\n - test',
- summary_fields: {
- credential: {
- id: 71,
- name: 'CG',
- description: 'Container Group',
- kind: 'kubernetes_bearer_token',
- cloud: false,
- kubernetes: true,
- credential_type_id: 17,
- },
- user_capabilities: {
- edit: true,
- delete: true,
- },
- },
-};
-
-describe('', () => {
- let wrapper;
- test('should render details properly', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
-
- expect(wrapper.find('Detail[label="Name"]').prop('value')).toEqual(
- instanceGroup.name
- );
- expect(wrapper.find('Detail[label="Type"]').prop('value')).toEqual(
- 'Container group'
- );
- expect(wrapper.find('Detail[label="Credential"]').text()).toContain(
- instanceGroup.summary_fields.credential.name
- );
- expect(wrapper.find('VariablesDetail').prop('value')).toEqual(
- instanceGroup.pod_spec_override
- );
- const dates = wrapper.find('UserDateDetail');
- expect(dates).toHaveLength(2);
- expect(dates.at(0).prop('date')).toEqual(instanceGroup.created);
- expect(dates.at(1).prop('date')).toEqual(instanceGroup.modified);
- });
-
- test('expected api call is made for delete', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/credential_types/42/details'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
- await act(async () => {
- wrapper.find('DeleteButton').invoke('onConfirm')();
- });
- expect(InstanceGroupsAPI.destroy).toHaveBeenCalledTimes(1);
- expect(history.location.pathname).toBe('/instance_groups');
- });
-
- test('should not render delete button', async () => {
- instanceGroup.summary_fields.user_capabilities.delete = false;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
-
- expect(wrapper.find('Button[aria-label="Delete"]').length).toBe(0);
- });
-
- test('should not render edit button', async () => {
- instanceGroup.summary_fields.user_capabilities.edit = false;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
-
- expect(wrapper.find('Button[aria-label="Edit"]').length).toBe(0);
- });
-});
diff --git a/awx/ui/src/screens/InstanceGroup/ContainerGroupDetails/index.js b/awx/ui/src/screens/InstanceGroup/ContainerGroupDetails/index.js
deleted file mode 100644
index b1b7e0d8c542..000000000000
--- a/awx/ui/src/screens/InstanceGroup/ContainerGroupDetails/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ContainerGroupDetails';
diff --git a/awx/ui/src/screens/InstanceGroup/ContainerGroupEdit/ContainerGroupEdit.js b/awx/ui/src/screens/InstanceGroup/ContainerGroupEdit/ContainerGroupEdit.js
deleted file mode 100644
index 795f7a33b3b4..000000000000
--- a/awx/ui/src/screens/InstanceGroup/ContainerGroupEdit/ContainerGroupEdit.js
+++ /dev/null
@@ -1,90 +0,0 @@
-import React, { useState, useEffect, useCallback } from 'react';
-import { useHistory } from 'react-router-dom';
-import { Card, PageSection } from '@patternfly/react-core';
-
-import { CardBody } from 'components/Card';
-import { InstanceGroupsAPI } from 'api';
-import useRequest from 'hooks/useRequest';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import ContainerGroupForm from '../shared/ContainerGroupForm';
-
-function ContainerGroupEdit({ instanceGroup }) {
- const history = useHistory();
- const [submitError, setSubmitError] = useState(null);
- const detailsIUrl = `/instance_groups/container_group/${instanceGroup.id}/details`;
-
- const {
- error: fetchError,
- isLoading,
- request: fetchInitialPodSpec,
- result: initialPodSpec,
- } = useRequest(
- useCallback(async () => {
- const { data } = await InstanceGroupsAPI.readOptions();
- return data.actions.POST.pod_spec_override.default;
- }, []),
- {
- initialPodSpec: {},
- }
- );
-
- useEffect(() => {
- fetchInitialPodSpec();
- }, [fetchInitialPodSpec]);
-
- const handleSubmit = async (values) => {
- try {
- await InstanceGroupsAPI.update(instanceGroup.id, {
- name: values.name,
- credential: values.credential ? values.credential.id : null,
- pod_spec_override: values.override ? values.pod_spec_override : null,
- max_forks: values.max_forks ? values.max_forks : 0,
- max_concurrent_jobs: values.max_concurrent_jobs
- ? values.max_concurrent_jobs
- : 0,
- is_container_group: true,
- });
- history.push(detailsIUrl);
- } catch (error) {
- setSubmitError(error);
- }
- };
- const handleCancel = () => {
- history.push(detailsIUrl);
- };
-
- if (fetchError) {
- return (
-
-
-
-
-
-
-
- );
- }
-
- if (isLoading) {
- return (
-
-
-
- );
- }
-
- return (
-
-
-
- );
-}
-
-export default ContainerGroupEdit;
diff --git a/awx/ui/src/screens/InstanceGroup/ContainerGroupEdit/ContainerGroupEdit.test.js b/awx/ui/src/screens/InstanceGroup/ContainerGroupEdit/ContainerGroupEdit.test.js
deleted file mode 100644
index ef91701c7783..000000000000
--- a/awx/ui/src/screens/InstanceGroup/ContainerGroupEdit/ContainerGroupEdit.test.js
+++ /dev/null
@@ -1,157 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-
-import { InstanceGroupsAPI, CredentialsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import ContainerGroupEdit from './ContainerGroupEdit';
-
-jest.mock('../../../api');
-
-const instanceGroup = {
- id: 123,
- type: 'instance_group',
- url: '/api/v2/instance_groups/123/',
- related: {
- named_url: '/api/v2/instance_groups/Foo/',
- jobs: '/api/v2/instance_groups/123/jobs/',
- instances: '/api/v2/instance_groups/123/instances/',
- credential: '/api/v2/credentials/71/',
- },
- name: 'Foo',
- created: '2020-09-02T17:20:01.214170Z',
- modified: '2020-09-02T17:20:01.214236Z',
- capacity: 0,
- committed_capacity: 0,
- consumed_capacity: 0,
- percent_capacity_remaining: 0.0,
- jobs_running: 0,
- jobs_total: 0,
- instances: 0,
- controller: null,
- is_container_group: true,
- credential: 71,
- policy_instance_percentage: 0,
- policy_instance_minimum: 0,
- policy_instance_list: [],
- max_concurrent_jobs: 0,
- max_forks: 0,
- pod_spec_override: '',
- summary_fields: {
- credential: {
- id: 71,
- name: 'CG',
- description: 'a',
- kind: 'kubernetes_bearer_token',
- cloud: false,
- kubernetes: true,
- credential_type_id: 17,
- },
- user_capabilities: {
- edit: true,
- delete: true,
- },
- },
-};
-
-const updatedInstanceGroup = {
- name: 'Bar',
- credential: { id: 12, name: 'CGX' },
-};
-
-const initialPodSpec = {
- default: {
- apiVersion: 'v1',
- kind: 'Pod',
- metadata: {
- namespace: 'default',
- },
- spec: {
- containers: [
- {
- image: 'ansible/ansible-runner',
- tty: true,
- stdin: true,
- imagePullPolicy: 'Always',
- args: ['sleep', 'infinity'],
- },
- ],
- },
- },
-};
-
-InstanceGroupsAPI.readOptions.mockResolvedValue({
- data: {
- results: initialPodSpec,
- },
-});
-
-CredentialsAPI.read.mockResolvedValue({
- data: {
- results: [
- {
- id: 71,
- name: 'Test',
- },
- ],
- },
-});
-
-describe('', () => {
- let wrapper;
- let history;
-
- beforeEach(async () => {
- history = createMemoryHistory({ initialEntries: ['/instance_groups'] });
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('initially renders successfully', async () => {
- expect(wrapper.find('ContainerGroupEdit').length).toBe(1);
- });
-
- test('called InstanceGroupsAPI.readOptions', async () => {
- expect(InstanceGroupsAPI.readOptions).toHaveBeenCalled();
- });
-
- test('handleCancel returns the user to container group detail', async () => {
- await act(async () => {
- wrapper.find('Button[aria-label="Cancel"]').simulate('click');
- });
- expect(history.location.pathname).toEqual(
- '/instance_groups/container_group/123/details'
- );
- });
-
- test('handleSubmit should call the api and redirect to details page', async () => {
- await act(async () => {
- wrapper.find('ContainerGroupForm').prop('onSubmit')({
- ...updatedInstanceGroup,
- override: false,
- });
- });
- wrapper.update();
- expect(InstanceGroupsAPI.update).toHaveBeenCalledWith(123, {
- ...updatedInstanceGroup,
- credential: 12,
- pod_spec_override: null,
- max_concurrent_jobs: 0,
- max_forks: 0,
- is_container_group: true,
- });
- expect(history.location.pathname).toEqual(
- '/instance_groups/container_group/123/details'
- );
- });
-});
diff --git a/awx/ui/src/screens/InstanceGroup/ContainerGroupEdit/index.js b/awx/ui/src/screens/InstanceGroup/ContainerGroupEdit/index.js
deleted file mode 100644
index cb97abe8abc0..000000000000
--- a/awx/ui/src/screens/InstanceGroup/ContainerGroupEdit/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ContainerGroupEdit';
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceDetails/InstanceDetails.js b/awx/ui/src/screens/InstanceGroup/InstanceDetails/InstanceDetails.js
deleted file mode 100644
index e0dd8a09faad..000000000000
--- a/awx/ui/src/screens/InstanceGroup/InstanceDetails/InstanceDetails.js
+++ /dev/null
@@ -1,349 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-
-import { useParams, useHistory } from 'react-router-dom';
-import { t, Plural } from '@lingui/macro';
-import {
- Button,
- Progress,
- ProgressMeasureLocation,
- ProgressSize,
- CodeBlock,
- CodeBlockCode,
- Tooltip,
- Slider,
-} from '@patternfly/react-core';
-import { CaretLeftIcon, OutlinedClockIcon } from '@patternfly/react-icons';
-import styled from 'styled-components';
-
-import { useConfig } from 'contexts/Config';
-import { InstancesAPI, InstanceGroupsAPI } from 'api';
-import useDebounce from 'hooks/useDebounce';
-import AlertModal from 'components/AlertModal';
-import ErrorDetail from 'components/ErrorDetail';
-import DisassociateButton from 'components/DisassociateButton';
-import InstanceToggle from 'components/InstanceToggle';
-import { CardBody, CardActionsRow } from 'components/Card';
-import getDocsBaseUrl from 'util/getDocsBaseUrl';
-import { formatDateString } from 'util/dates';
-import RoutedTabs from 'components/RoutedTabs';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import { Detail, DetailList } from 'components/DetailList';
-import HealthCheckAlert from 'components/HealthCheckAlert';
-import StatusLabel from 'components/StatusLabel';
-import useRequest, {
- useDeleteItems,
- useDismissableError,
-} from 'hooks/useRequest';
-
-const Unavailable = styled.span`
- color: var(--pf-global--danger-color--200);
-`;
-
-const SliderHolder = styled.div`
- display: flex;
- align-items: center;
- justify-content: space-between;
-`;
-
-const SliderForks = styled.div`
- flex-grow: 1;
- margin-right: 8px;
- margin-left: 8px;
- text-align: center;
-`;
-
-function computeForks(memCapacity, cpuCapacity, selectedCapacityAdjustment) {
- const minCapacity = Math.min(memCapacity, cpuCapacity);
- const maxCapacity = Math.max(memCapacity, cpuCapacity);
-
- return Math.floor(
- minCapacity + (maxCapacity - minCapacity) * selectedCapacityAdjustment
- );
-}
-
-function InstanceDetails({ setBreadcrumb, instanceGroup }) {
- const config = useConfig();
- const { id, instanceId } = useParams();
- const history = useHistory();
-
- const [healthCheck, setHealthCheck] = useState({});
- const [showHealthCheckAlert, setShowHealthCheckAlert] = useState(false);
- const [forks, setForks] = useState();
-
- const {
- isLoading,
- error: contentError,
- request: fetchDetails,
- result: { instance },
- } = useRequest(
- useCallback(async () => {
- const {
- data: { results },
- } = await InstanceGroupsAPI.readInstances(instanceGroup.id);
- const isAssociated = results.some(
- ({ id: instId }) => instId === parseInt(instanceId, 10)
- );
-
- if (isAssociated) {
- const { data: details } = await InstancesAPI.readDetail(instanceId);
- if (details.node_type === 'execution') {
- const { data: healthCheckData } =
- await InstancesAPI.readHealthCheckDetail(instanceId);
- setHealthCheck(healthCheckData);
- }
- setBreadcrumb(instanceGroup, details);
- setForks(
- computeForks(
- details.mem_capacity,
- details.cpu_capacity,
- details.capacity_adjustment
- )
- );
- return { instance: details };
- }
- throw new Error(
- `This instance is not associated with this instance group`
- );
- }, [instanceId, setBreadcrumb, instanceGroup]),
- { instance: {}, isLoading: true }
- );
- useEffect(() => {
- fetchDetails();
- }, [fetchDetails]);
- const { error: healthCheckError, request: fetchHealthCheck } = useRequest(
- useCallback(async () => {
- const { status } = await InstancesAPI.healthCheck(instanceId);
- if (status === 200) {
- setShowHealthCheckAlert(true);
- }
- }, [instanceId])
- );
-
- const {
- deleteItems: disassociateInstance,
- deletionError: disassociateError,
- } = useDeleteItems(
- useCallback(async () => {
- await InstanceGroupsAPI.disassociateInstance(
- instanceGroup.id,
- instance.id
- );
- history.push(`/instance_groups/${instanceGroup.id}/instances`);
- }, [instanceGroup.id, instance.id, history])
- );
-
- const { error: updateInstanceError, request: updateInstance } = useRequest(
- useCallback(
- async (values) => {
- await InstancesAPI.update(instance.id, values);
- },
- [instance]
- )
- );
- const debounceUpdateInstance = useDebounce(updateInstance, 200);
-
- const handleChangeValue = (value) => {
- const roundedValue = Math.round(value * 100) / 100;
- setForks(
- computeForks(instance.mem_capacity, instance.cpu_capacity, roundedValue)
- );
- debounceUpdateInstance({ capacity_adjustment: roundedValue });
- };
-
- const formatHealthCheckTimeStamp = (last) => (
- <>
- {formatDateString(last)}
- {instance.health_check_pending ? (
- <>
- {' '}
-
- >
- ) : null}
- >
- );
-
- const { error, dismissError } = useDismissableError(
- disassociateError || updateInstanceError || healthCheckError
- );
-
- const tabsArray = [
- {
- name: (
- <>
-
- {t`Back to Instances`}
- >
- ),
- link: `/instance_groups/${id}/instances`,
- id: 99,
- },
- {
- name: t`Details`,
- link: `/instance_groups/${id}/instances/${instanceId}/details`,
- id: 0,
- },
- ];
- if (contentError) {
- return ;
- }
- if (isLoading) {
- return ;
- }
-
- const isExecutionNode = instance.node_type === 'execution';
-
- return (
- <>
-
- {showHealthCheckAlert ? (
-
- ) : null}
-
-
-
-
- ) : null
- }
- />
-
-
-
-
- {t`Health checks are asynchronous tasks. See the`}{' '}
-
- {t`documentation`}
- {' '}
- {t`for more info.`}
- >
- }
- value={formatHealthCheckTimeStamp(instance.last_health_check)}
- />
-
-
- {t`CPU ${instance.cpu_capacity}`}
-
-
-
-
- {t`RAM ${instance.mem_capacity}`}
-
- }
- />
-
- ) : (
- {t`Unavailable`}
- )
- }
- />
- {healthCheck?.errors && (
-
- {healthCheck?.errors}
-
- }
- />
- )}
-
-
- {isExecutionNode && (
-
-
-
- )}
- {config?.me?.is_superuser && instance.node_type !== 'control' && (
-
- )}
-
-
-
- {error && (
-
- {updateInstanceError
- ? t`Failed to update capacity adjustment.`
- : t`Failed to disassociate one or more instances.`}
-
-
- )}
-
- >
- );
-}
-
-export default InstanceDetails;
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceDetails/InstanceDetails.test.js b/awx/ui/src/screens/InstanceGroup/InstanceDetails/InstanceDetails.test.js
deleted file mode 100644
index 68b73a7a98a5..000000000000
--- a/awx/ui/src/screens/InstanceGroup/InstanceDetails/InstanceDetails.test.js
+++ /dev/null
@@ -1,504 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import * as ConfigContext from 'contexts/Config';
-import useDebounce from 'hooks/useDebounce';
-import { InstancesAPI, InstanceGroupsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import InstanceDetails from './InstanceDetails';
-
-jest.mock('../../../api');
-jest.mock('../../../hooks/useDebounce');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useParams: () => ({
- id: 2,
- instanceId: 1,
- }),
-}));
-
-const instanceGroup = {
- id: 2,
- type: 'instance_group',
- url: '/api/v2/instance_groups/2/',
- related: {
- named_url: '/api/v2/instance_groups/default/',
- jobs: '/api/v2/instance_groups/2/jobs/',
- instances: '/api/v2/instance_groups/2/instances/',
- },
- name: 'default',
- created: '2021-09-08T17:10:39.947029Z',
- modified: '2021-09-08T17:10:39.959187Z',
- capacity: 38,
- committed_capacity: 0,
- consumed_capacity: 0,
- percent_capacity_remaining: 100.0,
- jobs_running: 0,
- jobs_total: 0,
- instances: 3,
- is_container_group: false,
- credential: null,
- policy_instance_percentage: 100,
- policy_instance_minimum: 0,
- max_concurrent_jobs: 0,
- max_forks: 0,
- policy_instance_list: ['receptor-1', 'receptor-2'],
- pod_spec_override: '',
- summary_fields: {
- user_capabilities: {
- edit: true,
- delete: true,
- },
- },
-};
-describe('', () => {
- let wrapper;
- beforeEach(() => {
- useDebounce.mockImplementation((fn) => fn);
-
- InstancesAPI.readDetail.mockResolvedValue({
- data: {
- id: 1,
- type: 'instance',
- url: '/api/v2/instances/1/',
- related: {
- named_url: '/api/v2/instances/awx_1/',
- jobs: '/api/v2/instances/1/jobs/',
- instance_groups: '/api/v2/instances/1/instance_groups/',
- health_check: '/api/v2/instances/1/health_check/',
- },
- uuid: '00000000-0000-0000-0000-000000000000',
- hostname: 'awx_1',
- created: '2021-09-08T17:10:34.484569Z',
- modified: '2021-09-09T13:55:44.219900Z',
- last_seen: '2021-09-09T20:20:31.623148Z',
- last_health_check: '2021-09-09T20:20:31.623148Z',
- errors: '',
- capacity_adjustment: '1.00',
- version: '19.1.0',
- capacity: 38,
- consumed_capacity: 0,
- percent_capacity_remaining: 100.0,
- jobs_running: 0,
- jobs_total: 0,
- cpu: 8,
- memory: 6232231936,
- cpu_capacity: 32,
- mem_capacity: 38,
- enabled: true,
- managed_by_policy: true,
- node_type: 'execution',
- node_state: 'ready',
- health_check_pending: false,
- },
- });
- InstancesAPI.readHealthCheckDetail.mockResolvedValue({
- data: {
- uuid: '00000000-0000-0000-0000-000000000000',
- hostname: 'awx_1',
- version: '19.1.0',
- last_health_check: '2021-09-10T16:16:19.729676Z',
- errors: '',
- cpu: 8,
- memory: 6232231936,
- cpu_capacity: 32,
- mem_capacity: 38,
- capacity: 38,
- },
- });
- });
- afterEach(() => {
- jest.clearAllMocks();
- wrapper.unmount();
- });
- test('Should render proper data', async () => {
- InstanceGroupsAPI.readInstances.mockResolvedValue({
- data: {
- results: [
- {
- id: 1,
- },
- {
- id: 2,
- },
- ],
- },
- });
- jest.spyOn(ConfigContext, 'useConfig').mockImplementation(() => ({
- me: { is_superuser: true },
- }));
- await act(async () => {
- wrapper = mountWithContexts(
- {}}
- />
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('InstanceDetails')).toHaveLength(1);
-
- expect(InstanceGroupsAPI.readInstances).toBeCalledWith(2);
- expect(InstancesAPI.readHealthCheckDetail).toBeCalledWith(1);
- expect(InstancesAPI.readDetail).toBeCalledWith(1);
- expect(
- wrapper.find("Button[ouiaId='disassociate-button']").prop('isDisabled')
- ).toBe(false);
- expect(
- wrapper.find("Button[ouiaId='health-check-button']").prop('isDisabled')
- ).toBe(false);
- });
-
- test('should calculate number of forks when slide changes', async () => {
- InstanceGroupsAPI.readInstances.mockResolvedValue({
- data: {
- results: [
- {
- id: 1,
- },
- {
- id: 2,
- },
- ],
- },
- });
- jest.spyOn(ConfigContext, 'useConfig').mockImplementation(() => ({
- me: { is_superuser: true },
- }));
- await act(async () => {
- wrapper = mountWithContexts(
- {}}
- />
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
-
- expect(wrapper.find('InstanceDetails').length).toBe(1);
- expect(wrapper.find('div[data-cy="number-forks"]').text()).toContain(
- '38 forks'
- );
-
- await act(async () => {
- wrapper.find('Slider').prop('onChange')(4);
- });
-
- wrapper.update();
-
- expect(wrapper.find('div[data-cy="number-forks"]').text()).toContain(
- '56 forks'
- );
-
- await act(async () => {
- wrapper.find('Slider').prop('onChange')(0);
- });
- wrapper.update();
- expect(wrapper.find('div[data-cy="number-forks"]').text()).toContain(
- '32 forks'
- );
-
- await act(async () => {
- wrapper.find('Slider').prop('onChange')(0.5);
- });
- wrapper.update();
- expect(wrapper.find('div[data-cy="number-forks"]').text()).toContain(
- '35 forks'
- );
- });
-
- test('buttons should be disabled', async () => {
- InstanceGroupsAPI.readInstances.mockResolvedValue({
- data: {
- results: [
- {
- id: 1,
- },
- {
- id: 2,
- },
- ],
- },
- });
- jest.spyOn(ConfigContext, 'useConfig').mockImplementation(() => ({
- me: { is_system_auditor: true },
- }));
- await act(async () => {
- wrapper = mountWithContexts(
- {}}
- />
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find("Button[ouiaId='disassociate-button']")).toHaveLength(
- 0
- );
- expect(
- wrapper.find("Button[ouiaId='health-check-button']").prop('isDisabled')
- ).toBe(true);
- });
-
- test('should display instance toggle', async () => {
- InstanceGroupsAPI.readInstances.mockResolvedValue({
- data: {
- results: [
- {
- id: 1,
- },
- {
- id: 2,
- },
- ],
- },
- });
- jest.spyOn(ConfigContext, 'useConfig').mockImplementation(() => ({
- me: { is_system_auditor: true },
- }));
- await act(async () => {
- wrapper = mountWithContexts(
- {}}
- />
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('InstanceToggle').length).toBe(1);
- });
-
- test('should throw error because intance is not associated with instance group', async () => {
- InstanceGroupsAPI.readInstances.mockResolvedValue({
- data: {
- results: [
- {
- id: 3,
- },
- {
- id: 3,
- },
- ],
- },
- });
- jest.spyOn(ConfigContext, 'useConfig').mockImplementation(() => ({
- me: { is_superuser: true },
- }));
- await act(async () => {
- wrapper = mountWithContexts(
- {}}
- />
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('ContentError')).toHaveLength(1);
- expect(InstanceGroupsAPI.readInstances).toBeCalledWith(2);
- expect(InstancesAPI.readHealthCheckDetail).not.toBeCalled();
- expect(InstancesAPI.readDetail).not.toBeCalled();
- });
-
- test('Should handle api error for health check', async () => {
- InstancesAPI.healthCheck.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'post',
- url: '/api/v2/instances/1/health_check',
- },
- data: 'An error occurred',
- status: 403,
- },
- })
- );
- InstanceGroupsAPI.readInstances.mockResolvedValue({
- data: {
- results: [
- {
- id: 1,
- },
- {
- id: 2,
- },
- ],
- },
- });
- jest.spyOn(ConfigContext, 'useConfig').mockImplementation(() => ({
- me: { is_superuser: true },
- }));
- await act(async () => {
- wrapper = mountWithContexts(
- {}}
- />
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(
- wrapper.find("Button[ouiaId='health-check-button']").prop('isDisabled')
- ).toBe(false);
- await act(async () => {
- wrapper.find("Button[ouiaId='health-check-button']").prop('onClick')();
- });
- expect(InstancesAPI.healthCheck).toBeCalledWith(1);
- wrapper.update();
- expect(wrapper.find('AlertModal')).toHaveLength(1);
- expect(wrapper.find('ErrorDetail')).toHaveLength(1);
- });
-
- test.each([
- [1, 'hybrid', 0],
- [2, 'hop', 0],
- [3, 'control', 0],
- ])(
- 'hide health check button for non-execution type nodes',
- async (a, b, expected) => {
- InstancesAPI.readDetail.mockResolvedValue({
- data: {
- id: a,
- type: 'instance',
- url: '/api/v2/instances/1/',
- related: {
- named_url: '/api/v2/instances/awx_1/',
- jobs: '/api/v2/instances/1/jobs/',
- instance_groups: '/api/v2/instances/1/instance_groups/',
- health_check: '/api/v2/instances/1/health_check/',
- },
- uuid: '00000000-0000-0000-0000-000000000000',
- hostname: 'awx_1',
- created: '2021-09-08T17:10:34.484569Z',
- modified: '2021-09-09T13:55:44.219900Z',
- last_seen: '2021-09-09T20:20:31.623148Z',
- last_health_check: '2021-09-09T20:20:31.623148Z',
- errors: '',
- capacity_adjustment: '1.00',
- version: '19.1.0',
- capacity: 38,
- consumed_capacity: 0,
- percent_capacity_remaining: 100.0,
- jobs_running: 0,
- jobs_total: 0,
- cpu: 8,
- memory: 6232231936,
- cpu_capacity: 32,
- mem_capacity: 38,
- enabled: true,
- managed_by_policy: true,
- node_type: b,
- node_state: 'ready',
- health_check_pending: false,
- },
- });
- jest.spyOn(ConfigContext, 'useConfig').mockImplementation(() => ({
- me: { is_superuser: true },
- }));
- await act(async () => {
- wrapper = mountWithContexts(
- {}}
- />
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find("Button[ouiaId='health-check-button']")).toHaveLength(
- expected
- );
- }
- );
-
- test('Should call disassociate', async () => {
- InstanceGroupsAPI.readInstances.mockResolvedValue({
- data: {
- results: [
- {
- id: 1,
- },
- {
- id: 2,
- },
- ],
- },
- });
- jest.spyOn(ConfigContext, 'useConfig').mockImplementation(() => ({
- me: { is_superuser: true },
- }));
- await act(async () => {
- wrapper = mountWithContexts(
- {}}
- />
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- await act(async () =>
- wrapper.find('Button[ouiaId="disassociate-button"]').prop('onClick')()
- );
- wrapper.update();
- await act(async () =>
- wrapper
- .find('Button[ouiaId="disassociate-modal-confirm"]')
- .prop('onClick')()
- );
- wrapper.update();
-
- expect(InstanceGroupsAPI.disassociateInstance).toHaveBeenCalledWith(2, 1);
- });
-
- test('Should throw disassociate error', async () => {
- InstanceGroupsAPI.disassociateInstance.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'post',
- url: '/api/v2/instance_groups',
- },
- data: 'An error occurred',
- status: 403,
- },
- })
- );
- InstanceGroupsAPI.readInstances.mockResolvedValue({
- data: {
- results: [
- {
- id: 1,
- },
- {
- id: 2,
- },
- ],
- },
- });
- jest.spyOn(ConfigContext, 'useConfig').mockImplementation(() => ({
- me: { is_superuser: true },
- }));
- await act(async () => {
- wrapper = mountWithContexts(
- {}}
- />
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
-
- await act(async () =>
- wrapper.find('Button[ouiaId="disassociate-button"]').prop('onClick')()
- );
- wrapper.update();
- await act(async () =>
- wrapper
- .find('Button[ouiaId="disassociate-modal-confirm"]')
- .prop('onClick')()
- );
- wrapper.update();
- expect(wrapper.find('AlertModal')).toHaveLength(1);
- expect(wrapper.find('ErrorDetail')).toHaveLength(1);
- });
-});
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceDetails/index.js b/awx/ui/src/screens/InstanceGroup/InstanceDetails/index.js
deleted file mode 100644
index 930572ad8cd7..000000000000
--- a/awx/ui/src/screens/InstanceGroup/InstanceDetails/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InstanceDetails';
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceGroup.js b/awx/ui/src/screens/InstanceGroup/InstanceGroup.js
deleted file mode 100644
index b4d0d404c5c6..000000000000
--- a/awx/ui/src/screens/InstanceGroup/InstanceGroup.js
+++ /dev/null
@@ -1,150 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-import {
- Link,
- Redirect,
- Route,
- Switch,
- useLocation,
- useParams,
-} from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { CaretLeftIcon } from '@patternfly/react-icons';
-import { Card, PageSection } from '@patternfly/react-core';
-
-import useRequest from 'hooks/useRequest';
-import { InstanceGroupsAPI } from 'api';
-import RoutedTabs from 'components/RoutedTabs';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import JobList from 'components/JobList';
-
-import InstanceGroupDetails from './InstanceGroupDetails';
-import InstanceGroupEdit from './InstanceGroupEdit';
-import Instances from './Instances/Instances';
-
-function InstanceGroup({ setBreadcrumb }) {
- const { id } = useParams();
- const { pathname } = useLocation();
-
- const {
- isLoading,
- error: contentError,
- request: fetchInstanceGroups,
- result: { instanceGroup },
- } = useRequest(
- useCallback(async () => {
- const { data } = await InstanceGroupsAPI.readDetail(id);
-
- return {
- instanceGroup: data,
- };
- }, [id]),
- { instanceGroup: null }
- );
-
- useEffect(() => {
- fetchInstanceGroups();
- }, [fetchInstanceGroups, pathname]);
-
- useEffect(() => {
- if (instanceGroup) {
- setBreadcrumb(instanceGroup);
- }
- }, [instanceGroup, setBreadcrumb]);
-
- const tabsArray = [
- {
- name: (
- <>
-
- {t`Back to Instance Groups`}
- >
- ),
- link: '/instance_groups',
- id: 99,
- isBackButton: true,
- },
- {
- name: t`Details`,
- link: `/instance_groups/${id}/details`,
- id: 0,
- },
- {
- name: t`Instances`,
- link: `/instance_groups/${id}/instances`,
- id: 1,
- },
- {
- name: t`Jobs`,
- link: `/instance_groups/${id}/jobs`,
- id: 2,
- },
- ];
-
- if (!isLoading && contentError) {
- return (
-
-
-
- {contentError.response?.status === 404 && (
-
- {t`Instance group not found.`}
-
- {t`View all instance groups`}
-
- )}
-
-
-
- );
- }
-
- let cardHeader = ;
-
- if (['edit', 'instances/'].some((name) => pathname.includes(name))) {
- cardHeader = null;
- }
-
- return (
-
-
- {cardHeader}
- {isLoading && }
- {!isLoading && instanceGroup && (
-
-
- {instanceGroup && (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
- >
- )}
-
- )}
-
-
- );
-}
-
-export default InstanceGroup;
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceGroup.test.js b/awx/ui/src/screens/InstanceGroup/InstanceGroup.test.js
deleted file mode 100644
index 7c725a2ae7ba..000000000000
--- a/awx/ui/src/screens/InstanceGroup/InstanceGroup.test.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-
-import { InstanceGroupsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../testUtils/enzymeHelpers';
-
-import InstanceGroup from './InstanceGroup';
-
-jest.mock('../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useRouteMatch: () => ({
- url: '/instance_groups',
- }),
- useParams: () => ({ id: 42 }),
-}));
-
-describe('', () => {
- let wrapper;
- test('should render details properly', async () => {
- await act(async () => {
- wrapper = mountWithContexts( {}} />);
- });
- wrapper.update();
- expect(wrapper.find('InstanceGroup').length).toBe(1);
- expect(InstanceGroupsAPI.readDetail).toBeCalledWith(42);
- });
-
- test('should render expected tabs', async () => {
- const expectedTabs = [
- 'Back to Instance Groups',
- 'Details',
- 'Instances',
- 'Jobs',
- ];
- await act(async () => {
- wrapper = mountWithContexts( {}} />);
- });
- wrapper.find('RoutedTabs li').forEach((tab, index) => {
- expect(tab.text()).toEqual(expectedTabs[index]);
- });
- });
-
- test('should show content error when user attempts to navigate to erroneous route', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/instance_groups/42/foobar'],
- });
- await act(async () => {
- wrapper = mountWithContexts( {}} />, {
- context: {
- router: {
- history,
- },
- },
- });
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-});
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceGroupAdd/InstanceGroupAdd.js b/awx/ui/src/screens/InstanceGroup/InstanceGroupAdd/InstanceGroupAdd.js
deleted file mode 100644
index ae582ab2f9c1..000000000000
--- a/awx/ui/src/screens/InstanceGroup/InstanceGroupAdd/InstanceGroupAdd.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import React, { useState } from 'react';
-import { Card, PageSection } from '@patternfly/react-core';
-import { useHistory } from 'react-router-dom';
-
-import { CardBody } from 'components/Card';
-import { InstanceGroupsAPI } from 'api';
-import InstanceGroupForm from '../shared/InstanceGroupForm';
-
-function InstanceGroupAdd() {
- const history = useHistory();
- const [submitError, setSubmitError] = useState(null);
-
- const handleSubmit = async (values) => {
- try {
- const { data: response } = await InstanceGroupsAPI.create(values);
- history.push(`/instance_groups/${response.id}/details`);
- } catch (error) {
- setSubmitError(error);
- }
- };
-
- const handleCancel = () => {
- history.push(`/instance_groups`);
- };
-
- return (
-
-
-
-
-
-
-
- );
-}
-
-export default InstanceGroupAdd;
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceGroupAdd/InstanceGroupAdd.test.js b/awx/ui/src/screens/InstanceGroup/InstanceGroupAdd/InstanceGroupAdd.test.js
deleted file mode 100644
index 8372e6b2f600..000000000000
--- a/awx/ui/src/screens/InstanceGroup/InstanceGroupAdd/InstanceGroupAdd.test.js
+++ /dev/null
@@ -1,97 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-
-import { InstanceGroupsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import InstanceGroupAdd from './InstanceGroupAdd';
-
-jest.mock('../../../api');
-
-const instanceGroupData = {
- id: 42,
- type: 'instance_group',
- url: '/api/v2/instance_groups/42/',
- related: {
- jobs: '/api/v2/instance_groups/42/jobs/',
- instances: '/api/v2/instance_groups/7/instances/',
- },
- name: 'Bar',
- created: '2020-07-21T18:41:02.818081Z',
- modified: '2020-07-24T20:32:03.121079Z',
- capacity: 24,
- committed_capacity: 0,
- consumed_capacity: 0,
- percent_capacity_remaining: 100.0,
- jobs_running: 0,
- jobs_total: 0,
- instances: 1,
- controller: null,
- is_container_group: false,
- credential: null,
- policy_instance_percentage: 46,
- policy_instance_minimum: 12,
- policy_instance_list: [],
- pod_spec_override: '',
- summary_fields: {
- user_capabilities: {
- edit: true,
- delete: true,
- },
- },
-};
-
-describe('', () => {
- let wrapper;
- let history;
-
- beforeEach(async () => {
- history = createMemoryHistory({
- initialEntries: ['/instance_groups'],
- });
- InstanceGroupsAPI.create.mockResolvedValue({
- data: {
- id: 42,
- },
- });
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('handleSubmit should call the api and redirect to details page', async () => {
- await act(async () => {
- wrapper.find('InstanceGroupForm').prop('onSubmit')(instanceGroupData);
- });
- wrapper.update();
- expect(InstanceGroupsAPI.create).toHaveBeenCalledWith(instanceGroupData);
- expect(history.location.pathname).toBe('/instance_groups/42/details');
- });
-
- test('handleCancel should return the user back to the instance group list', async () => {
- wrapper.find('Button[aria-label="Cancel"]').simulate('click');
- expect(history.location.pathname).toEqual('/instance_groups');
- });
-
- test('failed form submission should show an error message', async () => {
- const error = {
- response: {
- data: { detail: 'An error occurred' },
- },
- };
- InstanceGroupsAPI.create.mockImplementationOnce(() =>
- Promise.reject(error)
- );
- await act(async () => {
- wrapper.find('InstanceGroupForm').invoke('onSubmit')(instanceGroupData);
- });
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceGroupAdd/index.js b/awx/ui/src/screens/InstanceGroup/InstanceGroupAdd/index.js
deleted file mode 100644
index b60610120ddf..000000000000
--- a/awx/ui/src/screens/InstanceGroup/InstanceGroupAdd/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InstanceGroupAdd';
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js b/awx/ui/src/screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js
deleted file mode 100644
index 13cc8c40ba35..000000000000
--- a/awx/ui/src/screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.js
+++ /dev/null
@@ -1,159 +0,0 @@
-import React, { useCallback } from 'react';
-
-import { t } from '@lingui/macro';
-import { Link, useHistory } from 'react-router-dom';
-import styled from 'styled-components';
-import { Button } from '@patternfly/react-core';
-
-import AlertModal from 'components/AlertModal';
-import { CardBody, CardActionsRow } from 'components/Card';
-import ErrorDetail from 'components/ErrorDetail';
-import DeleteButton from 'components/DeleteButton';
-import {
- Detail,
- DetailList,
- UserDateDetail,
- DetailBadge,
-} from 'components/DetailList';
-import useRequest, { useDismissableError } from 'hooks/useRequest';
-import { InstanceGroupsAPI } from 'api';
-import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
-
-const Unavailable = styled.span`
- color: var(--pf-global--danger-color--200);
-`;
-
-function InstanceGroupDetails({ instanceGroup }) {
- const { id, name } = instanceGroup;
-
- const history = useHistory();
-
- const {
- request: deleteInstanceGroup,
- isLoading,
- error: deleteError,
- } = useRequest(
- useCallback(async () => {
- await InstanceGroupsAPI.destroy(id);
- history.push(`/instance_groups`);
- }, [id, history])
- );
-
- const { error, dismissError } = useDismissableError(deleteError);
- const deleteDetailsRequests =
- relatedResourceDeleteRequests.instanceGroup(instanceGroup);
- return (
-
-
-
-
-
-
-
-
- {instanceGroup.capacity ? (
-
- ) : (
- {t`Unavailable`}}
- dataCy="instance-group-used-capacity"
- />
- )}
-
-
-
-
-
-
- {instanceGroup.summary_fields.user_capabilities &&
- instanceGroup.summary_fields.user_capabilities.edit && (
-
- )}
- {instanceGroup.summary_fields.user_capabilities &&
- instanceGroup.summary_fields.user_capabilities.delete && (
-
- {t`Delete`}
-
- )}
-
- {error && (
-
-
-
- )}
-
- );
-}
-
-export default InstanceGroupDetails;
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.test.js b/awx/ui/src/screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.test.js
deleted file mode 100644
index 78a88ad2007a..000000000000
--- a/awx/ui/src/screens/InstanceGroup/InstanceGroupDetails/InstanceGroupDetails.test.js
+++ /dev/null
@@ -1,154 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-
-import { InstanceGroupsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import InstanceGroupDetails from './InstanceGroupDetails';
-
-jest.mock('../../../api');
-
-const instanceGroups = [
- {
- id: 1,
- name: 'Foo',
- type: 'instance_group',
- url: '/api/v2/instance_groups/1/',
- capacity: 10,
- policy_instance_minimum: 10,
- policy_instance_percentage: 50,
- percent_capacity_remaining: 60,
- max_concurrent_jobs: 0,
- max_forks: 0,
- is_container_group: false,
- created: '2020-07-21T18:41:02.818081Z',
- modified: '2020-07-24T20:32:03.121079Z',
- summary_fields: {
- user_capabilities: {
- edit: true,
- delete: true,
- },
- },
- },
- {
- id: 2,
- name: 'Bar',
- type: 'instance_group',
- url: '/api/v2/instance_groups/2/',
- capacity: 0,
- policy_instance_minimum: 0,
- policy_instance_percentage: 0,
- percent_capacity_remaining: 0,
- max_concurrent_jobs: 0,
- max_forks: 0,
- is_container_group: true,
- created: '2020-07-21T18:41:02.818081Z',
- modified: '2020-07-24T20:32:03.121079Z',
- summary_fields: {
- user_capabilities: {
- edit: false,
- delete: false,
- },
- },
- },
-];
-
-function expectDetailToMatch(wrapper, label, value) {
- const detail = wrapper.find(`Detail[label="${label}"]`);
- expect(detail).toHaveLength(1);
- expect(detail.prop('value')).toEqual(value);
-}
-
-describe('', () => {
- let wrapper;
- test('should render details properly', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
-
- wrapper.update();
-
- expect(wrapper.find('Detail[label="Name"]').text()).toEqual(
- expect.stringContaining(instanceGroups[0].name)
- );
- expect(wrapper.find('Detail[label="Name"]')).toHaveLength(1);
- expectDetailToMatch(wrapper, 'Type', `Instance group`);
- const dates = wrapper.find('UserDateDetail');
- expect(dates).toHaveLength(2);
- expect(dates.at(0).prop('date')).toEqual(instanceGroups[0].created);
- expect(dates.at(1).prop('date')).toEqual(instanceGroups[0].modified);
-
- expect(
- wrapper.find('DetailBadge[label="Used capacity"]').prop('content')
- ).toBe(`${100 - instanceGroups[0].percent_capacity_remaining} %`);
-
- expect(
- wrapper
- .find('DetailBadge[label="Policy instance minimum"]')
- .prop('content')
- ).toBe(instanceGroups[0].policy_instance_minimum);
-
- expect(
- wrapper
- .find('DetailBadge[label="Policy instance percentage"]')
- .prop('content')
- ).toBe(`${instanceGroups[0].policy_instance_percentage} %`);
- });
-
- test('expected api call is made for delete', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/instance_groups/1/details'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
- await act(async () => {
- wrapper.find('DeleteButton').invoke('onConfirm')();
- });
- expect(InstanceGroupsAPI.destroy).toHaveBeenCalledTimes(1);
- expect(history.location.pathname).toBe('/instance_groups');
- });
-
- test('should not render delete button for tower instance group', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
-
- expect(wrapper.find('Button[aria-label="Delete"]').length).toBe(0);
- });
-
- test('should not render delete button', async () => {
- instanceGroups[0].summary_fields.user_capabilities.delete = false;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
-
- expect(wrapper.find('Button[aria-label="Delete"]').length).toBe(0);
- });
-
- test('should not render edit button', async () => {
- instanceGroups[0].summary_fields.user_capabilities.edit = false;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
-
- expect(wrapper.find('Button[aria-label="Edit"]').length).toBe(0);
- });
-});
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceGroupDetails/index.js b/awx/ui/src/screens/InstanceGroup/InstanceGroupDetails/index.js
deleted file mode 100644
index d92e89e96105..000000000000
--- a/awx/ui/src/screens/InstanceGroup/InstanceGroupDetails/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InstanceGroupDetails';
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceGroupEdit/InstanceGroupEdit.js b/awx/ui/src/screens/InstanceGroup/InstanceGroupEdit/InstanceGroupEdit.js
deleted file mode 100644
index 298e1c06155c..000000000000
--- a/awx/ui/src/screens/InstanceGroup/InstanceGroupEdit/InstanceGroupEdit.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import React, { useState } from 'react';
-import { useHistory } from 'react-router-dom';
-
-import { CardBody } from 'components/Card';
-import { InstanceGroupsAPI } from 'api';
-import InstanceGroupForm from '../shared/InstanceGroupForm';
-
-function InstanceGroupEdit({ instanceGroup }) {
- const history = useHistory();
- const [submitError, setSubmitError] = useState(null);
- const detailsUrl = `/instance_groups/${instanceGroup.id}/details`;
-
- const handleSubmit = async (values) => {
- try {
- await InstanceGroupsAPI.update(instanceGroup.id, values);
- history.push(detailsUrl);
- } catch (error) {
- setSubmitError(error);
- }
- };
-
- const handleCancel = () => {
- history.push(detailsUrl);
- };
-
- return (
-
-
-
- );
-}
-
-export default InstanceGroupEdit;
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceGroupEdit/InstanceGroupEdit.test.js b/awx/ui/src/screens/InstanceGroup/InstanceGroupEdit/InstanceGroupEdit.test.js
deleted file mode 100644
index 7e85d9bfbc9b..000000000000
--- a/awx/ui/src/screens/InstanceGroup/InstanceGroupEdit/InstanceGroupEdit.test.js
+++ /dev/null
@@ -1,117 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-
-import { InstanceGroupsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import InstanceGroupEdit from './InstanceGroupEdit';
-
-jest.mock('../../../api');
-
-const instanceGroupData = {
- id: 42,
- type: 'instance_group',
- url: '/api/v2/instance_groups/42/',
- related: {
- jobs: '/api/v2/instance_groups/42/jobs/',
- instances: '/api/v2/instance_groups/7/instances/',
- },
- name: 'Foo',
- created: '2020-07-21T18:41:02.818081Z',
- modified: '2020-07-24T20:32:03.121079Z',
- capacity: 24,
- committed_capacity: 0,
- consumed_capacity: 0,
- percent_capacity_remaining: 100.0,
- jobs_running: 0,
- jobs_total: 0,
- instances: 1,
- controller: null,
- is_container_group: false,
- credential: null,
- policy_instance_percentage: 46,
- policy_instance_minimum: 12,
- policy_instance_list: [],
- pod_spec_override: '',
- summary_fields: {
- user_capabilities: {
- edit: true,
- delete: true,
- },
- },
-};
-
-const updatedInstanceGroup = {
- name: 'Bar',
- policy_instance_percentage: 42,
-};
-
-describe('', () => {
- let wrapper;
- let history;
-
- beforeAll(async () => {
- history = createMemoryHistory();
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('handleSubmit should call the api and redirect to details page', async () => {
- await act(async () => {
- wrapper.find('InstanceGroupForm').invoke('onSubmit')(
- updatedInstanceGroup
- );
- });
- expect(InstanceGroupsAPI.update).toHaveBeenCalledWith(
- 42,
- updatedInstanceGroup
- );
- });
-
- test('should navigate to instance group details when cancel is clicked', async () => {
- await act(async () => {
- wrapper.find('button[aria-label="Cancel"]').prop('onClick')();
- });
- expect(history.location.pathname).toEqual('/instance_groups/42/details');
- });
-
- test('should navigate to instance group details after successful submission', async () => {
- await act(async () => {
- wrapper.find('InstanceGroupForm').invoke('onSubmit')(
- updatedInstanceGroup
- );
- });
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(0);
- expect(history.location.pathname).toEqual('/instance_groups/42/details');
- });
-
- test('failed form submission should show an error message', async () => {
- const error = {
- response: {
- data: { detail: 'An error occurred' },
- },
- };
- InstanceGroupsAPI.update.mockImplementationOnce(() =>
- Promise.reject(error)
- );
- await act(async () => {
- wrapper.find('InstanceGroupForm').invoke('onSubmit')(
- updatedInstanceGroup
- );
- });
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceGroupEdit/index.js b/awx/ui/src/screens/InstanceGroup/InstanceGroupEdit/index.js
deleted file mode 100644
index 324bdabb31f1..000000000000
--- a/awx/ui/src/screens/InstanceGroup/InstanceGroupEdit/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InstanceGroupEdit';
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js b/awx/ui/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js
deleted file mode 100644
index 7868be6c14e8..000000000000
--- a/awx/ui/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupList.js
+++ /dev/null
@@ -1,232 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-import { useLocation, useRouteMatch, Link } from 'react-router-dom';
-
-import { t, Plural } from '@lingui/macro';
-import { Card, PageSection, DropdownItem } from '@patternfly/react-core';
-
-import { InstanceGroupsAPI } from 'api';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import useRequest, { useDeleteItems } from 'hooks/useRequest';
-import useSelected from 'hooks/useSelected';
-import PaginatedTable, {
- HeaderRow,
- HeaderCell,
- ToolbarDeleteButton,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import ErrorDetail from 'components/ErrorDetail';
-import AlertModal from 'components/AlertModal';
-import DatalistToolbar from 'components/DataListToolbar';
-import AddDropDownButton from 'components/AddDropDownButton';
-import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
-import InstanceGroupListItem from './InstanceGroupListItem';
-
-const QS_CONFIG = getQSConfig('instance-group', {
- page: 1,
- page_size: 20,
-});
-
-function InstanceGroupList() {
- const location = useLocation();
- const match = useRouteMatch();
-
- const {
- error: contentError,
- isLoading,
- request: fetchInstanceGroups,
- result: {
- instanceGroups,
- instanceGroupsCount,
- actions,
- relatedSearchableKeys,
- searchableKeys,
- },
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
-
- const [response, responseActions] = await Promise.all([
- InstanceGroupsAPI.read(params),
- InstanceGroupsAPI.readOptions(),
- ]);
-
- return {
- instanceGroups: response.data.results,
- instanceGroupsCount: response.data.count,
- actions: responseActions.data.actions,
- relatedSearchableKeys: (
- responseActions?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(responseActions.data.actions?.GET),
- };
- }, [location]),
- {
- instanceGroups: [],
- instanceGroupsCount: 0,
- actions: {},
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- useEffect(() => {
- fetchInstanceGroups();
- }, [fetchInstanceGroups]);
-
- const { selected, isAllSelected, handleSelect, clearSelected, selectAll } =
- useSelected(instanceGroups);
-
- const {
- isLoading: deleteLoading,
- deletionError,
- deleteItems: deleteInstanceGroups,
- clearDeletionError,
- } = useDeleteItems(
- useCallback(
- () =>
- Promise.all(selected.map(({ id }) => InstanceGroupsAPI.destroy(id))),
- [selected]
- ),
- {
- qsConfig: QS_CONFIG,
- allItemsSelected: isAllSelected,
- fetchItems: fetchInstanceGroups,
- }
- );
-
- const handleDelete = async () => {
- await deleteInstanceGroups();
- clearSelected();
- };
-
- const canAdd = actions && actions.POST;
-
- const cannotDelete = (item) => !item.summary_fields.user_capabilities.delete;
-
- const pluralizedItemName = t`Instance Groups`;
-
- const addContainerGroup = t`Add container group`;
- const addInstanceGroup = t`Add instance group`;
-
- const addButton = (
-
- {addContainerGroup}
- ,
-
- {addInstanceGroup}
- ,
- ]}
- />
- );
-
- const getDetailUrl = (item) =>
- item.is_container_group
- ? `${match.url}/container_group/${item.id}/details`
- : `${match.url}/${item.id}/details`;
- const deleteDetailsRequests = relatedResourceDeleteRequests.instanceGroup(
- selected[0]
- );
- return (
- <>
-
-
- (
-
- }
- />,
- ]}
- />
- )}
- headerRow={
-
- {t`Name`}
- {t`Type`}
- {t`Running Jobs`}
- {t`Total Jobs`}
- {t`Instances`}
- {t`Capacity`}
- {t`Actions`}
-
- }
- renderRow={(instanceGroup, index) => (
- handleSelect(instanceGroup)}
- isSelected={selected.some((row) => row.id === instanceGroup.id)}
- rowIndex={index}
- />
- )}
- emptyStateControls={canAdd && addButton}
- />
-
-
-
- {t`Failed to delete one or more instance groups.`}
-
-
- >
- );
-}
-
-export default InstanceGroupList;
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupList.test.js b/awx/ui/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupList.test.js
deleted file mode 100644
index afd9f888964a..000000000000
--- a/awx/ui/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupList.test.js
+++ /dev/null
@@ -1,228 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import {
- InstanceGroupsAPI,
- OrganizationsAPI,
- InventoriesAPI,
- UnifiedJobTemplatesAPI,
- SettingsAPI,
-} from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import InstanceGroupList from './InstanceGroupList';
-
-jest.mock('../../../api/models/InstanceGroups');
-jest.mock('../../../api/models/Organizations');
-jest.mock('../../../api/models/Inventories');
-jest.mock('../../../api/models/UnifiedJobTemplates');
-jest.mock('../../../api/models/Settings');
-
-const instanceGroups = {
- data: {
- results: [
- {
- id: 1,
- name: 'Foo',
- type: 'instance_group',
- url: '/api/v2/instance_groups/1',
- consumed_capacity: 10,
- summary_fields: { user_capabilities: { edit: true, delete: true } },
- },
- {
- id: 2,
- name: 'controlplan',
- type: 'instance_group',
- url: '/api/v2/instance_groups/2',
- consumed_capacity: 42,
- summary_fields: { user_capabilities: { edit: true, delete: true } },
- },
- {
- id: 3,
- name: 'default',
- type: 'instance_group',
- url: '/api/v2/instance_groups/2',
- consumed_capacity: 42,
- summary_fields: { user_capabilities: { edit: true, delete: true } },
- },
- {
- id: 4,
- name: 'Bar',
- type: 'instance_group',
- url: '/api/v2/instance_groups/3',
- consumed_capacity: 42,
- summary_fields: { user_capabilities: { edit: true, delete: false } },
- },
- ],
- count: 4,
- },
-};
-
-const options = { data: { actions: { POST: true } } };
-const settings = {
- data: {
- DEFAULT_CONTROL_PLANE_QUEUE_NAME: 'controlplan',
- DEFAULT_EXECUTION_QUEUE_NAME: 'default',
- },
-};
-
-describe('', () => {
- let wrapper;
-
- beforeEach(() => {
- OrganizationsAPI.read.mockResolvedValue({ data: { count: 0 } });
- InventoriesAPI.read.mockResolvedValue({ data: { count: 0 } });
- UnifiedJobTemplatesAPI.read.mockResolvedValue({ data: { count: 0 } });
- InstanceGroupsAPI.read.mockResolvedValue(instanceGroups);
- InstanceGroupsAPI.readOptions.mockResolvedValue(options);
- SettingsAPI.readAll.mockResolvedValue(settings);
- });
-
- test('should have data fetched and render 3 rows', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'InstanceGroupList', (el) => el.length > 0);
- expect(wrapper.find('InstanceGroupListItem').length).toBe(4);
- expect(InstanceGroupsAPI.read).toBeCalled();
- expect(InstanceGroupsAPI.readOptions).toBeCalled();
- });
-
- test('should delete item successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'InstanceGroupList', (el) => el.length > 0);
-
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .simulate('change', instanceGroups);
- wrapper.update();
-
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').prop('checked')
- ).toBe(true);
-
- await act(async () => {
- wrapper.find('Button[aria-label="Delete"]').prop('onClick')();
- });
- wrapper.update();
-
- await act(async () =>
- wrapper.find('Button[aria-label="confirm delete"]').prop('onClick')()
- );
-
- expect(InstanceGroupsAPI.destroy).toBeCalledWith(
- instanceGroups.data.results[0].id
- );
- });
-
- test('should not be able to delete controlplan or default instance group', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'InstanceGroupList', (el) => el.length > 0);
-
- const instanceGroupIndex = [0, 1, 2, 3];
-
- instanceGroupIndex.forEach((element) => {
- wrapper
- .find('.pf-c-table__check')
- .at(element)
- .find('input')
- .simulate('change', instanceGroups);
- wrapper.update();
-
- expect(
- wrapper
- .find('.pf-c-table__check')
- .at(element)
- .find('input')
- .prop('checked')
- ).toBe(true);
- });
-
- expect(wrapper.find('Button[aria-label="Delete"]').prop('isDisabled')).toBe(
- true
- );
- });
-
- test('should thrown content error', async () => {
- InstanceGroupsAPI.read = jest.fn();
- InstanceGroupsAPI.read.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'GET',
- url: '/api/v2/instance_groups',
- },
- data: 'An error occurred',
- },
- })
- );
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'InstanceGroupList', (el) => el.length > 0);
- expect(wrapper.find('ContentError').length).toBe(1);
- });
-
- test('should render deletion error modal', async () => {
- InstanceGroupsAPI.destroy = jest.fn();
- InstanceGroupsAPI.destroy.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'DELETE',
- url: '/api/v2/instance_groups',
- },
- data: 'An error occurred',
- },
- })
- );
- await act(async () => {
- wrapper = mountWithContexts();
- });
- waitForElement(wrapper, 'InstanceGroupList', (el) => el.length > 0);
-
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .simulate('change', 'a');
- wrapper.update();
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').prop('checked')
- ).toBe(true);
-
- await act(async () =>
- wrapper.find('Button[aria-label="Delete"]').prop('onClick')()
- );
- wrapper.update();
-
- await act(async () =>
- wrapper.find('Button[aria-label="confirm delete"]').prop('onClick')()
- );
- wrapper.update();
- expect(wrapper.find('ErrorDetail').length).toBe(1);
- });
-
- test('should not render add button', async () => {
- InstanceGroupsAPI.read.mockResolvedValue(instanceGroups);
- InstanceGroupsAPI.readOptions.mockResolvedValue({
- data: { actions: { POST: false } },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- waitForElement(wrapper, 'InstanceGroupList', (el) => el.length > 0);
- expect(wrapper.find('ToolbarAddButton').length).toBe(0);
- });
-});
-
-describe('modifyInstanceGroups', () => {});
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js b/awx/ui/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js
deleted file mode 100644
index 4f271826f525..000000000000
--- a/awx/ui/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.js
+++ /dev/null
@@ -1,105 +0,0 @@
-import React from 'react';
-import { string, bool, func } from 'prop-types';
-
-import { t } from '@lingui/macro';
-import { Link } from 'react-router-dom';
-import 'styled-components/macro';
-import {
- Button,
- Progress,
- ProgressMeasureLocation,
- ProgressSize,
-} from '@patternfly/react-core';
-import { Tr, Td } from '@patternfly/react-table';
-import { PencilAltIcon } from '@patternfly/react-icons';
-import styled from 'styled-components';
-import { ActionsTd, ActionItem, TdBreakWord } from 'components/PaginatedTable';
-import { InstanceGroup } from 'types';
-
-const Unavailable = styled.span`
- color: var(--pf-global--danger-color--200);
-`;
-
-function InstanceGroupListItem({
- instanceGroup,
- detailUrl,
- isSelected,
- onSelect,
- rowIndex,
-}) {
- const labelId = `check-action-${instanceGroup.id}`;
-
- const isContainerGroup = (item) => item.is_container_group;
-
- function usedCapacity(item) {
- if (!isContainerGroup(item)) {
- if (item.capacity) {
- return (
-
- );
- }
- return {t`Unavailable`};
- }
- return null;
- }
-
- return (
-
- |
-
-
- {instanceGroup.name}
-
-
-
- {isContainerGroup(instanceGroup)
- ? t`Container group`
- : t`Instance group`}
- |
- {instanceGroup.jobs_running} |
- {instanceGroup.jobs_total} |
- {instanceGroup.instances} |
- {usedCapacity(instanceGroup)} |
-
-
-
-
-
-
- );
-}
-InstanceGroupListItem.prototype = {
- instanceGroup: InstanceGroup.isRequired,
- detailUrl: string.isRequired,
- isSelected: bool.isRequired,
- onSelect: func.isRequired,
-};
-
-export default InstanceGroupListItem;
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.test.js b/awx/ui/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.test.js
deleted file mode 100644
index 5da76764c13d..000000000000
--- a/awx/ui/src/screens/InstanceGroup/InstanceGroupList/InstanceGroupListItem.test.js
+++ /dev/null
@@ -1,147 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import InstanceGroupListItem from './InstanceGroupListItem';
-
-describe('', () => {
- let wrapper;
- const instanceGroups = [
- {
- id: 1,
- name: 'Foo',
- type: 'instance_group',
- url: '/api/v2/instance_groups/1',
- capacity: 10,
- policy_instance_minimum: 10,
- policy_instance_percentage: 50,
- percent_capacity_remaining: 60,
- is_container_group: false,
- summary_fields: {
- user_capabilities: {
- edit: true,
- delete: true,
- },
- },
- },
- {
- id: 2,
- name: 'Bar',
- type: 'instance_group',
- url: '/api/v2/instance_groups/2',
- capacity: 0,
- policy_instance_minimum: 0,
- policy_instance_percentage: 0,
- percent_capacity_remaining: 0,
- is_container_group: true,
- summary_fields: {
- user_capabilities: {
- edit: false,
- delete: false,
- },
- },
- },
- ];
-
- test('should mount successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('InstanceGroupListItem').length).toBe(1);
- });
-
- test('should render the proper data instance group', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('Td').at(1).text()).toBe('Foo');
- expect(wrapper.find('Progress').prop('value')).toBe(40);
- expect(wrapper.find('Td').at(2).text()).toBe('Instance group');
- expect(wrapper.find('PencilAltIcon').length).toBe(1);
- expect(wrapper.find('.pf-c-table__check input').prop('checked')).toBe(
- undefined
- );
- });
-
- test('should render the proper data container group', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('Td').at(1).text()).toBe('Bar');
-
- expect(wrapper.find('Td').at(2).text()).toBe('Container group');
- expect(wrapper.find('PencilAltIcon').length).toBe(0);
- });
-
- test('edit button shown to users with edit capabilities', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
-
- expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
- });
-
- test('edit button hidden from users without edit capabilities', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
-
- expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
- });
-});
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceGroupList/index.js b/awx/ui/src/screens/InstanceGroup/InstanceGroupList/index.js
deleted file mode 100644
index 3b1a71ec1b89..000000000000
--- a/awx/ui/src/screens/InstanceGroup/InstanceGroupList/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InstanceGroupList';
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceGroups.js b/awx/ui/src/screens/InstanceGroup/InstanceGroups.js
deleted file mode 100644
index d399c9cdbb6b..000000000000
--- a/awx/ui/src/screens/InstanceGroup/InstanceGroups.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import React, { useCallback, useState } from 'react';
-
-import { t } from '@lingui/macro';
-import { Route, Switch, useLocation } from 'react-router-dom';
-import ScreenHeader from 'components/ScreenHeader';
-import PersistentFilters from 'components/PersistentFilters';
-import InstanceGroupAdd from './InstanceGroupAdd';
-import InstanceGroupList from './InstanceGroupList';
-import InstanceGroup from './InstanceGroup';
-import ContainerGroupAdd from './ContainerGroupAdd';
-import ContainerGroup from './ContainerGroup';
-
-function InstanceGroups() {
- const { pathname } = useLocation();
- const [breadcrumbConfig, setBreadcrumbConfig] = useState({
- '/instance_groups': t`Instance Groups`,
- '/instance_groups/add': t`Create new instance group`,
- '/instance_groups/container_group/add': t`Create new container group`,
- });
-
- const buildBreadcrumbConfig = useCallback((instanceGroups, instance) => {
- if (!instanceGroups) {
- return;
- }
- setBreadcrumbConfig({
- '/instance_groups': t`Instance Groups`,
- '/instance_groups/add': t`Create new instance group`,
- '/instance_groups/container_group/add': t`Create new container group`,
-
- [`/instance_groups/${instanceGroups.id}/details`]: t`Details`,
- [`/instance_groups/${instanceGroups.id}/instances`]: t`Instances`,
- [`/instance_groups/${instanceGroups.id}/instances/${instance?.id}`]: `${instance?.hostname}`,
- [`/instance_groups/${instanceGroups.id}/instances/${instance?.id}/details`]: t`Instance details`,
- [`/instance_groups/${instanceGroups.id}/jobs`]: t`Jobs`,
- [`/instance_groups/${instanceGroups.id}/edit`]: t`Edit details`,
- [`/instance_groups/${instanceGroups.id}`]: `${instanceGroups.name}`,
-
- [`/instance_groups/container_group/${instanceGroups.id}/details`]: t`Details`,
- [`/instance_groups/container_group/${instanceGroups.id}/jobs`]: t`Jobs`,
- [`/instance_groups/container_group/${instanceGroups.id}/edit`]: t`Edit details`,
- [`/instance_groups/container_group/${instanceGroups.id}`]: `${instanceGroups.name}`,
- });
- }, []);
-
- const streamType = pathname.includes('instances')
- ? 'instance'
- : 'instance_group';
-
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-}
-
-export default InstanceGroups;
diff --git a/awx/ui/src/screens/InstanceGroup/InstanceGroups.test.js b/awx/ui/src/screens/InstanceGroup/InstanceGroups.test.js
deleted file mode 100644
index 84f269cc0afd..000000000000
--- a/awx/ui/src/screens/InstanceGroup/InstanceGroups.test.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import { InstanceGroupsAPI } from 'api';
-import InstanceGroups from './InstanceGroups';
-import { useUserProfile } from 'contexts/Config';
-
-const mockUseLocationValue = {
- pathname: '',
-};
-jest.mock('api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useLocation: () => mockUseLocationValue,
-}));
-
-beforeEach(() => {
- useUserProfile.mockImplementation(() => {
- return {
- isSuperUser: true,
- isSystemAuditor: false,
- isOrgAdmin: false,
- isNotificationAdmin: false,
- isExecEnvAdmin: false,
- };
- });
-});
-
-describe('', () => {
- test('should set breadcrumbs', () => {
- mockUseLocationValue.pathname = '/instance_groups';
-
- const wrapper = shallow();
-
- const header = wrapper.find('ScreenHeader');
- expect(header.prop('streamType')).toEqual('instance_group');
- expect(header.prop('breadcrumbConfig')).toEqual({
- '/instance_groups': 'Instance Groups',
- '/instance_groups/add': 'Create new instance group',
- '/instance_groups/container_group/add': 'Create new container group',
- });
- });
- test('should set breadcrumbs', async () => {
- mockUseLocationValue.pathname = '/instance_groups/1/instances';
- InstanceGroupsAPI.readInstances.mockResolvedValue({
- data: { results: [{ hostname: 'EC2', id: 1 }] },
- });
- InstanceGroupsAPI.readInstanceOptions.mockResolvedValue({
- data: { actions: {} },
- });
-
- const wrapper = shallow();
-
- expect(wrapper.find('ScreenHeader').prop('streamType')).toEqual('instance');
- });
-});
diff --git a/awx/ui/src/screens/InstanceGroup/Instances/InstanceList.js b/awx/ui/src/screens/InstanceGroup/Instances/InstanceList.js
deleted file mode 100644
index cac7daa04dea..000000000000
--- a/awx/ui/src/screens/InstanceGroup/Instances/InstanceList.js
+++ /dev/null
@@ -1,345 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { t } from '@lingui/macro';
-import { useLocation, useParams } from 'react-router-dom';
-import 'styled-components/macro';
-
-import useExpanded from 'hooks/useExpanded';
-import DataListToolbar from 'components/DataListToolbar';
-import PaginatedTable, {
- HeaderRow,
- HeaderCell,
- ToolbarAddButton,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import DisassociateButton from 'components/DisassociateButton';
-import AssociateModal from 'components/AssociateModal';
-import AlertModal from 'components/AlertModal';
-import ErrorDetail from 'components/ErrorDetail';
-import useRequest, {
- useDeleteItems,
- useDismissableError,
-} from 'hooks/useRequest';
-import useSelected from 'hooks/useSelected';
-import { InstanceGroupsAPI, InstancesAPI } from 'api';
-import { getQSConfig, parseQueryString, mergeParams } from 'util/qs';
-import HealthCheckButton from 'components/HealthCheckButton/HealthCheckButton';
-import HealthCheckAlert from 'components/HealthCheckAlert';
-import InstanceListItem from './InstanceListItem';
-
-const QS_CONFIG = getQSConfig('instance', {
- page: 1,
- page_size: 20,
- order_by: 'hostname',
-});
-
-function InstanceList({ instanceGroup }) {
- const [isModalOpen, setIsModalOpen] = useState(false);
- const [showHealthCheckAlert, setShowHealthCheckAlert] = useState(false);
- const [pendingHealthCheck, setPendingHealthCheck] = useState(false);
- const [canRunHealthCheck, setCanRunHealthCheck] = useState(true);
- const location = useLocation();
- const { id: instanceGroupId } = useParams();
-
- const {
- result: {
- instances,
- count,
- actions,
- relatedSearchableKeys,
- searchableKeys,
- },
- error: contentError,
- isLoading,
- request: fetchInstances,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
- const [response, responseActions] = await Promise.all([
- InstanceGroupsAPI.readInstances(instanceGroupId, params),
- InstanceGroupsAPI.readInstanceOptions(instanceGroupId),
- ]);
- const isPending = response.data.results.some(
- (i) => i.health_check_pending === true
- );
- setPendingHealthCheck(isPending);
- return {
- instances: response.data.results,
- count: response.data.count,
- actions: responseActions.data.actions,
- relatedSearchableKeys: (
- responseActions?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(responseActions.data.actions?.GET),
- };
- }, [location.search, instanceGroupId]),
- {
- instances: [],
- count: 0,
- actions: {},
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- const { selected, isAllSelected, handleSelect, clearSelected, selectAll } =
- useSelected(instances);
-
- useEffect(() => {
- fetchInstances();
- }, [fetchInstances]);
-
- const {
- error: healthCheckError,
- request: fetchHealthCheck,
- isLoading: isHealthCheckLoading,
- } = useRequest(
- useCallback(async () => {
- const [...response] = await Promise.all(
- selected
- .filter(({ node_type }) => node_type === 'execution')
- .map(({ id }) => InstancesAPI.healthCheck(id))
- );
- if (response) {
- setShowHealthCheckAlert(true);
- }
- }, [selected])
- );
-
- useEffect(() => {
- if (selected) {
- selected.forEach((i) => {
- if (i.node_type === 'execution') {
- setCanRunHealthCheck(true);
- } else {
- setCanRunHealthCheck(false);
- }
- });
- }
- }, [selected]);
-
- const handleHealthCheck = async () => {
- await fetchHealthCheck();
- clearSelected();
- };
-
- const {
- isLoading: isDisassociateLoading,
- deleteItems: disassociateInstances,
- deletionError: disassociateError,
- } = useDeleteItems(
- useCallback(
- () =>
- Promise.all(
- selected
- .filter((s) => s.node_type !== 'control')
- .map((instance) =>
- InstanceGroupsAPI.disassociateInstance(
- instanceGroupId,
- instance.id
- )
- )
- ),
- [instanceGroupId, selected]
- ),
- {
- qsConfig: QS_CONFIG,
- allItemsSelected: isAllSelected,
- fetchItems: fetchInstances,
- }
- );
-
- const { request: handleAssociate, error: associateError } = useRequest(
- useCallback(
- async (instancesToAssociate) => {
- await Promise.all(
- instancesToAssociate
- .filter((i) => i.node_type !== 'control' || i.node_type !== 'hop')
- .map((instance) =>
- InstanceGroupsAPI.associateInstance(instanceGroupId, instance.id)
- )
- );
- fetchInstances();
- },
- [instanceGroupId, fetchInstances]
- )
- );
-
- const handleDisassociate = async () => {
- await disassociateInstances();
- clearSelected();
- };
-
- const { error, dismissError } = useDismissableError(
- associateError || disassociateError || healthCheckError
- );
-
- const canAdd =
- actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
-
- const fetchInstancesToAssociate = useCallback(
- (params) =>
- InstancesAPI.read(
- mergeParams(params, {
- ...{ not__rampart_groups__id: instanceGroupId },
- ...{ not__node_type: ['hop', 'control'] },
- })
- ),
- [instanceGroupId]
- );
-
- const readInstancesOptions = useCallback(
- () => InstanceGroupsAPI.readInstanceOptions(instanceGroupId),
- [instanceGroupId]
- );
-
- const { expanded, isAllExpanded, handleExpand, expandAll } =
- useExpanded(instances);
-
- return (
- <>
- {showHealthCheckAlert ? (
-
- ) : null}
- (
- setIsModalOpen(true)}
- defaultLabel={t`Associate`}
- />,
- ]
- : []),
- s.node_type === 'control') ||
- instanceGroup.name === 'controlplane'
- }
- key="disassociate"
- onDisassociate={handleDisassociate}
- itemsToDisassociate={selected}
- modalTitle={t`Disassociate instance from instance group?`}
- isProtectedInstanceGroup={instanceGroup.name === 'controlplane'}
- />,
- ,
- ]}
- emptyStateControls={
- canAdd ? (
- setIsModalOpen(true)}
- />
- ) : null
- }
- />
- )}
- headerRow={
-
- {t`Name`}
- {t`Status`}
- {t`Node Type`}
- {t`Capacity Adjustment`}
- {t`Used Capacity`}
- {t`Actions`}
-
- }
- renderRow={(instance, index) => (
- row.id === instance.id)}
- onExpand={() => handleExpand(instance)}
- key={instance.id}
- value={instance.hostname}
- instance={instance}
- onSelect={() => handleSelect(instance)}
- isSelected={selected.some((row) => row.id === instance.id)}
- fetchInstances={fetchInstances}
- rowIndex={index}
- />
- )}
- />
- {isModalOpen && (
- setIsModalOpen(false)}
- title={t`Select Instances`}
- optionsRequest={readInstancesOptions}
- displayKey="hostname"
- columns={[
- { key: 'hostname', name: t`Name` },
- { key: 'node_type', name: t`Node Type` },
- ]}
- />
- )}
- {error && (
-
- {associateError && t`Failed to associate.`}
- {disassociateError &&
- t`Failed to disassociate one or more instances.`}
- {healthCheckError &&
- t`Failed to run a health check on one or more instances.`}
-
-
- )}
- >
- );
-}
-
-export default InstanceList;
diff --git a/awx/ui/src/screens/InstanceGroup/Instances/InstanceList.test.js b/awx/ui/src/screens/InstanceGroup/Instances/InstanceList.test.js
deleted file mode 100644
index 9aa3ce0bda48..000000000000
--- a/awx/ui/src/screens/InstanceGroup/Instances/InstanceList.test.js
+++ /dev/null
@@ -1,231 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Route } from 'react-router-dom';
-import { createMemoryHistory } from 'history';
-
-import { InstancesAPI, InstanceGroupsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import InstanceList from './InstanceList';
-
-jest.mock('../../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useParams: () => ({
- id: 1,
- instanceGroupId: 2,
- }),
-}));
-
-const instances = [
- {
- id: 1,
- type: 'instance',
- url: '/api/v2/instances/1/',
- related: {
- jobs: '/api/v2/instances/1/jobs/',
- instance_groups: '/api/v2/instances/1/instance_groups/',
- },
- uuid: '00000000-0000-0000-0000-000000000000',
- hostname: 'awx',
- created: '2020-07-14T19:03:49.000054Z',
- modified: '2020-08-12T20:08:02.836748Z',
- capacity_adjustment: '0.40',
- version: '13.0.0',
- capacity: 10,
- consumed_capacity: 0,
- percent_capacity_remaining: 60.0,
- jobs_running: 0,
- jobs_total: 68,
- cpu: 6,
- node_type: 'control',
- node_state: 'ready',
- memory: 2087469056,
- cpu_capacity: 24,
- mem_capacity: 1,
- enabled: true,
- managed_by_policy: true,
- },
- {
- id: 2,
- type: 'instance',
- url: '/api/v2/instances/2/',
- related: {
- jobs: '/api/v2/instances/2/jobs/',
- instance_groups: '/api/v2/instances/2/instance_groups/',
- },
- uuid: '00000000-0000-0000-0000-000000000000',
- hostname: 'foo',
- created: '2020-07-14T19:03:49.000054Z',
- modified: '2020-08-12T20:08:02.836748Z',
- capacity_adjustment: '0.40',
- version: '13.0.0',
- capacity: 10,
- consumed_capacity: 0,
- percent_capacity_remaining: 60.0,
- jobs_running: 0,
- jobs_total: 68,
- cpu: 6,
- node_type: 'hybrid',
- node_state: 'ready',
- memory: 2087469056,
- cpu_capacity: 24,
- mem_capacity: 1,
- enabled: true,
- managed_by_policy: false,
- },
- {
- id: 3,
- type: 'instance',
- url: '/api/v2/instances/3/',
- related: {
- jobs: '/api/v2/instances/3/jobs/',
- instance_groups: '/api/v2/instances/3/instance_groups/',
- },
- uuid: '00000000-0000-0000-0000-000000000000',
- hostname: 'bar',
- created: '2020-07-14T19:03:49.000054Z',
- modified: '2020-08-12T20:08:02.836748Z',
- capacity_adjustment: '0.40',
- version: '13.0.0',
- capacity: 10,
- consumed_capacity: 0,
- percent_capacity_remaining: 60.0,
- jobs_running: 0,
- jobs_total: 68,
- cpu: 6,
- node_type: 'execution',
- node_state: 'ready',
- memory: 2087469056,
- cpu_capacity: 24,
- mem_capacity: 1,
- enabled: false,
- managed_by_policy: true,
- },
-];
-
-const options = { data: { actions: { POST: true } } };
-
-describe('', () => {
- let wrapper;
-
- beforeEach(async () => {
- InstanceGroupsAPI.readInstances.mockResolvedValue({
- data: {
- count: instances.length,
- results: instances,
- },
- });
- InstanceGroupsAPI.readInstanceOptions.mockResolvedValue(options);
- const history = createMemoryHistory({
- initialEntries: ['/instance_groups/1/instances'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- ,
- {
- context: {
- router: { history, route: { location: history.location } },
- },
- }
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should have data fetched', () => {
- expect(wrapper.find('InstanceList').length).toBe(1);
- });
-
- test('should fetch instances from the api and render them in the list', () => {
- expect(InstanceGroupsAPI.readInstances).toHaveBeenCalled();
- expect(InstanceGroupsAPI.readInstanceOptions).toHaveBeenCalled();
- expect(wrapper.find('InstanceListItem').length).toBe(3);
- });
-
- test('should show associate group modal when adding an existing group', () => {
- wrapper.find('ToolbarAddButton').simulate('click');
- expect(wrapper.find('AssociateModal').length).toBe(1);
- wrapper.find('ModalBoxCloseButton').simulate('click');
- expect(wrapper.find('AssociateModal').length).toBe(0);
- });
- test('should run health check', async () => {
- expect(
- wrapper.find('Button[ouiaId="health-check"]').prop('isDisabled')
- ).toBe(true);
- await act(async () =>
- wrapper.find('DataListToolbar').prop('onSelectAll')(instances)
- );
- wrapper.update();
- expect(
- wrapper.find('Button[ouiaId="health-check"]').prop('isDisabled')
- ).toBe(false);
- await act(async () =>
- wrapper.find('Button[ouiaId="health-check"]').prop('onClick')()
- );
- expect(InstancesAPI.healthCheck).toBeCalledTimes(1);
- });
- test('should render health check error', async () => {
- InstancesAPI.healthCheck.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'create',
- url: '/api/v2/instances',
- },
- data: 'An error occurred',
- status: 403,
- },
- })
- );
- expect(
- wrapper.find('Button[ouiaId="health-check"]').prop('isDisabled')
- ).toBe(true);
- await act(async () =>
- wrapper.find('DataListToolbar').prop('onSelectAll')(instances)
- );
- wrapper.update();
- expect(
- wrapper.find('Button[ouiaId="health-check"]').prop('isDisabled')
- ).toBe(false);
- await act(async () =>
- wrapper.find('Button[ouiaId="health-check"]').prop('onClick')()
- );
- wrapper.update();
- expect(wrapper.find('AlertModal')).toHaveLength(1);
- });
-
- test('should disable disassociate button', async () => {
- expect(
- wrapper.find('Button[ouiaId="disassociate-button"]').prop('isDisabled')
- ).toBe(true);
- await act(async () =>
- wrapper.find('DataListToolbar').prop('onSelectAll')(instances)
- );
-
- wrapper.update();
- expect(
- wrapper.find('Button[ouiaId="disassociate-button"]').prop('isDisabled')
- ).toBe(true);
- await act(async () =>
- wrapper
- .find('Tr#instance-row-1')
- .find('SelectColumn[aria-label="Select row 0"]')
- .prop('onSelect')(false)
- );
-
- wrapper.update();
- expect(
- wrapper.find('Button[ouiaId="disassociate-button"]').prop('isDisabled')
- ).toBe(false);
- });
-});
diff --git a/awx/ui/src/screens/InstanceGroup/Instances/InstanceListItem.js b/awx/ui/src/screens/InstanceGroup/Instances/InstanceListItem.js
deleted file mode 100644
index 7a450030641b..000000000000
--- a/awx/ui/src/screens/InstanceGroup/Instances/InstanceListItem.js
+++ /dev/null
@@ -1,265 +0,0 @@
-import React, { useState, useCallback } from 'react';
-import { Link, useParams } from 'react-router-dom';
-import { bool, func } from 'prop-types';
-import { t, Plural } from '@lingui/macro';
-import styled from 'styled-components';
-import 'styled-components/macro';
-import {
- Progress,
- ProgressMeasureLocation,
- ProgressSize,
- Slider,
- Tooltip,
-} from '@patternfly/react-core';
-import { OutlinedClockIcon } from '@patternfly/react-icons';
-import { Tr, Td, ExpandableRowContent } from '@patternfly/react-table';
-import getDocsBaseUrl from 'util/getDocsBaseUrl';
-import { formatDateString } from 'util/dates';
-import { ActionsTd, ActionItem } from 'components/PaginatedTable';
-import InstanceToggle from 'components/InstanceToggle';
-import StatusLabel from 'components/StatusLabel';
-import { Instance } from 'types';
-import useRequest, { useDismissableError } from 'hooks/useRequest';
-import useDebounce from 'hooks/useDebounce';
-import computeForks from 'util/computeForks';
-import { InstancesAPI } from 'api';
-import { useConfig } from 'contexts/Config';
-import AlertModal from 'components/AlertModal';
-import ErrorDetail from 'components/ErrorDetail';
-import { Detail, DetailList } from 'components/DetailList';
-
-const Unavailable = styled.span`
- color: var(--pf-global--danger-color--200);
-`;
-
-const SliderHolder = styled.div`
- display: flex;
- align-items: center;
- justify-content: space-between;
-`;
-
-const SliderForks = styled.div`
- flex-grow: 1;
- margin-right: 8px;
- margin-left: 8px;
- text-align: center;
-`;
-
-function InstanceListItem({
- instance,
- isExpanded,
- onExpand,
- isSelected,
- onSelect,
- fetchInstances,
- rowIndex,
-}) {
- const config = useConfig();
- const { id } = useParams();
- const [forks, setForks] = useState(
- computeForks(
- instance.mem_capacity,
- instance.cpu_capacity,
- instance.capacity_adjustment
- )
- );
-
- const labelId = `check-action-${instance.id}`;
-
- function usedCapacity(item) {
- if (item.enabled) {
- return (
-
- );
- }
- return {t`Unavailable`};
- }
-
- const { error: updateInstanceError, request: updateInstance } = useRequest(
- useCallback(
- async (values) => {
- await InstancesAPI.update(instance.id, values);
- },
- [instance]
- )
- );
-
- const { error: updateError, dismissError: dismissUpdateError } =
- useDismissableError(updateInstanceError);
-
- const debounceUpdateInstance = useDebounce(updateInstance, 200);
-
- const handleChangeValue = (value) => {
- const roundedValue = Math.round(value * 100) / 100;
- setForks(
- computeForks(instance.mem_capacity, instance.cpu_capacity, roundedValue)
- );
- debounceUpdateInstance({ capacity_adjustment: roundedValue });
- };
-
- const formatHealthCheckTimeStamp = (last) => (
- <>
- {formatDateString(last)}
- {instance.health_check_pending ? (
- <>
- {' '}
-
- >
- ) : null}
- >
- );
-
- return (
- <>
-
- |
- |
-
-
- {instance.hostname}
-
- |
-
-
- {t`Last Health Check`}
-
- {formatDateString(instance.last_health_check)}
-
- }
- >
-
-
- |
- {instance.node_type} |
-
-
- {t`CPU ${instance.cpu_capacity}`}
-
-
-
-
- {t`RAM ${instance.mem_capacity}`}
-
- |
-
- {usedCapacity(instance)}
- |
-
-
-
-
-
-
-
- |
-
-
-
-
-
-
-
- {t`Health checks are asynchronous tasks. See the`}{' '}
-
- {t`documentation`}
- {' '}
- {t`for more info.`}
- >
- }
- value={formatHealthCheckTimeStamp(instance.last_health_check)}
- />
-
-
- |
-
- {updateError && (
-
- {t`Failed to update capacity adjustment.`}
-
-
- )}
- >
- );
-}
-
-InstanceListItem.prototype = {
- instance: Instance.isRequired,
- isSelected: bool.isRequired,
- onSelect: func.isRequired,
-};
-
-export default InstanceListItem;
diff --git a/awx/ui/src/screens/InstanceGroup/Instances/InstanceListItem.test.js b/awx/ui/src/screens/InstanceGroup/Instances/InstanceListItem.test.js
deleted file mode 100644
index 9bed322d6d67..000000000000
--- a/awx/ui/src/screens/InstanceGroup/Instances/InstanceListItem.test.js
+++ /dev/null
@@ -1,288 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { InstancesAPI } from 'api';
-import useDebounce from 'hooks/useDebounce';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import InstanceListItem from './InstanceListItem';
-
-jest.mock('../../../api');
-jest.mock('../../../hooks/useDebounce');
-
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useParams: () => ({
- id: 1,
- }),
-}));
-const instance = [
- {
- id: 1,
- type: 'instance',
- url: '/api/v2/instances/1/',
- related: {
- jobs: '/api/v2/instances/1/jobs/',
- instance_groups: '/api/v2/instances/1/instance_groups/',
- },
- uuid: '00000000-0000-0000-0000-000000000000',
- hostname: 'awx',
- created: '2020-07-14T19:03:49.000054Z',
- modified: '2020-08-12T20:08:02.836748Z',
- capacity_adjustment: '0.40',
- version: '13.0.0',
- capacity: 10,
- consumed_capacity: 0,
- percent_capacity_remaining: 60.0,
- jobs_running: 0,
- jobs_total: 68,
- last_health_check: '2021-09-15T18:02:07.270664Z',
- cpu: 6,
- memory: 2087469056,
- cpu_capacity: 24,
- mem_capacity: 1,
- enabled: true,
- managed_by_policy: true,
- node_type: 'hybrid',
- node_state: 'ready',
- },
- {
- id: 2,
- type: 'instance',
- url: '/api/v2/instances/1/',
- related: {
- jobs: '/api/v2/instances/1/jobs/',
- instance_groups: '/api/v2/instances/1/instance_groups/',
- },
- uuid: '00000000-0000-0000-0000-000000000001',
- hostname: 'awx-control',
- created: '2020-07-14T19:03:49.000054Z',
- modified: '2020-08-12T20:08:02.836748Z',
- capacity_adjustment: '0.40',
- version: '13.0.0',
- last_health_check: '2021-09-15T18:02:07.270664Z',
- capacity: 10,
- consumed_capacity: 0,
- percent_capacity_remaining: 60.0,
- jobs_running: 0,
- jobs_total: 68,
- cpu: 6,
- memory: 2087469056,
- cpu_capacity: 24,
- mem_capacity: 1,
- enabled: true,
- managed_by_policy: true,
- node_type: 'control',
- node_state: 'ready',
- },
-];
-
-describe('', () => {
- let wrapper;
-
- beforeEach(() => {
- useDebounce.mockImplementation((fn) => fn);
- });
-
- test('should mount successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- {}}
- fetchInstances={() => {}}
- />
-
-
- );
- });
- expect(wrapper.find('InstanceListItem').length).toBe(1);
- });
-
- test('should calculate number of forks when slide changes', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- {}}
- fetchInstances={() => {}}
- />
-
-
- );
- });
- expect(wrapper.find('InstanceListItem').length).toBe(1);
- expect(wrapper.find('InstanceListItem__SliderForks').text()).toContain(
- '10 forks'
- );
-
- await act(async () => {
- wrapper.find('Slider').prop('onChange')(1);
- });
-
- wrapper.update();
- expect(wrapper.find('InstanceListItem__SliderForks').text()).toContain(
- '24 forks'
- );
-
- await act(async () => {
- wrapper.find('Slider').prop('onChange')(0);
- });
- wrapper.update();
- expect(wrapper.find('InstanceListItem__SliderForks').text()).toContain(
- '1 fork'
- );
-
- await act(async () => {
- wrapper.find('Slider').prop('onChange')(0.5);
- });
- wrapper.update();
- expect(wrapper.find('InstanceListItem__SliderForks').text()).toContain(
- '12 forks'
- );
- });
-
- test('should render the proper data instance', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- {}}
- fetchInstances={() => {}}
- />
-
-
- );
- });
- expect(wrapper.find('Td[dataLabel="Name"]').find('Link').prop('to')).toBe(
- '/instance_groups/1/instances/1/details'
- );
- expect(wrapper.find('Td').at(2).text()).toBe('awx');
- expect(wrapper.find('Progress').prop('value')).toBe(40);
- expect(
- wrapper
- .find('Td')
- .at(5)
- .containsMatchingElement(CPU 24
)
- );
- expect(
- wrapper
- .find('Td')
- .at(5)
- .containsMatchingElement(RAM 24
)
- );
- expect(wrapper.find('InstanceListItem__SliderForks').text()).toContain(
- '10 forks'
- );
- });
-
- test('should render checkbox', async () => {
- const onSelect = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('Td').at(1).prop('select').onSelect).toEqual(onSelect);
- });
-
- test('should display instance toggle', () => {
- expect(wrapper.find('InstanceToggle').length).toBe(1);
- });
-
- test('should display error', async () => {
- jest.useFakeTimers();
- InstancesAPI.update.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'patch',
- url: '/api/v2/instances/1',
- data: { capacity_adjustment: 0.30001 },
- },
- data: {
- capacity_adjustment: [
- 'Ensure that there are no more than 3 digits in total.',
- ],
- },
- status: 400,
- statusText: 'Bad Request',
- },
- })
- );
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- {}}
- fetchInstances={() => {}}
- />
-
-
,
- { context: { network: { handleHttpError: () => {} } } }
- );
- });
- await act(async () => {
- wrapper.update();
- });
- expect(wrapper.find('ErrorDetail').length).toBe(0);
- await act(async () => {
- wrapper.find('Slider').prop('onChange')(0.30001);
- });
- await act(async () => {
- wrapper.update();
- });
- jest.advanceTimersByTime(210);
- await act(async () => {
- wrapper.update();
- });
- expect(wrapper.find('ErrorDetail').length).toBe(1);
- });
-
- test('Should render expanded row with the correct data points', async () => {
- const onSelect = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- {}}
- isExpanded
- />
-
-
- );
- });
- expect(wrapper.find('InstanceListItem').prop('isExpanded')).toBe(true);
- expect(wrapper.find('Detail[label="Running Jobs"]').prop('value')).toBe(0);
- expect(wrapper.find('Detail[label="Total Jobs"]').prop('value')).toBe(68);
- expect(wrapper.find('Detail[label="Policy Type"]').prop('value')).toBe(
- 'Auto'
- );
- expect(wrapper.find('Detail[label="Last Health Check"]').text()).toBe(
- 'Last Health Check9/15/2021, 6:02:07 PM'
- );
- });
-});
diff --git a/awx/ui/src/screens/InstanceGroup/Instances/Instances.js b/awx/ui/src/screens/InstanceGroup/Instances/Instances.js
deleted file mode 100644
index 8a7d9132f7e0..000000000000
--- a/awx/ui/src/screens/InstanceGroup/Instances/Instances.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import React from 'react';
-import { Redirect, Route, Switch } from 'react-router-dom';
-import InstanceList from './InstanceList';
-import InstanceDetails from '../InstanceDetails';
-
-function Instances({ setBreadcrumb, instanceGroup }) {
- return (
-
-
-
-
-
-
-
-
-
- );
-}
-
-export default Instances;
diff --git a/awx/ui/src/screens/InstanceGroup/Instances/index.js b/awx/ui/src/screens/InstanceGroup/Instances/index.js
deleted file mode 100644
index e58909444fca..000000000000
--- a/awx/ui/src/screens/InstanceGroup/Instances/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export { default as InstanceList } from './InstanceList';
-export { default as InstanceListItem } from './InstanceListItem';
-export { default as Instances } from './Instances';
diff --git a/awx/ui/src/screens/InstanceGroup/index.js b/awx/ui/src/screens/InstanceGroup/index.js
deleted file mode 100644
index 92673c5c20b2..000000000000
--- a/awx/ui/src/screens/InstanceGroup/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InstanceGroups';
diff --git a/awx/ui/src/screens/InstanceGroup/shared/ContainerGroupForm.js b/awx/ui/src/screens/InstanceGroup/shared/ContainerGroupForm.js
deleted file mode 100644
index 22022258e908..000000000000
--- a/awx/ui/src/screens/InstanceGroup/shared/ContainerGroupForm.js
+++ /dev/null
@@ -1,166 +0,0 @@
-import React, { useCallback } from 'react';
-import { func, shape } from 'prop-types';
-import { Formik, useField, useFormikContext } from 'formik';
-
-import { t } from '@lingui/macro';
-import { Form, FormGroup } from '@patternfly/react-core';
-import { jsonToYaml } from 'util/yaml';
-
-import FormField, {
- FormSubmitError,
- CheckboxField,
-} from 'components/FormField';
-import FormActionGroup from 'components/FormActionGroup';
-import { required, minMaxValue } from 'util/validators';
-import {
- FormColumnLayout,
- FormFullWidthLayout,
- FormCheckboxLayout,
- SubFormLayout,
-} from 'components/FormLayout';
-import CredentialLookup from 'components/Lookup/CredentialLookup';
-import { VariablesField } from 'components/CodeEditor';
-
-function ContainerGroupFormFields({ instanceGroup }) {
- const { setFieldValue, setFieldTouched } = useFormikContext();
- const [credentialField, credentialMeta, credentialHelpers] =
- useField('credential');
-
- const [overrideField] = useField('override');
-
- const handleCredentialUpdate = useCallback(
- (value) => {
- setFieldValue('credential', value);
- setFieldTouched('credential', true, false);
- },
- [setFieldValue, setFieldTouched]
- );
-
- return (
- <>
-
- credentialHelpers.setTouched()}
- onChange={handleCredentialUpdate}
- value={credentialField.value}
- tooltip={t`Credential to authenticate with Kubernetes or OpenShift. Must be of type "Kubernetes/OpenShift API Bearer Token". If left blank, the underlying Pod's service account will be used.`}
- autoPopulate={!instanceGroup?.id}
- />
-
-
-
-
-
-
-
-
-
- {overrideField.value && (
-
-
-
-
-
- )}
- >
- );
-}
-
-function ContainerGroupForm({
- initialPodSpec,
- instanceGroup,
- onSubmit,
- onCancel,
- submitError,
- ...rest
-}) {
- const isCheckboxChecked = Boolean(instanceGroup?.pod_spec_override) || false;
-
- const initialValues = {
- name: instanceGroup?.name || '',
- max_concurrent_jobs: instanceGroup.max_concurrent_jobs || 0,
- max_forks: instanceGroup.max_forks || 0,
- credential: instanceGroup?.summary_fields?.credential,
- pod_spec_override: isCheckboxChecked
- ? instanceGroup?.pod_spec_override
- : jsonToYaml(JSON.stringify(initialPodSpec)),
- override: isCheckboxChecked,
- };
-
- return (
- {
- onSubmit(values);
- }}
- >
- {(formik) => (
-
- )}
-
- );
-}
-
-ContainerGroupForm.propTypes = {
- instanceGroup: shape({}),
- onCancel: func.isRequired,
- onSubmit: func.isRequired,
- submitError: shape({}),
- initialPodSpec: shape({}),
-};
-
-ContainerGroupForm.defaultProps = {
- instanceGroup: {},
- submitError: null,
- initialPodSpec: {},
-};
-
-export default ContainerGroupForm;
diff --git a/awx/ui/src/screens/InstanceGroup/shared/ContainerGroupForm.test.js b/awx/ui/src/screens/InstanceGroup/shared/ContainerGroupForm.test.js
deleted file mode 100644
index bd9859c122f3..000000000000
--- a/awx/ui/src/screens/InstanceGroup/shared/ContainerGroupForm.test.js
+++ /dev/null
@@ -1,149 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import ContainerGroupForm from './ContainerGroupForm';
-
-jest.mock('../../../api');
-
-const instanceGroup = {
- id: 7,
- type: 'instance_group',
- url: '/api/v2/instance_groups/7/',
- related: {
- jobs: '/api/v2/instance_groups/7/jobs/',
- instances: '/api/v2/instance_groups/7/instances/',
- },
- name: 'Bar',
- created: '2020-07-21T18:41:02.818081Z',
- modified: '2020-07-24T20:32:03.121079Z',
- capacity: 24,
- committed_capacity: 0,
- consumed_capacity: 0,
- percent_capacity_remaining: 100.0,
- jobs_running: 0,
- jobs_total: 0,
- instances: 1,
- controller: null,
- is_container_group: false,
- credential: 3,
- policy_instance_percentage: 46,
- policy_instance_minimum: 12,
- policy_instance_list: [],
- pod_spec_override: '',
- summary_fields: {
- credential: {
- id: 3,
- name: 'test',
- description: 'Simple one',
- kind: 'kubernetes_bearer_token',
- cloud: false,
- kubernetes: true,
- credential_type_id: 17,
- },
- user_capabilities: {
- edit: true,
- delete: true,
- },
- },
-};
-
-const initialPodSpec = {
- default: {
- apiVersion: 'v1',
- kind: 'Pod',
- metadata: {
- namespace: 'default',
- },
- spec: {
- containers: [
- {
- image: 'ansible/ansible-runner',
- tty: true,
- stdin: true,
- imagePullPolicy: 'Always',
- args: ['sleep', 'infinity'],
- },
- ],
- },
- },
-};
-
-describe('', () => {
- let wrapper;
- let onCancel;
- let onSubmit;
-
- beforeEach(async () => {
- onCancel = jest.fn();
- onSubmit = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('Initially renders successfully', () => {
- expect(wrapper.length).toBe(1);
- });
-
- test('should display form fields properly', () => {
- expect(wrapper.find('FormGroup[label="Name"]').length).toBe(1);
- expect(wrapper.find('VariablesField[label="Custom pod spec"]').length).toBe(
- 0
- );
- expect(
- wrapper
- .find('Checkbox[aria-label="Customize pod specification"]')
- .prop('isChecked')
- ).toBeFalsy();
- expect(wrapper.find('CredentialLookup').prop('value').name).toBe('test');
- });
-
- test('should update form values', async () => {
- act(() => {
- wrapper.find('CredentialLookup').invoke('onBlur')();
- wrapper.find('CredentialLookup').invoke('onChange')({
- id: 99,
- name: 'credential',
- });
- wrapper.find('TextInputBase#container-group-name').simulate('change', {
- target: { value: 'new Foo', name: 'name' },
- });
- });
- await act(async () => {
- wrapper.update();
- });
- expect(wrapper.find('CredentialLookup').prop('value')).toEqual({
- id: 99,
- name: 'credential',
- });
- expect(
- wrapper.find('TextInputBase#container-group-name').prop('value')
- ).toEqual('new Foo');
- });
-
- test('should call onSubmit when form submitted', async () => {
- expect(onSubmit).not.toHaveBeenCalled();
- await act(async () => {
- wrapper.find('button[aria-label="Save"]').simulate('click');
- });
- expect(onSubmit).toHaveBeenCalledTimes(1);
- });
-
- test('should call handleCancel when Cancel button is clicked', async () => {
- expect(onCancel).not.toHaveBeenCalled();
- wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
- expect(onCancel).toBeCalled();
- });
-});
diff --git a/awx/ui/src/screens/InstanceGroup/shared/InstanceGroupForm.js b/awx/ui/src/screens/InstanceGroup/shared/InstanceGroupForm.js
deleted file mode 100644
index 0e9396be24c4..000000000000
--- a/awx/ui/src/screens/InstanceGroup/shared/InstanceGroupForm.js
+++ /dev/null
@@ -1,116 +0,0 @@
-import React from 'react';
-import { func, shape } from 'prop-types';
-import { Formik } from 'formik';
-
-import { t } from '@lingui/macro';
-import { Form } from '@patternfly/react-core';
-
-import FormField, { FormSubmitError } from 'components/FormField';
-import FormActionGroup from 'components/FormActionGroup';
-import { required, minMaxValue } from 'util/validators';
-import { FormColumnLayout } from 'components/FormLayout';
-
-function InstanceGroupFormFields() {
- return (
- <>
-
-
-
-
-
- >
- );
-}
-
-function InstanceGroupForm({
- instanceGroup = {},
- onSubmit,
- onCancel,
- submitError,
- ...rest
-}) {
- const initialValues = {
- name: instanceGroup.name || '',
- policy_instance_minimum: instanceGroup.policy_instance_minimum || 0,
- policy_instance_percentage: instanceGroup.policy_instance_percentage || 0,
- max_concurrent_jobs: instanceGroup.max_concurrent_jobs || 0,
- max_forks: instanceGroup.max_forks || 0,
- };
- return (
- onSubmit(values)}
- >
- {(formik) => (
-
- )}
-
- );
-}
-
-InstanceGroupForm.propTypes = {
- instanceGroup: shape({}),
- onCancel: func.isRequired,
- onSubmit: func.isRequired,
- submitError: shape({}),
-};
-
-InstanceGroupForm.defaultProps = {
- instanceGroup: {},
- submitError: null,
-};
-
-export default InstanceGroupForm;
diff --git a/awx/ui/src/screens/InstanceGroup/shared/InstanceGroupForm.test.js b/awx/ui/src/screens/InstanceGroup/shared/InstanceGroupForm.test.js
deleted file mode 100644
index cb646ed608e2..000000000000
--- a/awx/ui/src/screens/InstanceGroup/shared/InstanceGroupForm.test.js
+++ /dev/null
@@ -1,120 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import InstanceGroupForm from './InstanceGroupForm';
-
-jest.mock('../../../api');
-
-const instanceGroup = {
- id: 7,
- type: 'instance_group',
- url: '/api/v2/instance_groups/7/',
- related: {
- jobs: '/api/v2/instance_groups/7/jobs/',
- instances: '/api/v2/instance_groups/7/instances/',
- },
- name: 'Bar',
- created: '2020-07-21T18:41:02.818081Z',
- modified: '2020-07-24T20:32:03.121079Z',
- capacity: 24,
- committed_capacity: 0,
- consumed_capacity: 0,
- percent_capacity_remaining: 100.0,
- jobs_running: 0,
- jobs_total: 0,
- instances: 1,
- controller: null,
- is_container_group: false,
- credential: null,
- policy_instance_percentage: 46,
- policy_instance_minimum: 12,
- policy_instance_list: [],
- pod_spec_override: '',
- summary_fields: {
- user_capabilities: {
- edit: true,
- delete: true,
- },
- },
-};
-
-describe('', () => {
- let wrapper;
- let onCancel;
- let onSubmit;
-
- beforeEach(async () => {
- onCancel = jest.fn();
- onSubmit = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- wrapper.unmount();
- });
-
- test('Initially renders successfully', () => {
- expect(wrapper.length).toBe(1);
- });
-
- test('should display form fields properly', () => {
- expect(wrapper.find('FormGroup[label="Name"]').length).toBe(1);
- expect(
- wrapper.find('FormGroup[label="Policy instance minimum"]').length
- ).toBe(1);
- expect(
- wrapper.find('FormGroup[label="Policy instance percentage"]').length
- ).toBe(1);
- });
-
- test('should call onSubmit when form submitted', async () => {
- expect(onSubmit).not.toHaveBeenCalled();
- await act(async () => {
- wrapper.find('button[aria-label="Save"]').simulate('click');
- });
- expect(onSubmit).toHaveBeenCalledTimes(1);
- });
-
- test('should update form values', async () => {
- act(() => {
- wrapper.find('input#instance-group-name').simulate('change', {
- target: { value: 'Foo', name: 'name' },
- });
- wrapper
- .find('input#instance-group-policy-instance-minimum')
- .simulate('change', {
- target: { value: 10, name: 'policy_instance_minimum' },
- });
- });
- await act(async () => {
- wrapper.update();
- });
- expect(wrapper.find('input#instance-group-name').prop('value')).toEqual(
- 'Foo'
- );
- expect(
- wrapper.find('input#instance-group-policy-instance-minimum').prop('value')
- ).toEqual(10);
- expect(
- wrapper
- .find('input#instance-group-policy-instance-percentage')
- .prop('value')
- ).toEqual(46);
- });
-
- test('should call handleCancel when Cancel button is clicked', async () => {
- expect(onCancel).not.toHaveBeenCalled();
- wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
- expect(onCancel).toBeCalled();
- });
-});
diff --git a/awx/ui/src/screens/Instances/Instance.js b/awx/ui/src/screens/Instances/Instance.js
deleted file mode 100644
index cd1169962bf2..000000000000
--- a/awx/ui/src/screens/Instances/Instance.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { t } from '@lingui/macro';
-
-import { Switch, Route, Redirect, Link, useRouteMatch } from 'react-router-dom';
-import { CaretLeftIcon } from '@patternfly/react-icons';
-import { Card, PageSection } from '@patternfly/react-core';
-import ContentError from 'components/ContentError';
-import RoutedTabs from 'components/RoutedTabs';
-import useRequest from 'hooks/useRequest';
-import { SettingsAPI } from 'api';
-import ContentLoading from 'components/ContentLoading';
-import InstanceDetail from './InstanceDetail';
-import InstancePeerList from './InstancePeers';
-
-function Instance({ setBreadcrumb }) {
- const match = useRouteMatch();
- const tabsArray = [
- {
- name: (
- <>
-
- {t`Back to Instances`}
- >
- ),
- link: `/instances`,
- id: 99,
- isBackButton: true,
- },
- { name: t`Details`, link: `${match.url}/details`, id: 0 },
- ];
-
- const {
- result: { isK8s },
- error,
- isLoading,
- request,
- } = useRequest(
- useCallback(async () => {
- const { data } = await SettingsAPI.readCategory('system');
- return {
- isK8s: data.IS_K8S,
- };
- }, []),
- { isK8s: false, isLoading: true }
- );
- useEffect(() => {
- request();
- }, [request]);
-
- if (isK8s) {
- tabsArray.push({ name: t`Peers`, link: `${match.url}/peers`, id: 1 });
- }
- if (isLoading) {
- return ;
- }
-
- if (error) {
- return ;
- }
- return (
-
-
-
-
-
-
-
-
- {isK8s && (
-
-
-
- )}
-
-
- {match.params.id && (
-
- {t`View Instance Details`}
-
- )}
-
-
-
-
-
- );
-}
-
-export default Instance;
diff --git a/awx/ui/src/screens/Instances/InstanceAdd/InstanceAdd.js b/awx/ui/src/screens/Instances/InstanceAdd/InstanceAdd.js
deleted file mode 100644
index 1c0e86400dcb..000000000000
--- a/awx/ui/src/screens/Instances/InstanceAdd/InstanceAdd.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import React, { useState } from 'react';
-import { useHistory } from 'react-router-dom';
-import { Card, PageSection } from '@patternfly/react-core';
-import { InstancesAPI } from 'api';
-import InstanceForm from '../Shared/InstanceForm';
-
-function InstanceAdd() {
- const history = useHistory();
- const [formError, setFormError] = useState();
- const handleSubmit = async (values) => {
- try {
- const {
- data: { id },
- } = await InstancesAPI.create(values);
-
- history.push(`/instances/${id}/details`);
- } catch (err) {
- setFormError(err);
- }
- };
-
- const handleCancel = () => {
- history.push('/instances');
- };
-
- return (
-
-
-
-
-
- );
-}
-
-export default InstanceAdd;
diff --git a/awx/ui/src/screens/Instances/InstanceAdd/InstanceAdd.test.js b/awx/ui/src/screens/Instances/InstanceAdd/InstanceAdd.test.js
deleted file mode 100644
index e79b0471c839..000000000000
--- a/awx/ui/src/screens/Instances/InstanceAdd/InstanceAdd.test.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { InstancesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import InstanceAdd from './InstanceAdd';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
- let history;
-
- beforeEach(async () => {
- history = createMemoryHistory({ initialEntries: ['/instances'] });
- InstancesAPI.create.mockResolvedValue({ data: { id: 13 } });
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
- });
-
- test('Initially renders successfully', () => {
- expect(wrapper.length).toBe(1);
- });
- test('handleSubmit should call the api and redirect to details page', async () => {
- await waitForElement(wrapper, 'isLoading', (el) => el.length === 0);
- await act(async () => {
- wrapper.find('InstanceForm').prop('handleSubmit')({
- name: 'new Foo',
- node_type: 'hop',
- });
- });
- expect(InstancesAPI.create).toHaveBeenCalledWith({
- name: 'new Foo',
- node_type: 'hop',
- });
- expect(history.location.pathname).toBe('/instances/13/details');
- });
-
- test('handleCancel should return the user back to the instances list', async () => {
- await waitForElement(wrapper, 'isLoading', (el) => el.length === 0);
- await act(async () => {
- wrapper.find('Button[aria-label="Cancel"]').simulate('click');
- });
- expect(history.location.pathname).toEqual('/instances');
- });
-});
diff --git a/awx/ui/src/screens/Instances/InstanceAdd/index.js b/awx/ui/src/screens/Instances/InstanceAdd/index.js
deleted file mode 100644
index c6ddcff5bc0c..000000000000
--- a/awx/ui/src/screens/Instances/InstanceAdd/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InstanceAdd';
diff --git a/awx/ui/src/screens/Instances/InstanceDetail/InstanceDetail.js b/awx/ui/src/screens/Instances/InstanceDetail/InstanceDetail.js
deleted file mode 100644
index 7d727a1c88ee..000000000000
--- a/awx/ui/src/screens/Instances/InstanceDetail/InstanceDetail.js
+++ /dev/null
@@ -1,397 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-
-import { useHistory, useParams } from 'react-router-dom';
-import { t, Plural } from '@lingui/macro';
-import {
- Button,
- Progress,
- ProgressMeasureLocation,
- ProgressSize,
- CodeBlock,
- CodeBlockCode,
- Tooltip,
- Slider,
-} from '@patternfly/react-core';
-import { DownloadIcon, OutlinedClockIcon } from '@patternfly/react-icons';
-import styled from 'styled-components';
-
-import { useConfig } from 'contexts/Config';
-import { InstancesAPI } from 'api';
-import useDebounce from 'hooks/useDebounce';
-import AlertModal from 'components/AlertModal';
-import ErrorDetail from 'components/ErrorDetail';
-import InstanceToggle from 'components/InstanceToggle';
-import { CardBody, CardActionsRow } from 'components/Card';
-import getDocsBaseUrl from 'util/getDocsBaseUrl';
-import { formatDateString } from 'util/dates';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import { Detail, DetailList } from 'components/DetailList';
-import StatusLabel from 'components/StatusLabel';
-import useRequest, {
- useDeleteItems,
- useDismissableError,
-} from 'hooks/useRequest';
-import HealthCheckAlert from 'components/HealthCheckAlert';
-import InstanceGroupLabels from 'components/InstanceGroupLabels';
-import RemoveInstanceButton from '../Shared/RemoveInstanceButton';
-
-const Unavailable = styled.span`
- color: var(--pf-global--danger-color--200);
-`;
-
-const SliderHolder = styled.div`
- display: flex;
- align-items: center;
- justify-content: space-between;
-`;
-
-const SliderForks = styled.div`
- flex-grow: 1;
- margin-right: 8px;
- margin-left: 8px;
- text-align: center;
-`;
-
-function computeForks(memCapacity, cpuCapacity, selectedCapacityAdjustment) {
- const minCapacity = Math.min(memCapacity, cpuCapacity);
- const maxCapacity = Math.max(memCapacity, cpuCapacity);
-
- return Math.floor(
- minCapacity + (maxCapacity - minCapacity) * selectedCapacityAdjustment
- );
-}
-
-function InstanceDetail({ setBreadcrumb, isK8s }) {
- const config = useConfig();
-
- const { id } = useParams();
- const [forks, setForks] = useState();
- const history = useHistory();
- const [healthCheck, setHealthCheck] = useState({});
- const [showHealthCheckAlert, setShowHealthCheckAlert] = useState(false);
-
- const {
- isLoading,
- error: contentError,
- request: fetchDetails,
- result: { instance, instanceGroups },
- } = useRequest(
- useCallback(async () => {
- const [
- { data: details },
- {
- data: { results },
- },
- ] = await Promise.all([
- InstancesAPI.readDetail(id),
- InstancesAPI.readInstanceGroup(id),
- ]);
- if (details.node_type === 'execution') {
- const { data: healthCheckData } =
- await InstancesAPI.readHealthCheckDetail(id);
- setHealthCheck(healthCheckData);
- }
-
- setForks(
- computeForks(
- details.mem_capacity,
- details.cpu_capacity,
- details.capacity_adjustment
- )
- );
- return {
- instance: details,
- instanceGroups: results,
- };
- }, [id]),
- { instance: {}, instanceGroups: [] }
- );
- useEffect(() => {
- fetchDetails();
- }, [fetchDetails]);
-
- useEffect(() => {
- if (instance) {
- setBreadcrumb(instance);
- }
- }, [instance, setBreadcrumb]);
- const { error: healthCheckError, request: fetchHealthCheck } = useRequest(
- useCallback(async () => {
- const { status } = await InstancesAPI.healthCheck(id);
- if (status === 200) {
- setShowHealthCheckAlert(true);
- }
- }, [id])
- );
-
- const { error: updateInstanceError, request: updateInstance } = useRequest(
- useCallback(
- async (values) => {
- await InstancesAPI.update(id, values);
- },
- [id]
- )
- );
-
- const debounceUpdateInstance = useDebounce(updateInstance, 200);
-
- const handleChangeValue = (value) => {
- const roundedValue = Math.round(value * 100) / 100;
- setForks(
- computeForks(instance.mem_capacity, instance.cpu_capacity, roundedValue)
- );
- debounceUpdateInstance({ capacity_adjustment: roundedValue });
- };
-
- const formatHealthCheckTimeStamp = (last) => (
- <>
- {formatDateString(last)}
- {instance.health_check_pending ? (
- <>
- {' '}
-
- >
- ) : null}
- >
- );
-
- const { error, dismissError } = useDismissableError(
- updateInstanceError || healthCheckError
- );
- const {
- isLoading: isRemoveLoading,
- deleteItems: removeInstances,
- deletionError: removeError,
- clearDeletionError,
- } = useDeleteItems(
- async () => {
- await InstancesAPI.deprovisionInstance(instance.id);
- history.push('/instances');
- },
- {
- fetchItems: fetchDetails,
- }
- );
-
- if (contentError) {
- return ;
- }
- if (isLoading || isRemoveLoading) {
- return ;
- }
- const isHopNode = instance.node_type === 'hop';
- const isExecutionNode = instance.node_type === 'execution';
-
- return (
- <>
- {showHealthCheckAlert ? (
-
- ) : null}
-
-
-
-
- ) : null
- }
- />
-
- {!isHopNode && (
- <>
-
-
-
-
- {instanceGroups && (
-
- }
- isEmpty={instanceGroups.length === 0}
- />
- )}
-
- {t`Health checks are asynchronous tasks. See the`}{' '}
-
- {t`documentation`}
- {' '}
- {t`for more info.`}
- >
- }
- value={formatHealthCheckTimeStamp(instance.last_health_check)}
- />
- {instance.related?.install_bundle && (
-
-
-
- }
- />
- )}
-
- {t`CPU ${instance.cpu_capacity}`}
-
-
-
-
- {t`RAM ${instance.mem_capacity}`}
-
- }
- />
-
- ) : (
- {t`Unavailable`}
- )
- }
- />
- >
- )}
- {healthCheck?.errors && (
-
- {healthCheck?.errors}
-
- }
- />
- )}
-
- {!isHopNode && (
-
- {config?.me?.is_superuser && isK8s && isExecutionNode && (
-
- )}
- {isExecutionNode && (
-
-
-
- )}
-
-
- )}
-
- {error && (
-
- {updateInstanceError
- ? t`Failed to update capacity adjustment.`
- : t`Failed to disassociate one or more instances.`}
-
-
- )}
-
- {removeError && (
-
- {t`Failed to remove one or more instances.`}
-
-
- )}
-
- >
- );
-}
-
-export default InstanceDetail;
diff --git a/awx/ui/src/screens/Instances/InstanceDetail/InstanceDetail.test.js b/awx/ui/src/screens/Instances/InstanceDetail/InstanceDetail.test.js
deleted file mode 100644
index baa1ab3e54c3..000000000000
--- a/awx/ui/src/screens/Instances/InstanceDetail/InstanceDetail.test.js
+++ /dev/null
@@ -1,200 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import * as ConfigContext from 'contexts/Config';
-import useDebounce from 'hooks/useDebounce';
-import { InstancesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import InstanceDetail from './InstanceDetail';
-
-jest.mock('../../../api');
-jest.mock('../../../hooks/useDebounce');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useParams: () => ({
- id: 1,
- }),
-}));
-
-describe('', () => {
- let wrapper;
- beforeEach(() => {
- useDebounce.mockImplementation((fn) => fn);
-
- InstancesAPI.readDetail.mockResolvedValue({
- data: {
- related: {},
- id: 1,
- type: 'instance',
- url: '/api/v2/instances/1/',
- uuid: '00000000-0000-0000-0000-000000000000',
- hostname: 'awx_1',
- created: '2021-09-08T17:10:34.484569Z',
- modified: '2021-09-09T13:55:44.219900Z',
- last_seen: '2021-09-09T20:20:31.623148Z',
- last_health_check: '2021-09-09T20:20:31.623148Z',
- errors: '',
- capacity_adjustment: '1.00',
- version: '19.1.0',
- capacity: 38,
- consumed_capacity: 0,
- percent_capacity_remaining: 100.0,
- jobs_running: 0,
- jobs_total: 0,
- cpu: 8,
- memory: 6232231936,
- cpu_capacity: 32,
- mem_capacity: 38,
- enabled: true,
- managed_by_policy: true,
- node_type: 'execution',
- node_state: 'ready',
- health_check_pending: false,
- },
- });
- InstancesAPI.readInstanceGroup.mockResolvedValue({
- data: {
- results: [
- {
- id: 1,
- name: 'Foo',
- },
- ],
- },
- });
- InstancesAPI.readHealthCheckDetail.mockResolvedValue({
- data: {
- uuid: '00000000-0000-0000-0000-000000000000',
- hostname: 'awx_1',
- version: '19.1.0',
- last_health_check: '2021-09-10T16:16:19.729676Z',
- errors: '',
- cpu: 8,
- memory: 6232231936,
- cpu_capacity: 32,
- mem_capacity: 38,
- capacity: 38,
- },
- });
- });
- afterEach(() => {
- jest.clearAllMocks();
- wrapper.unmount();
- });
- test('Should render proper data', async () => {
- jest.spyOn(ConfigContext, 'useConfig').mockImplementation(() => ({
- me: { is_superuser: true },
- }));
- await act(async () => {
- wrapper = mountWithContexts( {}} />);
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('InstanceDetail')).toHaveLength(1);
-
- expect(InstancesAPI.readDetail).toBeCalledWith(1);
- expect(InstancesAPI.readHealthCheckDetail).toBeCalledWith(1);
- expect(
- wrapper.find("Button[ouiaId='health-check-button']").prop('isDisabled')
- ).toBe(false);
- });
-
- test('should calculate number of forks when slide changes', async () => {
- jest.spyOn(ConfigContext, 'useConfig').mockImplementation(() => ({
- me: { is_superuser: true },
- }));
- await act(async () => {
- wrapper = mountWithContexts( {}} />);
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
-
- expect(wrapper.find('InstanceDetail').length).toBe(1);
- expect(wrapper.find('div[data-cy="number-forks"]').text()).toContain(
- '38 forks'
- );
-
- await act(async () => {
- wrapper.find('Slider').prop('onChange')(4);
- });
-
- wrapper.update();
-
- expect(wrapper.find('div[data-cy="number-forks"]').text()).toContain(
- '56 forks'
- );
-
- await act(async () => {
- wrapper.find('Slider').prop('onChange')(0);
- });
- wrapper.update();
- expect(wrapper.find('div[data-cy="number-forks"]').text()).toContain(
- '32 forks'
- );
-
- await act(async () => {
- wrapper.find('Slider').prop('onChange')(0.5);
- });
- wrapper.update();
- expect(wrapper.find('div[data-cy="number-forks"]').text()).toContain(
- '35 forks'
- );
- });
-
- test('buttons should be disabled', async () => {
- jest.spyOn(ConfigContext, 'useConfig').mockImplementation(() => ({
- me: { is_system_auditor: true },
- }));
- await act(async () => {
- wrapper = mountWithContexts( {}} />);
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
-
- expect(
- wrapper.find("Button[ouiaId='health-check-button']").prop('isDisabled')
- ).toBe(true);
- });
-
- test('should display instance toggle', async () => {
- jest.spyOn(ConfigContext, 'useConfig').mockImplementation(() => ({
- me: { is_system_auditor: true },
- }));
- await act(async () => {
- wrapper = mountWithContexts( {}} />);
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('InstanceToggle').length).toBe(1);
- });
-
- test('Should handle api error for health check', async () => {
- InstancesAPI.healthCheck.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'post',
- url: '/api/v2/instances/1/health_check',
- },
- data: 'An error occurred',
- status: 403,
- },
- })
- );
- jest.spyOn(ConfigContext, 'useConfig').mockImplementation(() => ({
- me: { is_superuser: true },
- }));
- await act(async () => {
- wrapper = mountWithContexts( {}} />);
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(
- wrapper.find("Button[ouiaId='health-check-button']").prop('isDisabled')
- ).toBe(false);
- await act(async () => {
- wrapper.find("Button[ouiaId='health-check-button']").prop('onClick')();
- });
- expect(InstancesAPI.healthCheck).toBeCalledWith(1);
- wrapper.update();
- expect(wrapper.find('AlertModal')).toHaveLength(1);
- expect(wrapper.find('ErrorDetail')).toHaveLength(1);
- });
-});
diff --git a/awx/ui/src/screens/Instances/InstanceDetail/index.js b/awx/ui/src/screens/Instances/InstanceDetail/index.js
deleted file mode 100644
index 21bc62ec4a3b..000000000000
--- a/awx/ui/src/screens/Instances/InstanceDetail/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InstanceDetail';
diff --git a/awx/ui/src/screens/Instances/InstanceList/InstanceList.js b/awx/ui/src/screens/Instances/InstanceList/InstanceList.js
deleted file mode 100644
index c16ac3ffd431..000000000000
--- a/awx/ui/src/screens/Instances/InstanceList/InstanceList.js
+++ /dev/null
@@ -1,274 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { t } from '@lingui/macro';
-import { useLocation } from 'react-router-dom';
-import 'styled-components/macro';
-import { PageSection, Card } from '@patternfly/react-core';
-
-import useExpanded from 'hooks/useExpanded';
-import DataListToolbar from 'components/DataListToolbar';
-import PaginatedTable, {
- HeaderRow,
- HeaderCell,
- getSearchableKeys,
- ToolbarAddButton,
-} from 'components/PaginatedTable';
-import AlertModal from 'components/AlertModal';
-import ErrorDetail from 'components/ErrorDetail';
-import { useConfig } from 'contexts/Config';
-import useRequest, {
- useDismissableError,
- useDeleteItems,
-} from 'hooks/useRequest';
-import useSelected from 'hooks/useSelected';
-import { InstancesAPI, SettingsAPI } from 'api';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import HealthCheckButton from 'components/HealthCheckButton';
-import HealthCheckAlert from 'components/HealthCheckAlert';
-import InstanceListItem from './InstanceListItem';
-import RemoveInstanceButton from '../Shared/RemoveInstanceButton';
-
-const QS_CONFIG = getQSConfig('instance', {
- page: 1,
- page_size: 20,
- order_by: 'hostname',
-});
-
-function InstanceList() {
- const location = useLocation();
- const { me } = useConfig();
- const [showHealthCheckAlert, setShowHealthCheckAlert] = useState(false);
- const [pendingHealthCheck, setPendingHealthCheck] = useState(false);
- const [canRunHealthCheck, setCanRunHealthCheck] = useState(true);
-
- const {
- result: { instances, count, relatedSearchableKeys, searchableKeys, isK8s },
- error: contentError,
- isLoading,
- request: fetchInstances,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
- const [response, responseActions, sysSettings] = await Promise.all([
- InstancesAPI.read(params),
- InstancesAPI.readOptions(),
- SettingsAPI.readCategory('system'),
- ]);
- const isPending = response.data.results.some(
- (i) => i.health_check_pending === true
- );
- setPendingHealthCheck(isPending);
- return {
- instances: response.data.results,
- isK8s: sysSettings.data.IS_K8S,
- count: response.data.count,
- actions: responseActions.data.actions,
- relatedSearchableKeys: (
- responseActions?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(responseActions.data.actions?.GET),
- };
- }, [location.search]),
- {
- instances: [],
- count: 0,
- actions: {},
- relatedSearchableKeys: [],
- searchableKeys: [],
- isK8s: false,
- }
- );
-
- useEffect(() => {
- fetchInstances();
- }, [fetchInstances]);
-
- const { selected, isAllSelected, handleSelect, clearSelected, selectAll } =
- useSelected(instances.filter((i) => i.node_type !== 'hop'));
-
- const {
- error: healthCheckError,
- request: fetchHealthCheck,
- isLoading: isHealthCheckLoading,
- } = useRequest(
- useCallback(async () => {
- const [...response] = await Promise.all(
- selected
- .filter(({ node_type }) => node_type === 'execution')
- .map(({ id }) => InstancesAPI.healthCheck(id))
- );
- if (response) {
- setShowHealthCheckAlert(true);
- }
- }, [selected])
- );
-
- useEffect(() => {
- if (selected) {
- selected.forEach((i) => {
- if (i.node_type === 'execution') {
- setCanRunHealthCheck(true);
- } else {
- setCanRunHealthCheck(false);
- }
- });
- }
- }, [selected]);
-
- const handleHealthCheck = async () => {
- await fetchHealthCheck();
- clearSelected();
- };
-
- const { error, dismissError } = useDismissableError(healthCheckError);
-
- const { expanded, isAllExpanded, handleExpand, expandAll } =
- useExpanded(instances);
-
- const {
- isLoading: isRemoveLoading,
- deleteItems: handleRemoveInstances,
- deletionError: removeError,
- clearDeletionError,
- } = useDeleteItems(
- () =>
- Promise.all(
- selected.map(({ id }) => InstancesAPI.deprovisionInstance(id))
- ),
- { fetchItems: fetchInstances, qsConfig: QS_CONFIG }
- );
-
- return (
- <>
- {showHealthCheckAlert ? (
-
- ) : null}
-
-
- (
- ,
- ,
- ]
- : []),
- ,
- ]}
- />
- )}
- headerRow={
-
- {t`Name`}
- {t`Status`}
- {t`Node Type`}
- {t`Capacity Adjustment`}
- {t`Used Capacity`}
- {t`Actions`}
-
- }
- renderRow={(instance, index) => (
- row.id === instance.id)}
- onExpand={() => handleExpand(instance)}
- key={instance.id}
- value={instance.hostname}
- instance={instance}
- onSelect={() => {
- handleSelect(instance);
- }}
- isSelected={selected.some((row) => row.id === instance.id)}
- fetchInstances={fetchInstances}
- rowIndex={index}
- />
- )}
- />
-
-
- {error && (
-
- {t`Failed to run a health check on one or more instances.`}
-
-
- )}
- {removeError && (
-
- {t`Failed to remove one or more instances.`}
-
-
- )}
- >
- );
-}
-
-export default InstanceList;
diff --git a/awx/ui/src/screens/Instances/InstanceList/InstanceList.test.js b/awx/ui/src/screens/Instances/InstanceList/InstanceList.test.js
deleted file mode 100644
index db6ad983a037..000000000000
--- a/awx/ui/src/screens/Instances/InstanceList/InstanceList.test.js
+++ /dev/null
@@ -1,246 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Route } from 'react-router-dom';
-import { createMemoryHistory } from 'history';
-
-import { InstancesAPI, SettingsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import InstanceList from './InstanceList';
-
-jest.mock('../../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useParams: () => ({
- id: 1,
- }),
-}));
-
-const instances = [
- {
- id: 1,
- type: 'instance',
- url: '/api/v2/instances/1/',
- capacity_adjustment: '0.40',
- version: '13.0.0',
- capacity: 10,
- consumed_capacity: 0,
- percent_capacity_remaining: 60.0,
- jobs_running: 0,
- jobs_total: 68,
- cpu: 6,
- node_type: 'execution',
- node_state: 'ready',
- memory: 2087469056,
- cpu_capacity: 24,
- mem_capacity: 1,
- enabled: true,
- managed_by_policy: true,
- },
- {
- id: 2,
- type: 'instance',
- url: '/api/v2/instances/2/',
- capacity_adjustment: '0.40',
- version: '13.0.0',
- capacity: 10,
- consumed_capacity: 0,
- percent_capacity_remaining: 60.0,
- jobs_running: 0,
- jobs_total: 68,
- cpu: 6,
- node_type: 'execution',
- node_state: 'ready',
- memory: 2087469056,
- cpu_capacity: 24,
- mem_capacity: 1,
- enabled: true,
- managed_by_policy: false,
- },
- {
- id: 3,
- type: 'instance',
- url: '/api/v2/instances/3/',
- capacity_adjustment: '0.40',
- version: '13.0.0',
- capacity: 10,
- consumed_capacity: 0,
- percent_capacity_remaining: 60.0,
- jobs_running: 0,
- jobs_total: 68,
- cpu: 6,
- node_type: 'execution',
- node_state: 'ready',
- memory: 2087469056,
- cpu_capacity: 24,
- mem_capacity: 1,
- enabled: false,
- managed_by_policy: true,
- },
- {
- id: 4,
- type: 'instance',
- url: '/api/v2/instances/4/',
- capacity_adjustment: '0.40',
- version: '13.0.0',
- capacity: 10,
- consumed_capacity: 0,
- percent_capacity_remaining: 60.0,
- jobs_running: 0,
- jobs_total: 68,
- cpu: 6,
- node_type: 'hop',
- node_state: 'ready',
- memory: 2087469056,
- cpu_capacity: 24,
- mem_capacity: 1,
- enabled: false,
- managed_by_policy: true,
- },
-];
-
-describe('', () => {
- let wrapper;
-
- const options = { data: { actions: { POST: true } } };
-
- beforeEach(async () => {
- InstancesAPI.read.mockResolvedValue({
- data: {
- count: instances.length,
- results: instances,
- },
- });
- InstancesAPI.readOptions.mockResolvedValue(options);
- SettingsAPI.readCategory.mockResolvedValue({ data: { IS_K8S: false } });
- const history = createMemoryHistory({
- initialEntries: ['/instances/1'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- ,
- {
- context: {
- router: { history, route: { location: history.location } },
- },
- }
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should have data fetched', () => {
- expect(wrapper.find('InstanceList').length).toBe(1);
- });
-
- test('should fetch instances from the api and render them in the list', () => {
- expect(InstancesAPI.read).toHaveBeenCalled();
- expect(InstancesAPI.readOptions).toHaveBeenCalled();
- expect(wrapper.find('InstanceListItem').length).toBe(4);
- });
-
- test('Should run health check', async () => {
- // Ensures health check button is disabled on mount
- expect(
- wrapper.find('Button[ouiaId="health-check"]').prop('isDisabled')
- ).toBe(true);
- await act(async () =>
- wrapper.find('DataListToolbar').prop('onSelectAll')(instances)
- );
- wrapper.update();
- await act(async () =>
- wrapper.find('input[aria-label="Select row 3"]').prop('onChange')(false)
- );
- wrapper.update();
- await act(async () =>
- wrapper.find('Button[ouiaId="health-check"]').prop('onClick')()
- );
- expect(InstancesAPI.healthCheck).toBeCalledTimes(3);
- });
- test('Should render health check error', async () => {
- InstancesAPI.healthCheck.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'create',
- url: '/api/v2/instances',
- },
- data: 'An error occurred',
- status: 403,
- },
- })
- );
- expect(
- wrapper.find('Button[ouiaId="health-check"]').prop('isDisabled')
- ).toBe(true);
- await act(async () =>
- wrapper.find('input[aria-label="Select row 1"]').prop('onChange')(true)
- );
- wrapper.update();
- expect(
- wrapper.find('Button[ouiaId="health-check"]').prop('isDisabled')
- ).toBe(false);
- await act(async () =>
- wrapper.find('Button[ouiaId="health-check"]').prop('onClick')()
- );
- wrapper.update();
- expect(wrapper.find('AlertModal')).toHaveLength(1);
- });
- test('Should not show Add button', () => {
- expect(wrapper.find('Button[ouiaId="instances-add-button"]')).toHaveLength(
- 0
- );
- });
-});
-
-describe('InstanceList should show Add button', () => {
- let wrapper;
-
- const options = { data: { actions: { POST: true } } };
-
- beforeEach(async () => {
- InstancesAPI.read.mockResolvedValue({
- data: {
- count: instances.length,
- results: instances,
- },
- });
- InstancesAPI.readOptions.mockResolvedValue(options);
- SettingsAPI.readCategory.mockResolvedValue({ data: { IS_K8S: true } });
- const history = createMemoryHistory({
- initialEntries: ['/instances/1'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- ,
- {
- context: {
- router: { history, route: { location: history.location } },
- },
- }
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('Should show Add button', () => {
- expect(wrapper.find('Button[ouiaId="instances-add-button"]')).toHaveLength(
- 1
- );
- });
-});
diff --git a/awx/ui/src/screens/Instances/InstanceList/InstanceListItem.js b/awx/ui/src/screens/Instances/InstanceList/InstanceListItem.js
deleted file mode 100644
index 6b27f3a2252c..000000000000
--- a/awx/ui/src/screens/Instances/InstanceList/InstanceListItem.js
+++ /dev/null
@@ -1,283 +0,0 @@
-import React, { useState, useCallback } from 'react';
-import { Link } from 'react-router-dom';
-import { bool, func } from 'prop-types';
-import { t, Plural } from '@lingui/macro';
-import styled from 'styled-components';
-import 'styled-components/macro';
-import {
- Progress,
- ProgressMeasureLocation,
- ProgressSize,
- Slider,
- Tooltip,
-} from '@patternfly/react-core';
-import { OutlinedClockIcon } from '@patternfly/react-icons';
-import { Tr, Td, ExpandableRowContent } from '@patternfly/react-table';
-import getDocsBaseUrl from 'util/getDocsBaseUrl';
-import { formatDateString } from 'util/dates';
-import computeForks from 'util/computeForks';
-import { ActionsTd, ActionItem } from 'components/PaginatedTable';
-import InstanceToggle from 'components/InstanceToggle';
-import StatusLabel from 'components/StatusLabel';
-import { Instance } from 'types';
-import useRequest, { useDismissableError } from 'hooks/useRequest';
-import useDebounce from 'hooks/useDebounce';
-import { InstancesAPI } from 'api';
-import { useConfig } from 'contexts/Config';
-import AlertModal from 'components/AlertModal';
-import ErrorDetail from 'components/ErrorDetail';
-import { Detail, DetailList } from 'components/DetailList';
-
-const Unavailable = styled.span`
- color: var(--pf-global--danger-color--200);
-`;
-
-const SliderHolder = styled.div`
- display: flex;
- align-items: center;
- justify-content: space-between;
-`;
-
-const SliderForks = styled.div`
- flex-grow: 1;
- margin-right: 8px;
- margin-left: 8px;
- text-align: center;
-`;
-
-function InstanceListItem({
- instance,
- isExpanded,
- onExpand,
- isSelected,
- onSelect,
- fetchInstances,
- rowIndex,
-}) {
- const config = useConfig();
- const [forks, setForks] = useState(
- computeForks(
- instance.mem_capacity,
- instance.cpu_capacity,
- instance.capacity_adjustment
- )
- );
-
- const labelId = `check-action-${instance.id}`;
-
- function usedCapacity(item) {
- if (item.enabled) {
- return (
-
- );
- }
- return {t`Unavailable`};
- }
-
- const { error: updateInstanceError, request: updateInstance } = useRequest(
- useCallback(
- async (values) => {
- await InstancesAPI.update(instance.id, values);
- },
- [instance]
- )
- );
-
- const { error: updateError, dismissError: dismissUpdateError } =
- useDismissableError(updateInstanceError);
-
- const debounceUpdateInstance = useDebounce(updateInstance, 200);
-
- const handleChangeValue = (value) => {
- const roundedValue = Math.round(value * 100) / 100;
- setForks(
- computeForks(instance.mem_capacity, instance.cpu_capacity, roundedValue)
- );
- debounceUpdateInstance({ capacity_adjustment: roundedValue });
- };
-
- const formatHealthCheckTimeStamp = (last) => (
- <>
- {formatDateString(last)}
- {instance.health_check_pending ? (
- <>
- {' '}
-
- >
- ) : null}
- >
- );
-
- const isHopNode = instance.node_type === 'hop';
- const isExecutionNode = instance.node_type === 'execution';
- return (
- <>
-
- {isHopNode ? (
- |
- ) : (
- |
- )}
- |
-
-
- {instance.hostname}
-
- |
-
-
-
- {t`Last Health Check`}
-
- {formatDateString(
- instance.last_health_check ?? instance.last_seen
- )}
-
- }
- >
-
-
- |
-
- {instance.node_type} |
- {!isHopNode && (
- <>
-
-
- {t`CPU ${instance.cpu_capacity}`}
-
-
-
-
- {t`RAM ${instance.mem_capacity}`}
-
- |
-
-
- {usedCapacity(instance)}
- |
-
-
-
-
-
-
- >
- )}
-
- {!isHopNode && (
-
- |
-
-
-
-
-
-
-
- {t`Health checks are asynchronous tasks. See the`}{' '}
-
- {t`documentation`}
- {' '}
- {t`for more info.`}
- >
- }
- value={formatHealthCheckTimeStamp(instance.last_health_check)}
- />
-
-
- |
-
- )}
- {updateError && (
-
- {t`Failed to update capacity adjustment.`}
-
-
- )}
- >
- );
-}
-
-InstanceListItem.prototype = {
- instance: Instance.isRequired,
- isSelected: bool.isRequired,
- onSelect: func.isRequired,
-};
-
-export default InstanceListItem;
diff --git a/awx/ui/src/screens/Instances/InstanceList/InstanceListItem.test.js b/awx/ui/src/screens/Instances/InstanceList/InstanceListItem.test.js
deleted file mode 100644
index add1ffbe1241..000000000000
--- a/awx/ui/src/screens/Instances/InstanceList/InstanceListItem.test.js
+++ /dev/null
@@ -1,300 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { InstancesAPI } from 'api';
-import useDebounce from 'hooks/useDebounce';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import InstanceListItem from './InstanceListItem';
-
-jest.mock('../../../api');
-jest.mock('../../../hooks/useDebounce');
-
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useParams: () => ({
- id: 1,
- }),
-}));
-const instance = [
- {
- id: 1,
- type: 'instance',
- url: '/api/v2/instances/1/',
- uuid: '00000000-0000-0000-0000-000000000000',
- hostname: 'awx',
- created: '2020-07-14T19:03:49.000054Z',
- modified: '2020-08-12T20:08:02.836748Z',
- capacity_adjustment: '0.40',
- version: '13.0.0',
- capacity: 10,
- consumed_capacity: 0,
- percent_capacity_remaining: 60.0,
- jobs_running: 0,
- jobs_total: 68,
- last_health_check: '2021-09-15T18:02:07.270664Z',
- cpu: 6,
- memory: 2087469056,
- cpu_capacity: 24,
- mem_capacity: 1,
- enabled: true,
- managed_by_policy: true,
- node_type: 'hybrid',
- node_state: 'ready',
- },
- {
- id: 2,
- type: 'instance',
- url: '/api/v2/instances/1/',
- uuid: '00000000-0000-0000-0000-000000000001',
- hostname: 'awx-control',
- created: '2020-07-14T19:03:49.000054Z',
- modified: '2020-08-12T20:08:02.836748Z',
- capacity_adjustment: '0.40',
- version: '13.0.0',
- last_health_check: '2021-09-15T18:02:07.270664Z',
- capacity: 10,
- consumed_capacity: 0,
- percent_capacity_remaining: 60.0,
- jobs_running: 0,
- jobs_total: 68,
- cpu: 6,
- memory: 2087469056,
- cpu_capacity: 24,
- mem_capacity: 1,
- enabled: true,
- managed_by_policy: true,
- node_type: 'hop',
- node_state: 'ready',
- },
-];
-
-describe('', () => {
- let wrapper;
-
- beforeEach(() => {
- useDebounce.mockImplementation((fn) => fn);
- });
-
- test('should mount successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- {}}
- fetchInstances={() => {}}
- />
-
-
- );
- });
- expect(wrapper.find('InstanceListItem').length).toBe(1);
- });
-
- test('should calculate number of forks when slide changes', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- {}}
- fetchInstances={() => {}}
- />
-
-
- );
- });
- expect(wrapper.find('InstanceListItem').length).toBe(1);
- expect(wrapper.find('InstanceListItem__SliderForks').text()).toContain(
- '10 forks'
- );
-
- await act(async () => {
- wrapper.find('Slider').prop('onChange')(1);
- });
-
- wrapper.update();
- expect(wrapper.find('InstanceListItem__SliderForks').text()).toContain(
- '24 forks'
- );
-
- await act(async () => {
- wrapper.find('Slider').prop('onChange')(0);
- });
- wrapper.update();
- expect(wrapper.find('InstanceListItem__SliderForks').text()).toContain(
- '1 fork'
- );
-
- await act(async () => {
- wrapper.find('Slider').prop('onChange')(0.5);
- });
- wrapper.update();
- expect(wrapper.find('InstanceListItem__SliderForks').text()).toContain(
- '12 forks'
- );
- });
-
- test('should render the proper data instance', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- {}}
- fetchInstances={() => {}}
- />
-
-
- );
- });
- expect(wrapper.find('Td[dataLabel="Name"]').find('Link').prop('to')).toBe(
- '/instances/1/details'
- );
- expect(wrapper.find('Td').at(2).text()).toBe('awx');
- expect(wrapper.find('Progress').prop('value')).toBe(40);
- expect(
- wrapper
- .find('Td')
- .at(5)
- .containsMatchingElement(CPU 24
)
- );
- expect(
- wrapper
- .find('Td')
- .at(5)
- .containsMatchingElement(RAM 24
)
- );
- expect(wrapper.find('InstanceListItem__SliderForks').text()).toContain(
- '10 forks'
- );
- });
-
- test('should render checkbox', async () => {
- const onSelect = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('Td').at(1).prop('select').onSelect).toEqual(onSelect);
- });
-
- test('should display instance toggle', () => {
- expect(wrapper.find('InstanceToggle').length).toBe(1);
- });
-
- test('should display error', async () => {
- jest.useFakeTimers();
- InstancesAPI.update.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'patch',
- url: '/api/v2/instances/1',
- data: { capacity_adjustment: 0.30001 },
- },
- data: {
- capacity_adjustment: [
- 'Ensure that there are no more than 3 digits in total.',
- ],
- },
- status: 400,
- statusText: 'Bad Request',
- },
- })
- );
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- {}}
- fetchInstances={() => {}}
- />
-
-
,
- { context: { network: { handleHttpError: () => {} } } }
- );
- });
- await act(async () => {
- wrapper.update();
- });
- expect(wrapper.find('ErrorDetail').length).toBe(0);
- await act(async () => {
- wrapper.find('Slider').prop('onChange')(0.30001);
- });
- await act(async () => {
- wrapper.update();
- });
- jest.advanceTimersByTime(210);
- await act(async () => {
- wrapper.update();
- });
- expect(wrapper.find('ErrorDetail').length).toBe(1);
- });
-
- test('Should render expanded row with the correct data points', async () => {
- const onSelect = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- {}}
- isExpanded
- />
-
-
- );
- });
- expect(wrapper.find('InstanceListItem').prop('isExpanded')).toBe(true);
-
- expect(wrapper.find('Detail[label="Policy Type"]').prop('value')).toBe(
- 'Auto'
- );
- expect(wrapper.find('Detail[label="Last Health Check"]').text()).toBe(
- 'Last Health Check9/15/2021, 6:02:07 PM'
- );
- });
- test('Hop should not render some things', async () => {
- const onSelect = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('InstanceToggle').length).toBe(0);
- expect(
- wrapper.find("Td[dataLabel='Instance group used capacity']").length
- ).toBe(0);
- expect(wrapper.find("Td[dataLabel='Capacity Adjustment']").length).toBe(0);
- });
-});
diff --git a/awx/ui/src/screens/Instances/InstanceList/index.js b/awx/ui/src/screens/Instances/InstanceList/index.js
deleted file mode 100644
index 2567e3c8e749..000000000000
--- a/awx/ui/src/screens/Instances/InstanceList/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as InstanceList } from './InstanceList';
-export { default as InstanceListItem } from './InstanceListItem';
diff --git a/awx/ui/src/screens/Instances/InstancePeers/InstancePeerList.js b/awx/ui/src/screens/Instances/InstancePeers/InstancePeerList.js
deleted file mode 100644
index a1d104d6672c..000000000000
--- a/awx/ui/src/screens/Instances/InstancePeers/InstancePeerList.js
+++ /dev/null
@@ -1,123 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { t } from '@lingui/macro';
-import { CardBody } from 'components/Card';
-import PaginatedTable, {
- getSearchableKeys,
- HeaderCell,
- HeaderRow,
-} from 'components/PaginatedTable';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import { useLocation, useParams } from 'react-router-dom';
-import useRequest from 'hooks/useRequest';
-import DataListToolbar from 'components/DataListToolbar';
-import { InstancesAPI } from 'api';
-import useExpanded from 'hooks/useExpanded';
-import InstancePeerListItem from './InstancePeerListItem';
-
-const QS_CONFIG = getQSConfig('peer', {
- page: 1,
- page_size: 20,
- order_by: 'hostname',
-});
-
-function InstancePeerList() {
- const location = useLocation();
- const { id } = useParams();
- const {
- isLoading,
- error: contentError,
- request: fetchPeers,
- result: { peers, count, relatedSearchableKeys, searchableKeys },
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
- const [
- {
- data: { results, count: itemNumber },
- },
- actions,
- ] = await Promise.all([
- InstancesAPI.readPeers(id, params),
- InstancesAPI.readOptions(),
- ]);
- return {
- peers: results,
- count: itemNumber,
- relatedSearchableKeys: (actions?.data?.related_search_fields || []).map(
- (val) => val.slice(0, -8)
- ),
- searchableKeys: getSearchableKeys(actions.data.actions?.GET),
- };
- }, [id, location]),
- {
- peers: [],
- count: 0,
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- useEffect(() => {
- fetchPeers();
- }, [fetchPeers]);
-
- const { expanded, isAllExpanded, handleExpand, expandAll } =
- useExpanded(peers);
-
- return (
-
-
- {t`Name`}
- {t`Status`}
- {t`Node Type`}
-
- }
- renderToolbar={(props) => (
-
- )}
- renderRow={(peer, index) => (
- row.id === peer.id)}
- onExpand={() => handleExpand(peer)}
- key={peer.id}
- peerInstance={peer}
- rowIndex={index}
- />
- )}
- />
-
- );
-}
-
-export default InstancePeerList;
diff --git a/awx/ui/src/screens/Instances/InstancePeers/InstancePeerListItem.js b/awx/ui/src/screens/Instances/InstancePeers/InstancePeerListItem.js
deleted file mode 100644
index cce09300b042..000000000000
--- a/awx/ui/src/screens/Instances/InstancePeers/InstancePeerListItem.js
+++ /dev/null
@@ -1,99 +0,0 @@
-import React from 'react';
-import { Link } from 'react-router-dom';
-import { t } from '@lingui/macro';
-import 'styled-components/macro';
-import { Tooltip } from '@patternfly/react-core';
-import { Tr, Td, ExpandableRowContent } from '@patternfly/react-table';
-import { formatDateString } from 'util/dates';
-import StatusLabel from 'components/StatusLabel';
-import { Detail, DetailList } from 'components/DetailList';
-
-function InstancePeerListItem({
- peerInstance,
- isExpanded,
- onExpand,
- rowIndex,
-}) {
- const labelId = `check-action-${peerInstance.id}`;
- const isHopNode = peerInstance.node_type === 'hop';
- return (
- <>
-
- {isHopNode ? (
- |
- ) : (
- |
- )}
- |
-
-
- {peerInstance.hostname}
-
- |
-
-
-
- {t`Last Health Check`}
-
- {formatDateString(
- peerInstance.last_health_check ?? peerInstance.last_seen
- )}
-
- }
- >
-
-
- |
-
- {peerInstance.node_type} |
-
- {!isHopNode && (
-
- |
-
-
-
-
-
-
-
-
-
- |
-
- )}
- >
- );
-}
-
-export default InstancePeerListItem;
diff --git a/awx/ui/src/screens/Instances/InstancePeers/index.js b/awx/ui/src/screens/Instances/InstancePeers/index.js
deleted file mode 100644
index 1be96e838104..000000000000
--- a/awx/ui/src/screens/Instances/InstancePeers/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InstancePeerList';
diff --git a/awx/ui/src/screens/Instances/Instances.js b/awx/ui/src/screens/Instances/Instances.js
deleted file mode 100644
index ca42498e417b..000000000000
--- a/awx/ui/src/screens/Instances/Instances.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import React, { useCallback, useState } from 'react';
-
-import { t } from '@lingui/macro';
-import { Route, Switch } from 'react-router-dom';
-import ScreenHeader from 'components/ScreenHeader';
-import PersistentFilters from 'components/PersistentFilters';
-import { InstanceList } from './InstanceList';
-import Instance from './Instance';
-import InstanceAdd from './InstanceAdd';
-
-function Instances() {
- const [breadcrumbConfig, setBreadcrumbConfig] = useState({
- '/instances': t`Instances`,
- '/instances/add': t`Create new Instance`,
- });
-
- const buildBreadcrumbConfig = useCallback((instance) => {
- if (!instance) {
- return;
- }
- setBreadcrumbConfig({
- '/instances': t`Instances`,
- [`/instances/${instance.id}`]: `${instance.hostname}`,
- [`/instances/${instance.id}/details`]: t`Details`,
- });
- }, []);
-
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-}
-
-export default Instances;
diff --git a/awx/ui/src/screens/Instances/Shared/InstanceForm.js b/awx/ui/src/screens/Instances/Shared/InstanceForm.js
deleted file mode 100644
index 53fefbef7cd3..000000000000
--- a/awx/ui/src/screens/Instances/Shared/InstanceForm.js
+++ /dev/null
@@ -1,104 +0,0 @@
-import React from 'react';
-import { t } from '@lingui/macro';
-import { Formik } from 'formik';
-import { Form, FormGroup, CardBody } from '@patternfly/react-core';
-import { FormColumnLayout } from 'components/FormLayout';
-import FormField, {
- FormSubmitError,
- CheckboxField,
-} from 'components/FormField';
-import FormActionGroup from 'components/FormActionGroup';
-import { required } from 'util/validators';
-
-function InstanceFormFields() {
- return (
- <>
-
-
-
-
-
-
-
-
- >
- );
-}
-
-function InstanceForm({
- instance = {},
- submitError,
- handleCancel,
- handleSubmit,
-}) {
- return (
-
- {
- handleSubmit(values);
- }}
- >
- {(formik) => (
-
- )}
-
-
- );
-}
-
-export default InstanceForm;
diff --git a/awx/ui/src/screens/Instances/Shared/InstanceForm.test.js b/awx/ui/src/screens/Instances/Shared/InstanceForm.test.js
deleted file mode 100644
index c2cf6d9a0aec..000000000000
--- a/awx/ui/src/screens/Instances/Shared/InstanceForm.test.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import InstanceForm from './InstanceForm';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
- let handleCancel;
- let handleSubmit;
-
- beforeAll(async () => {
- handleCancel = jest.fn();
- handleSubmit = jest.fn();
-
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('Initially renders successfully', () => {
- expect(wrapper.length).toBe(1);
- });
-
- test('should display form fields properly', async () => {
- await waitForElement(wrapper, 'InstanceForm', (el) => el.length > 0);
- expect(wrapper.find('FormGroup[label="Host Name"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Description"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Instance State"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Listener Port"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Instance Type"]').length).toBe(1);
- });
-
- test('should update form values', async () => {
- await act(async () => {
- wrapper.find('input#hostname').simulate('change', {
- target: { value: 'new Foo', name: 'hostname' },
- });
- });
-
- wrapper.update();
- expect(wrapper.find('input#hostname').prop('value')).toEqual('new Foo');
- });
-
- test('should call handleCancel when Cancel button is clicked', async () => {
- expect(handleCancel).not.toHaveBeenCalled();
- wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
- wrapper.update();
- expect(handleCancel).toBeCalled();
- });
-
- test('should call handleSubmit when Cancel button is clicked', async () => {
- expect(handleSubmit).not.toHaveBeenCalled();
- await act(async () => {
- wrapper.find('input#hostname').simulate('change', {
- target: { value: 'new Foo', name: 'hostname' },
- });
- wrapper.find('input#instance-description').simulate('change', {
- target: { value: 'This is a repeat song', name: 'description' },
- });
- wrapper.find('input#instance-port').simulate('change', {
- target: { value: 'This is a repeat song', name: 'listener_port' },
- });
- });
- wrapper.update();
- expect(
- wrapper.find('FormField[label="Instance State"]').prop('isDisabled')
- ).toBe(true);
- await act(async () => {
- wrapper.find('button[aria-label="Save"]').invoke('onClick')();
- });
-
- expect(handleSubmit).toBeCalledWith({
- description: 'This is a repeat song',
- enabled: true,
- hostname: 'new Foo',
- listener_port: 'This is a repeat song',
- node_state: 'installed',
- node_type: 'execution',
- });
- });
-});
diff --git a/awx/ui/src/screens/Instances/Shared/RemoveInstanceButton.js b/awx/ui/src/screens/Instances/Shared/RemoveInstanceButton.js
deleted file mode 100644
index b6b1fb298608..000000000000
--- a/awx/ui/src/screens/Instances/Shared/RemoveInstanceButton.js
+++ /dev/null
@@ -1,198 +0,0 @@
-import React, { useContext, useState, useEffect } from 'react';
-import { t, Plural } from '@lingui/macro';
-import { KebabifiedContext } from 'contexts/Kebabified';
-import {
- getRelatedResourceDeleteCounts,
- relatedResourceDeleteRequests,
-} from 'util/getRelatedResourceDeleteDetails';
-import {
- Button,
- DropdownItem,
- Tooltip,
- Alert,
- Badge,
-} from '@patternfly/react-core';
-import AlertModal from 'components/AlertModal';
-import styled from 'styled-components';
-import ErrorDetail from 'components/ErrorDetail';
-
-const WarningMessage = styled(Alert)`
- margin-top: 10px;
-`;
-
-const Label = styled.span`
- && {
- margin-right: 10px;
- }
-`;
-
-function RemoveInstanceButton({ itemsToRemove, onRemove, isK8s }) {
- const { isKebabified, onKebabModalChange } = useContext(KebabifiedContext);
- const [removeMessageError, setRemoveMessageError] = useState(null);
- const [isModalOpen, setIsModalOpen] = useState(false);
- const [removeDetails, setRemoveDetails] = useState(null);
- const [isLoading, setIsLoading] = useState(false);
-
- const cannotRemove = (item) => item.node_type !== 'execution';
-
- const toggleModal = async (isOpen) => {
- setRemoveDetails(null);
- setIsLoading(true);
- if (isOpen && itemsToRemove.length > 0) {
- const { results, error } = await getRelatedResourceDeleteCounts(
- relatedResourceDeleteRequests.instance(itemsToRemove[0])
- );
-
- if (error) {
- setRemoveMessageError(error);
- } else {
- setRemoveDetails(results);
- }
- }
- setIsModalOpen(isOpen);
- setIsLoading(false);
- };
-
- const handleRemove = async () => {
- await onRemove();
- toggleModal(false);
- };
- useEffect(() => {
- if (isKebabified) {
- onKebabModalChange(isModalOpen);
- }
- }, [isKebabified, isModalOpen, onKebabModalChange]);
-
- const renderTooltip = () => {
- const itemsUnableToremove = itemsToRemove
- .filter(cannotRemove)
- .map((item) => item.hostname)
- .join(', ');
- if (itemsToRemove.some(cannotRemove)) {
- return t`You do not have permission to remove instances: ${itemsUnableToremove}`;
- }
- if (itemsToRemove.length) {
- return t`Remove`;
- }
- return t`Select a row to remove`;
- };
-
- const isDisabled =
- itemsToRemove.length === 0 || itemsToRemove.some(cannotRemove);
-
- const buildRemoveWarning = () => (
-
-
- {removeDetails &&
- Object.entries(removeDetails).map(([key, value]) => (
-
-
- {value}
-
- ))}
-
- );
-
- if (removeMessageError) {
- return (
- {
- toggleModal(false);
- setRemoveMessageError();
- }}
- >
-
-
- );
- }
- return (
- <>
- {isKebabified ? (
-
- {
- toggleModal(true);
- }}
- >
- {t`Remove`}
-
-
- ) : (
-
-
-
-
-
- )}
-
- {isModalOpen && (
- toggleModal(false)}
- actions={[
- ,
- ,
- ]}
- >
- {t`This action will remove the following instances:`}
- {itemsToRemove.map((item) => (
-
- {item.hostname}
-
-
- ))}
- {removeDetails && (
-
- )}
-
- )}
- >
- );
-}
-
-export default RemoveInstanceButton;
diff --git a/awx/ui/src/screens/Instances/Shared/RemoveInstanceButton.test.js b/awx/ui/src/screens/Instances/Shared/RemoveInstanceButton.test.js
deleted file mode 100644
index 3d2d467956fd..000000000000
--- a/awx/ui/src/screens/Instances/Shared/RemoveInstanceButton.test.js
+++ /dev/null
@@ -1,133 +0,0 @@
-import React from 'react';
-import { within, render, screen, waitFor } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import { InstanceGroupsAPI } from 'api';
-import RemoveInstanceButton from './RemoveInstanceButton';
-import { I18nProvider } from '@lingui/react';
-import { i18n } from '@lingui/core';
-import { en } from 'make-plural/plurals';
-import english from '../../../../src/locales/en/messages';
-
-jest.mock('api');
-
-const instances = [
- {
- id: 1,
- type: 'instance',
- url: '/api/v2/instances/1/',
- capacity_adjustment: '0.40',
- version: '13.0.0',
- capacity: 10,
- consumed_capacity: 0,
- percent_capacity_remaining: 60.0,
- jobs_running: 0,
- jobs_total: 68,
- cpu: 6,
- node_type: 'execution',
- node_state: 'ready',
- memory: 2087469056,
- cpu_capacity: 24,
- mem_capacity: 1,
- enabled: true,
- managed_by_policy: true,
- },
- {
- id: 2,
- type: 'instance',
- url: '/api/v2/instances/2/',
- capacity_adjustment: '0.40',
- version: '13.0.0',
- capacity: 10,
- consumed_capacity: 0,
- percent_capacity_remaining: 60.0,
- jobs_running: 0,
- jobs_total: 68,
- cpu: 6,
- node_type: 'control',
- node_state: 'ready',
- memory: 2087469056,
- cpu_capacity: 24,
- mem_capacity: 1,
- enabled: true,
- managed_by_policy: false,
- },
-];
-describe('', () => {
- test('Should open modal and deprovision node', async () => {
- i18n.loadLocaleData({ en: { plurals: en } });
- i18n.load({ en: english });
- i18n.activate('en');
- InstanceGroupsAPI.read.mockResolvedValue({
- data: { results: [{ id: 1 }], count: 1 },
- });
- const user = userEvent.setup();
- const onRemove = jest.fn();
- render(
-
-
-
- );
-
- const button = screen.getByRole('button');
- await user.click(button);
- await waitFor(() => screen.getByRole('dialog'));
- const modal = screen.getByRole('dialog');
- const removeButton = within(modal).getByRole('button', {
- name: 'Confirm remove',
- });
-
- await user.click(removeButton);
-
- await waitFor(() => expect(onRemove).toBeCalled());
- });
-
- test('Should be disabled', async () => {
- const user = userEvent.setup();
- render(
-
- );
-
- const button = screen.getByRole('button');
- await user.hover(button);
- await waitFor(() =>
- screen.getByText('You do not have permission to remove instances:')
- );
- });
-
- test('Should handle error when fetching warning message details.', async () => {
- InstanceGroupsAPI.read.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'get',
- url: '/api/v2/instance_groups',
- },
- data: 'An error occurred',
- status: 403,
- },
- })
- );
- const user = userEvent.setup();
- const onRemove = jest.fn();
- render(
-
- );
-
- const button = screen.getByRole('button');
- await user.click(button);
- await waitFor(() => screen.getByRole('dialog'));
- screen.getByText('Error!');
- });
-});
diff --git a/awx/ui/src/screens/Instances/index.js b/awx/ui/src/screens/Instances/index.js
deleted file mode 100644
index b018ebb04930..000000000000
--- a/awx/ui/src/screens/Instances/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './Instances';
diff --git a/awx/ui/src/screens/Inventory/Inventories.js b/awx/ui/src/screens/Inventory/Inventories.js
deleted file mode 100644
index 49bf4d771017..000000000000
--- a/awx/ui/src/screens/Inventory/Inventories.js
+++ /dev/null
@@ -1,133 +0,0 @@
-import React, { useState, useCallback, useRef } from 'react';
-
-import { t } from '@lingui/macro';
-import { Route, Switch } from 'react-router-dom';
-
-import { Config } from 'contexts/Config';
-import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
-import PersistentFilters from 'components/PersistentFilters';
-import { InventoryList } from './InventoryList';
-import Inventory from './Inventory';
-import SmartInventory from './SmartInventory';
-import InventoryAdd from './InventoryAdd';
-import SmartInventoryAdd from './SmartInventoryAdd';
-
-function Inventories() {
- const initScreenHeader = useRef({
- '/inventories': t`Inventories`,
- '/inventories/inventory/add': t`Create new inventory`,
- '/inventories/smart_inventory/add': t`Create new smart inventory`,
- });
-
- const [breadcrumbConfig, setScreenHeader] = useState(
- initScreenHeader.current
- );
-
- const [inventory, setInventory] = useState();
- const [nestedObject, setNestedGroup] = useState();
- const [schedule, setSchedule] = useState();
-
- const setBreadcrumbConfig = useCallback(
- (passedInventory, passedNestedObject, passedSchedule) => {
- if (passedInventory && passedInventory.name !== inventory?.name) {
- setInventory(passedInventory);
- }
- if (
- passedNestedObject &&
- passedNestedObject.name !== nestedObject?.name
- ) {
- setNestedGroup(passedNestedObject);
- }
- if (passedSchedule && passedSchedule.name !== schedule?.name) {
- setSchedule(passedSchedule);
- }
- if (!inventory) {
- return;
- }
-
- const inventoryKind =
- inventory.kind === 'smart' ? 'smart_inventory' : 'inventory';
-
- const inventoryPath = `/inventories/${inventoryKind}/${inventory.id}`;
- const inventoryHostsPath = `${inventoryPath}/hosts`;
- const inventoryGroupsPath = `${inventoryPath}/groups`;
- const inventorySourcesPath = `${inventoryPath}/sources`;
-
- setScreenHeader({
- ...initScreenHeader.current,
- [inventoryPath]: `${inventory.name}`,
- [`${inventoryPath}/access`]: t`Access`,
- [`${inventoryPath}/jobs`]: t`Jobs`,
- [`${inventoryPath}/details`]: t`Details`,
- [`${inventoryPath}/job_templates`]: t`Job Templates`,
- [`${inventoryPath}/edit`]: t`Edit details`,
-
- [inventoryHostsPath]: t`Hosts`,
- [`${inventoryHostsPath}/add`]: t`Create new host`,
- [`${inventoryHostsPath}/${nestedObject?.id}`]: `${nestedObject?.name}`,
- [`${inventoryHostsPath}/${nestedObject?.id}/edit`]: t`Edit details`,
- [`${inventoryHostsPath}/${nestedObject?.id}/details`]: t`Host details`,
- [`${inventoryHostsPath}/${nestedObject?.id}/jobs`]: t`Jobs`,
- [`${inventoryHostsPath}/${nestedObject?.id}/facts`]: t`Facts`,
- [`${inventoryHostsPath}/${nestedObject?.id}/groups`]: t`Groups`,
-
- [inventoryGroupsPath]: t`Groups`,
- [`${inventoryGroupsPath}/add`]: t`Create new group`,
- [`${inventoryGroupsPath}/${nestedObject?.id}`]: `${nestedObject?.name}`,
- [`${inventoryGroupsPath}/${nestedObject?.id}/edit`]: t`Edit details`,
- [`${inventoryGroupsPath}/${nestedObject?.id}/details`]: t`Group details`,
- [`${inventoryGroupsPath}/${nestedObject?.id}/nested_hosts`]: t`Hosts`,
- [`${inventoryGroupsPath}/${nestedObject?.id}/nested_hosts/add`]: t`Create new host`,
- [`${inventoryGroupsPath}/${nestedObject?.id}/nested_groups`]: t`Related Groups`,
- [`${inventoryGroupsPath}/${nestedObject?.id}/nested_groups/add`]: t`Create new group`,
-
- [`${inventorySourcesPath}`]: t`Sources`,
- [`${inventorySourcesPath}/add`]: t`Create new source`,
- [`${inventorySourcesPath}/${nestedObject?.id}`]: `${nestedObject?.name}`,
- [`${inventorySourcesPath}/${nestedObject?.id}/details`]: t`Details`,
- [`${inventorySourcesPath}/${nestedObject?.id}/edit`]: t`Edit details`,
- [`${inventorySourcesPath}/${nestedObject?.id}/schedules`]: t`Schedules`,
- [`${inventorySourcesPath}/${nestedObject?.id}/schedules/${schedule?.id}`]: `${schedule?.name}`,
- [`${inventorySourcesPath}/${nestedObject?.id}/schedules/add`]: t`Create New Schedule`,
- [`${inventorySourcesPath}/${nestedObject?.id}/schedules/${schedule?.id}/details`]: t`Schedule details`,
- [`${inventorySourcesPath}/${nestedObject?.id}/notifications`]: t`Notifications`,
- });
- },
- [inventory, nestedObject, schedule]
- );
-
- return (
- <>
-
-
-
-
-
-
-
-
-
-
- {({ me }) => (
-
- )}
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-}
-
-export { Inventories as _Inventories };
-export default Inventories;
diff --git a/awx/ui/src/screens/Inventory/Inventories.test.js b/awx/ui/src/screens/Inventory/Inventories.test.js
deleted file mode 100644
index 23b5779e9cbd..000000000000
--- a/awx/ui/src/screens/Inventory/Inventories.test.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react';
-
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-
-import Inventories from './Inventories';
-
-describe('', () => {
- let pageWrapper;
-
- beforeEach(() => {
- pageWrapper = mountWithContexts();
- });
-
- test('initially renders without crashing', () => {
- expect(pageWrapper.length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/Inventory.js b/awx/ui/src/screens/Inventory/Inventory.js
deleted file mode 100644
index 53da122cd6f7..000000000000
--- a/awx/ui/src/screens/Inventory/Inventory.js
+++ /dev/null
@@ -1,206 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { t } from '@lingui/macro';
-
-import {
- Switch,
- Route,
- Redirect,
- Link,
- useLocation,
- useRouteMatch,
-} from 'react-router-dom';
-import { CaretLeftIcon } from '@patternfly/react-icons';
-import { Card, PageSection } from '@patternfly/react-core';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import JobList from 'components/JobList';
-import RoutedTabs from 'components/RoutedTabs';
-import { ResourceAccessList } from 'components/ResourceAccessList';
-import RelatedTemplateList from 'components/RelatedTemplateList';
-import { InventoriesAPI } from 'api';
-import InventoryDetail from './InventoryDetail';
-import InventoryEdit from './InventoryEdit';
-import InventoryGroups from './InventoryGroups';
-import InventoryHosts from './InventoryHosts/InventoryHosts';
-import InventorySources from './InventorySources';
-
-function Inventory({ setBreadcrumb }) {
- const [contentError, setContentError] = useState(null);
- const [hasContentLoading, setHasContentLoading] = useState(true);
- const [inventory, setInventory] = useState(null);
- const location = useLocation();
- const match = useRouteMatch({
- path: '/inventories/inventory/:id',
- });
-
- useEffect(() => {
- async function fetchData() {
- try {
- const { data } = await InventoriesAPI.readDetail(match.params.id);
- setBreadcrumb(data);
- setInventory(data);
- } catch (error) {
- setContentError(error);
- } finally {
- setHasContentLoading(false);
- }
- }
-
- fetchData();
- }, [match.params.id, location.pathname, setBreadcrumb]);
-
- const tabsArray = [
- {
- name: (
- <>
-
- {t`Back to Inventories`}
- >
- ),
- link: `/inventories`,
- id: 99,
- isBackButton: true,
- },
- { name: t`Details`, link: `${match.url}/details`, id: 0 },
- { name: t`Access`, link: `${match.url}/access`, id: 1 },
- { name: t`Groups`, link: `${match.url}/groups`, id: 2 },
- { name: t`Hosts`, link: `${match.url}/hosts`, id: 3 },
- { name: t`Sources`, link: `${match.url}/sources`, id: 4 },
- {
- name: t`Jobs`,
- link: `${match.url}/jobs`,
- id: 5,
- },
- { name: t`Job Templates`, link: `${match.url}/job_templates`, id: 6 },
- ];
-
- if (hasContentLoading) {
- return (
-
-
-
-
-
- );
- }
-
- if (contentError) {
- return (
-
-
-
- {contentError.response?.status === 404 && (
-
- {t`Inventory not found.`}{' '}
- {t`View all Inventories.`}
-
- )}
-
-
-
- );
- }
-
- let showCardHeader = true;
-
- if (
- ['edit', 'add', 'groups/', 'hosts/', 'sources/'].some((name) =>
- location.pathname.includes(name)
- )
- ) {
- showCardHeader = false;
- }
-
- if (inventory?.kind === 'smart') {
- return (
-
- );
- }
-
- return (
-
-
- {showCardHeader && }
-
-
- {inventory && [
-
-
- ,
-
-
- ,
-
-
- ,
-
-
- ,
-
-
- ,
-
-
- ,
-
-
- ,
-
-
- ,
-
-
- {match.params.id && (
-
- {t`View Inventory Details`}
-
- )}
-
- ,
- ]}
-
-
-
- );
-}
-
-export { Inventory as _Inventory };
-export default Inventory;
diff --git a/awx/ui/src/screens/Inventory/Inventory.test.js b/awx/ui/src/screens/Inventory/Inventory.test.js
deleted file mode 100644
index 55a37b6c39c5..000000000000
--- a/awx/ui/src/screens/Inventory/Inventory.test.js
+++ /dev/null
@@ -1,80 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { InventoriesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../testUtils/enzymeHelpers';
-import mockInventory from './shared/data.inventory.json';
-import Inventory from './Inventory';
-
-jest.mock('../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useRouteMatch: () => ({
- url: '/inventories/1',
- params: { id: 1 },
- }),
-}));
-
-describe('', () => {
- let wrapper;
-
- beforeEach(async () => {
- InventoriesAPI.readDetail.mockResolvedValue({
- data: mockInventory,
- });
- });
-
- test('initially renders successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts( {}} />);
- });
- wrapper.update();
- expect(wrapper.find('Inventory').length).toBe(1);
- expect(wrapper.find('RoutedTabs li').length).toBe(8);
- });
-
- test('should render expected tabs', async () => {
- const expectedTabs = [
- 'Back to Inventories',
- 'Details',
- 'Access',
- 'Groups',
- 'Hosts',
- 'Jobs',
- 'Job Templates',
- ];
- await act(async () => {
- wrapper = mountWithContexts( {}} />);
- });
- wrapper.find('RoutedTabs li').forEach((tab, index) => {
- expect(tab.text()).toEqual(expectedTabs[index]);
- });
- });
-
- test('should show content error when user attempts to navigate to erroneous route', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/1/foobar'],
- });
- await act(async () => {
- wrapper = mountWithContexts( {}} />, {
- context: {
- router: {
- history,
- route: {
- location: history.location,
- match: {
- params: { id: 1 },
- url: '/inventories/inventory/1/foobar',
- path: '/inventories/inventory/1/foobar',
- },
- },
- },
- },
- });
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryAdd/InventoryAdd.js b/awx/ui/src/screens/Inventory/InventoryAdd/InventoryAdd.js
deleted file mode 100644
index edbfd54f5fbd..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryAdd/InventoryAdd.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import React, { useState } from 'react';
-import { useHistory } from 'react-router-dom';
-import { PageSection, Card } from '@patternfly/react-core';
-import { CardBody } from 'components/Card';
-
-import { InventoriesAPI } from 'api';
-import InventoryForm from '../shared/InventoryForm';
-
-function InventoryAdd() {
- const [error, setError] = useState(null);
- const history = useHistory();
-
- const handleCancel = () => {
- history.push('/inventories');
- };
-
- async function submitLabels(inventoryId, orgId, labels = []) {
- const associationPromises = labels.map((label) =>
- InventoriesAPI.associateLabel(inventoryId, label, orgId)
- );
-
- return Promise.all([...associationPromises]);
- }
-
- const handleSubmit = async (values) => {
- const { instanceGroups, organization, ...remainingValues } = values;
- try {
- const {
- data: { id: inventoryId },
- } = await InventoriesAPI.create({
- organization: organization.id,
- ...remainingValues,
- });
- /* eslint-disable no-await-in-loop, no-restricted-syntax */
- // Resolve Promises sequentially to maintain order and avoid race condition
-
- await submitLabels(inventoryId, values.organization?.id, values.labels);
- for (const group of instanceGroups) {
- await InventoriesAPI.associateInstanceGroup(inventoryId, group.id);
- }
- /* eslint-enable no-await-in-loop, no-restricted-syntax */
- const url = history.location.pathname.startsWith(
- '/inventories/smart_inventory'
- )
- ? `/inventories/smart_inventory/${inventoryId}/details`
- : `/inventories/inventory/${inventoryId}/details`;
-
- history.push(`${url}`);
- } catch (err) {
- setError(err);
- }
- };
-
- return (
-
-
-
-
-
-
-
- );
-}
-
-export { InventoryAdd as _InventoryAdd };
-export default InventoryAdd;
diff --git a/awx/ui/src/screens/Inventory/InventoryAdd/InventoryAdd.test.js b/awx/ui/src/screens/Inventory/InventoryAdd/InventoryAdd.test.js
deleted file mode 100644
index be66e131db35..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryAdd/InventoryAdd.test.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { LabelsAPI, InventoriesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import InventoryAdd from './InventoryAdd';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
- let history;
-
- beforeEach(async () => {
- history = createMemoryHistory({ initialEntries: ['/inventories'] });
- LabelsAPI.read.mockResolvedValue({ data: { results: [] } });
- InventoriesAPI.create.mockResolvedValue({ data: { id: 13 } });
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
- });
-
- test('Initially renders successfully', () => {
- expect(wrapper.length).toBe(1);
- });
- test('handleSubmit should call the api and redirect to details page', async () => {
- const instanceGroups = [
- { name: 'Bizz', id: 1 },
- { name: 'Buzz', id: 2 },
- ];
- await waitForElement(wrapper, 'isLoading', (el) => el.length === 0);
-
- await act(async () => {
- wrapper.find('InventoryForm').prop('onSubmit')({
- name: 'new Foo',
- organization: { id: 2 },
- instanceGroups,
- labels: [{ name: 'label' }],
- });
- });
- expect(InventoriesAPI.create).toHaveBeenCalledWith({
- name: 'new Foo',
- organization: 2,
- labels: [{ name: 'label' }],
- });
- expect(InventoriesAPI.associateLabel).toBeCalledWith(
- 13,
- { name: 'label' },
- 2
- );
- instanceGroups.map((IG) =>
- expect(InventoriesAPI.associateInstanceGroup).toHaveBeenCalledWith(
- 13,
- IG.id
- )
- );
- expect(history.location.pathname).toBe('/inventories/inventory/13/details');
- });
-
- test('handleCancel should return the user back to the inventories list', async () => {
- await waitForElement(wrapper, 'isLoading', (el) => el.length === 0);
- await act(async () => {
- wrapper.find('Button[aria-label="Cancel"]').simulate('click');
- });
- expect(history.location.pathname).toEqual('/inventories');
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryAdd/index.js b/awx/ui/src/screens/Inventory/InventoryAdd/index.js
deleted file mode 100644
index 2b5872b91ab8..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryAdd/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventoryAdd';
diff --git a/awx/ui/src/screens/Inventory/InventoryDetail/InventoryDetail.js b/awx/ui/src/screens/Inventory/InventoryDetail/InventoryDetail.js
deleted file mode 100644
index 0dd780341557..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryDetail/InventoryDetail.js
+++ /dev/null
@@ -1,209 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { Link, useHistory } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import {
- Button,
- Chip,
- TextList,
- TextListItem,
- TextListItemVariants,
- TextListVariants,
-} from '@patternfly/react-core';
-import AlertModal from 'components/AlertModal';
-import { CardBody, CardActionsRow } from 'components/Card';
-import { DetailList, Detail, UserDateDetail } from 'components/DetailList';
-import { VariablesDetail } from 'components/CodeEditor';
-import DeleteButton from 'components/DeleteButton';
-import ErrorDetail from 'components/ErrorDetail';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import ChipGroup from 'components/ChipGroup';
-import { InventoriesAPI } from 'api';
-import useRequest, { useDismissableError } from 'hooks/useRequest';
-import { Inventory } from 'types';
-import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
-import InstanceGroupLabels from 'components/InstanceGroupLabels';
-import getHelpText from '../shared/Inventory.helptext';
-
-function InventoryDetail({ inventory }) {
- const history = useHistory();
- const helpText = getHelpText();
- const {
- result: instanceGroups,
- isLoading,
- error: instanceGroupsError,
- request: fetchInstanceGroups,
- } = useRequest(
- useCallback(async () => {
- const { data } = await InventoriesAPI.readInstanceGroups(inventory.id);
- return data.results;
- }, [inventory.id]),
- []
- );
-
- useEffect(() => {
- fetchInstanceGroups();
- }, [fetchInstanceGroups]);
-
- const { request: deleteInventory, error: deleteError } = useRequest(
- useCallback(async () => {
- await InventoriesAPI.destroy(inventory.id);
- history.push(`/inventories`);
- }, [inventory.id, history])
- );
-
- const { error, dismissError } = useDismissableError(deleteError);
-
- const { organization, user_capabilities: userCapabilities } =
- inventory.summary_fields;
-
- const { prevent_instance_group_fallback } = inventory;
-
- const deleteDetailsRequests =
- relatedResourceDeleteRequests.inventory(inventory);
-
- const renderOptionsField = prevent_instance_group_fallback;
-
- const renderOptions = (
-
- {prevent_instance_group_fallback && (
-
- {t`Prevent Instance Group Fallback`}
-
- )}
-
- );
-
- if (isLoading) {
- return ;
- }
-
- if (instanceGroupsError) {
- return ;
- }
-
- return (
-
-
-
-
-
-
- {organization.name}
-
- }
- />
-
- {instanceGroups && (
- }
- isEmpty={instanceGroups.length === 0}
- />
- )}
- {prevent_instance_group_fallback && (
-
- )}
- {renderOptionsField && (
-
- )}
- {inventory.summary_fields.labels && (
-
- {inventory.summary_fields.labels?.results?.map((l) => (
-
- {l.name}
-
- ))}
-
- }
- isEmpty={inventory.summary_fields.labels?.results?.length === 0}
- />
- )}
-
-
-
-
-
- {userCapabilities.edit && (
-
- )}
- {userCapabilities.delete && (
-
- {t`Delete`}
-
- )}
-
- {/* Update delete modal to show dependencies https://github.com/ansible/awx/issues/5546 */}
- {error && (
-
- {t`Failed to delete inventory.`}
-
-
- )}
-
- );
-}
-InventoryDetail.propTypes = {
- inventory: Inventory.isRequired,
-};
-
-export default InventoryDetail;
diff --git a/awx/ui/src/screens/Inventory/InventoryDetail/InventoryDetail.test.js b/awx/ui/src/screens/Inventory/InventoryDetail/InventoryDetail.test.js
deleted file mode 100644
index 153c78d8babf..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryDetail/InventoryDetail.test.js
+++ /dev/null
@@ -1,160 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { InventoriesAPI, CredentialTypesAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import InventoryDetail from './InventoryDetail';
-
-jest.mock('../../../api');
-
-const mockInventory = {
- id: 1,
- type: 'inventory',
- url: '/api/v2/inventories/1/',
- summary_fields: {
- organization: {
- id: 1,
- name: 'The Organization',
- description: '',
- },
- user_capabilities: {
- edit: true,
- delete: true,
- copy: true,
- adhoc: true,
- },
- },
- created: '2019-10-04T16:56:48.025455Z',
- modified: '2019-10-04T16:56:48.025468Z',
- name: 'Inv no hosts',
- description: '',
- organization: 1,
- kind: '',
- host_filter: null,
- variables: '---\nfoo: bar',
- has_active_failures: false,
- total_hosts: 0,
- hosts_with_active_failures: 0,
- total_groups: 0,
- groups_with_active_failures: 0,
- has_inventory_sources: false,
- total_inventory_sources: 0,
- inventory_sources_with_failures: 0,
- pending_deletion: false,
-};
-
-const associatedInstanceGroups = [
- {
- id: 1,
- name: 'Foo',
- },
-];
-
-function expectDetailToMatch(wrapper, label, value) {
- const detail = wrapper.find(`Detail[label="${label}"]`);
- expect(detail).toHaveLength(1);
- expect(detail.prop('value')).toEqual(value);
-}
-
-describe('', () => {
- beforeEach(async () => {
- CredentialTypesAPI.read.mockResolvedValue({
- data: {
- results: [
- {
- id: 14,
- name: 'insights',
- },
- ],
- },
- });
- });
- test('should render details', async () => {
- InventoriesAPI.readInstanceGroups.mockResolvedValue({
- data: {
- results: associatedInstanceGroups,
- },
- });
-
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
- expectDetailToMatch(wrapper, 'Name', mockInventory.name);
- expectDetailToMatch(wrapper, 'Description', mockInventory.description);
- expectDetailToMatch(wrapper, 'Type', 'Inventory');
- expectDetailToMatch(wrapper, 'Total hosts', mockInventory.total_hosts);
- const link = wrapper.find('Detail[label="Organization"]').find('Link');
-
- const org = wrapper.find('Detail[label="Organization"]');
-
- expect(link.prop('to')).toEqual('/organizations/1/details');
- expect(org.length).toBe(1);
-
- const vars = wrapper.find('VariablesDetail');
- expect(vars).toHaveLength(1);
- expect(vars.prop('value')).toEqual(mockInventory.variables);
- const dates = wrapper.find('UserDateDetail');
- expect(dates).toHaveLength(2);
- expect(dates.at(0).prop('date')).toEqual(mockInventory.created);
- expect(dates.at(1).prop('date')).toEqual(mockInventory.modified);
- });
-
- test('should have proper number of delete detail requests', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(
- wrapper.find('DeleteButton').prop('deleteDetailsRequests')
- ).toHaveLength(2);
- });
-
- test('should load instance groups', async () => {
- InventoriesAPI.readInstanceGroups.mockResolvedValue({
- data: {
- results: associatedInstanceGroups,
- },
- });
-
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
- expect(InventoriesAPI.readInstanceGroups).toHaveBeenCalledWith(
- mockInventory.id
- );
- const label = wrapper.find('Label').at(0);
- expect(label.prop('children')).toEqual('Foo');
- });
-
- test('should not load instance groups', async () => {
- InventoriesAPI.readInstanceGroups.mockResolvedValue({
- data: {
- results: [],
- },
- });
-
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
- expect(InventoriesAPI.readInstanceGroups).toHaveBeenCalledWith(
- mockInventory.id
- );
- const instance_groups_detail = wrapper
- .find(`Detail[label="Instance Groups"]`)
- .at(0);
- expect(instance_groups_detail.prop('isEmpty')).toEqual(true);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryDetail/index.js b/awx/ui/src/screens/Inventory/InventoryDetail/index.js
deleted file mode 100644
index de1946de63ec..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryDetail/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventoryDetail';
diff --git a/awx/ui/src/screens/Inventory/InventoryEdit/InventoryEdit.js b/awx/ui/src/screens/Inventory/InventoryEdit/InventoryEdit.js
deleted file mode 100644
index 218bd8f24a46..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryEdit/InventoryEdit.js
+++ /dev/null
@@ -1,116 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { useHistory } from 'react-router-dom';
-import { object } from 'prop-types';
-
-import { CardBody } from 'components/Card';
-import { InventoriesAPI } from 'api';
-import { getAddedAndRemoved } from 'util/lists';
-import ContentLoading from 'components/ContentLoading';
-import useIsMounted from 'hooks/useIsMounted';
-import InventoryForm from '../shared/InventoryForm';
-
-function InventoryEdit({ inventory }) {
- const [error, setError] = useState(null);
- const [associatedInstanceGroups, setInstanceGroups] = useState(null);
- const [contentLoading, setContentLoading] = useState(true);
- const history = useHistory();
- const isMounted = useIsMounted();
-
- useEffect(() => {
- const loadData = async () => {
- try {
- const {
- data: { results: loadedInstanceGroups },
- } = await InventoriesAPI.readInstanceGroups(inventory.id);
- if (!isMounted.current) {
- return;
- }
- setInstanceGroups(loadedInstanceGroups);
- } catch (err) {
- setError(err);
- } finally {
- if (isMounted.current) {
- setContentLoading(false);
- }
- }
- };
- loadData();
- /* eslint-disable-next-line react-hooks/exhaustive-deps */
- }, [inventory.id, contentLoading, inventory]);
-
- const handleCancel = () => {
- const url =
- inventory.kind === 'smart'
- ? `/inventories/smart_inventory/${inventory.id}/details`
- : `/inventories/inventory/${inventory.id}/details`;
-
- history.push(`${url}`);
- };
-
- const handleSubmit = async (values) => {
- const { instanceGroups, organization, ...remainingValues } = values;
- try {
- await InventoriesAPI.update(inventory.id, {
- organization: organization.id,
- ...remainingValues,
- });
- await InventoriesAPI.orderInstanceGroups(
- inventory.id,
- instanceGroups,
- associatedInstanceGroups
- );
- await submitLabels(values.organization.id, values.labels);
-
- const url =
- history.location.pathname.search('smart') > -1
- ? `/inventories/smart_inventory/${inventory.id}/details`
- : `/inventories/inventory/${inventory.id}/details`;
- history.push(`${url}`);
- } catch (err) {
- setError(err);
- }
- };
-
- const submitLabels = async (orgId, labels = []) => {
- const { added, removed } = getAddedAndRemoved(
- inventory.summary_fields.labels.results,
- labels
- );
-
- const disassociationPromises = removed.map((label) =>
- InventoriesAPI.disassociateLabel(inventory.id, label)
- );
- const associationPromises = added.map((label) =>
- InventoriesAPI.associateLabel(inventory.id, label, orgId)
- );
-
- const results = await Promise.all([
- ...disassociationPromises,
- ...associationPromises,
- ]);
- return results;
- };
-
- if (contentLoading) {
- return ;
- }
-
- return (
-
-
-
- );
-}
-
-InventoryEdit.propType = {
- inventory: object.isRequired,
-};
-
-export { InventoryEdit as _InventoryEdit };
-export default InventoryEdit;
diff --git a/awx/ui/src/screens/Inventory/InventoryEdit/InventoryEdit.test.js b/awx/ui/src/screens/Inventory/InventoryEdit/InventoryEdit.test.js
deleted file mode 100644
index ad76ceedd6de..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryEdit/InventoryEdit.test.js
+++ /dev/null
@@ -1,147 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { LabelsAPI, InventoriesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import InventoryEdit from './InventoryEdit';
-
-jest.mock('../../../api');
-
-const mockInventory = {
- id: 1,
- type: 'inventory',
- url: '/api/v2/inventories/1/',
- summary_fields: {
- organization: {
- id: 1,
- name: 'Default',
- description: '',
- },
- user_capabilities: {
- edit: true,
- delete: true,
- copy: true,
- adhoc: true,
- },
- labels: {
- results: [
- { name: 'Sushi', id: 1 },
- { name: 'Major', id: 2 },
- ],
- },
- },
- created: '2019-10-04T16:56:48.025455Z',
- modified: '2019-10-04T16:56:48.025468Z',
- name: 'Inv no hosts',
- description: '',
- organization: 1,
- kind: '',
- host_filter: null,
- variables: '---',
- has_active_failures: false,
- total_hosts: 0,
- hosts_with_active_failures: 0,
- total_groups: 0,
- groups_with_active_failures: 0,
- has_inventory_sources: false,
- total_inventory_sources: 0,
- inventory_sources_with_failures: 0,
- pending_deletion: false,
-};
-
-const associatedInstanceGroups = [
- {
- id: 1,
- name: 'Foo',
- },
-];
-
-describe('', () => {
- let wrapper;
- let history;
-
- beforeEach(async () => {
- LabelsAPI.read.mockResolvedValue({
- data: {
- results: [
- { name: 'Sushi', id: 1 },
- { name: 'Major', id: 2 },
- ],
- },
- });
-
- InventoriesAPI.readInstanceGroups.mockResolvedValue({
- data: {
- results: associatedInstanceGroups,
- },
- });
- history = createMemoryHistory({ initialEntries: ['/inventories'] });
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
- });
-
- test('initially renders successfully', async () => {
- expect(wrapper.find('InventoryEdit').length).toBe(1);
- });
-
- test('called InventoriesAPI.readInstanceGroups', async () => {
- expect(InventoriesAPI.readInstanceGroups).toBeCalledWith(1);
- });
-
- test('handleCancel returns the user to inventory detail', async () => {
- await waitForElement(wrapper, 'isLoading', (el) => el.length === 0);
- await act(async () => {
- wrapper.find('Button[aria-label="Cancel"]').simulate('click');
- });
- expect(history.location.pathname).toEqual(
- '/inventories/inventory/1/details'
- );
- });
-
- test('handleSubmit should post to the api', async () => {
- await waitForElement(wrapper, 'isLoading', (el) => el.length === 0);
- const instanceGroups = [
- { name: 'Bizz', id: 2 },
- { name: 'Buzz', id: 3 },
- ];
- const labels = [{ name: 'label' }, { name: 'Major', id: 2 }];
- await act(async () => {
- wrapper.find('InventoryForm').prop('onSubmit')({
- name: 'Foo',
- id: 13,
- organization: { id: 1 },
- instanceGroups,
- labels,
- });
- });
-
- expect(InventoriesAPI.update).toHaveBeenCalledWith(1, {
- id: 13,
- labels: [{ name: 'label' }, { name: 'Major', id: 2 }],
- name: 'Foo',
- organization: 1,
- });
-
- expect(InventoriesAPI.associateLabel).toBeCalledWith(
- 1,
- { name: 'label' },
- 1
- );
- expect(InventoriesAPI.disassociateLabel).toBeCalledWith(1, {
- name: 'Sushi',
- id: 1,
- });
- expect(InventoriesAPI.orderInstanceGroups).toHaveBeenCalledWith(
- mockInventory.id,
- instanceGroups,
- associatedInstanceGroups
- );
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryEdit/index.js b/awx/ui/src/screens/Inventory/InventoryEdit/index.js
deleted file mode 100644
index 9cdfac13904e..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryEdit/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventoryEdit';
diff --git a/awx/ui/src/screens/Inventory/InventoryGroup/InventoryGroup.js b/awx/ui/src/screens/Inventory/InventoryGroup/InventoryGroup.js
deleted file mode 100644
index 0a8bc7937406..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroup/InventoryGroup.js
+++ /dev/null
@@ -1,153 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { t } from '@lingui/macro';
-
-import {
- Switch,
- Route,
- Link,
- Redirect,
- useLocation,
- useParams,
-} from 'react-router-dom';
-import { CaretLeftIcon } from '@patternfly/react-icons';
-import RoutedTabs from 'components/RoutedTabs';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import { GroupsAPI } from 'api';
-import InventoryGroupEdit from '../InventoryGroupEdit/InventoryGroupEdit';
-import InventoryGroupDetail from '../InventoryGroupDetail/InventoryGroupDetail';
-import InventoryGroupHosts from '../InventoryGroupHosts';
-import InventoryRelatedGroups from '../InventoryRelatedGroups';
-
-function InventoryGroup({ setBreadcrumb, inventory }) {
- const [inventoryGroup, setInventoryGroup] = useState(null);
- const [contentLoading, setContentLoading] = useState(true);
- const [contentError, setContentError] = useState(null);
- const { id: inventoryId, groupId } = useParams();
- const location = useLocation();
-
- useEffect(() => {
- const loadData = async () => {
- try {
- const { data } = await GroupsAPI.readDetail(groupId);
- setInventoryGroup(data);
- setBreadcrumb(inventory, data);
- } catch (err) {
- setContentError(err);
- } finally {
- setContentLoading(false);
- }
- };
-
- loadData();
- }, [location.pathname, groupId, inventory, setBreadcrumb]);
-
- const tabsArray = [
- {
- name: (
- <>
-
- {t`Back to Groups`}
- >
- ),
- link: `/inventories/inventory/${inventory.id}/groups`,
- id: 99,
- },
- {
- name: t`Details`,
- link: `/inventories/inventory/${inventory.id}/groups/${inventoryGroup?.id}/details`,
- id: 0,
- },
- {
- name: t`Related Groups`,
- link: `/inventories/inventory/${inventory.id}/groups/${inventoryGroup?.id}/nested_groups`,
- id: 1,
- },
- {
- name: t`Hosts`,
- link: `/inventories/inventory/${inventory.id}/groups/${inventoryGroup?.id}/nested_hosts`,
- id: 2,
- },
- ];
-
- if (contentLoading) {
- return ;
- }
-
- if (contentError) {
- return ;
- }
-
- // In cases where a user manipulates the url such that they try to navigate to a
- // Inventory Group that is not associated with the Inventory Id in the Url this
- // Content Error is thrown. Inventory Groups have a 1:1 relationship to Inventories
- // thus their Ids must corrolate.
-
- if (
- inventoryGroup?.summary_fields?.inventory?.id !== parseInt(inventoryId, 10)
- ) {
- return (
-
-
- {t`View Inventory Groups`}
-
-
- );
- }
-
- let showCardHeader = true;
- if (['add', 'edit'].some((name) => location.pathname.includes(name))) {
- showCardHeader = false;
- }
-
- return (
- <>
- {showCardHeader && }
-
-
- {inventoryGroup && [
-
-
- ,
-
-
- ,
-
-
- ,
-
-
- ,
- ]}
-
-
- {inventory && (
-
- {t`View Inventory Details`}
-
- )}
-
-
-
- >
- );
-}
-
-export { InventoryGroup as _InventoryGroup };
-export default InventoryGroup;
diff --git a/awx/ui/src/screens/Inventory/InventoryGroup/InventoryGroup.test.js b/awx/ui/src/screens/Inventory/InventoryGroup/InventoryGroup.test.js
deleted file mode 100644
index ee468bf7d4d3..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroup/InventoryGroup.test.js
+++ /dev/null
@@ -1,94 +0,0 @@
-import React from 'react';
-import { Route } from 'react-router-dom';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { GroupsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import InventoryGroup from './InventoryGroup';
-
-jest.mock('../../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useParams: () => ({
- id: 1,
- groupId: 2,
- }),
-}));
-
-describe('', () => {
- let wrapper;
- let history;
- const inventory = { id: 1, name: 'Foo' };
-
- beforeEach(async () => {
- GroupsAPI.readDetail.mockResolvedValue({
- data: {
- id: 1,
- name: 'Foo',
- description: 'Bar',
- variables: 'bizz: buzz',
- summary_fields: {
- inventory: { id: 1 },
- created_by: { id: 1, username: 'Athena' },
- modified_by: { id: 1, username: 'Apollo' },
- },
- created: '2020-04-25T01:23:45.678901Z',
- modified: '2020-04-25T01:23:45.678901Z',
- },
- });
- history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/1/groups/1/details'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
- {}} inventory={inventory} />
- ,
- { context: { router: { history } } }
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- test('renders successfully', async () => {
- expect(wrapper.length).toBe(1);
- });
-
- test('expect all tabs to exist, including Back to Groups', async () => {
- const routedTabs = wrapper.find('RoutedTabs');
- expect(routedTabs).toHaveLength(1);
-
- const tabs = routedTabs.prop('tabsArray');
- expect(tabs[0].link).toEqual('/inventories/inventory/1/groups');
- expect(tabs[1].name).toEqual('Details');
- expect(tabs[2].name).toEqual('Related Groups');
- expect(tabs[3].name).toEqual('Hosts');
- });
-
- test('should show content error when user attempts to navigate to erroneous route', async () => {
- history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/1/groups/1/foobar'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
- {}} inventory={inventory} />,
- { context: { router: { history } } }
- );
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-
- test('should show content error when api throws error on initial render', async () => {
- GroupsAPI.readDetail.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryGroup/index.js b/awx/ui/src/screens/Inventory/InventoryGroup/index.js
deleted file mode 100644
index 9de3820a0165..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroup/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventoryGroup';
diff --git a/awx/ui/src/screens/Inventory/InventoryGroupAdd/InventoryGroupAdd.js b/awx/ui/src/screens/Inventory/InventoryGroupAdd/InventoryGroupAdd.js
deleted file mode 100644
index 5d87480bad8a..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroupAdd/InventoryGroupAdd.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import React, { useState } from 'react';
-
-import { useHistory, useParams } from 'react-router-dom';
-import { Card } from '@patternfly/react-core';
-import { GroupsAPI } from 'api';
-
-import InventoryGroupForm from '../shared/InventoryGroupForm';
-
-function InventoryGroupsAdd() {
- const [error, setError] = useState(null);
- const { id } = useParams();
- const history = useHistory();
-
- const handleSubmit = async (values) => {
- values.inventory = id;
- try {
- const { data } = await GroupsAPI.create(values);
- history.push(`/inventories/inventory/${id}/groups/${data.id}`);
- } catch (err) {
- setError(err);
- }
- };
-
- const handleCancel = () => {
- history.push(`/inventories/inventory/${id}/groups`);
- };
-
- return (
-
-
-
- );
-}
-export default InventoryGroupsAdd;
-export { InventoryGroupsAdd as _InventoryGroupsAdd };
diff --git a/awx/ui/src/screens/Inventory/InventoryGroupAdd/InventoryGroupAdd.test.js b/awx/ui/src/screens/Inventory/InventoryGroupAdd/InventoryGroupAdd.test.js
deleted file mode 100644
index 155ebf5b1e98..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroupAdd/InventoryGroupAdd.test.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import React from 'react';
-import { Route } from 'react-router-dom';
-
-import { createMemoryHistory } from 'history';
-import { act } from 'react-dom/test-utils';
-import { GroupsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import InventoryGroupAdd from './InventoryGroupAdd';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
- let history;
- beforeEach(async () => {
- history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/1/groups/add'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- ,
- {
- context: {
- router: { history, route: { location: history.location } },
- },
- }
- );
- });
- });
- test('InventoryGroupAdd renders successfully', () => {
- expect(wrapper.length).toBe(1);
- });
- test('cancel should navigate user to Inventory Groups List', async () => {
- wrapper.find('button[aria-label="Cancel"]').simulate('click');
- expect(history.location.pathname).toEqual(
- '/inventories/inventory/1/groups'
- );
- });
- test('handleSubmit should call api', async () => {
- GroupsAPI.create.mockResolvedValue({ data: {} });
- await act(async () => {
- wrapper.find('InventoryGroupForm').prop('handleSubmit')({
- name: 'Bar',
- description: 'Ansible',
- variables: 'ying: yang',
- });
- });
-
- expect(GroupsAPI.create).toBeCalled();
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryGroupAdd/index.js b/awx/ui/src/screens/Inventory/InventoryGroupAdd/index.js
deleted file mode 100644
index 0e15c69a5507..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroupAdd/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventoryGroupAdd';
diff --git a/awx/ui/src/screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js b/awx/ui/src/screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js
deleted file mode 100644
index 94fd28407664..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import React, { useState } from 'react';
-import { t } from '@lingui/macro';
-
-import { Button } from '@patternfly/react-core';
-
-import { useHistory, useParams } from 'react-router-dom';
-import { VariablesDetail } from 'components/CodeEditor';
-import { CardBody, CardActionsRow } from 'components/Card';
-import ErrorDetail from 'components/ErrorDetail';
-import AlertModal from 'components/AlertModal';
-import { DetailList, Detail, UserDateDetail } from 'components/DetailList';
-import InventoryGroupsDeleteModal from '../shared/InventoryGroupsDeleteModal';
-
-function InventoryGroupDetail({ inventoryGroup }) {
- const {
- summary_fields: { created_by, modified_by, user_capabilities },
- created,
- modified,
- name,
- description,
- variables,
- } = inventoryGroup;
- const [error, setError] = useState(false);
- const history = useHistory();
- const params = useParams();
-
- return (
-
-
-
-
-
-
-
-
-
- {user_capabilities?.edit && (
-
- )}
- {user_capabilities?.delete && (
-
- history.push(`/inventories/inventory/${params.id}/groups`)
- }
- />
- )}
-
- {error && (
- setError(false)}
- >
- {t`Failed to delete group ${inventoryGroup.name}.`}
-
-
- )}
-
- );
-}
-export default InventoryGroupDetail;
diff --git a/awx/ui/src/screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.test.js b/awx/ui/src/screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.test.js
deleted file mode 100644
index 3cedc8551b51..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.test.js
+++ /dev/null
@@ -1,162 +0,0 @@
-import React from 'react';
-import { Route } from 'react-router-dom';
-import { createMemoryHistory } from 'history';
-import { act } from 'react-dom/test-utils';
-import { GroupsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import InventoryGroupDetail from './InventoryGroupDetail';
-
-jest.mock('../../../api');
-
-const inventoryGroup = {
- name: 'Foo',
- description: 'Bar',
- variables: 'bizz: buzz',
- id: 1,
- created: '2019-12-02T15:58:16.276813Z',
- modified: '2019-12-03T20:33:46.207654Z',
- summary_fields: {
- created_by: {
- username: 'James',
- id: 13,
- },
- modified_by: {
- username: 'Bond',
- id: 14,
- },
- user_capabilities: {
- delete: true,
- edit: true,
- },
- },
-};
-
-describe('', () => {
- let wrapper;
- let history;
-
- describe('User has full permissions', () => {
- beforeEach(async () => {
- await act(async () => {
- history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/1/groups/1/details'],
- });
- wrapper = mountWithContexts(
-
-
- ,
- {
- context: {
- router: {
- history,
- route: {
- location: history.location,
- match: { params: { id: 1 } },
- },
- },
- },
- }
- );
- await waitForElement(
- wrapper,
- 'ContentLoading',
- (el) => el.length === 0
- );
- });
- });
- afterEach(() => {
- jest.clearAllMocks();
- });
- test('InventoryGroupDetail renders successfully', () => {
- expect(wrapper.length).toBe(1);
- });
-
- test('should open delete modal and then call api to delete the group', async () => {
- expect(wrapper.find('Modal').length).toBe(1); // variables modal already mounted
- await act(async () => {
- wrapper.find('button[aria-label="Delete"]').simulate('click');
- });
- wrapper.update();
- expect(wrapper.find('Modal').length).toBe(2);
- await act(async () => {
- wrapper.find('Radio[id="radio-delete"]').invoke('onChange')();
- });
- wrapper.update();
- expect(
- wrapper.find('Button[aria-label="Confirm Delete"]').prop('isDisabled')
- ).toBe(false);
- await act(() =>
- wrapper.find('Button[aria-label="Confirm Delete"]').prop('onClick')()
- );
- expect(GroupsAPI.destroy).toBeCalledWith(1);
- });
-
- test('should navigate user to edit form on edit button click', async () => {
- wrapper.find('button[aria-label="Edit"]').simulate('click');
- expect(history.location.pathname).toEqual(
- '/inventories/inventory/1/groups/1/edit'
- );
- });
-
- test('details should render with the proper values and action buttons shown', () => {
- expect(wrapper.find('Detail[label="Name"]').prop('value')).toBe('Foo');
- expect(wrapper.find('Detail[label="Description"]').prop('value')).toBe(
- 'Bar'
- );
- expect(wrapper.find('Detail[label="Created"]').length).toBe(1);
- expect(wrapper.find('Detail[label="Last Modified"]').length).toBe(1);
- expect(wrapper.find('VariablesDetail').prop('value')).toBe('bizz: buzz');
-
- expect(wrapper.find('button[aria-label="Edit"]').length).toBe(1);
- expect(wrapper.find('button[aria-label="Delete"]').length).toBe(1);
- });
- });
-
- describe('User has read-only permissions', () => {
- test('should hide edit/delete buttons', async () => {
- const readOnlyGroup = {
- ...inventoryGroup,
- summary_fields: {
- ...inventoryGroup.summary_fields,
- user_capabilities: {
- delete: false,
- edit: false,
- },
- },
- };
-
- await act(async () => {
- history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/1/groups/1/details'],
- });
- wrapper = mountWithContexts(
-
-
- ,
- {
- context: {
- router: {
- history,
- route: {
- location: history.location,
- match: { params: { id: 1 } },
- },
- },
- },
- }
- );
- await waitForElement(
- wrapper,
- 'ContentLoading',
- (el) => el.length === 0
- );
- });
-
- expect(wrapper.find('button[aria-label="Edit"]').length).toBe(0);
- expect(wrapper.find('button[aria-label="Delete"]').length).toBe(0);
- });
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryGroupDetail/index.js b/awx/ui/src/screens/Inventory/InventoryGroupDetail/index.js
deleted file mode 100644
index 155a1c8e1006..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroupDetail/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventoryGroupDetail';
diff --git a/awx/ui/src/screens/Inventory/InventoryGroupEdit/InventoryGroupEdit.js b/awx/ui/src/screens/Inventory/InventoryGroupEdit/InventoryGroupEdit.js
deleted file mode 100644
index 5c0a34796a60..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroupEdit/InventoryGroupEdit.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import React, { useState } from 'react';
-
-import { useParams, useHistory } from 'react-router-dom';
-import { GroupsAPI } from 'api';
-
-import InventoryGroupForm from '../shared/InventoryGroupForm';
-
-function InventoryGroupEdit({ inventoryGroup }) {
- const [error, setError] = useState(null);
- const { id, groupId } = useParams();
- const history = useHistory();
-
- const handleSubmit = async (values) => {
- try {
- await GroupsAPI.update(groupId, values);
- history.push(`/inventories/inventory/${id}/groups/${groupId}/details`);
- } catch (err) {
- setError(err);
- }
- };
-
- const handleCancel = () => {
- history.push(`/inventories/inventory/${id}/groups/${groupId}`);
- };
-
- return (
-
- );
-}
-export default InventoryGroupEdit;
-export { InventoryGroupEdit as _InventoryGroupEdit };
diff --git a/awx/ui/src/screens/Inventory/InventoryGroupEdit/InventoryGroupEdit.test.js b/awx/ui/src/screens/Inventory/InventoryGroupEdit/InventoryGroupEdit.test.js
deleted file mode 100644
index db4ffa649076..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroupEdit/InventoryGroupEdit.test.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import React from 'react';
-import { Route } from 'react-router-dom';
-import { createMemoryHistory } from 'history';
-import { act } from 'react-dom/test-utils';
-import { GroupsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import InventoryGroupEdit from './InventoryGroupEdit';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
- let history;
- beforeEach(async () => {
- GroupsAPI.readDetail.mockResolvedValue({
- data: {
- name: 'Foo',
- description: 'Bar',
- variables: 'bizz: buzz',
- },
- });
- history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/1/groups/2/edit'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- ,
- {
- context: {
- router: {
- history,
- },
- },
- }
- );
- });
- });
-
- test('InventoryGroupEdit renders successfully', () => {
- expect(wrapper.length).toBe(1);
- });
- test('cancel should navigate user to Inventory Groups List', async () => {
- wrapper.find('button[aria-label="Cancel"]').simulate('click');
- expect(history.location.pathname).toEqual(
- '/inventories/inventory/1/groups/2'
- );
- });
- test('handleSubmit should call api', async () => {
- wrapper.find('InventoryGroupForm').prop('handleSubmit')({
- name: 'Bar',
- description: 'Ansible',
- variables: 'ying: yang',
- });
- expect(GroupsAPI.update).toBeCalled();
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryGroupEdit/index.js b/awx/ui/src/screens/Inventory/InventoryGroupEdit/index.js
deleted file mode 100644
index 75519c821b8f..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroupEdit/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventoryGroupEdit';
diff --git a/awx/ui/src/screens/Inventory/InventoryGroupHostAdd/InventoryGroupHostAdd.js b/awx/ui/src/screens/Inventory/InventoryGroupHostAdd/InventoryGroupHostAdd.js
deleted file mode 100644
index c1eb2eeea64a..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroupHostAdd/InventoryGroupHostAdd.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import React, { useState } from 'react';
-import { useHistory } from 'react-router-dom';
-import { CardBody } from 'components/Card';
-import HostForm from 'components/HostForm';
-
-import { GroupsAPI } from 'api';
-
-function InventoryGroupHostAdd({ inventoryGroup }) {
- const [formError, setFormError] = useState(null);
- const baseUrl = `/inventories/inventory/${inventoryGroup.inventory}`;
- const history = useHistory();
-
- const handleSubmit = async (formData) => {
- try {
- const values = {
- ...formData,
- inventory: inventoryGroup.inventory,
- };
-
- const { data: response } = await GroupsAPI.createHost(
- inventoryGroup.id,
- values
- );
- history.push(`${baseUrl}/hosts/${response.id}/details`);
- } catch (error) {
- setFormError(error);
- }
- };
-
- const handleCancel = () => {
- history.push(`${baseUrl}/groups/${inventoryGroup.id}/nested_hosts`);
- };
-
- return (
-
-
-
- );
-}
-
-export default InventoryGroupHostAdd;
diff --git a/awx/ui/src/screens/Inventory/InventoryGroupHostAdd/InventoryGroupHostAdd.test.js b/awx/ui/src/screens/Inventory/InventoryGroupHostAdd/InventoryGroupHostAdd.test.js
deleted file mode 100644
index 95d33031f0f7..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroupHostAdd/InventoryGroupHostAdd.test.js
+++ /dev/null
@@ -1,75 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { GroupsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import InventoryGroupHostAdd from './InventoryGroupHostAdd';
-import mockHost from '../shared/data.host.json';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
- let history;
-
- beforeEach(async () => {
- history = createMemoryHistory();
-
- GroupsAPI.createHost.mockResolvedValue({
- data: {
- ...mockHost,
- },
- });
-
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('handleSubmit should post to api', async () => {
- await act(async () => {
- wrapper.find('HostForm').prop('handleSubmit')(mockHost);
- });
- expect(GroupsAPI.createHost).toHaveBeenCalledWith(123, mockHost);
- });
-
- test('should navigate to inventory group host list when cancel is clicked', () => {
- wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
- expect(history.location.pathname).toEqual(
- '/inventories/inventory/3/groups/123/nested_hosts'
- );
- });
-
- test('successful form submission should trigger redirect', async () => {
- await act(async () => {
- wrapper.find('HostForm').invoke('handleSubmit')(mockHost);
- });
- expect(wrapper.find('FormSubmitError').length).toBe(0);
- expect(history.location.pathname).toEqual(
- '/inventories/inventory/3/hosts/2/details'
- );
- });
-
- test('failed form submission should show an error message', async () => {
- const error = {
- response: {
- data: { detail: 'An error occurred' },
- },
- };
- GroupsAPI.createHost.mockImplementationOnce(() => Promise.reject(error));
- await act(async () => {
- wrapper.find('HostForm').invoke('handleSubmit')(mockHost);
- });
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryGroupHostAdd/index.js b/awx/ui/src/screens/Inventory/InventoryGroupHostAdd/index.js
deleted file mode 100644
index 7d79317ac665..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroupHostAdd/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventoryGroupHostAdd';
diff --git a/awx/ui/src/screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js b/awx/ui/src/screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js
deleted file mode 100644
index 2825715edefc..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.js
+++ /dev/null
@@ -1,307 +0,0 @@
-import React, { useEffect, useCallback, useState } from 'react';
-import { useLocation, useParams, Link } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { DropdownItem } from '@patternfly/react-core';
-import { getQSConfig, mergeParams, parseQueryString } from 'util/qs';
-import { GroupsAPI, InventoriesAPI } from 'api';
-
-import useRequest, {
- useDeleteItems,
- useDismissableError,
-} from 'hooks/useRequest';
-import useSelected from 'hooks/useSelected';
-import AlertModal from 'components/AlertModal';
-import DataListToolbar from 'components/DataListToolbar';
-import ErrorDetail from 'components/ErrorDetail';
-import PaginatedTable, {
- HeaderCell,
- HeaderRow,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import AssociateModal from 'components/AssociateModal';
-import DisassociateButton from 'components/DisassociateButton';
-import AdHocCommands from 'components/AdHocCommands/AdHocCommands';
-import AddDropDownButton from 'components/AddDropDownButton';
-import InventoryGroupHostListItem from './InventoryGroupHostListItem';
-
-const QS_CONFIG = getQSConfig('host', {
- page: 1,
- page_size: 20,
- order_by: 'name',
-});
-
-function InventoryGroupHostList() {
- const [isAdHocLaunchLoading, setIsAdHocLaunchLoading] = useState(false);
- const [isModalOpen, setIsModalOpen] = useState(false);
- const { id: inventoryId, groupId } = useParams();
- const location = useLocation();
-
- const {
- result: {
- hosts,
- hostCount,
- actions,
- relatedSearchableKeys,
- searchableKeys,
- moduleOptions,
- isAdHocDisabled,
- },
- error: contentError,
- isLoading,
- request: fetchHosts,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
- const [response, actionsResponse, options] = await Promise.all([
- GroupsAPI.readAllHosts(groupId, params),
- InventoriesAPI.readHostsOptions(inventoryId),
- InventoriesAPI.readAdHocOptions(inventoryId),
- ]);
-
- return {
- moduleOptions: options.data.actions.GET.module_name.choices,
- isAdHocDisabled: !options.data.actions.POST,
- hosts: response.data.results,
- hostCount: response.data.count,
- actions: actionsResponse.data.actions,
- relatedSearchableKeys: (
- actionsResponse?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
- };
- }, [groupId, inventoryId, location.search]),
- {
- moduleOptions: [],
- isAdHocDisabled: true,
- hosts: [],
- hostCount: 0,
- actions: {},
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- const { selected, isAllSelected, handleSelect, setSelected } =
- useSelected(hosts);
-
- useEffect(() => {
- fetchHosts();
- }, [fetchHosts]);
-
- const {
- isLoading: isDisassociateLoading,
- deleteItems: disassociateHosts,
- deletionError: disassociateErr,
- } = useDeleteItems(
- useCallback(
- () =>
- Promise.all(
- selected.map((host) => GroupsAPI.disassociateHost(groupId, host))
- ),
- [groupId, selected]
- ),
- {
- qsConfig: QS_CONFIG,
- allItemsSelected: isAllSelected,
- fetchItems: fetchHosts,
- }
- );
-
- const handleDisassociate = async () => {
- await disassociateHosts();
- setSelected([]);
- };
-
- const fetchHostsToAssociate = useCallback(
- (params) =>
- InventoriesAPI.readHosts(
- inventoryId,
- mergeParams(params, { not__groups: groupId })
- ),
- [groupId, inventoryId]
- );
-
- const fetchHostsOptions = useCallback(
- () => InventoriesAPI.readHostsOptions(inventoryId),
- [inventoryId]
- );
-
- const { request: handleAssociate, error: associateErr } = useRequest(
- useCallback(
- async (hostsToAssociate) => {
- await Promise.all(
- hostsToAssociate.map((host) =>
- GroupsAPI.associateHost(groupId, host.id)
- )
- );
- fetchHosts();
- },
- [groupId, fetchHosts]
- )
- );
-
- const { error: associateError, dismissError: dismissAssociateError } =
- useDismissableError(associateErr);
- const { error: disassociateError, dismissError: dismissDisassociateError } =
- useDismissableError(disassociateErr);
-
- const canAdd =
- actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
- const addFormUrl = `/inventories/inventory/${inventoryId}/groups/${groupId}/nested_hosts/add`;
- const addExistingHost = t`Add existing host`;
- const addNewHost = t`Add new host`;
-
- const addButton = (
- setIsModalOpen(true)}
- key={addExistingHost}
- aria-label={addExistingHost}
- ouiaId="add-existing-host-dropdown-item"
- >
- {addExistingHost}
- ,
-
- {addNewHost}
- ,
- ]}
- />
- );
- return (
- <>
-
- {t`Name`}
- {t`Description`}
- {t`Activity`}
- {t`Actions`}
-
- }
- toolbarSearchableKeys={searchableKeys}
- toolbarRelatedSearchableKeys={relatedSearchableKeys}
- renderToolbar={(props) => (
-
- setSelected(isSelected ? [...hosts] : [])
- }
- qsConfig={QS_CONFIG}
- additionalControls={[
- ...(canAdd ? [addButton] : []),
- ...(!isAdHocDisabled
- ? [
- 0}
- moduleOptions={moduleOptions}
- onLaunchLoading={setIsAdHocLaunchLoading}
- />,
- ]
- : []),
- ,
- ]}
- />
- )}
- renderRow={(host, index) => (
- row.id === host.id)}
- onSelect={() => handleSelect(host)}
- />
- )}
- emptyStateControls={canAdd && addButton}
- />
- {isModalOpen && (
- setIsModalOpen(false)}
- title={t`Select Hosts`}
- />
- )}
- {associateError && (
-
- {t`Failed to associate.`}
-
-
- )}
- {disassociateError && (
-
- {t`Failed to disassociate one or more hosts.`}
-
-
- )}
- >
- );
-}
-
-export default InventoryGroupHostList;
diff --git a/awx/ui/src/screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.test.js b/awx/ui/src/screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.test.js
deleted file mode 100644
index 4205a43171b8..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroupHosts/InventoryGroupHostList.test.js
+++ /dev/null
@@ -1,305 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { GroupsAPI, InventoriesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import InventoryGroupHostList from './InventoryGroupHostList';
-import mockHosts from '../shared/data.hosts.json';
-
-jest.mock('../../../api/models/Groups');
-jest.mock('../../../api/models/Inventories');
-jest.mock('../../../api/models/CredentialTypes');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useParams: () => ({
- id: 1,
- groupId: 2,
- }),
-}));
-
-describe('', () => {
- let wrapper;
-
- beforeEach(async () => {
- GroupsAPI.readAllHosts.mockResolvedValue({
- data: { ...mockHosts },
- });
- InventoriesAPI.readHostsOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- },
- });
- InventoriesAPI.readAdHocOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {
- module_name: {
- choices: [
- ['command', 'command'],
- ['shell', 'shell'],
- ],
- },
- },
- POST: {},
- },
- },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('initially renders successfully ', () => {
- expect(wrapper.find('InventoryGroupHostList').length).toBe(1);
- });
-
- test('should fetch inventory group hosts from api and render them in the list', () => {
- expect(GroupsAPI.readAllHosts).toHaveBeenCalled();
- expect(InventoriesAPI.readHostsOptions).toHaveBeenCalled();
- expect(InventoriesAPI.readAdHocOptions).toHaveBeenCalled();
- expect(wrapper.find('InventoryGroupHostListItem').length).toBe(3);
- });
-
- test('should check and uncheck the row item', async () => {
- expect(
- wrapper.find('input[aria-label="Select row 2"]').props().checked
- ).toBe(false);
- await act(async () => {
- wrapper.find('input[aria-label="Select row 2"]').invoke('onChange')();
- });
- wrapper.update();
- expect(
- wrapper.find('input[aria-label="Select row 2"]').props().checked
- ).toBe(true);
- await act(async () => {
- wrapper.find('input[aria-label="Select row 2"]').invoke('onChange')();
- });
- wrapper.update();
- expect(
- wrapper.find('input[aria-label="Select row 2"]').props().checked
- ).toBe(false);
- });
-
- test('should check all row items when select all is checked', async () => {
- wrapper.find('DataListCheck').forEach((el) => {
- expect(el.props().checked).toBe(false);
- });
- await act(async () => {
- wrapper.find('Checkbox#select-all').invoke('onChange')(true);
- });
- wrapper.update();
- wrapper.find('DataListCheck').forEach((el) => {
- expect(el.props().checked).toBe(true);
- });
- await act(async () => {
- wrapper.find('Checkbox#select-all').invoke('onChange')(false);
- });
- wrapper.update();
- wrapper.find('DataListCheck').forEach((el) => {
- expect(el.props().checked).toBe(false);
- });
- });
-
- test('should show add dropdown button and Run Commands according to permissions', async () => {
- expect(wrapper.find('AddDropDownButton').length).toBe(1);
- InventoriesAPI.readHostsOptions.mockResolvedValueOnce({
- data: {
- actions: {
- GET: {},
- },
- },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('AddDropDownButton').length).toBe(0);
- expect(wrapper.find('AdHocCommands').length).toBe(1);
- });
-
- test('expected api calls are made for multi-delete', async () => {
- expect(GroupsAPI.disassociateHost).toHaveBeenCalledTimes(0);
- expect(GroupsAPI.readAllHosts).toHaveBeenCalledTimes(1);
- await act(async () => {
- wrapper.find('Checkbox#select-all').invoke('onChange')(true);
- });
- wrapper.update();
- wrapper.find('button[aria-label="Disassociate"]').simulate('click');
- expect(wrapper.find('AlertModal Title').text()).toEqual(
- 'Disassociate host from group?'
- );
- await act(async () => {
- wrapper
- .find('button[aria-label="confirm disassociate"]')
- .simulate('click');
- });
- expect(GroupsAPI.disassociateHost).toHaveBeenCalledTimes(3);
- expect(GroupsAPI.readAllHosts).toHaveBeenCalledTimes(2);
- });
-
- test('should show error modal for failed disassociation', async () => {
- GroupsAPI.disassociateHost.mockRejectedValue(new Error());
- await act(async () => {
- wrapper.find('Checkbox#select-all').invoke('onChange')(true);
- });
- wrapper.update();
- wrapper.find('button[aria-label="Disassociate"]').simulate('click');
- expect(wrapper.find('AlertModal Title').text()).toEqual(
- 'Disassociate host from group?'
- );
- await act(async () => {
- wrapper
- .find('button[aria-label="confirm disassociate"]')
- .simulate('click');
- });
- wrapper.update();
- expect(wrapper.find('AlertModal ErrorDetail').length).toBe(1);
- expect(wrapper.find('AlertModal ModalBoxBody').text()).toEqual(
- expect.stringContaining('Failed to disassociate one or more hosts.')
- );
- });
-
- test('should show associate host modal when adding an existing host', () => {
- const dropdownToggle = wrapper.find(
- 'ToolbarAddButton button[aria-label="Add"]'
- );
- dropdownToggle.simulate('click');
-
- wrapper
- .find('DropdownItem[aria-label="Add existing host"]')
- .simulate('click');
- expect(wrapper.find('AssociateModal').length).toBe(1);
- wrapper.find('ModalBoxCloseButton').simulate('click');
- expect(wrapper.find('AssociateModal').length).toBe(0);
- });
-
- test('should make expected api request when associating hosts', async () => {
- GroupsAPI.associateHost.mockResolvedValue();
- InventoriesAPI.readHosts.mockResolvedValue({
- data: {
- count: 1,
- results: [{ id: 123, name: 'foo', url: '/api/v2/hosts/123/' }],
- },
- });
- wrapper.find('ToolbarAddButton button[aria-label="Add"]').simulate('click');
- await act(async () => {
- wrapper
- .find('DropdownItem[aria-label="Add existing host"]')
- .simulate('click');
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- await act(async () => {
- wrapper.find('CheckboxListItem').first().invoke('onSelect')();
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('button[aria-label="Save"]').simulate('click');
- });
- await waitForElement(wrapper, 'AssociateModal', (el) => el.length === 0);
- expect(InventoriesAPI.readHosts).toHaveBeenCalledTimes(1);
- expect(GroupsAPI.associateHost).toHaveBeenCalledTimes(1);
- });
-
- test('should show error modal for failed host association', async () => {
- GroupsAPI.associateHost.mockRejectedValue(new Error());
- InventoriesAPI.readHosts.mockResolvedValue({
- data: {
- count: 1,
- results: [{ id: 123, name: 'foo', url: '/api/v2/hosts/123/' }],
- },
- });
- wrapper.find('ToolbarAddButton[aria-label="Add"]').simulate('click');
- await act(async () => {
- wrapper
- .find('DropdownItem[aria-label="Add existing host"]')
- .simulate('click');
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- await act(async () => {
- wrapper.find('CheckboxListItem').first().invoke('onSelect')();
- });
- await act(async () => {
- wrapper.find('button[aria-label="Save"]').simulate('click');
- });
- wrapper.update();
- expect(wrapper.find('AlertModal ErrorDetail').length).toBe(1);
- expect(wrapper.find('AlertModal ModalBoxBody').text()).toEqual(
- expect.stringContaining('Failed to associate.')
- );
- });
-
- test('should navigate to host add form when adding a new host', async () => {
- GroupsAPI.readAllHosts.mockResolvedValue({
- data: { ...mockHosts },
- });
- InventoriesAPI.readHostsOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- },
- });
- const history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/1/groups/2/nested_hosts/add'],
- });
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: {
- router: { history },
- },
- });
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- const dropdownToggle = wrapper.find(
- 'ToolbarAddButton button[aria-label="Add"]'
- );
- dropdownToggle.simulate('click');
- wrapper.find('DropdownItem[aria-label="Add new host"]').simulate('click');
- wrapper.update();
- expect(history.location.pathname).toEqual(
- '/inventories/inventory/1/groups/2/nested_hosts/add'
- );
- });
-
- test('should show content error when api throws error on initial render', async () => {
- InventoriesAPI.readHostsOptions.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
- test('should not render ad hoc commands button', async () => {
- InventoriesAPI.readAdHocOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {
- module_name: {
- choices: [
- ['command', 'command'],
- ['shell', 'shell'],
- ],
- },
- },
- },
- },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- expect(wrapper.find('AdHocCommands')).toHaveLength(0);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js b/awx/ui/src/screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js
deleted file mode 100644
index b5d56925b452..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.js
+++ /dev/null
@@ -1,90 +0,0 @@
-import 'styled-components/macro';
-import React from 'react';
-import { Link } from 'react-router-dom';
-import { string, bool, func, number } from 'prop-types';
-import { t } from '@lingui/macro';
-import { Button, Tooltip } from '@patternfly/react-core';
-import { PencilAltIcon } from '@patternfly/react-icons';
-
-import { Td, Tr } from '@patternfly/react-table';
-import { ActionItem, ActionsTd } from 'components/PaginatedTable';
-import HostToggle from 'components/HostToggle';
-import Sparkline from 'components/Sparkline';
-import { Host } from 'types';
-
-function InventoryGroupHostListItem({
- detailUrl,
- editUrl,
- host,
- rowIndex,
- isSelected,
- onSelect,
-}) {
- const recentPlaybookJobs = host.summary_fields.recent_jobs.map((job) => ({
- ...job,
- type: 'job',
- }));
-
- const labelId = `check-action-${host.id}`;
-
- return (
-
- |
-
-
- {host.name}
-
- |
- {host.description} |
-
-
- |
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-InventoryGroupHostListItem.propTypes = {
- detailUrl: string.isRequired,
- editUrl: string.isRequired,
- rowIndex: number.isRequired,
- host: Host.isRequired,
- isSelected: bool.isRequired,
- onSelect: func.isRequired,
-};
-
-export default InventoryGroupHostListItem;
diff --git a/awx/ui/src/screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.test.js b/awx/ui/src/screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.test.js
deleted file mode 100644
index c26ac566f81c..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroupHosts/InventoryGroupHostListItem.test.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import InventoryGroupHostListItem from './InventoryGroupHostListItem';
-import mockHosts from '../shared/data.hosts.json';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
- const mockHost = mockHosts.results[0];
-
- beforeEach(() => {
- wrapper = mountWithContexts(
-
-
- {}}
- rowIndex={0}
- />
-
-
- );
- });
-
- test('should display expected details', () => {
- expect(wrapper.find('InventoryGroupHostListItem').length).toBe(1);
- expect(
- wrapper.find('Td[dataLabel="host-name-2"]').find('Link').prop('to')
- ).toBe('/host/1');
- expect(wrapper.find('Td[dataLabel="host-description-2"]').text()).toBe(
- 'Bar'
- );
- });
-
- test('should display expected row item content', () => {
- expect(wrapper.find('b').text()).toContain(
- '.host-000001.group-00000.dummy'
- );
- expect(wrapper.find('Sparkline').length).toBe(1);
- expect(wrapper.find('HostToggle').length).toBe(1);
- });
-
- test('edit button shown to users with edit capabilities', () => {
- expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
- });
-
- test('edit button hidden from users without edit capabilities', () => {
- const copyMockHost = { ...mockHost };
- copyMockHost.summary_fields.user_capabilities.edit = false;
- wrapper = mountWithContexts(
-
-
- {}}
- rowIndex={0}
- />
-
-
- );
- expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryGroupHosts/InventoryGroupHosts.js b/awx/ui/src/screens/Inventory/InventoryGroupHosts/InventoryGroupHosts.js
deleted file mode 100644
index d0e4c34d7082..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroupHosts/InventoryGroupHosts.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import React from 'react';
-import { Switch, Route } from 'react-router-dom';
-import InventoryGroupHostAdd from '../InventoryGroupHostAdd';
-import InventoryGroupHostList from './InventoryGroupHostList';
-
-function InventoryGroupHosts({ inventoryGroup }) {
- return (
-
-
-
-
-
-
-
-
- );
-}
-
-export default InventoryGroupHosts;
diff --git a/awx/ui/src/screens/Inventory/InventoryGroupHosts/InventoryGroupHosts.test.js b/awx/ui/src/screens/Inventory/InventoryGroupHosts/InventoryGroupHosts.test.js
deleted file mode 100644
index ee955fcbcae0..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroupHosts/InventoryGroupHosts.test.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import InventoryGroupHosts from './InventoryGroupHosts';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
-
- test('initially renders successfully', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/1/groups/1/nested_hosts'],
- });
-
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: {
- router: { history, route: { location: history.location } },
- },
- });
- });
- expect(wrapper.length).toBe(1);
- expect(wrapper.find('InventoryGroupHostList').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryGroupHosts/index.js b/awx/ui/src/screens/Inventory/InventoryGroupHosts/index.js
deleted file mode 100644
index 58e24ac90e3c..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroupHosts/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventoryGroupHosts';
diff --git a/awx/ui/src/screens/Inventory/InventoryGroups/InventoryGroupItem.js b/awx/ui/src/screens/Inventory/InventoryGroups/InventoryGroupItem.js
deleted file mode 100644
index 966f4fe2a519..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroups/InventoryGroupItem.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import React from 'react';
-import { bool, func, number, oneOfType, string } from 'prop-types';
-import { t } from '@lingui/macro';
-
-import { Button } from '@patternfly/react-core';
-import { Tr, Td } from '@patternfly/react-table';
-
-import { Link } from 'react-router-dom';
-import { PencilAltIcon } from '@patternfly/react-icons';
-import { ActionsTd, ActionItem } from 'components/PaginatedTable';
-import { Group } from 'types';
-
-function InventoryGroupItem({
- group,
- inventoryId,
- isSelected,
- onSelect,
- rowIndex,
-}) {
- const labelId = `check-action-${group.id}`;
- const detailUrl = `/inventories/inventory/${inventoryId}/groups/${group.id}/details`;
- const editUrl = `/inventories/inventory/${inventoryId}/groups/${group.id}/edit`;
-
- return (
-
- |
-
-
- {group.name}
-
- |
-
-
-
-
-
-
- );
-}
-
-InventoryGroupItem.propTypes = {
- group: Group.isRequired,
- inventoryId: oneOfType([number, string]).isRequired,
- isSelected: bool.isRequired,
- onSelect: func.isRequired,
-};
-
-export default InventoryGroupItem;
diff --git a/awx/ui/src/screens/Inventory/InventoryGroups/InventoryGroupItem.test.js b/awx/ui/src/screens/Inventory/InventoryGroups/InventoryGroupItem.test.js
deleted file mode 100644
index cb4956e44a7d..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroups/InventoryGroupItem.test.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import InventoryGroupItem from './InventoryGroupItem';
-
-describe('', () => {
- let wrapper;
- const mockGroup = {
- id: 2,
- type: 'group',
- name: 'foo',
- inventory: 1,
- summary_fields: {
- user_capabilities: {
- edit: true,
- },
- },
- };
-
- beforeEach(() => {
- wrapper = mountWithContexts(
-
- );
- });
-
- test('initially renders successfully', () => {
- expect(wrapper.find('InventoryGroupItem').length).toBe(1);
- });
-
- test('edit button should be shown to users with edit capabilities', () => {
- expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
- });
-
- test('edit button should be hidden from users without edit capabilities', () => {
- const copyMockGroup = { ...mockGroup };
- copyMockGroup.summary_fields.user_capabilities.edit = false;
-
- wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryGroups/InventoryGroups.js b/awx/ui/src/screens/Inventory/InventoryGroups/InventoryGroups.js
deleted file mode 100644
index ae19f0966017..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroups/InventoryGroups.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import React from 'react';
-
-import { Switch, Route } from 'react-router-dom';
-
-import InventoryGroupAdd from '../InventoryGroupAdd/InventoryGroupAdd';
-
-import InventoryGroup from '../InventoryGroup/InventoryGroup';
-import InventoryGroupsList from './InventoryGroupsList';
-
-function InventoryGroups({ setBreadcrumb, inventory }) {
- return (
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-export { InventoryGroups as _InventoryGroups };
-export default InventoryGroups;
diff --git a/awx/ui/src/screens/Inventory/InventoryGroups/InventoryGroups.test.js b/awx/ui/src/screens/Inventory/InventoryGroups/InventoryGroups.test.js
deleted file mode 100644
index 97fff4596c9d..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroups/InventoryGroups.test.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import InventoryGroups from './InventoryGroups';
-
-jest.mock('../../../api');
-
-describe('', () => {
- test('initially renders successfully', async () => {
- let wrapper;
- const history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/1/groups'],
- });
- const inventory = { id: 1, name: 'Foo' };
-
- await act(async () => {
- wrapper = mountWithContexts(
- {}} inventory={inventory} />,
-
- {
- context: {
- router: { history, route: { location: history.location } },
- },
- }
- );
- });
- expect(wrapper.length).toBe(1);
- expect(wrapper.find('InventoryGroupsList').length).toBe(1);
- });
- test('test that InventoryGroupsAdd renders', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/1/groups/add'],
- });
- const inventory = { id: 1, name: 'Foo' };
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
- {}} inventory={inventory} />,
- {
- context: {
- router: { history, route: { location: history.location } },
- },
- }
- );
- });
- expect(wrapper.find('InventoryGroupsAdd').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryGroups/InventoryGroupsList.js b/awx/ui/src/screens/Inventory/InventoryGroups/InventoryGroupsList.js
deleted file mode 100644
index 77bd67c404e8..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroups/InventoryGroupsList.js
+++ /dev/null
@@ -1,208 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { useParams, useLocation } from 'react-router-dom';
-import { t } from '@lingui/macro';
-import { Tooltip } from '@patternfly/react-core';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import useSelected from 'hooks/useSelected';
-import useRequest from 'hooks/useRequest';
-import { InventoriesAPI } from 'api';
-import DataListToolbar from 'components/DataListToolbar';
-import PaginatedTable, {
- HeaderRow,
- HeaderCell,
- ToolbarAddButton,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import AdHocCommands from 'components/AdHocCommands/AdHocCommands';
-import InventoryGroupItem from './InventoryGroupItem';
-import InventoryGroupsDeleteModal from '../shared/InventoryGroupsDeleteModal';
-
-const QS_CONFIG = getQSConfig('group', {
- page: 1,
- page_size: 20,
- order_by: 'name',
-});
-
-function cannotDelete(item) {
- return !item.summary_fields.user_capabilities.delete;
-}
-
-function InventoryGroupsList() {
- const location = useLocation();
- const { id: inventoryId } = useParams();
- const [isAdHocLaunchLoading, setIsAdHocLaunchLoading] = useState(false);
-
- const {
- result: {
- groups,
- groupCount,
- actions,
- relatedSearchableKeys,
- searchableKeys,
- moduleOptions,
- isAdHocDisabled,
- },
- error: contentError,
- isLoading,
- request: fetchData,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
- const [response, groupOptions, options] = await Promise.all([
- InventoriesAPI.readGroups(inventoryId, params),
- InventoriesAPI.readGroupsOptions(inventoryId),
- InventoriesAPI.readAdHocOptions(inventoryId),
- ]);
-
- return {
- moduleOptions: options.data.actions.GET.module_name.choices,
- isAdHocDisabled: !options.data.actions.POST,
- groups: response.data.results,
- groupCount: response.data.count,
- actions: groupOptions.data.actions,
- relatedSearchableKeys: (
- groupOptions?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(groupOptions.data.actions?.GET),
- };
- }, [inventoryId, location]),
- {
- groups: [],
- groupCount: 0,
- actions: {},
- relatedSearchableKeys: [],
- searchableKeys: [],
- moduleOptions: [],
- isAdHocDisabled: true,
- }
- );
-
- useEffect(() => {
- fetchData();
- }, [fetchData]);
-
- const { selected, isAllSelected, handleSelect, clearSelected, selectAll } =
- useSelected(groups);
-
- const renderTooltip = () => {
- const itemsUnableToDelete = selected
- .filter(cannotDelete)
- .map((item) => item.name)
- .join(', ');
-
- if (selected.some(cannotDelete)) {
- return (
-
- {t`You do not have permission to delete the following Groups: ${itemsUnableToDelete}`}
-
- );
- }
- if (selected.length) {
- return t`Delete`;
- }
- return t`Select a row to delete`;
- };
-
- const canAdd =
- actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
-
- return (
-
- {t`Name`}
- {t`Actions`}
-
- }
- renderRow={(item, index) => (
- row.id === item.id)}
- onSelect={() => handleSelect(item)}
- rowIndex={index}
- />
- )}
- renderToolbar={(props) => (
- ,
- ]
- : []),
- ...(!isAdHocDisabled
- ? [
- 0}
- onLaunchLoading={setIsAdHocLaunchLoading}
- moduleOptions={moduleOptions}
- />,
- ]
- : []),
-
-
- {
- fetchData();
- clearSelected();
- }}
- />
-
- ,
- ]}
- />
- )}
- emptyStateControls={
- canAdd && (
-
- )
- }
- />
- );
-}
-export default InventoryGroupsList;
diff --git a/awx/ui/src/screens/Inventory/InventoryGroups/InventoryGroupsList.test.js b/awx/ui/src/screens/Inventory/InventoryGroups/InventoryGroupsList.test.js
deleted file mode 100644
index 5743fc96c821..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroups/InventoryGroupsList.test.js
+++ /dev/null
@@ -1,318 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Route } from 'react-router-dom';
-import { createMemoryHistory } from 'history';
-import { InventoriesAPI, GroupsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import InventoryGroupsList from './InventoryGroupsList';
-
-jest.mock('../../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useParams: () => ({
- id: 1,
- }),
-}));
-const mockGroups = [
- {
- id: 1,
- type: 'group',
- name: 'foo',
- inventory: 1,
- url: '/api/v2/groups/1',
- summary_fields: {
- user_capabilities: {
- delete: true,
- edit: true,
- },
- },
- },
- {
- id: 2,
- type: 'group',
- name: 'bar',
- inventory: 1,
- url: '/api/v2/groups/2',
- summary_fields: {
- user_capabilities: {
- delete: true,
- edit: true,
- },
- },
- },
- {
- id: 3,
- type: 'group',
- name: 'baz',
- inventory: 1,
- url: '/api/v2/groups/3',
- summary_fields: {
- user_capabilities: {
- delete: false,
- edit: false,
- },
- },
- },
-];
-
-describe('', () => {
- let wrapper;
-
- beforeEach(async () => {
- InventoriesAPI.readGroups.mockResolvedValue({
- data: {
- count: mockGroups.length,
- results: mockGroups,
- },
- });
- InventoriesAPI.readGroupsOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- },
- });
- InventoriesAPI.readAdHocOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {
- module_name: {
- choices: [
- ['command', 'command'],
- ['shell', 'shell'],
- ],
- },
- },
- POST: {},
- },
- },
- });
- const history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/3/groups'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- ,
- {
- context: {
- router: {
- history,
- route: {
- location: history.location,
- },
- },
- },
- }
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should fetch groups from api and render them in the list', async () => {
- expect(InventoriesAPI.readGroups).toHaveBeenCalled();
- expect(wrapper.find('InventoryGroupItem').length).toBe(3);
- });
-
- test('should render Run Commands button', async () => {
- expect(wrapper.find('AdHocCommands')).toHaveLength(1);
- });
-
- test('should check and uncheck the row item', async () => {
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').props().checked
- ).toBe(false);
-
- await act(async () => {
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .invoke('onChange')(true);
- });
- wrapper.update();
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').props().checked
- ).toBe(true);
-
- await act(async () => {
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .invoke('onChange')(false);
- });
- wrapper.update();
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').props().checked
- ).toBe(false);
- });
-
- test('should check all row items when select all is checked', async () => {
- expect.assertions(9);
- wrapper.find('.pf-c-table__check').forEach((el) => {
- expect(el.find('input').props().checked).toBe(false);
- });
- await act(async () => {
- wrapper.find('Checkbox#select-all').invoke('onChange')(true);
- });
- wrapper.update();
- wrapper.find('.pf-c-table__check').forEach((el) => {
- expect(el.find('input').props().checked).toBe(true);
- });
- await act(async () => {
- wrapper.find('Checkbox#select-all').invoke('onChange')(false);
- });
- wrapper.update();
- wrapper.find('.pf-c-table__check').forEach((el) => {
- expect(el.find('input').props().checked).toBe(false);
- });
- });
-
- test('should not render ad hoc commands button', async () => {
- InventoriesAPI.readAdHocOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {
- module_name: {
- choices: [
- ['command', 'command'],
- ['shell', 'shell'],
- ],
- },
- },
- },
- },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- expect(wrapper.find('AdHocCommands')).toHaveLength(0);
- });
-});
-
-describe(' error handling', () => {
- let wrapper;
-
- beforeEach(() => {
- InventoriesAPI.readGroups.mockResolvedValue({
- data: {
- count: mockGroups.length,
- results: mockGroups,
- },
- });
- InventoriesAPI.readGroupsOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- },
- });
- InventoriesAPI.readAdHocOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {
- module_name: {
- choices: [
- ['command', 'command'],
- ['shell', 'shell'],
- ],
- },
- },
- POST: {},
- },
- },
- });
- GroupsAPI.destroy.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'delete',
- url: '/api/v2/groups/1',
- },
- data: 'An error occurred',
- },
- })
- );
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should show content error when api throws error on initial render', async () => {
- InventoriesAPI.readGroupsOptions.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length > 0);
- });
-
- test('should show content error if groups are not successfully fetched from api', async () => {
- InventoriesAPI.readGroups.mockImplementation(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length > 0);
- });
-
- test('should show error modal when group is not successfully deleted from api', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- waitForElement(wrapper, 'ContentEmpty', (el) => el.length === 0);
-
- await act(async () => {
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .invoke('onChange')();
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('Toolbar Button[aria-label="Delete"]').invoke('onClick')();
- });
- wrapper.update();
-
- await waitForElement(
- wrapper,
- 'AlertModal__Header',
- (el) => el.text() === 'Delete Group?'
- );
- await act(async () => {
- wrapper.find('Radio[id="radio-delete"]').invoke('onChange')();
- });
- wrapper.update();
- await act(async () => {
- wrapper
- .find('ModalBoxFooter Button[aria-label="Confirm Delete"]')
- .invoke('onClick')();
- });
- await waitForElement(
- wrapper,
- 'AlertModal[aria-label="deletion error"] Modal',
- (el) => el.props().isOpen === true && el.props().title === 'Error!'
- );
-
- await act(async () => {
- wrapper
- .find('AlertModal[aria-label="deletion error"]')
- .invoke('onClose')();
- });
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryGroups/index.js b/awx/ui/src/screens/Inventory/InventoryGroups/index.js
deleted file mode 100644
index 3402bebb5294..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryGroups/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventoryGroups';
diff --git a/awx/ui/src/screens/Inventory/InventoryHost/InventoryHost.js b/awx/ui/src/screens/Inventory/InventoryHost/InventoryHost.js
deleted file mode 100644
index af010a4c04c1..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHost/InventoryHost.js
+++ /dev/null
@@ -1,170 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-
-import { t } from '@lingui/macro';
-import {
- Switch,
- Route,
- Redirect,
- Link,
- useRouteMatch,
- useLocation,
-} from 'react-router-dom';
-import { Card } from '@patternfly/react-core';
-import { CaretLeftIcon } from '@patternfly/react-icons';
-import useRequest from 'hooks/useRequest';
-
-import { InventoriesAPI } from 'api';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import RoutedTabs from 'components/RoutedTabs';
-import JobList from 'components/JobList';
-import InventoryHostDetail from '../InventoryHostDetail';
-import InventoryHostEdit from '../InventoryHostEdit';
-import InventoryHostFacts from '../InventoryHostFacts';
-import InventoryHostGroups from '../InventoryHostGroups';
-
-function InventoryHost({ setBreadcrumb, inventory }) {
- const location = useLocation();
- const match = useRouteMatch('/inventories/inventory/:id/hosts/:hostId');
- const hostListUrl = `/inventories/inventory/${inventory.id}/hosts`;
-
- const {
- result: { host },
- error: contentError,
- isLoading,
- request: fetchHost,
- } = useRequest(
- useCallback(async () => {
- const response = await InventoriesAPI.readHostDetail(
- inventory.id,
- match.params.hostId
- );
- return {
- host: response,
- };
- }, [inventory.id, match.params.hostId]),
- {
- host: null,
- }
- );
-
- useEffect(() => {
- fetchHost();
- }, [fetchHost, location.pathname]);
-
- useEffect(() => {
- if (inventory && host) {
- setBreadcrumb(inventory, host);
- }
- }, [inventory, host, setBreadcrumb]);
-
- const tabsArray = [
- {
- name: (
- <>
-
- {t`Back to Hosts`}
- >
- ),
- link: `${hostListUrl}`,
- id: 0,
- },
- {
- name: t`Details`,
- link: `${match.url}/details`,
- id: 1,
- },
- {
- name: t`Facts`,
- link: `${match.url}/facts`,
- id: 2,
- },
- {
- name: t`Groups`,
- link: `${match.url}/groups`,
- id: 3,
- },
- {
- name: t`Jobs`,
- link: `${match.url}/jobs`,
- id: 4,
- },
- ];
-
- if (contentError) {
- return (
-
-
- {contentError.response && contentError.response.status === 404 && (
-
- {t`Host not found.`}{' '}
- {t`View all Inventory Hosts.`}
-
- )}
-
-
- );
- }
-
- let showCardHeader = true;
- if (['edit'].some((name) => location.pathname.includes(name))) {
- showCardHeader = false;
- }
-
- return (
- <>
- {showCardHeader && }
-
- {isLoading && }
-
- {!isLoading && host && (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {t`View Inventory Host Details`}
-
-
-
-
- )}
- >
- );
-}
-
-export default InventoryHost;
diff --git a/awx/ui/src/screens/Inventory/InventoryHost/InventoryHost.test.js b/awx/ui/src/screens/Inventory/InventoryHost/InventoryHost.test.js
deleted file mode 100644
index 61e611530340..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHost/InventoryHost.test.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { InventoriesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import mockHost from '../shared/data.host.json';
-import InventoryHost from './InventoryHost';
-
-jest.mock('../../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useRouteMatch: () => ({
- url: '/inventories/inventory/1/hosts/1',
- params: { id: 1, hostId: 1 },
- }),
-}));
-
-InventoriesAPI.readHostDetail.mockResolvedValue({
- data: { ...mockHost },
-});
-
-const mockInventory = {
- id: 1,
- name: 'Mock Inventory',
-};
-
-describe('', () => {
- let wrapper;
- let history;
-
- beforeEach(async () => {
- InventoriesAPI.readHostDetail.mockResolvedValue(mockHost);
-
- await act(async () => {
- wrapper = mountWithContexts(
- {}} />
- );
- });
- });
-
- test('should render expected tabs', async () => {
- const expectedTabs = [
- 'Back to Hosts',
- 'Details',
- 'Facts',
- 'Groups',
- 'Jobs',
- ];
- wrapper.find('RoutedTabs li').forEach((tab, index) => {
- expect(tab.text()).toEqual(expectedTabs[index]);
- });
- });
-
- test('should show content error when api throws error on initial render', async () => {
- InventoriesAPI.readHostDetail.mockRejectedValueOnce(new Error());
- await act(async () => {
- wrapper = mountWithContexts(
- {}} />
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-
- test('should show content error when user attempts to navigate to erroneous route', async () => {
- history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/1/hosts/1/foobar'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
- {}} />,
- { context: { router: { history } } }
- );
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-
- test('should show content error when inventory id does not match host inventory', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
- {}} />
- );
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryHost/index.js b/awx/ui/src/screens/Inventory/InventoryHost/index.js
deleted file mode 100644
index 5419035b15b1..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHost/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventoryHost';
diff --git a/awx/ui/src/screens/Inventory/InventoryHostAdd/InventoryHostAdd.js b/awx/ui/src/screens/Inventory/InventoryHostAdd/InventoryHostAdd.js
deleted file mode 100644
index dd5f61fa2e1d..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHostAdd/InventoryHostAdd.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import React, { useState } from 'react';
-import { useHistory } from 'react-router-dom';
-import { CardBody } from 'components/Card';
-import HostForm from 'components/HostForm';
-
-import { HostsAPI } from 'api';
-
-function InventoryHostAdd({ inventory }) {
- const [formError, setFormError] = useState(null);
- const hostsUrl = `/inventories/inventory/${inventory.id}/hosts`;
- const history = useHistory();
-
- const handleSubmit = async (formData) => {
- try {
- const values = {
- ...formData,
- inventory: inventory.id,
- };
- const { data: response } = await HostsAPI.create(values);
- history.push(`${hostsUrl}/${response.id}/details`);
- } catch (error) {
- setFormError(error);
- }
- };
-
- const handleCancel = () => {
- history.push(hostsUrl);
- };
-
- return (
-
-
-
- );
-}
-
-export default InventoryHostAdd;
diff --git a/awx/ui/src/screens/Inventory/InventoryHostAdd/InventoryHostAdd.test.js b/awx/ui/src/screens/Inventory/InventoryHostAdd/InventoryHostAdd.test.js
deleted file mode 100644
index 328efebfc678..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHostAdd/InventoryHostAdd.test.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { HostsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import InventoryHostAdd from './InventoryHostAdd';
-import mockHost from '../shared/data.host.json';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
- let history;
-
- beforeEach(async () => {
- history = createMemoryHistory();
- HostsAPI.create.mockResolvedValue({
- data: {
- ...mockHost,
- },
- });
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('handleSubmit should post to api', async () => {
- await act(async () => {
- wrapper.find('HostForm').prop('handleSubmit')(mockHost);
- });
- expect(HostsAPI.create).toHaveBeenCalledWith(mockHost);
- });
-
- test('should navigate to hosts list when cancel is clicked', () => {
- wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
- expect(history.location.pathname).toEqual('/inventories/inventory/3/hosts');
- });
-
- test('successful form submission should trigger redirect', async () => {
- await act(async () => {
- wrapper.find('HostForm').invoke('handleSubmit')(mockHost);
- });
- expect(wrapper.find('FormSubmitError').length).toBe(0);
- expect(history.location.pathname).toEqual(
- '/inventories/inventory/3/hosts/2/details'
- );
- });
-
- test('failed form submission should show an error message', async () => {
- const error = {
- response: {
- data: { detail: 'An error occurred' },
- },
- };
- HostsAPI.create.mockImplementationOnce(() => Promise.reject(error));
- await act(async () => {
- wrapper.find('HostForm').invoke('handleSubmit')(mockHost);
- });
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryHostAdd/index.js b/awx/ui/src/screens/Inventory/InventoryHostAdd/index.js
deleted file mode 100644
index 56bb7e05adc1..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHostAdd/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventoryHostAdd';
diff --git a/awx/ui/src/screens/Inventory/InventoryHostDetail/InventoryHostDetail.js b/awx/ui/src/screens/Inventory/InventoryHostDetail/InventoryHostDetail.js
deleted file mode 100644
index d7bfa590a276..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHostDetail/InventoryHostDetail.js
+++ /dev/null
@@ -1,133 +0,0 @@
-import 'styled-components/macro';
-import React, { useState } from 'react';
-import { Link, useHistory } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { Button } from '@patternfly/react-core';
-import { Host } from 'types';
-import { CardBody, CardActionsRow } from 'components/Card';
-import AlertModal from 'components/AlertModal';
-import ErrorDetail from 'components/ErrorDetail';
-import { DetailList, Detail, UserDateDetail } from 'components/DetailList';
-import { VariablesDetail } from 'components/CodeEditor';
-import Sparkline from 'components/Sparkline';
-import DeleteButton from 'components/DeleteButton';
-import { HostsAPI } from 'api';
-import HostToggle from 'components/HostToggle';
-
-function InventoryHostDetail({ host }) {
- const {
- created,
- description,
- id,
- modified,
- name,
- variables,
- summary_fields: {
- inventory,
- recent_jobs,
- created_by,
- modified_by,
- user_capabilities,
- },
- } = host;
-
- const [isLoading, setIsloading] = useState(false);
- const [deletionError, setDeletionError] = useState(false);
- const history = useHistory();
-
- const handleHostDelete = async () => {
- setIsloading(true);
- try {
- await HostsAPI.destroy(id);
- history.push(`/inventories/inventory/${inventory.id}/hosts`);
- } catch (err) {
- setDeletionError(err);
- } finally {
- setIsloading(false);
- }
- };
-
- if (!isLoading && deletionError) {
- return (
- setDeletionError(false)}
- >
- {t`Failed to delete ${name}.`}
-
-
- );
- }
-
- const recentPlaybookJobs = recent_jobs.map((job) => ({
- ...job,
- type: 'job',
- }));
-
- return (
-
-
-
-
- }
- isEmpty={recentPlaybookJobs?.length === 0}
- />
-
-
-
-
-
-
- {user_capabilities?.edit && (
-
- )}
- {user_capabilities?.delete && (
- handleHostDelete()}
- />
- )}
-
- {deletionError && (
- setDeletionError(null)}
- >
- {t`Failed to delete host.`}
-
-
- )}
-
- );
-}
-
-InventoryHostDetail.propTypes = {
- host: Host.isRequired,
-};
-
-export default InventoryHostDetail;
diff --git a/awx/ui/src/screens/Inventory/InventoryHostDetail/InventoryHostDetail.test.js b/awx/ui/src/screens/Inventory/InventoryHostDetail/InventoryHostDetail.test.js
deleted file mode 100644
index 29e6364ac180..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHostDetail/InventoryHostDetail.test.js
+++ /dev/null
@@ -1,102 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { HostsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import InventoryHostDetail from './InventoryHostDetail';
-import mockHost from '../shared/data.host.json';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
-
- describe('User has edit permissions', () => {
- beforeAll(() => {
- wrapper = mountWithContexts();
- });
-
- test('should render Details', async () => {
- function assertDetail(label, value) {
- expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
- expect(wrapper.find(`Detail[label="${label}"] dd`).text()).toBe(value);
- }
-
- assertDetail('Name', 'localhost');
- assertDetail('Description', 'localhost description');
- assertDetail('Created', '10/28/2019, 9:26:54 PM');
- assertDetail('Last Modified', '10/29/2019, 8:18:41 PM');
- expect(wrapper.find(`Detail[label="Activity"] Sparkline`)).toHaveLength(
- 1
- );
- });
-
- test('should show edit button for users with edit permission', () => {
- const editButton = wrapper.find('Button[aria-label="edit"]');
- expect(editButton.text()).toEqual('Edit');
- expect(editButton.prop('to')).toBe(
- '/inventories/inventory/3/hosts/2/edit'
- );
- });
-
- test('expected api call is made for delete', async () => {
- await act(async () => {
- wrapper.find('DeleteButton').invoke('onConfirm')();
- });
- expect(HostsAPI.destroy).toHaveBeenCalledTimes(1);
- });
-
- test('Error dialog shown for failed deletion', async () => {
- HostsAPI.destroy.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- wrapper.find('DeleteButton').invoke('onConfirm')();
- });
- await waitForElement(
- wrapper,
- 'Modal[title="Error!"]',
- (el) => el.length === 1
- );
- await act(async () => {
- wrapper.find('Modal[title="Error!"]').invoke('onClose')();
- });
- await waitForElement(
- wrapper,
- 'Modal[title="Error!"]',
- (el) => el.length === 0
- );
- });
- });
-
- describe('User has read-only permissions', () => {
- beforeAll(() => {
- const readOnlyHost = {
- ...mockHost,
- summary_fields: {
- ...mockHost.summary_fields,
- user_capabilities: {
- ...mockHost.summary_fields.user_capabilities,
- },
- },
- };
- readOnlyHost.summary_fields.user_capabilities.edit = false;
- readOnlyHost.summary_fields.recent_jobs = [];
- wrapper = mountWithContexts();
- });
-
- test('should hide activity stream when there are no recent jobs', async () => {
- expect(wrapper.find(`Detail[label="Activity"] Sparkline`)).toHaveLength(
- 0
- );
- const activity_detail = wrapper.find(`Detail[label="Activity"]`).at(0);
- expect(activity_detail.prop('isEmpty')).toEqual(true);
- });
-
- test('should hide edit button for users without edit permission', async () => {
- expect(wrapper.find('Button[aria-label="edit"]').length).toBe(0);
- });
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryHostDetail/index.js b/awx/ui/src/screens/Inventory/InventoryHostDetail/index.js
deleted file mode 100644
index df9deaf20d53..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHostDetail/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventoryHostDetail';
diff --git a/awx/ui/src/screens/Inventory/InventoryHostEdit/InventoryHostEdit.js b/awx/ui/src/screens/Inventory/InventoryHostEdit/InventoryHostEdit.js
deleted file mode 100644
index a891bd70ad62..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHostEdit/InventoryHostEdit.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import React, { useState } from 'react';
-import PropTypes from 'prop-types';
-import { useHistory } from 'react-router-dom';
-import { CardBody } from 'components/Card';
-import HostForm from 'components/HostForm';
-
-import { HostsAPI } from 'api';
-
-function InventoryHostEdit({ host, inventory }) {
- const [formError, setFormError] = useState(null);
- const detailsUrl = `/inventories/inventory/${inventory.id}/hosts/${host.id}/details`;
- const history = useHistory();
-
- const handleSubmit = async (values) => {
- try {
- await HostsAPI.update(host.id, values);
- history.push(detailsUrl);
- } catch (error) {
- setFormError(error);
- }
- };
-
- const handleCancel = () => {
- history.push(detailsUrl);
- };
-
- return (
-
-
-
- );
-}
-
-InventoryHostEdit.propTypes = {
- host: PropTypes.shape().isRequired,
-};
-
-export default InventoryHostEdit;
diff --git a/awx/ui/src/screens/Inventory/InventoryHostEdit/InventoryHostEdit.test.js b/awx/ui/src/screens/Inventory/InventoryHostEdit/InventoryHostEdit.test.js
deleted file mode 100644
index bcdf2da046a8..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHostEdit/InventoryHostEdit.test.js
+++ /dev/null
@@ -1,76 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { HostsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import InventoryHostEdit from './InventoryHostEdit';
-import mockHost from '../shared/data.host.json';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
- let history;
-
- const updatedHostData = {
- name: 'new name',
- description: 'new description',
- variables: '---\nfoo: bar',
- };
-
- beforeAll(async () => {
- history = createMemoryHistory();
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('handleSubmit should call api update', async () => {
- await act(async () => {
- wrapper.find('HostForm').prop('handleSubmit')(updatedHostData);
- });
- expect(HostsAPI.update).toHaveBeenCalledWith(2, updatedHostData);
- });
-
- test('should navigate to inventory host detail when cancel is clicked', async () => {
- await act(async () => {
- wrapper.find('button[aria-label="Cancel"]').prop('onClick')();
- });
- expect(history.location.pathname).toEqual(
- '/inventories/inventory/123/hosts/2/details'
- );
- });
-
- test('should navigate to inventory host detail after successful submission', async () => {
- await act(async () => {
- wrapper.find('HostForm').invoke('handleSubmit')(updatedHostData);
- });
- expect(wrapper.find('FormSubmitError').length).toBe(0);
- expect(history.location.pathname).toEqual(
- '/inventories/inventory/123/hosts/2/details'
- );
- });
-
- test('failed form submission should show an error message', async () => {
- const error = {
- response: {
- data: { detail: 'An error occurred' },
- },
- };
- HostsAPI.update.mockImplementationOnce(() => Promise.reject(error));
- await act(async () => {
- wrapper.find('HostForm').invoke('handleSubmit')(mockHost);
- });
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryHostEdit/index.js b/awx/ui/src/screens/Inventory/InventoryHostEdit/index.js
deleted file mode 100644
index 428da2e09c25..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHostEdit/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventoryHostEdit';
diff --git a/awx/ui/src/screens/Inventory/InventoryHostFacts/InventoryHostFacts.js b/awx/ui/src/screens/Inventory/InventoryHostFacts/InventoryHostFacts.js
deleted file mode 100644
index 80ea6879b03c..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHostFacts/InventoryHostFacts.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-
-import { t } from '@lingui/macro';
-import { Host } from 'types';
-import { CardBody } from 'components/Card';
-import { DetailList } from 'components/DetailList';
-import { VariablesDetail } from 'components/CodeEditor';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import useRequest from 'hooks/useRequest';
-import { HostsAPI } from 'api';
-
-function InventoryHostFacts({ host }) {
- const { request, isLoading, error, result } = useRequest(
- useCallback(async () => {
- const { data } = await HostsAPI.readFacts(host.id);
-
- return JSON.stringify(data, null, 4);
- }, [host]),
- null
- );
-
- useEffect(() => {
- request();
- }, [request]);
-
- if (error) {
- return ;
- }
-
- if (isLoading || result === null) {
- return ;
- }
-
- return (
-
-
-
-
-
- );
-}
-
-InventoryHostFacts.propTypes = {
- host: Host.isRequired,
-};
-
-export default InventoryHostFacts;
diff --git a/awx/ui/src/screens/Inventory/InventoryHostFacts/InventoryHostFacts.test.js b/awx/ui/src/screens/Inventory/InventoryHostFacts/InventoryHostFacts.test.js
deleted file mode 100644
index a5bf18fc85ff..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHostFacts/InventoryHostFacts.test.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { HostsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import InventoryHostFacts from './InventoryHostFacts';
-import mockHost from '../shared/data.host.json';
-import mockHostFacts from '../shared/data.hostFacts.json';
-
-jest.mock('../../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useParams: () => ({
- id: 1,
- hostId: 1,
- }),
-}));
-
-describe('', () => {
- let wrapper;
-
- beforeEach(async () => {
- HostsAPI.readFacts.mockResolvedValue({ data: mockHostFacts });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('initially renders successfully ', () => {
- expect(wrapper.find('InventoryHostFacts').length).toBe(1);
- });
-
- test('renders ContentError when facts GET fails', async () => {
- HostsAPI.readFacts.mockRejectedValueOnce(
- new Error({
- response: {
- config: {
- method: 'get',
- url: '/api/v2/hosts/1/ansible_facts',
- },
- data: 'An error occurred',
- status: 500,
- },
- })
- );
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryHostFacts/index.js b/awx/ui/src/screens/Inventory/InventoryHostFacts/index.js
deleted file mode 100644
index 7d20ee2705f9..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHostFacts/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventoryHostFacts';
diff --git a/awx/ui/src/screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js b/awx/ui/src/screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js
deleted file mode 100644
index 1d48038c1701..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import React from 'react';
-import { bool, func, number, oneOfType, string } from 'prop-types';
-import { t } from '@lingui/macro';
-import { Button } from '@patternfly/react-core';
-import { Tr, Td } from '@patternfly/react-table';
-
-import { Link } from 'react-router-dom';
-import { PencilAltIcon } from '@patternfly/react-icons';
-import { ActionsTd, ActionItem } from 'components/PaginatedTable';
-import { Group } from 'types';
-
-function InventoryHostGroupItem({
- group,
- inventoryId,
- isSelected,
- onSelect,
- rowIndex,
-}) {
- const labelId = `check-action-${group.id}`;
- const detailUrl = `/inventories/inventory/${inventoryId}/groups/${group.id}/details`;
- const editUrl = `/inventories/inventory/${inventoryId}/groups/${group.id}/edit`;
-
- return (
-
- |
-
-
- {group.name}
-
- |
-
-
-
-
-
-
- );
-}
-
-InventoryHostGroupItem.propTypes = {
- group: Group.isRequired,
- inventoryId: oneOfType([number, string]).isRequired,
- isSelected: bool.isRequired,
- onSelect: func.isRequired,
-};
-
-export default InventoryHostGroupItem;
diff --git a/awx/ui/src/screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.test.js b/awx/ui/src/screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.test.js
deleted file mode 100644
index 3a0273b4d236..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHostGroups/InventoryHostGroupItem.test.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import InventoryHostGroupItem from './InventoryHostGroupItem';
-
-describe('', () => {
- let wrapper;
- const mockGroup = {
- id: 2,
- type: 'group',
- name: 'foo',
- inventory: 1,
- summary_fields: {
- user_capabilities: {
- edit: true,
- },
- },
- };
-
- beforeEach(() => {
- wrapper = mountWithContexts(
-
- );
- });
-
- test('initially renders successfully', () => {
- expect(wrapper.find('InventoryHostGroupItem').length).toBe(1);
- });
-
- test('edit button should be shown to users with edit capabilities', () => {
- expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
- });
-
- test('edit button should be hidden from users without edit capabilities', () => {
- const copyMockGroup = { ...mockGroup };
- copyMockGroup.summary_fields.user_capabilities.edit = false;
-
- wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryHostGroups/InventoryHostGroups.js b/awx/ui/src/screens/Inventory/InventoryHostGroups/InventoryHostGroups.js
deleted file mode 100644
index c6775eb04fda..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHostGroups/InventoryHostGroups.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react';
-
-import { Switch, Route } from 'react-router-dom';
-
-import InventoryHostGroupsList from './InventoryHostGroupsList';
-
-function InventoryHostGroups() {
- return (
-
-
-
-
-
- );
-}
-
-export { InventoryHostGroups as _InventoryHostGroups };
-export default InventoryHostGroups;
diff --git a/awx/ui/src/screens/Inventory/InventoryHostGroups/InventoryHostGroups.test.js b/awx/ui/src/screens/Inventory/InventoryHostGroups/InventoryHostGroups.test.js
deleted file mode 100644
index aa51424008f3..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHostGroups/InventoryHostGroups.test.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { HostsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import InventoryHostGroups from './InventoryHostGroups';
-
-jest.mock('../../../api');
-HostsAPI.readAllGroups.mockResolvedValue({
- data: {
- count: 1,
- results: [
- {
- id: 1,
- url: 'www.google.com',
- summary_fields: {
- inventory: { id: 1, name: 'foo' },
- user_capabilities: { edit: true },
- },
- name: 'Bar',
- },
- ],
- },
-});
-HostsAPI.readGroupsOptions.mockResolvedValue({ data: { actions: {} } });
-
-describe('', () => {
- test('initially renders successfully', async () => {
- let wrapper;
- const history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/1/hosts/1/groups'],
- });
-
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: {
- router: { history, route: { location: history.location } },
- },
- });
- });
- expect(wrapper.length).toBe(1);
- expect(wrapper.find('InventoryHostGroupsList').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js b/awx/ui/src/screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js
deleted file mode 100644
index 6eeb5618f5d6..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.js
+++ /dev/null
@@ -1,273 +0,0 @@
-import React, { useState, useEffect, useCallback } from 'react';
-import { useParams, useLocation } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { getQSConfig, parseQueryString, mergeParams } from 'util/qs';
-import useRequest, {
- useDismissableError,
- useDeleteItems,
-} from 'hooks/useRequest';
-import useSelected from 'hooks/useSelected';
-import { HostsAPI, InventoriesAPI } from 'api';
-import DataListToolbar from 'components/DataListToolbar';
-import AlertModal from 'components/AlertModal';
-import ErrorDetail from 'components/ErrorDetail';
-import PaginatedTable, {
- HeaderRow,
- HeaderCell,
- ToolbarAddButton,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import AssociateModal from 'components/AssociateModal';
-import DisassociateButton from 'components/DisassociateButton';
-import AdHocCommands from 'components/AdHocCommands/AdHocCommands';
-import InventoryHostGroupItem from './InventoryHostGroupItem';
-
-const QS_CONFIG = getQSConfig('group', {
- page: 1,
- page_size: 20,
- order_by: 'name',
-});
-
-function InventoryHostGroupsList() {
- const [isModalOpen, setIsModalOpen] = useState(false);
- const [isAdHocLaunchLoading, setIsAdHocLaunchLoading] = useState(false);
- const { hostId, id: invId } = useParams();
- const { search } = useLocation();
-
- const {
- result: {
- groups,
- itemCount,
- actions,
- relatedSearchableKeys,
- searchableKeys,
- moduleOptions,
- isAdHocDisabled,
- },
- error: contentError,
- isLoading,
- request: fetchGroups,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, search);
-
- const [
- {
- data: { count, results },
- },
- hostGroupOptions,
- adHocOptions,
- ] = await Promise.all([
- HostsAPI.readAllGroups(hostId, params),
- HostsAPI.readGroupsOptions(hostId),
- InventoriesAPI.readAdHocOptions(invId),
- ]);
-
- return {
- moduleOptions: adHocOptions.data.actions.GET.module_name.choices,
- isAdHocDisabled: !adHocOptions.data.actions.POST,
- groups: results,
- itemCount: count,
- actions: hostGroupOptions.data.actions,
- relatedSearchableKeys: (
- hostGroupOptions?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(hostGroupOptions.data.actions?.GET),
- };
- }, [hostId, search]), // eslint-disable-line react-hooks/exhaustive-deps
- {
- groups: [],
- itemCount: 0,
- actions: {},
- relatedSearchableKeys: [],
- searchableKeys: [],
- moduleOptions: [],
- isAdHocDisabled: true,
- }
- );
-
- useEffect(() => {
- fetchGroups();
- }, [fetchGroups]);
-
- const { selected, isAllSelected, handleSelect, clearSelected, selectAll } =
- useSelected(groups);
-
- const {
- isLoading: isDisassociateLoading,
- deleteItems: disassociateHosts,
- deletionError: disassociateError,
- } = useDeleteItems(
- useCallback(
- () =>
- Promise.all(
- selected.map((group) => HostsAPI.disassociateGroup(hostId, group))
- ),
- [hostId, selected]
- ),
- {
- qsConfig: QS_CONFIG,
- allItemsSelected: isAllSelected,
- fetchItems: fetchGroups,
- }
- );
-
- const handleDisassociate = async () => {
- await disassociateHosts();
- clearSelected();
- };
-
- const fetchGroupsToAssociate = useCallback(
- (params) =>
- InventoriesAPI.readGroups(
- invId,
- mergeParams(params, { not__hosts: hostId })
- ),
- [invId, hostId]
- );
-
- const fetchGroupsOptions = useCallback(
- () => InventoriesAPI.readGroupsOptions(invId),
- [invId]
- );
-
- const { request: handleAssociate, error: associateError } = useRequest(
- useCallback(
- async (groupsToAssociate) => {
- await Promise.all(
- groupsToAssociate.map((group) =>
- HostsAPI.associateGroup(hostId, group.id)
- )
- );
- fetchGroups();
- },
- [hostId, fetchGroups]
- )
- );
-
- const { error, dismissError } = useDismissableError(
- associateError || disassociateError
- );
-
- const canAdd =
- actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
-
- return (
- <>
-
- {t`Name`}
- {t`Actions`}
-
- }
- renderRow={(item, index) => (
- row.id === item.id)}
- onSelect={() => handleSelect(item)}
- rowIndex={index}
- />
- )}
- renderToolbar={(props) => (
- setIsModalOpen(true)}
- />,
- ]
- : []),
- ...(!isAdHocDisabled
- ? [
- 0}
- moduleOptions={moduleOptions}
- onLaunchLoading={setIsAdHocLaunchLoading}
- />,
- ]
- : []),
- ,
- ]}
- />
- )}
- emptyStateControls={
- canAdd ? (
- setIsModalOpen(true)} />
- ) : null
- }
- />
- {isModalOpen && (
- setIsModalOpen(false)}
- title={t`Select Groups`}
- />
- )}
- {error && (
-
- {associateError
- ? t`Failed to associate.`
- : t`Failed to disassociate one or more groups.`}
-
-
- )}
- >
- );
-}
-export default InventoryHostGroupsList;
diff --git a/awx/ui/src/screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.test.js b/awx/ui/src/screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.test.js
deleted file mode 100644
index b1f0127b71f6..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHostGroups/InventoryHostGroupsList.test.js
+++ /dev/null
@@ -1,316 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Route } from 'react-router-dom';
-import { createMemoryHistory } from 'history';
-import { HostsAPI, InventoriesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import InventoryHostGroupsList from './InventoryHostGroupsList';
-
-jest.mock('../../../api');
-
-const mockGroups = [
- {
- id: 1,
- type: 'group',
- name: 'foo',
- inventory: 1,
- url: '/api/v2/groups/1',
- summary_fields: {
- inventory: {
- id: 1,
- },
- user_capabilities: {
- delete: true,
- edit: true,
- },
- },
- },
- {
- id: 2,
- type: 'group',
- name: 'bar',
- inventory: 1,
- url: '/api/v2/groups/2',
- summary_fields: {
- inventory: {
- id: 1,
- },
- user_capabilities: {
- delete: true,
- edit: true,
- },
- },
- },
- {
- id: 3,
- type: 'group',
- name: 'baz',
- inventory: 1,
- url: '/api/v2/groups/3',
- summary_fields: {
- inventory: {
- id: 1,
- },
- user_capabilities: {
- delete: true,
- edit: false,
- },
- },
- },
-];
-
-describe('', () => {
- let wrapper;
-
- beforeEach(async () => {
- HostsAPI.readAllGroups.mockResolvedValue({
- data: {
- count: mockGroups.length,
- results: mockGroups,
- },
- });
- HostsAPI.readGroupsOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- },
- });
- InventoriesAPI.readAdHocOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {
- module_name: {
- choices: [
- ['command', 'command'],
- ['shell', 'shell'],
- ],
- },
- },
- POST: {},
- },
- },
- });
- const history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/1/hosts/3/groups'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- ,
- {
- context: {
- router: { history, route: { location: history.location } },
- },
- }
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('initially renders successfully', () => {
- expect(wrapper.find('InventoryHostGroupsList').length).toBe(1);
- });
-
- test('should fetch groups from api and render them in the list', async () => {
- expect(HostsAPI.readAllGroups).toHaveBeenCalled();
- expect(wrapper.find('InventoryHostGroupItem').length).toBe(3);
- });
-
- test('should render Run Commands button', async () => {
- expect(wrapper.find('AdHocCommands')).toHaveLength(1);
- });
-
- test('should check and uncheck the row item', async () => {
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').props().checked
- ).toBe(false);
-
- await act(async () => {
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .invoke('onChange')(true);
- });
- wrapper.update();
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').props().checked
- ).toBe(true);
-
- await act(async () => {
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .invoke('onChange')(false);
- });
- wrapper.update();
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').props().checked
- ).toBe(false);
- });
-
- test('should check all row items when select all is checked', async () => {
- expect.assertions(9);
- wrapper.find('.pf-c-table__check input').forEach((el) => {
- expect(el.props().checked).toBe(false);
- });
- await act(async () => {
- wrapper.find('Checkbox#select-all').invoke('onChange')(true);
- });
- wrapper.update();
- wrapper.find('.pf-c-table__check input').forEach((el) => {
- expect(el.props().checked).toBe(true);
- });
- await act(async () => {
- wrapper.find('Checkbox#select-all').invoke('onChange')(false);
- });
- wrapper.update();
- wrapper.find('.pf-c-table__check input').forEach((el) => {
- expect(el.props().checked).toBe(false);
- });
- });
-
- test('should not render Run Commands button', async () => {
- InventoriesAPI.readAdHocOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {
- module_name: {
- choices: [
- ['command', 'command'],
- ['shell', 'shell'],
- ],
- },
- },
- },
- },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- expect(wrapper.find('AdHocCommands')).toHaveLength(0);
- });
-
- test('should show content error when api throws error on initial render', async () => {
- HostsAPI.readAllGroups.mockImplementation(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-
- test('should show add button according to permissions', async () => {
- expect(wrapper.find('ToolbarAddButton').length).toBe(1);
- HostsAPI.readGroupsOptions.mockResolvedValueOnce({
- data: {
- actions: {
- GET: {},
- },
- },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('ToolbarAddButton').length).toBe(0);
- });
-
- test('should show associate group modal when adding an existing group', () => {
- wrapper.find('ToolbarAddButton').simulate('click');
- expect(wrapper.find('AssociateModal').length).toBe(1);
- wrapper.find('ModalBoxCloseButton').simulate('click');
- expect(wrapper.find('AssociateModal').length).toBe(0);
- });
-
- test('should make expected api request when associating groups', async () => {
- HostsAPI.associateGroup.mockResolvedValue();
- InventoriesAPI.readGroups.mockResolvedValue({
- data: {
- count: 1,
- results: [{ id: 123, name: 'foo', url: '/api/v2/groups/123/' }],
- },
- });
- InventoriesAPI.readGroupsOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- related_search_fields: [],
- },
- });
- await act(async () => {
- wrapper.find('ToolbarAddButton').simulate('click');
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- wrapper.update();
- await act(async () => {
- wrapper.find('CheckboxListItem').first().invoke('onSelect')();
- });
- await act(async () => {
- wrapper.find('button[aria-label="Save"]').simulate('click');
- });
- await waitForElement(wrapper, 'AssociateModal', (el) => el.length === 0);
- expect(InventoriesAPI.readGroups).toHaveBeenCalledTimes(1);
- expect(HostsAPI.associateGroup).toHaveBeenCalledTimes(1);
- });
-
- test('expected api calls are made for multi-disassociation', async () => {
- expect(HostsAPI.disassociateGroup).toHaveBeenCalledTimes(0);
- expect(HostsAPI.readAllGroups).toHaveBeenCalledTimes(1);
- expect(wrapper.find('.pf-c-table__check').length).toBe(3);
- wrapper.find('.pf-c-table__check input').forEach((el) => {
- expect(el.props().checked).toBe(false);
- });
- await act(async () => {
- wrapper.find('Checkbox#select-all').invoke('onChange')(true);
- });
- wrapper.update();
- wrapper.find('.pf-c-table__check input').forEach((el) => {
- expect(el.props().checked).toBe(true);
- });
- wrapper.find('button[aria-label="Disassociate"]').simulate('click');
- expect(wrapper.find('AlertModal Title').text()).toEqual(
- 'Disassociate group from host?'
- );
- await act(async () => {
- wrapper
- .find('button[aria-label="confirm disassociate"]')
- .simulate('click');
- });
- expect(HostsAPI.disassociateGroup).toHaveBeenCalledTimes(3);
- expect(HostsAPI.readAllGroups).toHaveBeenCalledTimes(2);
- });
-
- test('should show error modal for failed disassociation', async () => {
- HostsAPI.disassociateGroup.mockRejectedValue(new Error());
- await act(async () => {
- wrapper.find('Checkbox#select-all').invoke('onChange')(true);
- });
- wrapper.update();
- wrapper.find('button[aria-label="Disassociate"]').simulate('click');
- expect(wrapper.find('AlertModal Title').text()).toEqual(
- 'Disassociate group from host?'
- );
- await act(async () => {
- wrapper
- .find('button[aria-label="confirm disassociate"]')
- .simulate('click');
- });
- wrapper.update();
- expect(wrapper.find('AlertModal ErrorDetail').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryHostGroups/index.js b/awx/ui/src/screens/Inventory/InventoryHostGroups/index.js
deleted file mode 100644
index b7644a361167..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHostGroups/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventoryHostGroups';
diff --git a/awx/ui/src/screens/Inventory/InventoryHosts/InventoryHostItem.js b/awx/ui/src/screens/Inventory/InventoryHosts/InventoryHostItem.js
deleted file mode 100644
index 3b84a80562ab..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHosts/InventoryHostItem.js
+++ /dev/null
@@ -1,136 +0,0 @@
-import React, { useCallback } from 'react';
-import { string, bool, func } from 'prop-types';
-import { t } from '@lingui/macro';
-import { Tr, Td } from '@patternfly/react-table';
-import { Link } from 'react-router-dom';
-import { PencilAltIcon } from '@patternfly/react-icons';
-import { Button, Chip } from '@patternfly/react-core';
-import { HostsAPI } from 'api';
-import AlertModal from 'components/AlertModal';
-import ChipGroup from 'components/ChipGroup';
-import ErrorDetail from 'components/ErrorDetail';
-import HostToggle from 'components/HostToggle';
-import { ActionsTd, ActionItem, TdBreakWord } from 'components/PaginatedTable';
-import useRequest, { useDismissableError } from 'hooks/useRequest';
-import { Host } from 'types';
-
-function InventoryHostItem({
- detailUrl,
- editUrl,
- host,
- isSelected,
- onSelect,
- rowIndex,
-}) {
- const labelId = `check-action-${host.id}`;
- const initialGroups = host?.summary_fields?.groups ?? {
- results: [],
- count: 0,
- };
-
- const {
- error,
- request: fetchRelatedGroups,
- result: relatedGroups,
- } = useRequest(
- useCallback(async (hostId) => {
- const { data } = await HostsAPI.readGroups(hostId);
- return data.results;
- }, []),
- initialGroups.results
- );
-
- const { error: dismissableError, dismissError } = useDismissableError(error);
-
- const handleOverflowChipClick = (hostId) => {
- if (relatedGroups.length === initialGroups.count) {
- return;
- }
- fetchRelatedGroups(hostId);
- };
-
- return (
- <>
-
- |
-
-
- {host.name}
-
-
-
- {host.description}
-
-
- handleOverflowChipClick(host.id)}
- >
- {relatedGroups.map((group) => (
-
- {group.name}
-
- ))}
-
-
-
-
-
-
-
-
-
- {dismissableError && (
-
- {t`Failed to load related groups.`}
-
-
- )}
- >
- );
-}
-
-InventoryHostItem.propTypes = {
- detailUrl: string.isRequired,
- host: Host.isRequired,
- isSelected: bool.isRequired,
- onSelect: func.isRequired,
-};
-
-export default InventoryHostItem;
diff --git a/awx/ui/src/screens/Inventory/InventoryHosts/InventoryHostItem.test.js b/awx/ui/src/screens/Inventory/InventoryHosts/InventoryHostItem.test.js
deleted file mode 100644
index 86ebf42ccc1b..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHosts/InventoryHostItem.test.js
+++ /dev/null
@@ -1,232 +0,0 @@
-import React from 'react';
-import { Router } from 'react-router-dom';
-import {
- render,
- fireEvent,
- screen,
- waitFor,
- within,
-} from '@testing-library/react';
-import '@testing-library/jest-dom';
-import { HostsAPI } from 'api';
-import { i18n } from '@lingui/core';
-import { en } from 'make-plural/plurals';
-import InventoryHostItem from './InventoryHostItem';
-import { createMemoryHistory } from 'history';
-import english from '../../../locales/en/messages';
-
-jest.mock('api');
-
-const mockHost = {
- id: 1,
- name: 'Host 1',
- url: '/api/v2/hosts/1',
- description: 'Bar',
- inventory: 1,
- summary_fields: {
- inventory: {
- id: 1,
- name: 'Inv 1',
- },
- user_capabilities: {
- edit: true,
- },
- recent_jobs: [
- {
- id: 123,
- name: 'Demo Job Template',
- status: 'failed',
- finished: '2020-02-26T22:38:41.037991Z',
- },
- ],
- groups: {
- count: 1,
- results: [
- {
- id: 11,
- name: 'group_11',
- },
- ],
- },
- },
-};
-
-describe('', () => {
- const history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/1/hosts'],
- });
-
- const getChips = (currentScreen) => {
- const list = currentScreen.getByRole('list', {
- name: 'Related Groups',
- });
- const { getAllByRole } = within(list);
- const items = getAllByRole('listitem');
- return items.map((item) => item.textContent);
- };
-
- const Component = (props) => (
-
-
-
- {}}
- {...props}
- />
-
-
-
- );
-
- beforeEach(() => {
- i18n.loadLocaleData({ en: { plurals: en } });
- i18n.load({ en: english });
- i18n.activate('en');
- });
-
- test('should display expected details', () => {
- render();
-
- expect(screen.getByRole('cell', { name: 'Bar' })).toBeInTheDocument();
- expect(
- screen.getByRole('checkbox', { name: 'Toggle host' })
- ).toBeInTheDocument();
- expect(screen.getByRole('link', { name: 'Host 1' })).toHaveAttribute(
- 'href',
- '/host/1'
- );
- expect(screen.getByRole('link', { name: 'Edit host' })).toHaveAttribute(
- 'href',
- '/inventories/inventory/1/hosts/1/edit'
- );
-
- const relatedGroupChips = getChips(screen);
- expect(relatedGroupChips).toEqual(['group_11']);
- });
-
- test('edit button hidden from users without edit capabilities', () => {
- const copyMockHost = { ...mockHost };
- copyMockHost.summary_fields.user_capabilities.edit = false;
-
- render();
- expect(screen.queryByText('Edit host')).toBeNull();
- });
-
- test('should show and hide related groups on overflow button click', async () => {
- const copyMockHost = { ...mockHost };
- const mockGroups = [
- {
- id: 1,
- name: 'group_1',
- },
- {
- id: 2,
- name: 'group_2',
- },
- {
- id: 3,
- name: 'group_3',
- },
- {
- id: 4,
- name: 'group_4',
- },
- {
- id: 5,
- name: 'group_5',
- },
- {
- id: 6,
- name: 'group_6',
- },
- ];
- copyMockHost.summary_fields.groups = {
- count: 6,
- results: mockGroups.slice(0, 5),
- };
- HostsAPI.readGroups.mockReturnValue({
- data: {
- results: mockGroups,
- },
- });
-
- render();
-
- const initialRelatedGroupChips = getChips(screen);
- expect(initialRelatedGroupChips).toEqual([
- 'group_1',
- 'group_2',
- 'group_3',
- 'group_4',
- '2 more',
- ]);
-
- const overflowGroupsButton = screen.queryByText('2 more');
- fireEvent.click(overflowGroupsButton);
-
- await waitFor(() => expect(HostsAPI.readGroups).toHaveBeenCalledWith(1));
-
- const expandedRelatedGroupChips = getChips(screen);
- expect(expandedRelatedGroupChips).toEqual([
- 'group_1',
- 'group_2',
- 'group_3',
- 'group_4',
- 'group_5',
- 'group_6',
- 'Show less',
- ]);
-
- const collapseGroupsButton = await screen.findByText('Show less');
- fireEvent.click(collapseGroupsButton);
-
- const collapsedRelatedGroupChips = getChips(screen);
- expect(collapsedRelatedGroupChips).toEqual(initialRelatedGroupChips);
- });
-
- test('should show error modal when related groups api request fails', async () => {
- const copyMockHost = { ...mockHost };
- const mockGroups = [
- {
- id: 1,
- name: 'group_1',
- },
- {
- id: 2,
- name: 'group_2',
- },
- {
- id: 3,
- name: 'group_3',
- },
- {
- id: 4,
- name: 'group_4',
- },
- {
- id: 5,
- name: 'group_5',
- },
- {
- id: 6,
- name: 'group_6',
- },
- ];
- copyMockHost.summary_fields.groups = {
- count: 6,
- results: mockGroups.slice(0, 5),
- };
- HostsAPI.readGroups.mockRejectedValueOnce(new Error());
-
- render();
- await waitFor(() => {
- const overflowGroupsButton = screen.queryByText('2 more');
- fireEvent.click(overflowGroupsButton);
- });
- expect(screen.getByRole('dialog', { name: 'Alert modal Error!' }));
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryHosts/InventoryHostList.js b/awx/ui/src/screens/Inventory/InventoryHosts/InventoryHostList.js
deleted file mode 100644
index b1a3d6e7207a..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHosts/InventoryHostList.js
+++ /dev/null
@@ -1,213 +0,0 @@
-import React, { useEffect, useState, useCallback } from 'react';
-import { useParams, useLocation } from 'react-router-dom';
-import { t } from '@lingui/macro';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import { InventoriesAPI, HostsAPI } from 'api';
-import useRequest, { useDeleteItems } from 'hooks/useRequest';
-import AlertModal from 'components/AlertModal';
-import DataListToolbar from 'components/DataListToolbar';
-import ErrorDetail from 'components/ErrorDetail';
-import PaginatedTable, {
- HeaderRow,
- HeaderCell,
- ToolbarAddButton,
- ToolbarDeleteButton,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import useSelected from 'hooks/useSelected';
-import AdHocCommands from 'components/AdHocCommands/AdHocCommands';
-import InventoryHostItem from './InventoryHostItem';
-
-const QS_CONFIG = getQSConfig('host', {
- page: 1,
- page_size: 20,
- order_by: 'name',
-});
-
-function InventoryHostList() {
- const [isAdHocLaunchLoading, setIsAdHocLaunchLoading] = useState(false);
- const { id } = useParams();
- const { search } = useLocation();
-
- const {
- result: {
- hosts,
- hostCount,
- actions,
- relatedSearchableKeys,
- searchableKeys,
- moduleOptions,
- isAdHocDisabled,
- },
- error: contentError,
- isLoading,
- request: fetchData,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, search);
- const [response, hostOptions, adHocOptions] = await Promise.all([
- InventoriesAPI.readHosts(id, params),
- InventoriesAPI.readHostsOptions(id),
- InventoriesAPI.readAdHocOptions(id),
- ]);
-
- return {
- moduleOptions: adHocOptions.data.actions.GET.module_name.choices,
- isAdHocDisabled: !adHocOptions.data.actions.POST,
- hosts: response.data.results,
- hostCount: response.data.count,
- actions: hostOptions.data.actions,
- relatedSearchableKeys: (
- hostOptions?.data?.related_search_fields || []
- ).map((val) => (val.endsWith('search') ? val.slice(0, -8) : val)),
- searchableKeys: getSearchableKeys(hostOptions.data.actions?.GET),
- };
- }, [id, search]),
- {
- hosts: [],
- hostCount: 0,
- actions: {},
- relatedSearchableKeys: [],
- searchableKeys: [],
- moduleOptions: [],
- isAdHocDisabled: true,
- }
- );
-
- useEffect(() => {
- fetchData();
- }, [fetchData]);
-
- const { selected, isAllSelected, handleSelect, selectAll, clearSelected } =
- useSelected(hosts);
-
- const {
- isLoading: isDeleteLoading,
- deleteItems: deleteHosts,
- deletionError,
- clearDeletionError,
- } = useDeleteItems(
- useCallback(
- () => Promise.all(selected.map((host) => HostsAPI.destroy(host.id))),
- [selected]
- ),
- { qsConfig: QS_CONFIG, fetchItems: fetchData }
- );
-
- const handleDeleteHosts = async () => {
- await deleteHosts();
- clearSelected();
- };
-
- const canAdd =
- actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
-
- return (
- <>
-
- {t`Name`}
- {t`Description`}
- {t`Related Groups`}
- {t`Actions`}
-
- }
- renderToolbar={(props) => (
- ,
- ]
- : []),
- ...(!isAdHocDisabled
- ? [
- 0}
- onLaunchLoading={setIsAdHocLaunchLoading}
- />,
- ]
- : []),
- ,
- ]}
- />
- )}
- renderRow={(host, index) => (
- row.id === host.id)}
- onSelect={() => handleSelect(host)}
- rowIndex={index}
- />
- )}
- emptyStateControls={
- canAdd && (
-
- )
- }
- />
- {deletionError && (
-
- {t`Failed to delete one or more hosts.`}
-
-
- )}
- >
- );
-}
-
-export default InventoryHostList;
diff --git a/awx/ui/src/screens/Inventory/InventoryHosts/InventoryHostList.test.js b/awx/ui/src/screens/Inventory/InventoryHosts/InventoryHostList.test.js
deleted file mode 100644
index e5851bd5c32d..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHosts/InventoryHostList.test.js
+++ /dev/null
@@ -1,361 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { InventoriesAPI, HostsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import InventoryHostList from './InventoryHostList';
-import mockInventory from '../shared/data.inventory.json';
-
-jest.mock('../../../api');
-
-const mockHosts = [
- {
- id: 1,
- name: 'Host 1',
- url: '/api/v2/hosts/1',
- inventory: 1,
- enabled: true,
- summary_fields: {
- inventory: {
- id: 1,
- name: 'inv 1',
- },
- user_capabilities: {
- delete: true,
- update: true,
- },
- recent_jobs: [],
- },
- },
- {
- id: 2,
- name: 'Host 2',
- url: '/api/v2/hosts/2',
- inventory: 1,
- enabled: true,
- summary_fields: {
- inventory: {
- id: 1,
- name: 'inv 1',
- },
- user_capabilities: {
- edit: true,
- delete: true,
- update: true,
- },
- recent_jobs: [],
- },
- },
- {
- id: 3,
- name: 'Host 3',
- url: '/api/v2/hosts/3',
- inventory: 1,
- enabled: true,
- summary_fields: {
- inventory: {
- id: 1,
- name: 'inv 1',
- },
- user_capabilities: {
- delete: false,
- update: false,
- },
- recent_jobs: [
- {
- id: 123,
- name: 'Recent Job',
- status: 'success',
- finished: '2020-01-27T19:40:36.208728Z',
- },
- ],
- },
- },
-];
-
-describe('', () => {
- let wrapper;
-
- beforeEach(async () => {
- InventoriesAPI.readHosts.mockResolvedValue({
- data: {
- count: mockHosts.length,
- results: mockHosts,
- },
- });
- InventoriesAPI.readHostsOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- related_search_fields: ['first_key__search', 'ansible_facts'],
- },
- });
-
- InventoriesAPI.readAdHocOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {
- module_name: {
- choices: [
- ['command', 'command'],
- ['shell', 'shell'],
- ],
- },
- },
- POST: {},
- },
- },
- });
-
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterEach(() => {
- jest.resetAllMocks();
- });
-
- test('should fetch hosts from api and render them in the list', async () => {
- expect(InventoriesAPI.readHosts).toHaveBeenCalled();
- expect(wrapper.find('InventoryHostItem').length).toBe(3);
- expect(
- wrapper.find('PaginatedTable').props().toolbarRelatedSearchableKeys
- ).toStrictEqual(['first_key', 'ansible_facts']);
- });
-
- test('should render Run Commands button', async () => {
- expect(wrapper.find('AdHocCommands')).toHaveLength(1);
- });
-
- test('should check and uncheck the row item', async () => {
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').props().checked
- ).toBe(false);
-
- await act(async () => {
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .invoke('onChange')(true);
- });
-
- wrapper.update();
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').props().checked
- ).toBe(true);
-
- await act(async () => {
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .invoke('onChange')(false);
- });
-
- wrapper.update();
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').props().checked
- ).toBe(false);
- });
-
- test('should check all row items when select all is checked', async () => {
- expect.assertions(9);
- wrapper.find('.pf-c-table__check').forEach((el) => {
- expect(el.find('input').props().checked).toBe(false);
- });
- await act(async () => {
- wrapper.find('Checkbox#select-all').invoke('onChange')(true);
- });
- wrapper.update();
- wrapper.find('.pf-c-table__check').forEach((el) => {
- expect(el.find('input').props().checked).toBe(true);
- });
- await act(async () => {
- wrapper.find('Checkbox#select-all').invoke('onChange')(false);
- });
- wrapper.update();
- wrapper.find('.pf-c-table__check').forEach((el) => {
- expect(el.find('input').props().checked).toBe(false);
- });
- });
-
- test('should call api if host toggle is clicked', async () => {
- HostsAPI.update.mockResolvedValueOnce({
- data: { ...mockHosts[1], enabled: false },
- });
- expect(wrapper.find('Switch[id="host-2-toggle"]').props().isChecked).toBe(
- true
- );
- await act(async () => {
- wrapper.find('Switch[id="host-2-toggle"]').invoke('onChange')();
- });
- wrapper.update();
- expect(wrapper.find('Switch[id="host-2-toggle"]').props().isChecked).toBe(
- false
- );
- expect(HostsAPI.update).toHaveBeenCalledTimes(1);
- });
-
- test('should show error modal if host is not successfully toggled', async () => {
- HostsAPI.update.mockImplementationOnce(() => Promise.reject(new Error()));
- await act(async () => {
- wrapper.find('Switch[id="host-2-toggle"]').invoke('onChange')();
- });
- wrapper.update();
- await waitForElement(
- wrapper,
- 'Modal',
- (el) => el.props().isOpen === true && el.props().title === 'Error!'
- );
- await act(async () => {
- wrapper.find('ModalBoxCloseButton').invoke('onClose')();
- });
- await waitForElement(wrapper, 'Modal', (el) => el.length === 0);
- });
-
- test('delete button is disabled if user does not have delete capabilities on a selected host', async () => {
- await act(async () => {
- wrapper
- .find('.pf-c-table__check')
- .at(2)
- .find('input')
- .invoke('onChange')();
- });
- wrapper.update();
- expect(wrapper.find('ToolbarDeleteButton button').props().disabled).toBe(
- true
- );
- });
-
- test('should call api delete hosts for each selected host', async () => {
- HostsAPI.destroy = jest.fn();
- await act(async () => {
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .invoke('onChange')();
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('ToolbarDeleteButton').invoke('onDelete')();
- });
- wrapper.update();
- expect(HostsAPI.destroy).toHaveBeenCalledTimes(1);
- });
-
- test('should show error modal when host is not successfully deleted from api', async () => {
- HostsAPI.destroy.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'delete',
- url: '/api/v2/hosts/1',
- },
- data: 'An error occurred',
- },
- })
- );
- await act(async () => {
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .invoke('onChange')();
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('ToolbarDeleteButton').invoke('onDelete')();
- });
- await waitForElement(
- wrapper,
- 'Modal',
- (el) => el.props().isOpen === true && el.props().title === 'Error!'
- );
- await act(async () => {
- wrapper.find('ModalBoxCloseButton').invoke('onClose')();
- });
- await waitForElement(wrapper, 'Modal', (el) => el.length === 0);
- });
-
- test('should show content error if hosts are not successfully fetched from api', async () => {
- InventoriesAPI.readHosts.mockImplementation(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .invoke('onChange')();
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('ToolbarDeleteButton').invoke('onDelete')();
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-
- test('should show Add button for users with ability to POST', async () => {
- expect(wrapper.find('ToolbarAddButton').length).toBe(1);
- });
-
- test('should hide Add button for users without ability to POST', async () => {
- InventoriesAPI.readHostsOptions.mockResolvedValueOnce({
- data: {
- actions: {
- GET: {},
- },
- },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('ToolbarAddButton').length).toBe(0);
- });
-
- test('should show content error when api throws error on initial render', async () => {
- InventoriesAPI.readHostsOptions.mockImplementation(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-
- test('should not render Run Commands button', async () => {
- InventoriesAPI.readAdHocOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {
- module_name: {
- choices: [
- ['command', 'command'],
- ['shell', 'shell'],
- ],
- },
- },
- },
- },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('AdHocCommands')).toHaveLength(0);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryHosts/InventoryHosts.js b/awx/ui/src/screens/Inventory/InventoryHosts/InventoryHosts.js
deleted file mode 100644
index a15fbebf6d4e..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHosts/InventoryHosts.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from 'react';
-import { Switch, Route } from 'react-router-dom';
-
-import InventoryHost from '../InventoryHost';
-import InventoryHostAdd from '../InventoryHostAdd';
-import InventoryHostList from './InventoryHostList';
-
-function InventoryHosts({ setBreadcrumb, inventory }) {
- return (
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-export default InventoryHosts;
diff --git a/awx/ui/src/screens/Inventory/InventoryHosts/InventoryHosts.test.js b/awx/ui/src/screens/Inventory/InventoryHosts/InventoryHosts.test.js
deleted file mode 100644
index c9b0ec3d7550..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHosts/InventoryHosts.test.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import React from 'react';
-import { createMemoryHistory } from 'history';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import InventoryHosts from './InventoryHosts';
-
-jest.mock('./InventoryHostList', () => {
- const InventoryHostList = () => ;
- return {
- __esModule: true,
- default: InventoryHostList,
- };
-});
-
-describe('', () => {
- test('should render inventory host list', () => {
- const history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/1/hosts'],
- });
-
- const match = {
- path: '/inventories/inventory/:id/hosts',
- url: '/inventories/inventory/1/hosts',
- isExact: true,
- };
-
- const wrapper = mountWithContexts(, {
- context: { router: { history, route: { match } } },
- });
-
- expect(wrapper.find('InventoryHostList').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryHosts/index.js b/awx/ui/src/screens/Inventory/InventoryHosts/index.js
deleted file mode 100644
index 0cb4fe95bc4a..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryHosts/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventoryHostList';
diff --git a/awx/ui/src/screens/Inventory/InventoryList/InventoryList.js b/awx/ui/src/screens/Inventory/InventoryList/InventoryList.js
deleted file mode 100644
index 0ad6dcc01beb..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryList/InventoryList.js
+++ /dev/null
@@ -1,297 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { useLocation, useRouteMatch, Link } from 'react-router-dom';
-import { t, Plural } from '@lingui/macro';
-import { Card, PageSection, DropdownItem } from '@patternfly/react-core';
-import { InventoriesAPI } from 'api';
-import useRequest, { useDeleteItems } from 'hooks/useRequest';
-import useSelected from 'hooks/useSelected';
-import useToast, { AlertVariant } from 'hooks/useToast';
-import AlertModal from 'components/AlertModal';
-import DatalistToolbar from 'components/DataListToolbar';
-import ErrorDetail from 'components/ErrorDetail';
-import PaginatedTable, {
- HeaderRow,
- HeaderCell,
- ToolbarDeleteButton,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import AddDropDownButton from 'components/AddDropDownButton';
-import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
-import useWsInventories from './useWsInventories';
-import InventoryListItem from './InventoryListItem';
-
-const QS_CONFIG = getQSConfig('inventory', {
- page: 1,
- page_size: 20,
- order_by: 'name',
-});
-
-function InventoryList() {
- const location = useLocation();
- const match = useRouteMatch();
- const { addToast, Toast, toastProps } = useToast();
-
- const {
- result: {
- results,
- itemCount,
- actions,
- relatedSearchableKeys,
- searchableKeys,
- },
- error: contentError,
- isLoading,
- request: fetchInventories,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
- const [response, actionsResponse] = await Promise.all([
- InventoriesAPI.read(params),
- InventoriesAPI.readOptions(),
- ]);
- return {
- results: response.data.results,
- itemCount: response.data.count,
- actions: actionsResponse.data.actions,
- relatedSearchableKeys: (
- actionsResponse?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
- };
- }, [location]),
- {
- results: [],
- itemCount: 0,
- actions: {},
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- useEffect(() => {
- fetchInventories();
- }, [fetchInventories]);
-
- const fetchInventoriesById = useCallback(
- async (ids) => {
- const params = { ...parseQueryString(QS_CONFIG, location.search) };
- params.id__in = ids.join(',');
- const { data } = await InventoriesAPI.read(params);
- return data.results;
- },
- [location.search] // eslint-disable-line react-hooks/exhaustive-deps
- );
-
- const inventories = useWsInventories(
- results,
- fetchInventories,
- fetchInventoriesById,
- QS_CONFIG
- );
-
- const { selected, isAllSelected, handleSelect, selectAll, clearSelected } =
- useSelected(inventories);
-
- const {
- isLoading: isDeleteLoading,
- deleteItems: deleteInventories,
- deletionError,
- clearDeletionError,
- } = useDeleteItems(
- useCallback(
- () =>
- Promise.all(selected.map((team) => InventoriesAPI.destroy(team.id))),
- [selected]
- ),
- {
- allItemsSelected: isAllSelected,
- }
- );
-
- const handleInventoryDelete = async () => {
- await deleteInventories();
- clearSelected();
- };
-
- const handleCopy = useCallback(
- (newInventoryId) => {
- addToast({
- id: newInventoryId,
- title: t`Inventory copied successfully`,
- variant: AlertVariant.success,
- hasTimeout: true,
- });
- },
- [addToast]
- );
-
- const hasContentLoading = isDeleteLoading || isLoading;
- const canAdd = actions && actions.POST;
-
- const deleteDetailsRequests = relatedResourceDeleteRequests.inventory(
- selected[0]
- );
-
- const addInventory = t`Add inventory`;
- const addSmartInventory = t`Add smart inventory`;
- const addButton = (
-
- {addInventory}
- ,
-
- {addSmartInventory}
- ,
- ]}
- />
- );
-
- return (
- <>
-
-
-
- {t`Name`}
- {t`Sync Status`}
- {t`Type`}
- {t`Organization`}
- {t`Actions`}
-
- }
- renderToolbar={(props) => (
-
- }
- warningMessage={
-
- }
- />,
- ]}
- />
- )}
- renderRow={(inventory, index) => (
- {
- if (!inventory.pending_deletion) {
- handleSelect(inventory);
- }
- }}
- onCopy={handleCopy}
- isSelected={selected.some((row) => row.id === inventory.id)}
- />
- )}
- emptyStateControls={canAdd && addButton}
- />
-
-
- {t`Failed to delete one or more inventories.`}
-
-
-
-
- >
- );
-}
-
-export default InventoryList;
diff --git a/awx/ui/src/screens/Inventory/InventoryList/InventoryList.test.js b/awx/ui/src/screens/Inventory/InventoryList/InventoryList.test.js
deleted file mode 100644
index 03edf73475db..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryList/InventoryList.test.js
+++ /dev/null
@@ -1,308 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { InventoriesAPI, JobTemplatesAPI, WorkflowJobTemplatesAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import InventoryList from './InventoryList';
-
-jest.mock('../../../api/models/Inventories');
-jest.mock('../../../api/models/JobTemplates');
-jest.mock('../../../api/models/WorkflowJobTemplates');
-
-const mockInventories = [
- {
- id: 1,
- type: 'inventory',
- url: '/api/v2/inventories/1/',
- summary_fields: {
- organization: {
- id: 1,
- name: 'Default',
- description: '',
- },
- user_capabilities: {
- edit: true,
- delete: true,
- copy: true,
- adhoc: true,
- },
- },
- created: '2019-10-04T16:56:48.025455Z',
- modified: '2019-10-04T16:56:48.025468Z',
- name: 'Inv no hosts',
- description: '',
- organization: 1,
- kind: '',
- host_filter: null,
- variables: '---',
- has_active_failures: false,
- total_hosts: 0,
- hosts_with_active_failures: 0,
- total_groups: 0,
- groups_with_active_failures: 0,
- has_inventory_sources: false,
- total_inventory_sources: 0,
- inventory_sources_with_failures: 0,
- pending_deletion: false,
- },
- {
- id: 2,
- type: 'inventory',
- url: '/api/v2/inventories/2/',
- summary_fields: {
- organization: {
- id: 1,
- name: 'Default',
- description: '',
- },
- user_capabilities: {
- edit: true,
- delete: true,
- copy: true,
- adhoc: true,
- },
- },
- created: '2019-10-04T14:28:04.765571Z',
- modified: '2019-10-04T14:28:04.765594Z',
- name: "Mike's Inventory",
- description: '',
- organization: 1,
- kind: '',
- host_filter: null,
- variables: '---',
- has_active_failures: false,
- total_hosts: 1,
- hosts_with_active_failures: 0,
- total_groups: 0,
- groups_with_active_failures: 0,
- has_inventory_sources: false,
- total_inventory_sources: 0,
- inventory_sources_with_failures: 0,
- pending_deletion: false,
- },
- {
- id: 3,
- type: 'inventory',
- url: '/api/v2/inventories/3/',
- summary_fields: {
- organization: {
- id: 1,
- name: 'Default',
- description: '',
- },
- user_capabilities: {
- edit: true,
- delete: false,
- copy: true,
- adhoc: true,
- },
- },
- created: '2019-10-04T15:29:11.542911Z',
- modified: '2019-10-04T15:29:11.542924Z',
- name: 'Smart Inv',
- description: '',
- organization: 1,
- kind: 'smart',
- host_filter: 'search=local',
- variables: '',
- has_active_failures: false,
- total_hosts: 1,
- hosts_with_active_failures: 0,
- total_groups: 0,
- groups_with_active_failures: 0,
- has_inventory_sources: false,
- total_inventory_sources: 0,
- inventory_sources_with_failures: 0,
- pending_deletion: false,
- },
-];
-
-describe('', () => {
- let debug;
- beforeEach(() => {
- InventoriesAPI.read.mockResolvedValue({
- data: {
- count: mockInventories.length,
- results: mockInventories,
- },
- });
-
- InventoriesAPI.readOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- },
- });
- JobTemplatesAPI.read.mockResolvedValue({ data: { count: 0 } });
- WorkflowJobTemplatesAPI.read.mockResolvedValue({ data: { count: 0 } });
- debug = global.console.debug; // eslint-disable-line prefer-destructuring
- global.console.debug = () => {};
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- global.console.debug = debug;
- });
-
- test('should load and render inventories', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
-
- expect(wrapper.find('InventoryListItem')).toHaveLength(3);
- });
-
- test('should have proper number of delete detail requests', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- expect(
- wrapper.find('ToolbarDeleteButton').prop('deleteDetailsRequests')
- ).toHaveLength(2);
- });
-
- test('should select inventory when checked', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
-
- await act(async () => {
- wrapper.find('InventoryListItem').first().invoke('onSelect')();
- });
- wrapper.update();
-
- expect(
- wrapper.find('InventoryListItem').first().prop('isSelected')
- ).toEqual(true);
- });
-
- test('should select all', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
-
- await act(async () => {
- wrapper.find('DataListToolbar').invoke('onSelectAll')(true);
- });
- wrapper.update();
-
- const items = wrapper.find('InventoryListItem');
- expect(items).toHaveLength(3);
- items.forEach((item) => {
- expect(item.prop('isSelected')).toEqual(true);
- });
-
- expect(
- wrapper.find('InventoryListItem').first().prop('isSelected')
- ).toEqual(true);
- });
-
- test('should disable delete button', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
-
- await act(async () => {
- wrapper.find('InventoryListItem').at(2).invoke('onSelect')();
- });
- wrapper.update();
-
- expect(wrapper.find('ToolbarDeleteButton button').prop('disabled')).toEqual(
- true
- );
- });
-
- test('should call delete api', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
-
- await act(async () => {
- wrapper.find('InventoryListItem').at(0).invoke('onSelect')();
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('InventoryListItem').at(1).invoke('onSelect')();
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('ToolbarDeleteButton').invoke('onDelete')();
- });
-
- expect(InventoriesAPI.destroy).toHaveBeenCalledTimes(2);
- });
-
- test('should show deletion error', async () => {
- InventoriesAPI.destroy.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'delete',
- url: '/api/v2/inventory/1',
- },
- data: 'An error occurred',
- },
- })
- );
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
- expect(InventoriesAPI.read).toHaveBeenCalledTimes(1);
- await act(async () => {
- wrapper.find('InventoryListItem').at(0).invoke('onSelect')();
- });
- wrapper.update();
-
- await act(async () => {
- wrapper.find('ToolbarDeleteButton').invoke('onDelete')();
- });
- wrapper.update();
-
- const modal = wrapper.find('Modal[aria-label="Deletion Error"]');
- expect(modal).toHaveLength(1);
- expect(modal.prop('title')).toEqual('Error!');
- });
-
- test('Add button shown for users without ability to POST', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
-
- expect(wrapper.find('ToolbarAddButton').length).toBe(1);
- });
-
- test('Add button hidden for users without ability to POST', async () => {
- InventoriesAPI.readOptions = () =>
- Promise.resolve({
- data: {
- actions: {
- GET: {},
- },
- },
- });
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
-
- expect(wrapper.find('ToolbarAddButton').length).toBe(0);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryList/InventoryListItem.js b/awx/ui/src/screens/Inventory/InventoryList/InventoryListItem.js
deleted file mode 100644
index c692c32f518f..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryList/InventoryListItem.js
+++ /dev/null
@@ -1,171 +0,0 @@
-import React, { useState, useCallback } from 'react';
-import { string, bool, func } from 'prop-types';
-
-import { Button, Label } from '@patternfly/react-core';
-import { Tr, Td } from '@patternfly/react-table';
-import { PencilAltIcon } from '@patternfly/react-icons';
-import { t, Plural } from '@lingui/macro';
-import { Link } from 'react-router-dom';
-import { timeOfDay } from 'util/dates';
-import { InventoriesAPI } from 'api';
-import { Inventory } from 'types';
-import { ActionsTd, ActionItem, TdBreakWord } from 'components/PaginatedTable';
-import CopyButton from 'components/CopyButton';
-import StatusLabel from 'components/StatusLabel';
-
-function InventoryListItem({
- inventory,
- rowIndex,
- isSelected,
- onSelect,
- onCopy,
- detailUrl,
- fetchInventories,
-}) {
- InventoryListItem.propTypes = {
- inventory: Inventory.isRequired,
- detailUrl: string.isRequired,
- isSelected: bool.isRequired,
- onSelect: func.isRequired,
- };
- const [isCopying, setIsCopying] = useState(false);
-
- const copyInventory = useCallback(async () => {
- const response = await InventoriesAPI.copy(inventory.id, {
- name: `${inventory.name} @ ${timeOfDay()}`,
- });
- if (response.status === 201) {
- onCopy(response.data.id);
- }
- await fetchInventories();
- }, [inventory.id, inventory.name, fetchInventories, onCopy]);
-
- const handleCopyStart = useCallback(() => {
- setIsCopying(true);
- }, []);
-
- const handleCopyFinish = useCallback(() => {
- setIsCopying(false);
- }, []);
-
- const labelId = `check-action-${inventory.id}`;
-
- let syncStatus = 'disabled';
- if (inventory.isSourceSyncRunning) {
- syncStatus = 'syncing';
- } else if (inventory.has_inventory_sources) {
- syncStatus =
- inventory.inventory_sources_with_failures > 0 ? 'error' : 'success';
- }
-
- let tooltipContent = '';
- if (inventory.has_inventory_sources) {
- if (inventory.inventory_sources_with_failures > 0) {
- tooltipContent = (
-
- );
- } else {
- tooltipContent = t`No inventory sync failures.`;
- }
- } else {
- tooltipContent = t`Not configured for inventory sync.`;
- }
-
- return (
-
- |
-
- {inventory.pending_deletion ? (
- {inventory.name}
- ) : (
-
- {inventory.name}
-
- )}
-
-
- {inventory.kind !== 'smart' &&
- (inventory.has_inventory_sources ? (
-
-
-
- ) : (
-
- ))}
- |
-
- {inventory.kind === 'smart' ? t`Smart Inventory` : t`Inventory`}
- |
-
-
- {inventory?.summary_fields?.organization?.name}
-
-
- {inventory.pending_deletion ? (
-
-
- |
- ) : (
-
-
-
-
-
-
-
-
- )}
-
- );
-}
-export default InventoryListItem;
diff --git a/awx/ui/src/screens/Inventory/InventoryList/InventoryListItem.test.js b/awx/ui/src/screens/Inventory/InventoryList/InventoryListItem.test.js
deleted file mode 100644
index 2cb1761a703f..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryList/InventoryListItem.test.js
+++ /dev/null
@@ -1,249 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { InventoriesAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import InventoryListItem from './InventoryListItem';
-
-jest.mock('../../../api/models/Inventories');
-
-describe('', () => {
- const inventory = {
- id: 1,
- name: 'Inventory',
- kind: '',
- has_active_failures: true,
- total_hosts: 10,
- hosts_with_active_failures: 4,
- has_inventory_sources: true,
- total_inventory_sources: 4,
- inventory_sources_with_failures: 5,
- summary_fields: {
- organization: {
- id: 1,
- name: 'Default',
- },
- user_capabilities: {
- edit: true,
- },
- },
- };
-
- test('initially renders successfully', () => {
- mountWithContexts(
-
- );
- });
-
- test('should render not configured tooltip', () => {
- const wrapper = mountWithContexts(
-
- );
-
- expect(wrapper.find('StatusLabel').prop('tooltipContent')).toBe(
- 'Not configured for inventory sync.'
- );
- });
-
- test('should render success tooltip', () => {
- const wrapper = mountWithContexts(
-
- );
-
- expect(wrapper.find('StatusLabel').prop('tooltipContent')).toBe(
- 'No inventory sync failures.'
- );
- });
-
- test('should render prompt list item data', () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('StatusLabel').length).toBe(1);
- expect(wrapper.find('StatusLabel').prop('status')).toBe('error');
- expect(wrapper.find('Td').at(1).text()).toBe('Inventory');
- expect(wrapper.find('Td').at(2).text()).toBe('Error');
- expect(wrapper.find('Td').at(3).text()).toBe('Inventory');
- expect(wrapper.find('Td').at(4).text()).toBe('Default');
- });
-
- test('edit button shown to users with edit capabilities', () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
- });
-
- test('edit button hidden from users without edit capabilities', () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
- });
-
- test('should call api to copy inventory', async () => {
- InventoriesAPI.copy.mockResolvedValue();
-
- const wrapper = mountWithContexts(
-
- );
-
- await act(async () =>
- wrapper.find('Button[aria-label="Copy"]').prop('onClick')()
- );
- expect(InventoriesAPI.copy).toHaveBeenCalled();
- jest.clearAllMocks();
- });
-
- test('should render proper alert modal on copy error', async () => {
- InventoriesAPI.copy.mockRejectedValue(new Error());
-
- const wrapper = mountWithContexts(
-
- );
- await act(async () =>
- wrapper.find('Button[aria-label="Copy"]').prop('onClick')()
- );
- wrapper.update();
- expect(wrapper.find('Modal').prop('isOpen')).toBe(true);
- jest.clearAllMocks();
- });
-
- test('should not render copy button', async () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('CopyButton').length).toBe(0);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryList/index.js b/awx/ui/src/screens/Inventory/InventoryList/index.js
deleted file mode 100644
index 81762ff30fcc..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryList/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as InventoryList } from './InventoryList';
-export { default as InventoryListItem } from './InventoryListItem';
diff --git a/awx/ui/src/screens/Inventory/InventoryList/useWsInventories.js b/awx/ui/src/screens/Inventory/InventoryList/useWsInventories.js
deleted file mode 100644
index 29a7f244d811..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryList/useWsInventories.js
+++ /dev/null
@@ -1,135 +0,0 @@
-import { useState, useEffect } from 'react';
-import { useLocation, useHistory } from 'react-router-dom';
-import { parseQueryString, updateQueryString } from 'util/qs';
-import useWebsocket from 'hooks/useWebsocket';
-import useThrottle from 'hooks/useThrottle';
-
-export default function useWsInventories(
- initialInventories,
- fetchInventories,
- fetchInventoriesById,
- qsConfig
-) {
- const location = useLocation();
- const history = useHistory();
- const [inventories, setInventories] = useState(initialInventories);
- const [inventoriesToFetch, setInventoriesToFetch] = useState([]);
- const throttledInventoriesToFetch = useThrottle(inventoriesToFetch, 5000);
- const lastMessage = useWebsocket({
- inventories: ['status_changed'],
- jobs: ['status_changed'],
- control: ['limit_reached_1'],
- });
-
- useEffect(() => {
- setInventories(initialInventories);
- }, [initialInventories]);
-
- const enqueueId = (id) => {
- if (!inventoriesToFetch.includes(id)) {
- setInventoriesToFetch((ids) => ids.concat(id));
- }
- };
- useEffect(
- () => {
- (async () => {
- if (!throttledInventoriesToFetch.length) {
- return;
- }
- setInventoriesToFetch([]);
- const newInventories = await fetchInventoriesById(
- throttledInventoriesToFetch
- );
- const updated = [...inventories];
- newInventories.forEach((inventory) => {
- const index = inventories.findIndex((i) => i.id === inventory.id);
- if (index === -1) {
- return;
- }
- updated[index] = inventory;
- });
- setInventories(updated);
- })();
- },
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [throttledInventoriesToFetch, fetchInventoriesById]
- );
-
- useEffect(
- () => {
- if (
- !lastMessage?.inventory_id ||
- (lastMessage.type !== 'inventory_update' &&
- lastMessage.group_name !== 'inventories')
- ) {
- return;
- }
- const index = inventories.findIndex(
- (p) => p.id === lastMessage.inventory_id
- );
- if (index === -1) {
- return;
- }
-
- const params = parseQueryString(qsConfig, location.search);
-
- const inventory = inventories[index];
- const updatedInventory = {
- ...inventory,
- };
-
- if (
- lastMessage.group_name === 'inventories' &&
- lastMessage.status === 'deleted' &&
- inventories.length === 1 &&
- params.page > 1
- ) {
- // We've deleted the last inventory on this page so we'll
- // try to navigate back to the previous page
- const qs = updateQueryString(qsConfig, location.search, {
- page: params.page - 1,
- });
- history.push(`${location.pathname}?${qs}`);
- return;
- }
-
- if (
- lastMessage.group_name === 'inventories' &&
- lastMessage.status === 'deleted'
- ) {
- fetchInventories();
- return;
- }
-
- if (
- !['pending', 'waiting', 'running', 'pending_deletion'].includes(
- lastMessage.status
- )
- ) {
- enqueueId(lastMessage.inventory_id);
- return;
- }
-
- if (
- lastMessage.group_name === 'inventories' &&
- lastMessage.status === 'pending_deletion'
- ) {
- updatedInventory.pending_deletion = true;
- }
-
- if (lastMessage.group_name !== 'inventories') {
- updatedInventory.isSourceSyncRunning = true;
- }
-
- setInventories([
- ...inventories.slice(0, index),
- updatedInventory,
- ...inventories.slice(index + 1),
- ]);
- },
- // eslint-disable-next-line react-hooks/exhaustive-deps,
- [lastMessage]
- );
-
- return inventories;
-}
diff --git a/awx/ui/src/screens/Inventory/InventoryList/useWsInventories.test.js b/awx/ui/src/screens/Inventory/InventoryList/useWsInventories.test.js
deleted file mode 100644
index af64a4111d9b..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryList/useWsInventories.test.js
+++ /dev/null
@@ -1,220 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import WS from 'jest-websocket-mock';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import useWsInventories from './useWsInventories';
-
-function TestInner() {
- return ;
-}
-function Test({
- inventories,
- fetchInventories,
- fetchInventoriesById,
- qsConfig,
-}) {
- const syncedInventories = useWsInventories(
- inventories,
- fetchInventories,
- fetchInventoriesById,
- qsConfig
- );
- return ;
-}
-
-const QS_CONFIG = {
- defaultParams: {},
-};
-
-describe('useWsInventories hook', () => {
- let debug;
- let wrapper;
- beforeEach(() => {
- /*
- Jest mock timers don’t play well with jest-websocket-mock,
- so we'll stub out throttling to resolve immediately
- */
- jest.mock('../../../hooks/useThrottle', () => ({
- __esModule: true,
- default: jest.fn((val) => val),
- }));
- debug = global.console.debug; // eslint-disable-line prefer-destructuring
- global.console.debug = () => {};
- });
-
- afterEach(() => {
- global.console.debug = debug;
- WS.clean();
- });
-
- test('should return inventories list', () => {
- const inventories = [{ id: 1 }];
- wrapper = mountWithContexts(
-
- );
-
- expect(wrapper.find('TestInner').prop('inventories')).toEqual(inventories);
- });
-
- test('should establish websocket connection', async () => {
- global.document.cookie = 'csrftoken=abc123';
- const mockServer = new WS('ws://localhost/websocket/');
-
- const inventories = [{ id: 1 }];
- await act(async () => {
- wrapper = await mountWithContexts(
-
- );
- });
-
- await mockServer.connected;
- await expect(mockServer).toReceiveMessage(
- JSON.stringify({
- xrftoken: 'abc123',
- groups: {
- inventories: ['status_changed'],
- jobs: ['status_changed'],
- control: ['limit_reached_1'],
- },
- })
- );
- });
-
- test('should update inventory sync status', async () => {
- global.document.cookie = 'csrftoken=abc123';
- const mockServer = new WS('ws://localhost/websocket/');
-
- const inventories = [{ id: 1 }];
- await act(async () => {
- wrapper = await mountWithContexts(
-
- );
- });
-
- await mockServer.connected;
- await expect(mockServer).toReceiveMessage(
- JSON.stringify({
- xrftoken: 'abc123',
- groups: {
- inventories: ['status_changed'],
- jobs: ['status_changed'],
- control: ['limit_reached_1'],
- },
- })
- );
- act(() => {
- mockServer.send(
- JSON.stringify({
- inventory_id: 1,
- type: 'inventory_update',
- status: 'running',
- })
- );
- });
- wrapper.update();
-
- expect(
- wrapper.find('TestInner').prop('inventories')[0].isSourceSyncRunning
- ).toEqual(true);
- });
-
- test('should fetch fresh inventory after sync runs', async () => {
- global.document.cookie = 'csrftoken=abc123';
- const mockServer = new WS('ws://localhost/websocket/');
- const inventories = [{ id: 1 }];
- const fetchInventories = jest.fn(() => []);
- const fetchInventoriesById = jest.fn(() => []);
- await act(async () => {
- wrapper = await mountWithContexts(
-
- );
- });
-
- await mockServer.connected;
- await act(async () => {
- mockServer.send(
- JSON.stringify({
- inventory_id: 1,
- type: 'inventory_update',
- status: 'successful',
- })
- );
- });
-
- expect(fetchInventoriesById).toHaveBeenCalledWith([1]);
- });
-
- test('should update inventory pending_deletion', async () => {
- global.document.cookie = 'csrftoken=abc123';
- const mockServer = new WS('ws://localhost/websocket/');
-
- const inventories = [{ id: 1, pending_deletion: false }];
- await act(async () => {
- wrapper = await mountWithContexts(
-
- );
- });
-
- await mockServer.connected;
- await expect(mockServer).toReceiveMessage(
- JSON.stringify({
- xrftoken: 'abc123',
- groups: {
- inventories: ['status_changed'],
- jobs: ['status_changed'],
- control: ['limit_reached_1'],
- },
- })
- );
- act(() => {
- mockServer.send(
- JSON.stringify({
- inventory_id: 1,
- group_name: 'inventories',
- status: 'pending_deletion',
- })
- );
- });
- wrapper.update();
-
- expect(
- wrapper.find('TestInner').prop('inventories')[0].pending_deletion
- ).toEqual(true);
- });
-
- test('should refetch inventories after an inventory is deleted', async () => {
- global.document.cookie = 'csrftoken=abc123';
- const mockServer = new WS('ws://localhost/websocket/');
- const inventories = [{ id: 1 }, { id: 2 }];
- const fetchInventories = jest.fn(() => []);
- const fetchInventoriesById = jest.fn(() => []);
- await act(async () => {
- wrapper = await mountWithContexts(
-
- );
- });
-
- await mockServer.connected;
- await act(async () => {
- mockServer.send(
- JSON.stringify({
- inventory_id: 1,
- group_name: 'inventories',
- status: 'deleted',
- })
- );
- });
-
- expect(fetchInventories).toHaveBeenCalled();
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryRelatedGroupAdd/InventoryRelatedGroupAdd.js b/awx/ui/src/screens/Inventory/InventoryRelatedGroupAdd/InventoryRelatedGroupAdd.js
deleted file mode 100644
index b91d5dbbdc72..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryRelatedGroupAdd/InventoryRelatedGroupAdd.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import React, { useState } from 'react';
-import { useHistory, useParams } from 'react-router-dom';
-import { GroupsAPI } from 'api';
-import InventoryGroupForm from '../shared/InventoryGroupForm';
-
-function InventoryRelatedGroupAdd() {
- const [error, setError] = useState(null);
- const history = useHistory();
- const { id, groupId } = useParams();
- const associateInventoryGroup = async (values) => {
- values.inventory = id;
- try {
- const { data } = await GroupsAPI.create(values);
- await GroupsAPI.associateChildGroup(groupId, data.id);
- history.push(`/inventories/inventory/${id}/groups/${data.id}/details`, {
- prevGroupId: groupId,
- });
- } catch (err) {
- setError(err);
- }
- };
-
- const handleCancel = () => {
- history.push(
- `/inventories/inventory/${id}/groups/${groupId}/nested_groups`
- );
- };
- return (
-
- );
-}
-
-export default InventoryRelatedGroupAdd;
diff --git a/awx/ui/src/screens/Inventory/InventoryRelatedGroupAdd/InventoryRelatedGroupAdd.test.js b/awx/ui/src/screens/Inventory/InventoryRelatedGroupAdd/InventoryRelatedGroupAdd.test.js
deleted file mode 100644
index 42dcb376a174..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryRelatedGroupAdd/InventoryRelatedGroupAdd.test.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { GroupsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import InventoryRelatedGroupAdd from './InventoryRelatedGroupAdd';
-
-jest.mock('../../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useParams: () => ({
- id: 1,
- groupId: 2,
- }),
- useHistory: () => ({ push: jest.fn() }),
-}));
-
-describe('', () => {
- let wrapper;
- const history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/1/groups/2/nested_groups'],
- });
-
- beforeEach(() => {
- wrapper = mountWithContexts();
- });
-
- test('should render properly', () => {
- expect(wrapper.find('InventoryRelatedGroupAdd').length).toBe(1);
- });
-
- test('should call api with proper data', async () => {
- GroupsAPI.create.mockResolvedValue({ data: { id: 3 } });
- await act(() =>
- wrapper.find('InventoryGroupForm').prop('handleSubmit')({
- name: 'foo',
- description: 'bar',
- })
- );
- expect(GroupsAPI.create).toBeCalledWith({
- inventory: 1,
- name: 'foo',
- description: 'bar',
- });
- expect(GroupsAPI.associateChildGroup).toBeCalledWith(2, 3);
- });
-
- test('cancel should navigate user to Inventory Groups List', async () => {
- wrapper.find('button[aria-label="Cancel"]').simulate('click');
- expect(history.location.pathname).toEqual(
- '/inventories/inventory/1/groups/2/nested_groups'
- );
- });
-
- test('should throw error on creation of group', async () => {
- GroupsAPI.create.mockRejectedValue({
- response: {
- config: {
- method: 'post',
- url: '/api/v2/groups/',
- },
- data: { detail: 'An error occurred' },
- },
- });
- await act(() =>
- wrapper.find('InventoryGroupForm').prop('handleSubmit')({
- name: 'foo',
- description: 'bar',
- })
- );
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(1);
- });
-
- test('should throw error on association of group', async () => {
- GroupsAPI.create.mockResolvedValue({ data: { id: 3 } });
- GroupsAPI.associateChildGroup.mockRejectedValue({
- response: {
- config: {
- method: 'post',
- url: '/api/v2/groups/',
- },
- data: { detail: 'An error occurred' },
- },
- });
- await act(() =>
- wrapper.find('InventoryGroupForm').prop('handleSubmit')({
- name: 'foo',
- description: 'bar',
- })
- );
- expect(GroupsAPI.create).toBeCalledWith({
- inventory: 1,
- name: 'foo',
- description: 'bar',
- });
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryRelatedGroupAdd/index.js b/awx/ui/src/screens/Inventory/InventoryRelatedGroupAdd/index.js
deleted file mode 100644
index 706faf0764e6..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryRelatedGroupAdd/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventoryRelatedGroupAdd';
diff --git a/awx/ui/src/screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js b/awx/ui/src/screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js
deleted file mode 100644
index 98aa701e002c..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.js
+++ /dev/null
@@ -1,276 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-
-import { t } from '@lingui/macro';
-import { useParams, useLocation, Link } from 'react-router-dom';
-
-import { DropdownItem } from '@patternfly/react-core';
-import { GroupsAPI, InventoriesAPI } from 'api';
-import useRequest, { useDismissableError } from 'hooks/useRequest';
-import { getQSConfig, parseQueryString, mergeParams } from 'util/qs';
-import useSelected from 'hooks/useSelected';
-
-import DataListToolbar from 'components/DataListToolbar';
-import PaginatedTable, {
- HeaderCell,
- HeaderRow,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import AddDropDownButton from 'components/AddDropDownButton';
-import AdHocCommands from 'components/AdHocCommands/AdHocCommands';
-import AlertModal from 'components/AlertModal';
-import ErrorDetail from 'components/ErrorDetail';
-import AssociateModal from 'components/AssociateModal';
-import DisassociateButton from 'components/DisassociateButton';
-import InventoryGroupRelatedGroupListItem from './InventoryRelatedGroupListItem';
-
-const QS_CONFIG = getQSConfig('group', {
- page: 1,
- page_size: 20,
- order_by: 'name',
-});
-function InventoryRelatedGroupList() {
- const [isModalOpen, setIsModalOpen] = useState(false);
- const [isAdHocLaunchLoading, setIsAdHocLaunchLoading] = useState(false);
- const [associateError, setAssociateError] = useState(null);
- const [disassociateError, setDisassociateError] = useState(null);
- const { id: inventoryId, groupId } = useParams();
- const location = useLocation();
-
- const {
- request: fetchRelated,
- result: {
- groups,
- itemCount,
- relatedSearchableKeys,
- searchableKeys,
- canAdd,
- moduleOptions,
- isAdHocDisabled,
- },
- isLoading,
- error: contentError,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
- const [response, actions, adHocOptions] = await Promise.all([
- GroupsAPI.readChildren(groupId, params),
- InventoriesAPI.readGroupsOptions(inventoryId),
- InventoriesAPI.readAdHocOptions(inventoryId),
- ]);
-
- return {
- moduleOptions: adHocOptions.data.actions.GET.module_name.choices,
- isAdHocDisabled: !adHocOptions.data.actions.POST,
- groups: response.data.results,
- itemCount: response.data.count,
- relatedSearchableKeys: (actions?.data?.related_search_fields || []).map(
- (val) => val.slice(0, -8)
- ),
- searchableKeys: getSearchableKeys(actions.data.actions?.GET),
- canAdd:
- actions.data.actions &&
- Object.prototype.hasOwnProperty.call(actions.data.actions, 'POST'),
- };
- }, [groupId, location.search, inventoryId]),
- {
- groups: [],
- itemCount: 0,
- canAdd: false,
- moduleOptions: [],
- isAdHocDisabled: true,
- }
- );
- useEffect(() => {
- fetchRelated();
- }, [fetchRelated]);
-
- const fetchGroupsToAssociate = useCallback(
- (params) =>
- GroupsAPI.readPotentialGroups(
- groupId,
- mergeParams(params, { not__id: groupId, not__parents: groupId })
- ),
- [groupId]
- );
-
- const associateGroup = useCallback(
- async (selectedGroups) => {
- try {
- await Promise.all(
- selectedGroups.map((selected) =>
- GroupsAPI.associateChildGroup(groupId, selected.id)
- )
- );
- } catch (err) {
- setAssociateError(err);
- }
- fetchRelated();
- },
- [groupId, fetchRelated]
- );
-
- const { selected, isAllSelected, handleSelect, setSelected } =
- useSelected(groups);
-
- const disassociateGroups = useCallback(async () => {
- try {
- await Promise.all(
- selected.map(({ id: childId }) =>
- GroupsAPI.disassociateChildGroup(parseInt(groupId, 10), childId)
- )
- );
- } catch (err) {
- setDisassociateError(err);
- }
- fetchRelated();
- setSelected([]);
- }, [groupId, selected, setSelected, fetchRelated]);
-
- const fetchGroupsOptions = useCallback(
- () => InventoriesAPI.readGroupsOptions(inventoryId),
- [inventoryId]
- );
-
- const { error, dismissError } = useDismissableError(
- associateError || disassociateError
- );
-
- const addFormUrl = `/inventories/inventory/${inventoryId}/groups/${groupId}/nested_groups/add`;
-
- const addExistingGroup = t`Add existing group`;
- const addNewGroup = t`Add new group`;
- const addButton = (
- setIsModalOpen(true)}
- aria-label={addExistingGroup}
- ouiaId="add-existing-group-dropdown-item"
- >
- {addExistingGroup}
- ,
-
- {addNewGroup}
- ,
- ]}
- />
- );
-
- return (
- <>
- (
-
- setSelected(isSelected ? [...groups] : [])
- }
- qsConfig={QS_CONFIG}
- additionalControls={[
- ...(canAdd ? [addButton] : []),
- ...(!isAdHocDisabled
- ? [
- 0}
- onLaunchLoading={setIsAdHocLaunchLoading}
- moduleOptions={moduleOptions}
- />,
- ]
- : []),
- ,
- ]}
- />
- )}
- headerRow={
-
- {t`Name`}
- {t`Actions`}
-
- }
- renderRow={(group, index) => (
- row.id === group.id)}
- onSelect={() => handleSelect(group)}
- />
- )}
- emptyStateControls={canAdd && addButton}
- />
- {isModalOpen && (
- setIsModalOpen(false)}
- title={t`Select Groups`}
- />
- )}
- {error && (
-
- {associateError
- ? t`Failed to associate.`
- : t`Failed to disassociate one or more groups.`}
-
-
- )}
- >
- );
-}
-export default InventoryRelatedGroupList;
diff --git a/awx/ui/src/screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.test.js b/awx/ui/src/screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.test.js
deleted file mode 100644
index 0c6045b7dd84..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupList.test.js
+++ /dev/null
@@ -1,263 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { GroupsAPI, InventoriesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import InventoryRelatedGroupList from './InventoryRelatedGroupList';
-import mockRelatedGroups from '../shared/data.relatedGroups.json';
-
-jest.mock('../../../api/models/Groups');
-jest.mock('../../../api/models/Inventories');
-jest.mock('../../../api/models/CredentialTypes');
-
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useParams: () => ({
- id: 1,
- groupId: 2,
- }),
-}));
-
-const mockGroups = [
- {
- id: 1,
- type: 'group',
- name: 'foo',
- inventory: 1,
- url: '/api/v2/groups/1',
- summary_fields: {
- user_capabilities: {
- delete: true,
- edit: true,
- },
- },
- },
- {
- id: 2,
- type: 'group',
- name: 'bar',
- inventory: 1,
- url: '/api/v2/groups/2',
- summary_fields: {
- user_capabilities: {
- delete: true,
- edit: true,
- },
- },
- },
- {
- id: 3,
- type: 'group',
- name: 'baz',
- inventory: 1,
- url: '/api/v2/groups/3',
- summary_fields: {
- user_capabilities: {
- delete: false,
- edit: false,
- },
- },
- },
-];
-
-describe('', () => {
- let wrapper;
-
- beforeEach(async () => {
- GroupsAPI.readChildren.mockResolvedValue({
- data: { ...mockRelatedGroups },
- });
- InventoriesAPI.readGroupsOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- related_search_fields: [
- 'parents__search',
- 'inventory__search',
- 'inventory_sources__search',
- 'created_by__search',
- 'children__search',
- 'modified_by__search',
- 'hosts__search',
- ],
- },
- });
- InventoriesAPI.readAdHocOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {
- module_name: {
- choices: [
- ['command', 'command'],
- ['shell', 'shell'],
- ],
- },
- },
- POST: {},
- },
- },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('initially renders successfully ', () => {
- expect(wrapper.find('InventoryRelatedGroupList').length).toBe(1);
- });
-
- test('should fetch inventory group hosts from api and render them in the list', () => {
- expect(GroupsAPI.readChildren).toHaveBeenCalled();
- expect(InventoriesAPI.readGroupsOptions).toHaveBeenCalled();
- expect(wrapper.find('InventoryRelatedGroupListItem').length).toBe(3);
- });
-
- test('should render Run Commands Button', async () => {
- expect(wrapper.find('AdHocCommands')).toHaveLength(1);
- });
-
- test('should check and uncheck the row item', async () => {
- expect(
- wrapper.find('input[aria-label="Select row 0"]').props().checked
- ).toBe(false);
- await act(async () => {
- wrapper.find('input[aria-label="Select row 0"]').invoke('onChange')();
- });
- wrapper.update();
- expect(
- wrapper.find('input[aria-label="Select row 0"]').props().checked
- ).toBe(true);
- await act(async () => {
- wrapper.find('input[aria-label="Select row 0"]').invoke('onChange')();
- });
- wrapper.update();
- expect(
- wrapper.find('input[aria-label="Select row 0"]').props().checked
- ).toBe(false);
- });
-
- test('should check all row items when select all is checked', async () => {
- wrapper.find('DataListCheck').forEach((el) => {
- expect(el.props().checked).toBe(false);
- });
- await act(async () => {
- wrapper.find('Checkbox#select-all').invoke('onChange')(true);
- });
- wrapper.update();
- wrapper.find('DataListCheck').forEach((el) => {
- expect(el.props().checked).toBe(true);
- });
- await act(async () => {
- wrapper.find('Checkbox#select-all').invoke('onChange')(false);
- });
- wrapper.update();
- wrapper.find('DataListCheck').forEach((el) => {
- expect(el.props().checked).toBe(false);
- });
- });
-
- test('should show content error when api throws error on initial render', async () => {
- GroupsAPI.readChildren.mockResolvedValueOnce({
- data: { ...mockRelatedGroups },
- });
- InventoriesAPI.readGroupsOptions.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-
- test('should show add dropdown button according to permissions', async () => {
- GroupsAPI.readChildren.mockResolvedValueOnce({
- data: { ...mockRelatedGroups },
- });
- InventoriesAPI.readGroupsOptions.mockResolvedValueOnce({
- data: {
- actions: {
- GET: {},
- },
- related_search_fields: [
- 'parents__search',
- 'inventory__search',
- 'inventory_sources__search',
- 'created_by__search',
- 'children__search',
- 'modified_by__search',
- 'hosts__search',
- ],
- },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('AddDropdown').length).toBe(0);
- });
-
- test('should associate existing group', async () => {
- GroupsAPI.readPotentialGroups.mockResolvedValue({
- data: { count: mockGroups.length, results: mockGroups },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
-
- act(() => wrapper.find('Button[aria-label="Add"]').prop('onClick')());
- wrapper.update();
- await act(async () =>
- wrapper
- .find('DropdownItem[aria-label="Add existing group"]')
- .prop('onClick')()
- );
- expect(GroupsAPI.readPotentialGroups).toBeCalledWith(2, {
- not__id: 2,
- not__parents: 2,
- order_by: 'name',
- page: 1,
- page_size: 5,
- });
- wrapper.update();
- act(() =>
- wrapper.find('CheckboxListItem[name="foo"]').prop('onSelect')({ id: 1 })
- );
- wrapper.update();
- await act(() =>
- wrapper.find('button[aria-label="Save"]').prop('onClick')()
- );
- expect(GroupsAPI.associateChildGroup).toBeCalledTimes(1);
- });
-
- test('should not render Run Commands button', async () => {
- InventoriesAPI.readAdHocOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {
- module_name: {
- choices: [
- ['command', 'command'],
- ['shell', 'shell'],
- ],
- },
- },
- },
- },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- expect(wrapper.find('AdHocCommands')).toHaveLength(0);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js b/awx/ui/src/screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js
deleted file mode 100644
index 3c2c9c090f2f..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import 'styled-components/macro';
-import React from 'react';
-import { Link } from 'react-router-dom';
-import { string, bool, func, number } from 'prop-types';
-
-import { t } from '@lingui/macro';
-
-import { Td, Tr } from '@patternfly/react-table';
-import { Button } from '@patternfly/react-core';
-import { PencilAltIcon } from '@patternfly/react-icons';
-
-import { Group } from 'types';
-import { ActionItem, ActionsTd } from 'components/PaginatedTable';
-
-function InventoryRelatedGroupListItem({
- detailUrl,
- editUrl,
- group,
- rowIndex,
- isSelected,
- onSelect,
-}) {
- const labelId = `check-action-${group.id}`;
-
- return (
-
- |
-
-
- {group.name}
-
- |
-
-
-
-
-
-
- );
-}
-
-InventoryRelatedGroupListItem.propTypes = {
- detailUrl: string.isRequired,
- editUrl: string.isRequired,
- group: Group.isRequired,
- rowIndex: number.isRequired,
- isSelected: bool.isRequired,
- onSelect: func.isRequired,
-};
-
-export default InventoryRelatedGroupListItem;
diff --git a/awx/ui/src/screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.test.js b/awx/ui/src/screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.test.js
deleted file mode 100644
index 4ab8fb17b138..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroupListItem.test.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import InventoryRelatedGroupListItem from './InventoryRelatedGroupListItem';
-import mockRelatedGroups from '../shared/data.relatedGroups.json';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
- const mockGroup = mockRelatedGroups.results[0];
-
- beforeEach(() => {
- wrapper = mountWithContexts(
-
-
- {}}
- rowIndex={0}
- />
-
-
- );
- });
-
- test('should display expected row item content', () => {
- expect(wrapper.find('b').text()).toContain('Group 2 Inventory 0');
- });
-
- test('edit button shown to users with edit capabilities', () => {
- expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
- });
-
- test('edit button hidden from users without edit capabilities', () => {
- wrapper = mountWithContexts(
-
-
- {}}
- rowIndex={0}
- />
-
-
- );
- expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroups.js b/awx/ui/src/screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroups.js
deleted file mode 100644
index d5904062b3b9..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryRelatedGroups/InventoryRelatedGroups.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from 'react';
-import { Switch, Route } from 'react-router-dom';
-import InventoryRelatedGroupList from './InventoryRelatedGroupList';
-import InventoryRelatedGroupAdd from '../InventoryRelatedGroupAdd';
-
-function InventoryRelatedGroups() {
- return (
-
-
-
-
-
-
-
-
- );
-}
-export default InventoryRelatedGroups;
diff --git a/awx/ui/src/screens/Inventory/InventoryRelatedGroups/index.js b/awx/ui/src/screens/Inventory/InventoryRelatedGroups/index.js
deleted file mode 100644
index 45a43e200a06..000000000000
--- a/awx/ui/src/screens/Inventory/InventoryRelatedGroups/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventoryRelatedGroups';
diff --git a/awx/ui/src/screens/Inventory/InventorySource/InventorySource.js b/awx/ui/src/screens/Inventory/InventorySource/InventorySource.js
deleted file mode 100644
index e62b5a992171..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySource/InventorySource.js
+++ /dev/null
@@ -1,177 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-
-import { t } from '@lingui/macro';
-import {
- Link,
- Switch,
- Route,
- Redirect,
- useRouteMatch,
- useLocation,
-} from 'react-router-dom';
-import { CaretLeftIcon } from '@patternfly/react-icons';
-import useRequest from 'hooks/useRequest';
-
-import { InventoriesAPI, InventorySourcesAPI, OrganizationsAPI } from 'api';
-import { Schedules } from 'components/Schedule';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import RoutedTabs from 'components/RoutedTabs';
-import NotificationList from 'components/NotificationList/NotificationList';
-import InventorySourceDetail from '../InventorySourceDetail';
-import InventorySourceEdit from '../InventorySourceEdit';
-
-function InventorySource({ inventory, setBreadcrumb, me }) {
- const location = useLocation();
- const match = useRouteMatch('/inventories/inventory/:id/sources/:sourceId');
- const sourceListUrl = `/inventories/inventory/${inventory.id}/sources`;
-
- const {
- result: { source, isNotifAdmin },
- error,
- isLoading,
- request: fetchSource,
- } = useRequest(
- useCallback(async () => {
- const [inventorySource, notifAdminRes] = await Promise.all([
- InventoriesAPI.readSourceDetail(inventory.id, match.params.sourceId),
- OrganizationsAPI.read({
- page_size: 1,
- role_level: 'notification_admin_role',
- }),
- ]);
- return {
- source: inventorySource,
- isNotifAdmin: notifAdminRes.data.results.length > 0,
- };
- }, [inventory.id, match.params.sourceId]),
- { source: null, isNotifAdmin: false }
- );
-
- useEffect(() => {
- fetchSource();
- }, [fetchSource, location.pathname]);
-
- useEffect(() => {
- if (inventory && source) {
- setBreadcrumb(inventory, source);
- }
- }, [inventory, source, setBreadcrumb]);
-
- const loadSchedules = useCallback(
- (params) => InventorySourcesAPI.readSchedules(source?.id, params),
- [source]
- );
-
- const loadScheduleOptions = useCallback(
- () => InventorySourcesAPI.readScheduleOptions(source?.id),
- [source]
- );
-
- const tabsArray = [
- {
- name: (
- <>
-
- {t`Back to Sources`}
- >
- ),
- link: `${sourceListUrl}`,
- id: 0,
- },
- {
- name: t`Details`,
- link: `${match.url}/details`,
- id: 1,
- },
- {
- name: t`Schedules`,
- link: `${match.url}/schedules`,
- id: 2,
- },
- ];
-
- const canToggleNotifications = isNotifAdmin;
- const canSeeNotificationsTab = me.is_system_auditor || isNotifAdmin;
-
- if (canSeeNotificationsTab) {
- tabsArray.push({
- name: t`Notifications`,
- link: `${match.url}/notifications`,
- id: 3,
- });
- }
-
- if (error) {
- return ;
- }
-
- let showCardHeader = true;
-
- if (['edit', 'schedules/'].some((name) => location.pathname.includes(name))) {
- showCardHeader = false;
- }
-
- return (
- <>
- {showCardHeader && }
-
- {isLoading && }
-
- {!isLoading && source && (
-
-
-
-
-
-
-
-
-
-
-
-
-
- setBreadcrumb(inventory, source, schedule)
- }
- resource={source}
- loadSchedules={loadSchedules}
- loadScheduleOptions={loadScheduleOptions}
- />
-
-
-
-
- {t`View inventory source details`}
-
-
-
-
- )}
- >
- );
-}
-
-export default InventorySource;
diff --git a/awx/ui/src/screens/Inventory/InventorySource/InventorySource.test.js b/awx/ui/src/screens/Inventory/InventorySource/InventorySource.test.js
deleted file mode 100644
index 9b7adf46f6b1..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySource/InventorySource.test.js
+++ /dev/null
@@ -1,135 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { InventoriesAPI, OrganizationsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import mockInventorySource from '../shared/data.inventory_source.json';
-import InventorySource from './InventorySource';
-
-jest.mock('../../../api/models/Inventories');
-jest.mock('../../../api/models/Organizations');
-jest.mock('../../../api/models/InventorySources');
-
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useRouteMatch: () => ({
- url: '/inventories/inventory/2/sources/123',
- params: { id: 2, sourceId: 123 },
- }),
-}));
-
-const mockInventory = {
- id: 2,
- name: 'Mock Inventory',
-};
-
-describe('', () => {
- let wrapper;
- let history;
-
- beforeEach(async () => {
- await act(async () => {
- wrapper = mountWithContexts(
- {}}
- />
- );
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should render expected tabs', () => {
- InventoriesAPI.readSourceDetail.mockResolvedValue({
- data: { ...mockInventorySource },
- });
- OrganizationsAPI.read.mockResolvedValue({
- data: { results: [{ id: 1, name: 'isNotifAdmin' }] },
- });
- const expectedTabs = [
- 'Back to Sources',
- 'Details',
- 'Schedules',
- 'Notifications',
- ];
- wrapper.find('RoutedTabs li').forEach((tab, index) => {
- expect(tab.text()).toEqual(expectedTabs[index]);
- });
- });
-
- test('should show content error when api throws error on initial render', async () => {
- InventoriesAPI.readSourceDetail.mockResolvedValue({
- data: { ...mockInventorySource },
- });
- OrganizationsAPI.read.mockResolvedValue({
- data: { results: [{ id: 1, name: 'isNotifAdmin' }] },
- });
- InventoriesAPI.readSourceDetail.mockRejectedValueOnce(new Error());
- await act(async () => {
- wrapper = mountWithContexts(
- {}}
- />
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- expect(wrapper.find('ContentError Title').text()).toEqual(
- 'Something went wrong...'
- );
- });
-
- test('should show content error when user attempts to navigate to erroneous route', async () => {
- InventoriesAPI.readSourceDetail.mockResolvedValue({
- data: { ...mockInventorySource },
- });
- OrganizationsAPI.read.mockResolvedValue({
- data: { results: [{ id: 1, name: 'isNotifAdmin' }] },
- });
- history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/2/sources/1/foobar'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
- {}}
- me={{ is_system_auditor: false }}
- />,
- { context: { router: { history } } }
- );
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- expect(wrapper.find('ContentError Title').text()).toEqual('Not Found');
- });
-
- test('should call api', () => {
- InventoriesAPI.readSourceDetail.mockResolvedValue({
- data: { ...mockInventorySource },
- });
- OrganizationsAPI.read.mockResolvedValue({
- data: { results: [{ id: 1, name: 'isNotifAdmin' }] },
- });
- expect(InventoriesAPI.readSourceDetail).toBeCalledWith(2, 123);
- expect(OrganizationsAPI.read).toBeCalled();
- });
-
- test('should not render notifications tab', () => {
- InventoriesAPI.readSourceDetail.mockResolvedValue({
- data: { ...mockInventorySource },
- });
- OrganizationsAPI.read.mockResolvedValue({
- data: { results: [] },
- });
- expect(wrapper.find('button[aria-label="Notifications"]').length).toBe(0);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventorySource/index.js b/awx/ui/src/screens/Inventory/InventorySource/index.js
deleted file mode 100644
index 35b12c82018a..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySource/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventorySource';
diff --git a/awx/ui/src/screens/Inventory/InventorySourceAdd/InventorySourceAdd.js b/awx/ui/src/screens/Inventory/InventorySourceAdd/InventorySourceAdd.js
deleted file mode 100644
index 3075830fad02..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySourceAdd/InventorySourceAdd.js
+++ /dev/null
@@ -1,75 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { useHistory } from 'react-router-dom';
-import { Card } from '@patternfly/react-core';
-import { InventorySourcesAPI } from 'api';
-import useRequest from 'hooks/useRequest';
-import { CardBody } from 'components/Card';
-import InventorySourceForm from '../shared/InventorySourceForm';
-
-function InventorySourceAdd({ inventory }) {
- const history = useHistory();
- const { id, organization } = inventory;
-
- const { error, request, result } = useRequest(
- useCallback(async (values) => {
- const { data } = await InventorySourcesAPI.create(values);
- return data;
- }, [])
- );
-
- useEffect(() => {
- if (result) {
- history.push(
- `/inventories/inventory/${result.inventory}/sources/${result.id}/details`
- );
- }
- }, [result, history]);
-
- const handleSubmit = async (form) => {
- const {
- credential,
- source_path,
- source_project,
- source_script,
- execution_environment,
- ...remainingForm
- } = form;
-
- const sourcePath = {};
- const sourceProject = {};
- if (form.source === 'scm') {
- sourcePath.source_path =
- source_path === '/ (project root)' ? '' : source_path;
- sourceProject.source_project = source_project.id;
- }
-
- await request({
- credential: credential?.id || null,
- inventory: id,
- source_script: source_script?.id || null,
- execution_environment: execution_environment?.id || null,
- ...sourcePath,
- ...sourceProject,
- ...remainingForm,
- });
- };
-
- const handleCancel = () => {
- history.push(`/inventories/inventory/${id}/sources`);
- };
-
- return (
-
-
-
-
-
- );
-}
-
-export default InventorySourceAdd;
diff --git a/awx/ui/src/screens/Inventory/InventorySourceAdd/InventorySourceAdd.test.js b/awx/ui/src/screens/Inventory/InventorySourceAdd/InventorySourceAdd.test.js
deleted file mode 100644
index bfc572a4e83d..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySourceAdd/InventorySourceAdd.test.js
+++ /dev/null
@@ -1,169 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { InventorySourcesAPI, ProjectsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import InventorySourceAdd from './InventorySourceAdd';
-
-jest.mock('../../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useParams: () => ({
- id: 111,
- }),
-}));
-
-describe('', () => {
- let wrapper;
- const invSourceData = {
- credential: { id: 222 },
- description: 'bar',
- inventory: 111,
- name: 'foo',
- overwrite: false,
- overwrite_vars: false,
- source: 'scm',
- source_path: 'mock/file.sh',
- source_project: { id: 999 },
- source_vars: '---↵',
- update_cache_timeout: 0,
- update_on_launch: false,
- verbosity: 1,
- };
-
- const mockInventory = {
- id: 111,
- name: 'Foo',
- organization: 2,
- };
-
- beforeEach(async () => {
- InventorySourcesAPI.readOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {
- source: {
- choices: [
- ['file', 'File, Directory or Script'],
- ['scm', 'Sourced from a Project'],
- ['ec2', 'Amazon EC2'],
- ['gce', 'Google Compute Engine'],
- ['azure_rm', 'Microsoft Azure Resource Manager'],
- ['vmware', 'VMware vCenter'],
- ['satellite6', 'Red Hat Satellite 6'],
- ['openstack', 'OpenStack'],
- ['rhv', 'Red Hat Virtualization'],
- ['controller', 'Red Hat Ansible Automation Platform'],
- ],
- },
- },
- },
- },
- });
-
- ProjectsAPI.readInventories.mockResolvedValue({
- data: [],
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('new form displays primary form fields', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('FormGroup[label="Name"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Description"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Source"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Ansible Environment"]')).toHaveLength(
- 0
- );
- });
-
- test('should navigate to inventory sources list when cancel is clicked', async () => {
- const history = createMemoryHistory({});
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
- await act(async () => {
- wrapper.find('InventorySourceForm').invoke('onCancel')();
- });
- expect(history.location.pathname).toEqual(
- '/inventories/inventory/111/sources'
- );
- });
-
- test('should post to the api when submit is clicked', async () => {
- InventorySourcesAPI.create.mockResolvedValueOnce({ data: {} });
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await act(async () => {
- wrapper.find('InventorySourceForm').invoke('onSubmit')(invSourceData);
- });
- expect(InventorySourcesAPI.create).toHaveBeenCalledTimes(1);
- expect(InventorySourcesAPI.create).toHaveBeenCalledWith({
- ...invSourceData,
- credential: 222,
- source_project: 999,
- source_script: null,
- execution_environment: null,
- });
- });
-
- test('successful form submission should trigger redirect', async () => {
- const history = createMemoryHistory({});
- InventorySourcesAPI.create.mockResolvedValueOnce({
- data: { id: 123, inventory: 111 },
- });
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
- await act(async () => {
- wrapper.find('InventorySourceForm').invoke('onSubmit')(invSourceData);
- });
- expect(history.location.pathname).toEqual(
- '/inventories/inventory/111/sources/123/details'
- );
- });
-
- test('unsuccessful form submission should show an error message', async () => {
- const error = {
- response: {
- data: { detail: 'An error occurred' },
- },
- };
- InventorySourcesAPI.create.mockImplementation(() => Promise.reject(error));
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('FormSubmitError').length).toBe(0);
- await act(async () => {
- wrapper.find('InventorySourceForm').invoke('onSubmit')({});
- });
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventorySourceAdd/index.js b/awx/ui/src/screens/Inventory/InventorySourceAdd/index.js
deleted file mode 100644
index 0a3020f194d6..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySourceAdd/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventorySourceAdd';
diff --git a/awx/ui/src/screens/Inventory/InventorySourceDetail/InventorySourceDetail.js b/awx/ui/src/screens/Inventory/InventorySourceDetail/InventorySourceDetail.js
deleted file mode 100644
index 87e9ece5cdcc..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySourceDetail/InventorySourceDetail.js
+++ /dev/null
@@ -1,334 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { Link, useHistory } from 'react-router-dom';
-import { t } from '@lingui/macro';
-import {
- Button,
- TextList,
- TextListItem,
- TextListVariants,
- TextListItemVariants,
- Tooltip,
-} from '@patternfly/react-core';
-import getDocsBaseUrl from 'util/getDocsBaseUrl';
-import { useConfig } from 'contexts/Config';
-import AlertModal from 'components/AlertModal';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import CredentialChip from 'components/CredentialChip';
-import DeleteButton from 'components/DeleteButton';
-import ErrorDetail from 'components/ErrorDetail';
-import ExecutionEnvironmentDetail from 'components/ExecutionEnvironmentDetail';
-import JobCancelButton from 'components/JobCancelButton';
-import StatusLabel from 'components/StatusLabel';
-import { CardBody, CardActionsRow } from 'components/Card';
-import { DetailList, Detail, UserDateDetail } from 'components/DetailList';
-import { VariablesDetail } from 'components/CodeEditor';
-import useRequest from 'hooks/useRequest';
-import { InventorySourcesAPI } from 'api';
-import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
-import useIsMounted from 'hooks/useIsMounted';
-import { formatDateString } from 'util/dates';
-import Popover from 'components/Popover';
-import { VERBOSITY } from 'components/VerbositySelectField';
-import InventorySourceSyncButton from '../shared/InventorySourceSyncButton';
-import useWsInventorySourcesDetails from '../InventorySources/useWsInventorySourcesDetails';
-import getHelpText from '../shared/Inventory.helptext';
-
-function InventorySourceDetail({ inventorySource }) {
- const helpText = getHelpText();
- const {
- created,
- custom_virtualenv,
- description,
- id,
- modified,
- name,
- overwrite,
- overwrite_vars,
- source,
- source_path,
- source_vars,
- update_cache_timeout,
- update_on_launch,
- verbosity,
- enabled_var,
- enabled_value,
- host_filter,
- summary_fields,
- } = useWsInventorySourcesDetails(inventorySource);
-
- const {
- created_by,
- credentials,
- inventory,
- modified_by,
- organization,
- source_project,
- user_capabilities,
- execution_environment,
- } = summary_fields;
-
- const [deletionError, setDeletionError] = useState(false);
- const config = useConfig();
- const history = useHistory();
- const isMounted = useIsMounted();
-
- const {
- result: sourceChoices,
- error,
- isLoading,
- request: fetchSourceChoices,
- } = useRequest(
- useCallback(async () => {
- const { data } = await InventorySourcesAPI.readOptions();
- return Object.assign(
- ...data.actions.GET.source.choices.map(([key, val]) => ({ [key]: val }))
- );
- }, []),
- {}
- );
-
- const docsBaseUrl = getDocsBaseUrl(config);
- useEffect(() => {
- fetchSourceChoices();
- }, [fetchSourceChoices]);
-
- const handleDelete = async () => {
- try {
- await Promise.all([
- InventorySourcesAPI.destroyHosts(id),
- InventorySourcesAPI.destroyGroups(id),
- InventorySourcesAPI.destroy(id),
- ]);
- history.push(`/inventories/inventory/${inventory.id}/sources`);
- } catch (err) {
- if (isMounted.current) {
- setDeletionError(err);
- }
- }
- };
-
- const deleteDetailsRequests = relatedResourceDeleteRequests.inventorySource(
- inventorySource.id
- );
-
- let optionsList = '';
- if (overwrite || overwrite_vars || update_on_launch) {
- optionsList = (
-
- {overwrite && (
-
- {t`Overwrite local groups and hosts from remote inventory source`}
-
-
- )}
- {overwrite_vars && (
-
- {t`Overwrite local variables from remote inventory source`}
-
-
- )}
- {update_on_launch && (
-
- {t`Update on launch`}
-
-
- )}
-
- );
- }
-
- if (isLoading) {
- return ;
- }
-
- if (error) {
- return ;
- }
-
- const generateLastJobTooltip = (job) => (
- <>
- {t`MOST RECENT SYNC`}
-
- {t`JOB ID:`} {job.id}
-
-
- {t`STATUS:`} {job.status.toUpperCase()}
-
- {job.finished && (
-
- {t`FINISHED:`} {formatDateString(job.finished)}
-
- )}
- >
- );
-
- let job = null;
-
- if (summary_fields?.current_job) {
- job = summary_fields.current_job;
- } else if (summary_fields?.last_job) {
- job = summary_fields.last_job;
- }
-
- return (
-
-
-
-
-
-
-
-
- )
- }
- />
-
-
- {organization && (
-
- {organization.name}
-
- }
- />
- )}
-
- {source_project && (
-
- {source_project.name}
-
- }
- />
- )}
- {source === 'scm' ? (
-
- ) : null}
-
-
-
-
-
- (
-
- ))}
- isEmpty={credentials?.length === 0}
- />
- {optionsList && (
-
- )}
- {source_vars && (
-
- )}
-
-
-
-
- {user_capabilities?.edit && (
-
- )}
- {user_capabilities?.start &&
- (['new', 'running', 'pending', 'waiting'].includes(job?.status) ? (
-
- ) : (
-
- ))}
- {user_capabilities?.delete && (
-
- {t`Delete`}
-
- )}
-
- {deletionError && (
- setDeletionError(false)}
- >
- {t`Failed to delete inventory source ${name}.`}
-
-
- )}
-
- );
-}
-export default InventorySourceDetail;
diff --git a/awx/ui/src/screens/Inventory/InventorySourceDetail/InventorySourceDetail.test.js b/awx/ui/src/screens/Inventory/InventorySourceDetail/InventorySourceDetail.test.js
deleted file mode 100644
index c74f06446ea2..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySourceDetail/InventorySourceDetail.test.js
+++ /dev/null
@@ -1,256 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import {
- InventorySourcesAPI,
- InventoriesAPI,
- WorkflowJobTemplateNodesAPI,
-} from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import InventorySourceDetail from './InventorySourceDetail';
-import mockInvSource from '../shared/data.inventory_source.json';
-
-jest.mock('../../../api');
-
-function assertDetail(wrapper, label, value) {
- expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
- expect(wrapper.find(`Detail[label="${label}"] dd`).text()).toBe(value);
-}
-
-describe('InventorySourceDetail', () => {
- let wrapper;
-
- beforeEach(async () => {
- InventoriesAPI.updateSources.mockResolvedValue({
- data: [{ inventory_source: 1 }],
- });
- WorkflowJobTemplateNodesAPI.read.mockResolvedValue({ data: { count: 0 } });
- InventorySourcesAPI.readOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {
- source: {
- choices: [
- ['file', 'File, Directory or Script'],
- ['scm', 'Sourced from a Project'],
- ['ec2', 'Amazon EC2'],
- ['gce', 'Google Compute Engine'],
- ['azure_rm', 'Microsoft Azure Resource Manager'],
- ['vmware', 'VMware vCenter'],
- ['satellite6', 'Red Hat Satellite 6'],
- ['openstack', 'OpenStack'],
- ['rhv', 'Red Hat Virtualization'],
- ['controller', 'Red Hat Ansible Automation Platform'],
- ],
- },
- },
- },
- },
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should render cancel button while job is running', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('InventorySourceDetail')).toHaveLength(1);
-
- expect(wrapper.find('JobCancelButton').length).toBe(1);
- });
-
- test('should render expected details', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('InventorySourceDetail')).toHaveLength(1);
- assertDetail(wrapper, 'Name', 'mock inv source');
- assertDetail(wrapper, 'Description', 'mock description');
- assertDetail(wrapper, 'Source', 'Sourced from a Project');
- assertDetail(wrapper, 'Organization', 'Mock Org');
- assertDetail(wrapper, 'Project', 'Mock Project');
- assertDetail(wrapper, 'Inventory file', 'foo');
- assertDetail(wrapper, 'Verbosity', '2 (More Verbose)');
- assertDetail(wrapper, 'Cache timeout', '2 seconds');
- const executionEnvironment = wrapper.find('ExecutionEnvironmentDetail');
- expect(executionEnvironment).toHaveLength(1);
- expect(executionEnvironment.find('dt').text()).toEqual(
- 'Execution Environment'
- );
- expect(executionEnvironment.find('dd').text()).toEqual(
- mockInvSource.summary_fields.execution_environment.name
- );
-
- expect(wrapper.find('CredentialChip').text()).toBe('Cloud: mock cred');
- expect(wrapper.find('VariablesDetail').prop('value')).toEqual(
- '---\nfoo: bar'
- );
- wrapper.find('Detail[label="Enabled Options"] li').forEach((option) => {
- expect([
- 'Overwrite local groups and hosts from remote inventory source',
- 'Overwrite local variables from remote inventory source',
- 'Update on launch',
- ]).toContain(option.text());
- });
- });
-
- test('should display expected action buttons for users with permissions', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- const editButton = wrapper.find('Button[aria-label="edit"]');
- expect(editButton.text()).toEqual('Edit');
- expect(editButton.prop('to')).toBe(
- '/inventories/inventory/2/sources/123/edit'
- );
- expect(wrapper.find('DeleteButton')).toHaveLength(1);
- expect(wrapper.find('InventorySourceSyncButton')).toHaveLength(1);
- });
-
- test('should have proper number of delete detail requests', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(
- wrapper.find('DeleteButton').prop('deleteDetailsRequests')
- ).toHaveLength(3);
- });
-
- test('should hide expected action buttons for users without permissions', async () => {
- const userCapabilities = {
- edit: false,
- delete: false,
- start: false,
- };
- const invSource = {
- ...mockInvSource,
- summary_fields: { ...userCapabilities },
- };
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('Button[aria-label="edit"]')).toHaveLength(0);
- expect(wrapper.find('DeleteButton')).toHaveLength(0);
- expect(wrapper.find('InventorySourceSyncButton')).toHaveLength(0);
- });
-
- test('expected api call is made for delete', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/2/sources/123/details'],
- });
- act(() => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
- expect(history.location.pathname).toEqual(
- '/inventories/inventory/2/sources/123/details'
- );
- await act(async () => {
- wrapper.find('DeleteButton').invoke('onConfirm')();
- });
- expect(InventorySourcesAPI.destroy).toHaveBeenCalledTimes(1);
- expect(InventorySourcesAPI.destroyHosts).toHaveBeenCalledTimes(1);
- expect(InventorySourcesAPI.destroyGroups).toHaveBeenCalledTimes(1);
- expect(history.location.pathname).toEqual(
- '/inventories/inventory/2/sources'
- );
- });
-
- test('Content error shown for failed options request', async () => {
- InventorySourcesAPI.readOptions.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
- expect(InventorySourcesAPI.readOptions).toHaveBeenCalledTimes(0);
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(InventorySourcesAPI.readOptions).toHaveBeenCalledTimes(1);
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- expect(wrapper.find('ContentError Title').text()).toEqual(
- 'Something went wrong...'
- );
- });
-
- test('Error dialog shown for failed deletion', async () => {
- InventorySourcesAPI.destroy.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('Modal[title="Error!"]')).toHaveLength(0);
- await act(async () => {
- wrapper.find('DeleteButton').invoke('onConfirm')();
- });
- await waitForElement(
- wrapper,
- 'Modal[title="Error!"]',
- (el) => el.length === 1
- );
- await act(async () => {
- wrapper.find('Modal[title="Error!"]').invoke('onClose')();
- });
- await waitForElement(
- wrapper,
- 'Modal[title="Error!"]',
- (el) => el.length === 0
- );
- });
-
- test('should not load Credentials', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- const credentials_detail = wrapper.find(`Detail[label="Credential"]`).at(0);
- expect(credentials_detail.prop('isEmpty')).toEqual(true);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventorySourceDetail/index.js b/awx/ui/src/screens/Inventory/InventorySourceDetail/index.js
deleted file mode 100644
index 1be625cff0fe..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySourceDetail/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventorySourceDetail';
diff --git a/awx/ui/src/screens/Inventory/InventorySourceEdit/InventorySourceEdit.js b/awx/ui/src/screens/Inventory/InventorySourceEdit/InventorySourceEdit.js
deleted file mode 100644
index e51ce44ab69e..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySourceEdit/InventorySourceEdit.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { useHistory } from 'react-router-dom';
-import { Card } from '@patternfly/react-core';
-import { CardBody } from 'components/Card';
-import useRequest from 'hooks/useRequest';
-import { InventorySourcesAPI } from 'api';
-import InventorySourceForm from '../shared/InventorySourceForm';
-
-function InventorySourceEdit({ source, inventory }) {
- const history = useHistory();
- const { id, organization } = inventory;
- const detailsUrl = `/inventories/inventory/${id}/sources/${source.id}/details`;
-
- const { error, request, result } = useRequest(
- useCallback(
- async (values) => {
- const { data } = await InventorySourcesAPI.replace(source.id, values);
- return data;
- },
- [source.id]
- ),
- null
- );
-
- useEffect(() => {
- if (result) {
- history.push(detailsUrl);
- }
- }, [result, detailsUrl, history]);
-
- const handleSubmit = async (form) => {
- const {
- credential,
- source_path,
- source_project,
- source_script,
- execution_environment,
- ...remainingForm
- } = form;
-
- const sourcePath = {};
- const sourceProject = {};
- if (form.source === 'scm') {
- sourcePath.source_path =
- source_path === '/ (project root)' ? '' : source_path;
- sourceProject.source_project = source_project.id;
- }
-
- await request({
- credential: credential?.id || null,
- inventory: id,
- source_script: source_script?.id || null,
- execution_environment: execution_environment?.id || null,
- ...sourcePath,
- ...sourceProject,
- ...remainingForm,
- });
- };
-
- const handleCancel = () => {
- history.push(detailsUrl);
- };
-
- return (
-
-
-
-
-
- );
-}
-
-export default InventorySourceEdit;
diff --git a/awx/ui/src/screens/Inventory/InventorySourceEdit/InventorySourceEdit.test.js b/awx/ui/src/screens/Inventory/InventorySourceEdit/InventorySourceEdit.test.js
deleted file mode 100644
index dd3c2de9683b..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySourceEdit/InventorySourceEdit.test.js
+++ /dev/null
@@ -1,151 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { CredentialsAPI, InventorySourcesAPI, ProjectsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import InventorySourceEdit from './InventorySourceEdit';
-
-jest.mock('../../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useParams: () => ({
- id: 1,
- }),
-}));
-
-describe('', () => {
- let wrapper;
- const mockInvSrc = {
- id: 23,
- description: 'bar',
- inventory: 1,
- name: 'foo',
- overwrite: false,
- overwrite_vars: false,
- source: 'scm',
- source_path: 'mock/file.sh',
- source_project: { id: 999 },
- source_vars: '---↵',
- update_cache_timeout: 0,
- update_on_launch: false,
- verbosity: 1,
- };
- const mockInventory = {
- id: 1,
- name: 'Foo',
- organization: 1,
- };
- const history = createMemoryHistory();
-
- beforeEach(async () => {
- InventorySourcesAPI.readOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {
- source: {
- choices: [
- ['file', 'File, Directory or Script'],
- ['scm', 'Sourced from a Project'],
- ['ec2', 'Amazon EC2'],
- ['gce', 'Google Compute Engine'],
- ['azure_rm', 'Microsoft Azure Resource Manager'],
- ['vmware', 'VMware vCenter'],
- ['satellite6', 'Red Hat Satellite 6'],
- ['openstack', 'OpenStack'],
- ['rhv', 'Red Hat Virtualization'],
- ['controller', 'Red Hat Ansible Automation Platform'],
- ],
- },
- },
- },
- },
- });
- InventorySourcesAPI.replace.mockResolvedValue({
- data: {
- ...mockInvSrc,
- },
- });
- ProjectsAPI.readInventories.mockResolvedValue({
- data: [],
- });
- CredentialsAPI.read.mockResolvedValue({
- data: { count: 0, results: [] },
- });
- ProjectsAPI.read.mockResolvedValue({
- data: {
- count: 2,
- results: [
- {
- id: 1,
- name: 'mock proj one',
- },
- {
- id: 2,
- name: 'mock proj two',
- },
- ],
- },
- });
-
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('handleSubmit should call api update', async () => {
- expect(InventorySourcesAPI.replace).toHaveBeenCalledTimes(0);
- await act(async () => {
- wrapper.find('InventorySourceForm').invoke('onSubmit')(mockInvSrc);
- });
- expect(InventorySourcesAPI.replace).toHaveBeenCalledTimes(1);
- });
-
- test('should navigate to inventory source detail after successful submission', () => {
- expect(wrapper.find('FormSubmitError').length).toBe(0);
- expect(history.location.pathname).toEqual(
- '/inventories/inventory/1/sources/23/details'
- );
- });
-
- test('should navigate to inventory source detail when cancel is clicked', async () => {
- await act(async () => {
- wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
- });
- expect(history.location.pathname).toEqual(
- '/inventories/inventory/1/sources/23/details'
- );
- });
-
- test('unsuccessful form submission should show an error message', async () => {
- const error = {
- response: {
- data: { detail: 'An error occurred' },
- },
- };
- InventorySourcesAPI.replace.mockImplementation(() => Promise.reject(error));
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('FormSubmitError').length).toBe(0);
- await act(async () => {
- wrapper.find('InventorySourceForm').invoke('onSubmit')({});
- });
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventorySourceEdit/index.js b/awx/ui/src/screens/Inventory/InventorySourceEdit/index.js
deleted file mode 100644
index b63d354049ca..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySourceEdit/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventorySourceEdit';
diff --git a/awx/ui/src/screens/Inventory/InventorySources/InventorySourceList.js b/awx/ui/src/screens/Inventory/InventorySources/InventorySourceList.js
deleted file mode 100644
index b83a72c055b1..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySources/InventorySourceList.js
+++ /dev/null
@@ -1,262 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { useParams, useLocation } from 'react-router-dom';
-import { t, Plural } from '@lingui/macro';
-
-import useRequest, {
- useDeleteItems,
- useDismissableError,
-} from 'hooks/useRequest';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import { InventoriesAPI, InventorySourcesAPI } from 'api';
-import PaginatedTable, {
- HeaderRow,
- HeaderCell,
- ToolbarAddButton,
- ToolbarDeleteButton,
- ToolbarSyncSourceButton,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import useSelected from 'hooks/useSelected';
-import DatalistToolbar from 'components/DataListToolbar';
-import AlertModal from 'components/AlertModal/AlertModal';
-import ErrorDetail from 'components/ErrorDetail/ErrorDetail';
-import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
-import InventorySourceListItem from './InventorySourceListItem';
-import useWsInventorySources from './useWsInventorySources';
-
-const QS_CONFIG = getQSConfig('inventory-sources', {
- page: 1,
- page_size: 20,
- order_by: 'name',
-});
-
-function InventorySourceList() {
- const { inventoryType, id } = useParams();
- const { search } = useLocation();
-
- const {
- isLoading,
- error: fetchError,
- result: {
- result,
- sourceCount,
- sourceChoices,
- sourceChoicesOptions,
- searchableKeys,
- relatedSearchableKeys,
- },
- request: fetchSources,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, search);
- const results = await Promise.all([
- InventoriesAPI.readSources(id, params),
- InventorySourcesAPI.readOptions(),
- ]);
- return {
- result: results[0].data.results,
- sourceCount: results[0].data.count,
- sourceChoices: results[1].data.actions.GET.source.choices,
- sourceChoicesOptions: results[1].data.actions,
- searchableKeys: getSearchableKeys(results[1].data.actions?.GET),
- relatedSearchableKeys: (
- results[1]?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- };
- }, [id, search]),
- {
- result: [],
- sourceCount: 0,
- sourceChoices: [],
- searchableKeys: [],
- relatedSearchableKeys: [],
- }
- );
-
- const sources = useWsInventorySources(result);
-
- const canSyncSources =
- sources.length > 0 &&
- sources.every((source) => source.summary_fields.user_capabilities.start);
- const {
- isLoading: isSyncAllLoading,
- error: syncAllError,
- request: syncAll,
- } = useRequest(
- useCallback(async () => {
- if (canSyncSources) {
- await InventoriesAPI.syncAllSources(id);
- }
- }, [id, canSyncSources])
- );
-
- useEffect(() => {
- fetchSources();
- }, [fetchSources]);
-
- const { selected, isAllSelected, handleSelect, clearSelected, selectAll } =
- useSelected(sources);
-
- const {
- isLoading: isDeleteLoading,
- deleteItems: handleDeleteSources,
- deletionError,
- clearDeletionError,
- } = useDeleteItems(
- useCallback(
- () =>
- Promise.all(
- selected.map(({ id: sourceId }) =>
- InventorySourcesAPI.destroy(sourceId)
- ),
- []
- ),
- [selected]
- ),
- {
- fetchItems: fetchSources,
- allItemsSelected: isAllSelected,
- qsConfig: QS_CONFIG,
- }
- );
- const { error: syncError, dismissError } = useDismissableError(syncAllError);
-
- const deleteRelatedInventoryResources = (resourceId) => [
- InventorySourcesAPI.destroyHosts(resourceId),
- InventorySourcesAPI.destroyGroups(resourceId),
- ];
-
- const {
- isLoading: deleteRelatedResourcesLoading,
- deletionError: deleteRelatedResourcesError,
- deleteItems: handleDeleteRelatedResources,
- } = useDeleteItems(
- useCallback(
- async () =>
- Promise.all(
- selected
- .map(({ id: resourceId }) =>
- deleteRelatedInventoryResources(resourceId)
- )
- .flat()
- ),
- [selected]
- )
- );
-
- const handleDelete = async () => {
- await handleDeleteRelatedResources();
- if (!deleteRelatedResourcesError) {
- await handleDeleteSources();
- }
- clearSelected();
- };
- const canAdd =
- sourceChoicesOptions &&
- Object.prototype.hasOwnProperty.call(sourceChoicesOptions, 'POST');
- const listUrl = `/inventories/${inventoryType}/${id}/sources/`;
-
- const deleteDetailsRequests = relatedResourceDeleteRequests.inventorySource(
- selected[0]?.id
- );
- return (
- <>
- (
- ]
- : []),
-
- }
- />,
- ...(canSyncSources
- ? []
- : []),
- ]}
- />
- )}
- headerRow={
-
- {t`Name`}
- {t`Status`}
- {t`Type`}
- {t`Actions`}
-
- }
- renderRow={(inventorySource, index) => {
- const label = sourceChoices.find(
- ([scMatch]) => inventorySource.source === scMatch
- );
- return (
- handleSelect(inventorySource)}
- label={label[1]}
- detailUrl={`${listUrl}${inventorySource.id}`}
- isSelected={selected.some((row) => row.id === inventorySource.id)}
- rowIndex={index}
- />
- );
- }}
- />
- {syncError && (
-
- {t`Failed to sync some or all inventory sources.`}
-
-
- )}
-
- {(deletionError || deleteRelatedResourcesError) && (
-
- {t`Failed to delete one or more inventory sources.`}
-
-
- )}
- >
- );
-}
-export default InventorySourceList;
diff --git a/awx/ui/src/screens/Inventory/InventorySources/InventorySourceList.test.js b/awx/ui/src/screens/Inventory/InventorySources/InventorySourceList.test.js
deleted file mode 100644
index c609614940ad..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySources/InventorySourceList.test.js
+++ /dev/null
@@ -1,379 +0,0 @@
-import React from 'react';
-import { Route } from 'react-router-dom';
-import { createMemoryHistory } from 'history';
-import { act } from 'react-dom/test-utils';
-import {
- InventoriesAPI,
- InventorySourcesAPI,
- WorkflowJobTemplateNodesAPI,
-} from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import InventorySourceList from './InventorySourceList';
-
-jest.mock('../../../api/models/InventorySources');
-jest.mock('../../../api/models/Inventories');
-jest.mock('../../../api/models/InventoryUpdates');
-jest.mock('../../../api/models/WorkflowJobTemplateNodes');
-
-const sources = {
- data: {
- results: [
- {
- id: 1,
- name: 'Source Foo',
- status: '',
- source: 'ec2',
- url: '/api/v2/inventory_sources/56/',
- summary_fields: {
- user_capabilities: {
- edit: true,
- delete: true,
- start: true,
- schedule: true,
- },
- },
- },
- {
- id: 2,
- name: 'Source Bar',
- status: '',
- source: 'scm',
- url: '/api/v2/inventory_sources/57/',
- summary_fields: {
- user_capabilities: {
- edit: true,
- delete: true,
- start: true,
- schedule: true,
- },
- },
- },
- ],
- count: 1,
- },
-};
-
-describe('', () => {
- let wrapper;
- let history;
- let debug;
-
- beforeEach(async () => {
- debug = global.console.debug; // eslint-disable-line prefer-destructuring
- global.console.debug = () => {};
- InventoriesAPI.readSources.mockResolvedValue(sources);
- InventoriesAPI.updateSources.mockResolvedValue({
- data: [{ inventory_source: 1 }],
- });
- InventorySourcesAPI.readGroups.mockResolvedValue({ data: { count: 0 } });
- InventorySourcesAPI.readHosts.mockResolvedValue({ data: { count: 0 } });
- WorkflowJobTemplateNodesAPI.read.mockResolvedValue({ data: { count: 0 } });
- InventorySourcesAPI.readOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {
- source: {
- choices: [
- ['scm', 'SCM'],
- ['ec2', 'EC2'],
- ],
- },
- },
- POST: {},
- },
- },
- });
- history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/1/sources'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- ,
- {
- context: {
- router: {
- history,
- route: {
- location: { search: '' },
- match: { params: { id: 1 } },
- },
- },
- },
- }
- );
- });
- wrapper.update();
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- global.console.debug = debug;
- });
-
- test('api calls should be made on mount', async () => {
- expect(InventoriesAPI.readSources).toHaveBeenCalledWith('1', {
- order_by: 'name',
- page: 1,
- page_size: 20,
- });
- expect(InventorySourcesAPI.readOptions).toHaveBeenCalled();
- });
-
- test('should have proper number of delete detail requests', async () => {
- expect(
- wrapper.find('ToolbarDeleteButton').prop('deleteDetailsRequests')
- ).toHaveLength(3);
- });
-
- test('source data should render properly', async () => {
- expect(wrapper.find('InventorySourceListItem')).toHaveLength(2);
- expect(
- wrapper.find('InventorySourceListItem').first().prop('source')
- ).toEqual(sources.data.results[0]);
- });
-
- test('add button is not disabled and delete button is disabled', async () => {
- const addButton = wrapper.find('ToolbarAddButton').find('Link');
- const deleteButton = wrapper.find('ToolbarDeleteButton').find('Button');
- expect(addButton.prop('aria-disabled')).toBe(false);
- expect(deleteButton.prop('isDisabled')).toBe(true);
- });
-
- test('delete button becomes enabled and properly calls api to delete', async () => {
- const deleteButton = wrapper.find('ToolbarDeleteButton').find('Button');
-
- expect(deleteButton.prop('isDisabled')).toBe(true);
-
- await act(async () =>
- wrapper.find('.pf-c-table__check').first().find('input').prop('onChange')(
- { id: 1 }
- )
- );
- wrapper.update();
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').prop('checked')
- ).toBe(true);
-
- await act(async () =>
- wrapper.find('Button[aria-label="Delete"]').prop('onClick')()
- );
- wrapper.update();
- expect(wrapper.find('AlertModal').length).toBe(1);
-
- await act(async () =>
- wrapper.find('Button[aria-label="confirm delete"]').prop('onClick')()
- );
- expect(InventorySourcesAPI.destroy).toHaveBeenCalledWith(1);
- expect(InventorySourcesAPI.destroyHosts).toHaveBeenCalledWith(1);
- expect(InventorySourcesAPI.destroyGroups).toHaveBeenCalledWith(1);
- });
-
- test('should throw error after deletion failure', async () => {
- InventorySourcesAPI.destroy.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'delete',
- url: '/api/v2/inventory_sources/',
- },
- data: 'An error occurred',
- status: 403,
- },
- })
- );
-
- await act(async () =>
- wrapper.find('.pf-c-table__check').first().find('input').prop('onChange')(
- { id: 1 }
- )
- );
- wrapper.update();
-
- await act(async () =>
- wrapper.find('Button[aria-label="Delete"]').prop('onClick')()
- );
- wrapper.update();
-
- await act(async () =>
- wrapper.find('Button[aria-label="confirm delete"]').prop('onClick')()
- );
- wrapper.update();
- expect(wrapper.find("AlertModal[aria-label='Delete error']").length).toBe(
- 1
- );
- });
-
- test('displays error after unsuccessful read sources fetch', async () => {
- InventorySourcesAPI.readOptions.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'get',
- url: '/api/v2/inventories/inventory_sources/',
- },
- data: 'An error occurred',
- status: 403,
- },
- })
- );
- InventoriesAPI.readSources.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'get',
- url: '/api/v2/inventories/inventory_sources/',
- },
- data: 'An error occurred',
- status: 403,
- },
- })
- );
-
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
-
- expect(wrapper.find('ContentError').length).toBe(1);
- });
-
- test('displays error after unsuccessful read options fetch', async () => {
- InventorySourcesAPI.readOptions.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'options',
- url: '/api/v2/inventory_sources/',
- },
- data: 'An error occurred',
- status: 403,
- },
- })
- );
-
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
-
- expect(wrapper.find('ContentError').length).toBe(1);
- });
-
- test('displays error after unsuccessful sync all button', async () => {
- InventoriesAPI.syncAllSources.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'post',
- url: '/api/v2/inventories/',
- },
- data: 'An error occurred',
- status: 403,
- },
- })
- );
- await act(async () =>
- wrapper.find('Button[aria-label="Sync all"]').prop('onClick')()
- );
- expect(InventoriesAPI.syncAllSources).toBeCalled();
- wrapper.update();
- expect(wrapper.find("AlertModal[aria-label='Sync error']").length).toBe(1);
- });
-
- test('should render sync all button and make api call to start sync for all', async () => {
- const syncAllButton = wrapper.find('Button[aria-label="Sync all"]');
- expect(syncAllButton.length).toBe(1);
- await act(async () => syncAllButton.prop('onClick')());
- expect(InventoriesAPI.syncAllSources).toBeCalled();
- expect(InventoriesAPI.readSources).toBeCalled();
- });
-});
-
-describe(' RBAC testing', () => {
- test('should not render add button', async () => {
- sources.data.results[0].summary_fields.user_capabilities = {
- edit: true,
- delete: true,
- start: true,
- schedule: true,
- };
- InventoriesAPI.readSources.mockResolvedValue(sources);
- InventorySourcesAPI.readOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {
- source: {
- choices: [
- ['scm', 'SCM'],
- ['ec2', 'EC2'],
- ],
- },
- },
- },
- },
- });
- let newWrapper;
- const history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/2/sources'],
- });
- await act(async () => {
- newWrapper = mountWithContexts(
-
-
- ,
- {
- context: {
- router: {
- history,
- route: {
- location: { search: '' },
- match: { params: { id: 2 } },
- },
- },
- },
- }
- );
- });
- newWrapper.update();
- expect(newWrapper.find('ToolbarAddButton').length).toBe(0);
- jest.clearAllMocks();
- });
-
- test('should not render Sync All button', async () => {
- sources.data.results[0].summary_fields.user_capabilities = {
- edit: true,
- delete: true,
- start: false,
- schedule: true,
- };
- InventoriesAPI.readSources.mockResolvedValue(sources);
- let newWrapper;
- const history = createMemoryHistory({
- initialEntries: ['/inventories/inventory/2/sources'],
- });
- await act(async () => {
- newWrapper = mountWithContexts(
-
-
- ,
- {
- context: {
- router: {
- history,
- route: {
- location: { search: '' },
- match: { params: { id: 2 } },
- },
- },
- },
- }
- );
- });
- newWrapper.update();
- expect(newWrapper.find('Button[aria-label="Sync All"]').length).toBe(0);
- jest.clearAllMocks();
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventorySources/InventorySourceListItem.js b/awx/ui/src/screens/Inventory/InventorySources/InventorySourceListItem.js
deleted file mode 100644
index 1c435095b371..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySources/InventorySourceListItem.js
+++ /dev/null
@@ -1,143 +0,0 @@
-import React from 'react';
-import { Link } from 'react-router-dom';
-import { t } from '@lingui/macro';
-import { Button, Tooltip } from '@patternfly/react-core';
-import { Tr, Td } from '@patternfly/react-table';
-import {
- ExclamationTriangleIcon as PFExclamationTriangleIcon,
- PencilAltIcon,
-} from '@patternfly/react-icons';
-import styled from 'styled-components';
-
-import { ActionsTd, ActionItem, TdBreakWord } from 'components/PaginatedTable';
-import StatusLabel from 'components/StatusLabel';
-import JobCancelButton from 'components/JobCancelButton';
-import { formatDateString } from 'util/dates';
-import { isJobRunning } from 'util/jobs';
-import InventorySourceSyncButton from '../shared/InventorySourceSyncButton';
-
-const ExclamationTriangleIcon = styled(PFExclamationTriangleIcon)`
- color: var(--pf-global--warning-color--100);
- margin-left: 18px;
-`;
-
-function InventorySourceListItem({
- source,
- isSelected,
- onSelect,
- detailUrl,
- label,
- rowIndex,
-}) {
- const generateLastJobTooltip = (job) => (
- <>
- {t`MOST RECENT SYNC`}
-
- {t`JOB ID:`} {job.id}
-
-
- {t`STATUS:`} {job.status.toUpperCase()}
-
- {job.finished && (
-
- {t`FINISHED:`} {formatDateString(job.finished)}
-
- )}
- >
- );
-
- const missingExecutionEnvironment =
- source.custom_virtualenv && !source.execution_environment;
-
- let job = null;
-
- if (source.summary_fields?.current_job) {
- job = source.summary_fields.current_job;
- } else if (source.summary_fields?.last_job) {
- job = source.summary_fields.last_job;
- }
-
- return (
-
- |
-
-
- {source.name}
-
- {missingExecutionEnvironment && (
-
-
-
-
-
- )}
-
-
- {job && (
-
-
-
-
-
- )}
- |
- {label} |
-
- {['running', 'pending', 'waiting'].includes(job?.status) ? (
-
- {source.summary_fields?.current_job?.id && (
-
- )}
-
- ) : (
-
-
-
- )}
-
-
-
-
-
- );
-}
-export default InventorySourceListItem;
diff --git a/awx/ui/src/screens/Inventory/InventorySources/InventorySourceListItem.test.js b/awx/ui/src/screens/Inventory/InventorySources/InventorySourceListItem.test.js
deleted file mode 100644
index c1bb876138b3..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySources/InventorySourceListItem.test.js
+++ /dev/null
@@ -1,209 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import InventorySourceListItem from './InventorySourceListItem';
-
-const source = {
- id: 1,
- name: 'Foo',
- source: 'Source Bar',
- summary_fields: {
- user_capabilities: { start: true, edit: true },
- last_job: {
- canceled_on: '2020-04-30T18:56:46.054087Z',
- description: '',
- failed: true,
- finished: '2020-04-30T18:56:46.054031Z',
- id: 664,
- license_error: false,
- name: ' Inventory 1 Org 0 - source 4',
- status: 'canceled',
- },
- },
-};
-describe('', () => {
- let wrapper;
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should mount properly', () => {
- const onSelect = jest.fn();
- wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('InventorySourceListItem').length).toBe(1);
- });
-
- test('all buttons and text fields should render properly', () => {
- const onSelect = jest.fn();
- wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('StatusLabel').length).toBe(1);
- expect(wrapper.find('Link').at(1).prop('to')).toBe('/jobs/inventory/664');
- expect(wrapper.find('.pf-c-table__check').length).toBe(1);
- expect(wrapper.find('Td').at(1).text()).toBe('Foo');
- expect(wrapper.find('Td').at(3).text()).toBe('Source Bar');
- expect(wrapper.find('InventorySourceSyncButton').length).toBe(1);
- expect(wrapper.find('PencilAltIcon').length).toBe(1);
- });
-
- test('item should be checked', () => {
- const onSelect = jest.fn();
- wrapper = mountWithContexts(
-
- );
- wrapper.update();
- expect(wrapper.find('.pf-c-table__check').length).toBe(1);
- expect(wrapper.find('Td').first().prop('select').isSelected).toEqual(true);
- });
-
- test('should not render status icon', () => {
- const onSelect = jest.fn();
- wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('StatusIcon').length).toBe(0);
- });
-
- test('should not render sync buttons', async () => {
- const onSelect = jest.fn();
- wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('InventorySourceSyncButton').length).toBe(0);
- expect(wrapper.find('Button[aria-label="Edit Source"]').length).toBe(1);
- });
-
- test('should not render edit buttons', async () => {
- const onSelect = jest.fn();
- wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('Button[aria-label="Edit Source"]').length).toBe(0);
- expect(wrapper.find('InventorySourceSyncButton').length).toBe(1);
- });
-
- test('should render warning about missing execution environment', () => {
- const onSelect = jest.fn();
- wrapper = mountWithContexts(
-
- );
- expect(
- wrapper.find('.missing-execution-environment').prop('content')
- ).toEqual(
- 'Custom virtual environment /var/lib/awx/env must be replaced by an execution environment.'
- );
- });
-
- test('should render cancel button while job is running', () => {
- const onSelect = jest.fn();
- wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('JobCancelButton').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventorySources/InventorySources.js b/awx/ui/src/screens/Inventory/InventorySources/InventorySources.js
deleted file mode 100644
index c2146dc4c154..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySources/InventorySources.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import React from 'react';
-import { Switch, Route } from 'react-router-dom';
-import { Config } from 'contexts/Config';
-import InventorySource from '../InventorySource';
-import InventorySourceAdd from '../InventorySourceAdd';
-import InventorySourceList from './InventorySourceList';
-
-function InventorySources({ inventory, setBreadcrumb }) {
- return (
-
-
-
-
-
-
- {({ me }) => (
-
- )}
-
-
-
-
-
-
- );
-}
-
-export default InventorySources;
diff --git a/awx/ui/src/screens/Inventory/InventorySources/InventorySources.test.js b/awx/ui/src/screens/Inventory/InventorySources/InventorySources.test.js
deleted file mode 100644
index ee6b0bd75a05..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySources/InventorySources.test.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import InventorySources from './InventorySources';
-
-describe('', () => {
- test('initially renders without crashing', () => {
- const wrapper = shallow();
- expect(wrapper.length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventorySources/index.js b/awx/ui/src/screens/Inventory/InventorySources/index.js
deleted file mode 100644
index 1d73a6f9b1b9..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySources/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventorySources';
diff --git a/awx/ui/src/screens/Inventory/InventorySources/useWsInventorySources.js b/awx/ui/src/screens/Inventory/InventorySources/useWsInventorySources.js
deleted file mode 100644
index 5cf68305e095..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySources/useWsInventorySources.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import { useState, useEffect } from 'react';
-import useWebsocket from 'hooks/useWebsocket';
-
-export default function useWsInventorySources(initialSources) {
- const [sources, setSources] = useState(initialSources);
- const lastMessage = useWebsocket({
- jobs: ['status_changed'],
- control: ['limit_reached_1'],
- });
-
- useEffect(() => {
- setSources(initialSources);
- }, [initialSources]);
-
- useEffect(
- () => {
- if (!lastMessage?.unified_job_id || !lastMessage?.inventory_source_id) {
- return;
- }
-
- const sourceId = lastMessage.inventory_source_id;
- const index = sources.findIndex((s) => s.id === sourceId);
- if (index > -1) {
- setSources(updateSource(sources, index, lastMessage));
- }
- },
- [lastMessage] // eslint-disable-line react-hooks/exhaustive-deps
- );
-
- return sources;
-}
-
-function updateSource(sources, index, message) {
- const source = {
- ...sources[index],
- status: message.status,
- last_updated: message.finished,
- summary_fields: {
- ...sources[index].summary_fields,
- current_job: {
- id: message.unified_job_id,
- status: message.status,
- finished: message.finished,
- },
- },
- };
- return [...sources.slice(0, index), source, ...sources.slice(index + 1)];
-}
diff --git a/awx/ui/src/screens/Inventory/InventorySources/useWsInventorySources.test.js b/awx/ui/src/screens/Inventory/InventorySources/useWsInventorySources.test.js
deleted file mode 100644
index 3243f5bcb6ab..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySources/useWsInventorySources.test.js
+++ /dev/null
@@ -1,124 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import WS from 'jest-websocket-mock';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import useWsInventorySources from './useWsInventorySources';
-
-/*
- Jest mock timers don’t play well with jest-websocket-mock,
- so we'll stub out throttling to resolve immediately
-*/
-jest.mock('../../../hooks/useThrottle', () => ({
- __esModule: true,
- default: jest.fn((val) => val),
-}));
-
-function TestInner() {
- return ;
-}
-function Test({ sources }) {
- const syncedSources = useWsInventorySources(sources);
- return ;
-}
-
-describe('useWsInventorySources hook', () => {
- let debug;
- let wrapper;
- beforeEach(() => {
- debug = global.console.debug; // eslint-disable-line prefer-destructuring
- global.console.debug = () => {};
- });
-
- afterEach(() => {
- global.console.debug = debug;
- });
-
- test('should return sources list', () => {
- const sources = [{ id: 1 }];
- wrapper = mountWithContexts();
-
- expect(wrapper.find('TestInner').prop('sources')).toEqual(sources);
- WS.clean();
- });
-
- test('should establish websocket connection', async () => {
- global.document.cookie = 'csrftoken=abc123';
- const mockServer = new WS('ws://localhost/websocket/');
-
- const sources = [{ id: 1 }];
- await act(async () => {
- wrapper = await mountWithContexts();
- });
-
- await mockServer.connected;
- await expect(mockServer).toReceiveMessage(
- JSON.stringify({
- xrftoken: 'abc123',
- groups: {
- jobs: ['status_changed'],
- control: ['limit_reached_1'],
- },
- })
- );
- WS.clean();
- });
-
- test('should update current job status', async () => {
- global.document.cookie = 'csrftoken=abc123';
- const mockServer = new WS('ws://localhost/websocket/');
-
- const sources = [
- {
- id: 3,
- status: 'running',
- summary_fields: {
- current_job: {
- id: 5,
- status: 'running',
- },
- },
- },
- ];
- await act(async () => {
- wrapper = await mountWithContexts();
- });
-
- await mockServer.connected;
- await expect(mockServer).toReceiveMessage(
- JSON.stringify({
- xrftoken: 'abc123',
- groups: {
- jobs: ['status_changed'],
- control: ['limit_reached_1'],
- },
- })
- );
- act(() => {
- mockServer.send(
- JSON.stringify({
- unified_job_id: 5,
- inventory_source_id: 3,
- type: 'job',
- status: 'successful',
- finished: 'the_time',
- })
- );
- });
- wrapper.update();
-
- const source = wrapper.find('TestInner').prop('sources')[0];
- expect(source).toEqual({
- id: 3,
- status: 'successful',
- last_updated: 'the_time',
- summary_fields: {
- current_job: {
- id: 5,
- status: 'successful',
- finished: 'the_time',
- },
- },
- });
- WS.clean();
- });
-});
diff --git a/awx/ui/src/screens/Inventory/InventorySources/useWsInventorySourcesDetails.js b/awx/ui/src/screens/Inventory/InventorySources/useWsInventorySourcesDetails.js
deleted file mode 100644
index e93f28f58b58..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySources/useWsInventorySourcesDetails.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import { useState, useEffect } from 'react';
-import useWebsocket from 'hooks/useWebsocket';
-
-export default function useWsInventorySourcesDetails(initialSources) {
- const [sources, setSources] = useState(initialSources);
- const lastMessage = useWebsocket({
- jobs: ['status_changed'],
- control: ['limit_reached_1'],
- });
-
- useEffect(() => {
- setSources(initialSources);
- }, [initialSources]);
-
- useEffect(
- () => {
- if (
- !lastMessage?.unified_job_id ||
- !lastMessage?.inventory_source_id ||
- lastMessage.type !== 'inventory_update'
- ) {
- return;
- }
- const updateSource = {
- ...sources,
- summary_fields: {
- ...sources.summary_fields,
- current_job: {
- id: lastMessage.unified_job_id,
- status: lastMessage.status,
- finished: lastMessage.finished,
- },
- },
- };
-
- setSources(updateSource);
- },
- [lastMessage] // eslint-disable-line react-hooks/exhaustive-deps
- );
-
- return sources;
-}
diff --git a/awx/ui/src/screens/Inventory/InventorySources/useWsInventorySourcesDetails.test.js b/awx/ui/src/screens/Inventory/InventorySources/useWsInventorySourcesDetails.test.js
deleted file mode 100644
index 25fb97850b56..000000000000
--- a/awx/ui/src/screens/Inventory/InventorySources/useWsInventorySourcesDetails.test.js
+++ /dev/null
@@ -1,116 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import WS from 'jest-websocket-mock';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import useWsInventorySourceDetails from './useWsInventorySourcesDetails';
-
-function TestInner() {
- return ;
-}
-function Test({ inventorySource }) {
- const synced = useWsInventorySourceDetails(inventorySource);
- return ;
-}
-
-describe('useWsProject', () => {
- let wrapper;
-
- test('should return inventory source detail', async () => {
- const inventorySource = { id: 1 };
- await act(async () => {
- wrapper = await mountWithContexts(
-
- );
- });
-
- expect(wrapper.find('TestInner').prop('inventorySource')).toEqual(
- inventorySource
- );
- WS.clean();
- });
-
- test('should establish websocket connection', async () => {
- global.document.cookie = 'csrftoken=abc123';
- const mockServer = new WS('ws://localhost/websocket/');
-
- const inventorySource = { id: 1 };
- await act(async () => {
- wrapper = await mountWithContexts(
-
- );
- });
-
- await mockServer.connected;
- await expect(mockServer).toReceiveMessage(
- JSON.stringify({
- xrftoken: 'abc123',
- groups: {
- jobs: ['status_changed'],
- control: ['limit_reached_1'],
- },
- })
- );
- WS.clean();
- });
-
- test('should update inventory source status', async () => {
- global.document.cookie = 'csrftoken=abc123';
- const mockServer = new WS('ws://localhost/websocket/');
-
- const inventorySource = {
- id: 1,
- summary_fields: {
- current_job: {
- id: 1,
- status: 'running',
- finished: null,
- },
- },
- };
- await act(async () => {
- wrapper = await mountWithContexts(
-
- );
- });
-
- await mockServer.connected;
- await expect(mockServer).toReceiveMessage(
- JSON.stringify({
- xrftoken: 'abc123',
- groups: {
- jobs: ['status_changed'],
- control: ['limit_reached_1'],
- },
- })
- );
- expect(
- wrapper.find('TestInner').prop('inventorySource').summary_fields
- .current_job.status
- ).toEqual('running');
-
- await act(async () => {
- mockServer.send(
- JSON.stringify({
- group_name: 'jobs',
- inventory_id: 1,
- status: 'pending',
- type: 'inventory_source',
- unified_job_id: 2,
- unified_job_template_id: 1,
- inventory_source_id: 1,
- })
- );
- });
- wrapper.update();
-
- expect(
- wrapper.find('TestInner').prop('inventorySource').summary_fields
- .current_job
- ).toEqual({
- id: 1,
- status: 'running',
- finished: null,
- });
- WS.clean();
- });
-});
diff --git a/awx/ui/src/screens/Inventory/SmartInventory.js b/awx/ui/src/screens/Inventory/SmartInventory.js
deleted file mode 100644
index 952cf5dc3128..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventory.js
+++ /dev/null
@@ -1,189 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { t } from '@lingui/macro';
-import {
- Link,
- Switch,
- Route,
- Redirect,
- useRouteMatch,
- useLocation,
-} from 'react-router-dom';
-import { CaretLeftIcon } from '@patternfly/react-icons';
-import { Card, PageSection } from '@patternfly/react-core';
-
-import useRequest from 'hooks/useRequest';
-import { InventoriesAPI } from 'api';
-
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import JobList from 'components/JobList';
-import { ResourceAccessList } from 'components/ResourceAccessList';
-import RoutedTabs from 'components/RoutedTabs';
-import RelatedTemplateList from 'components/RelatedTemplateList';
-import SmartInventoryDetail from './SmartInventoryDetail';
-import SmartInventoryEdit from './SmartInventoryEdit';
-import SmartInventoryHosts from './SmartInventoryHosts';
-
-function SmartInventory({ setBreadcrumb }) {
- const location = useLocation();
- const match = useRouteMatch('/inventories/smart_inventory/:id');
-
- const {
- result: inventory,
- error: contentError,
- isLoading: hasContentLoading,
- request: fetchInventory,
- } = useRequest(
- useCallback(async () => {
- const { data } = await InventoriesAPI.readDetail(match.params.id);
- return data;
- }, [match.params.id]),
-
- null
- );
-
- useEffect(() => {
- fetchInventory();
- }, [fetchInventory, location.pathname]);
-
- useEffect(() => {
- if (inventory) {
- setBreadcrumb(inventory);
- }
- }, [inventory, setBreadcrumb]);
-
- const tabsArray = [
- {
- name: (
- <>
-
- {t`Back to Inventories`}
- >
- ),
- link: `/inventories`,
- id: 99,
- },
- { name: t`Details`, link: `${match.url}/details`, id: 0 },
- { name: t`Access`, link: `${match.url}/access`, id: 1 },
- { name: t`Hosts`, link: `${match.url}/hosts`, id: 2 },
- {
- name: t`Jobs`,
- link: `${match.url}/jobs`,
- id: 3,
- },
- { name: t`Job Templates`, link: `${match.url}/job_templates`, id: 4 },
- ];
-
- if (hasContentLoading) {
- return (
-
-
-
-
-
- );
- }
-
- if (contentError) {
- return (
-
-
-
- {contentError?.response?.status === 404 && (
-
- {t`Smart Inventory not found.`}{' '}
- {t`View all Inventories.`}
-
- )}
-
-
-
- );
- }
-
- if (inventory?.kind === '') {
- return ;
- }
-
- let showCardHeader = true;
-
- if (['edit', 'hosts/'].some((name) => location.pathname.includes(name))) {
- showCardHeader = false;
- }
-
- return (
-
-
- {showCardHeader && }
-
-
- {inventory && [
-
-
- ,
-
-
- ,
-
-
- ,
-
-
- ,
-
-
- ,
-
-
- ,
-
- {!hasContentLoading && (
-
- {match.params.id && (
-
- {t`View Inventory Details`}
-
- )}
-
- )}
- ,
- ]}
-
-
-
- );
-}
-
-export { SmartInventory as _SmartInventory };
-export default SmartInventory;
diff --git a/awx/ui/src/screens/Inventory/SmartInventory.test.js b/awx/ui/src/screens/Inventory/SmartInventory.test.js
deleted file mode 100644
index 9d5ae971c436..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventory.test.js
+++ /dev/null
@@ -1,92 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { InventoriesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../testUtils/enzymeHelpers';
-import mockSmartInventory from './shared/data.smart_inventory.json';
-import SmartInventory from './SmartInventory';
-
-jest.mock('../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useRouteMatch: () => ({
- url: '/inventories/smart_inventory/1',
- params: { id: 1 },
- }),
-}));
-
-describe('', () => {
- let wrapper;
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('initially renders successfully', async () => {
- InventoriesAPI.readDetail.mockResolvedValue({
- data: mockSmartInventory,
- });
- await act(async () => {
- wrapper = mountWithContexts( {}} />);
- });
- wrapper.update();
- expect(wrapper.find('SmartInventory').length).toBe(1);
- expect(wrapper.find('RoutedTabs li').length).toBe(6);
- });
-
- test('should render expected tabs', async () => {
- const expectedTabs = [
- 'Back to Inventories',
- 'Details',
- 'Access',
- 'Hosts',
- 'Jobs',
- 'Job Templates',
- ];
- await act(async () => {
- wrapper = mountWithContexts( {}} />);
- });
- wrapper.find('RoutedTabs li').forEach((tab, index) => {
- expect(tab.text()).toEqual(expectedTabs[index]);
- });
- });
-
- test('should show content error when api throws an error', async () => {
- const error = new Error();
- error.response = { status: 404 };
- InventoriesAPI.readDetail.mockRejectedValueOnce(error);
- await act(async () => {
- wrapper = mountWithContexts( {}} />);
- });
- expect(InventoriesAPI.readDetail).toHaveBeenCalledTimes(1);
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- expect(wrapper.find('ContentError Title').text()).toEqual('Not Found');
- });
-
- test('should show content error when user attempts to navigate to erroneous route', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/inventories/smart_inventory/1/foobar'],
- });
- await act(async () => {
- wrapper = mountWithContexts( {}} />, {
- context: {
- router: {
- history,
- route: {
- location: history.location,
- match: {
- params: { id: 1 },
- url: '/inventories/smart_inventory/1/foobar',
- path: '/inventories/smart_inventory/1/foobar',
- },
- },
- },
- },
- });
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/SmartInventoryAdd/SmartInventoryAdd.js b/awx/ui/src/screens/Inventory/SmartInventoryAdd/SmartInventoryAdd.js
deleted file mode 100644
index 475cd06ad0e5..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventoryAdd/SmartInventoryAdd.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { useHistory } from 'react-router-dom';
-import { Card, PageSection } from '@patternfly/react-core';
-import { CardBody } from 'components/Card';
-import useRequest from 'hooks/useRequest';
-import { InventoriesAPI } from 'api';
-import SmartInventoryForm from '../shared/SmartInventoryForm';
-import parseHostFilter from '../shared/utils';
-
-function SmartInventoryAdd() {
- const history = useHistory();
-
- const {
- error: submitError,
- request: submitRequest,
- result: inventoryId,
- } = useRequest(
- useCallback(async (values, groupsToAssociate) => {
- const {
- data: { id: invId },
- } = await InventoriesAPI.create(values);
-
- /* eslint-disable no-await-in-loop, no-restricted-syntax */
- // Resolve Promises sequentially to maintain order and avoid race condition
- for (const group of groupsToAssociate) {
- await InventoriesAPI.associateInstanceGroup(invId, group.id);
- }
- /* eslint-enable no-await-in-loop, no-restricted-syntax */
- return invId;
- }, [])
- );
-
- const handleSubmit = async (form) => {
- const modifiedForm = parseHostFilter(form);
-
- const { instance_groups, organization, ...remainingForm } = modifiedForm;
-
- await submitRequest(
- {
- organization: organization?.id,
- ...remainingForm,
- },
- instance_groups
- );
- };
-
- const handleCancel = () => {
- history.push({
- pathname: '/inventories',
- search: '',
- });
- };
-
- useEffect(() => {
- if (inventoryId) {
- history.push({
- pathname: `/inventories/smart_inventory/${inventoryId}/details`,
- search: '',
- });
- }
- }, [inventoryId, history]);
-
- return (
-
-
-
-
-
-
-
- );
-}
-
-export default SmartInventoryAdd;
diff --git a/awx/ui/src/screens/Inventory/SmartInventoryAdd/SmartInventoryAdd.test.js b/awx/ui/src/screens/Inventory/SmartInventoryAdd/SmartInventoryAdd.test.js
deleted file mode 100644
index 8bd30fe00d54..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventoryAdd/SmartInventoryAdd.test.js
+++ /dev/null
@@ -1,180 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { InventoriesAPI, OrganizationsAPI, InstanceGroupsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import SmartInventoryAdd from './SmartInventoryAdd';
-
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useParams: () => ({
- id: 1,
- }),
-}));
-
-jest.mock('../../../api');
-
-const formData = {
- name: 'Mock',
- description: 'Foo',
- organization: { id: 1 },
- kind: 'smart',
- host_filter: 'name__icontains=mock',
- variables: '---',
- instance_groups: [{ id: 2 }],
-};
-
-describe('', () => {
- const history = createMemoryHistory({
- initialEntries: [`/inventories/smart_inventory/add`],
- });
-
- describe('when initialized by users with POST capability', () => {
- let wrapper;
- let consoleError;
-
- beforeAll(() => {
- consoleError = global.console.error;
- global.console.error = jest.fn();
- });
-
- beforeEach(async () => {
- OrganizationsAPI.read.mockResolvedValue({
- data: { results: [], count: 0 },
- });
- InstanceGroupsAPI.read.mockResolvedValue({
- data: { results: [], count: 0 },
- });
- InventoriesAPI.create.mockResolvedValueOnce({ data: { id: 1 } });
- InventoriesAPI.readOptions.mockResolvedValue({
- data: { actions: { POST: true } },
- });
- InstanceGroupsAPI.read.mockResolvedValue({
- data: { results: [], count: 0 },
- });
-
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterAll(() => {
- global.console.error = consoleError;
- jest.clearAllMocks();
- });
-
- test('should enable save button', () => {
- expect(wrapper.find('Button[aria-label="Save"]').prop('isDisabled')).toBe(
- false
- );
- });
-
- test('should post to the api when submit is clicked', async () => {
- await act(async () => {
- wrapper.find('SmartInventoryForm').invoke('onSubmit')(formData);
- });
- const { instance_groups, ...formRequest } = formData;
- expect(InventoriesAPI.create).toHaveBeenCalledTimes(1);
- expect(InventoriesAPI.create).toHaveBeenCalledWith({
- ...formRequest,
- organization: formRequest.organization.id,
- });
- expect(InventoriesAPI.associateInstanceGroup).toHaveBeenCalledTimes(1);
- expect(InventoriesAPI.associateInstanceGroup).toHaveBeenCalledWith(1, 2);
- });
-
- test('should parse host_filter with ansible facts', async () => {
- const modifiedForm = {
- ...formData,
- host_filter:
- 'host_filter=ansible_facts__ansible_env__PYTHONUNBUFFERED="true"',
- };
- await act(async () => {
- wrapper.find('SmartInventoryForm').invoke('onSubmit')(modifiedForm);
- });
- const { instance_groups, ...formRequest } = modifiedForm;
- expect(InventoriesAPI.create).toHaveBeenCalledTimes(1);
- expect(InventoriesAPI.create).toHaveBeenCalledWith({
- ...formRequest,
- organization: formRequest.organization.id,
- host_filter: 'ansible_facts__ansible_env__PYTHONUNBUFFERED="true"',
- });
- expect(InventoriesAPI.associateInstanceGroup).toHaveBeenCalledTimes(1);
- expect(InventoriesAPI.associateInstanceGroup).toHaveBeenCalledWith(1, 2);
- });
-
- test('successful form submission should trigger redirect to details', async () => {
- expect(history.location.pathname).toEqual(
- '/inventories/smart_inventory/1/details'
- );
- });
-
- test('should navigate to inventory list when cancel is clicked', async () => {
- await act(async () => {
- wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
- });
- expect(history.location.pathname).toEqual('/inventories');
- });
-
- test('unsuccessful form submission should show an error message', async () => {
- const error = {
- response: {
- data: { detail: 'An error occurred' },
- },
- };
- InventoriesAPI.create.mockImplementationOnce(() => Promise.reject(error));
- await act(async () => {
- wrapper = mountWithContexts();
- });
- expect(wrapper.find('FormSubmitError').length).toBe(0);
- await act(async () => {
- wrapper.find('SmartInventoryForm').invoke('onSubmit')({});
- });
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(1);
- });
- });
-
- describe('when initialized by users without POST capability', () => {
- let wrapper;
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- beforeEach(async () => {
- OrganizationsAPI.read.mockResolvedValue({
- data: { results: [], count: 0 },
- });
- InstanceGroupsAPI.read.mockResolvedValue({
- data: { results: [], count: 0 },
- });
- InventoriesAPI.create.mockResolvedValueOnce({ data: { id: 1 } });
- InventoriesAPI.readOptions.mockResolvedValue({
- data: { actions: { POST: false } },
- });
- InstanceGroupsAPI.read.mockResolvedValue({
- data: { results: [], count: 0 },
- });
-
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- test('should disable save button', () => {
- expect(wrapper.find('Button[aria-label="Save"]').prop('isDisabled')).toBe(
- true
- );
- });
- });
-});
diff --git a/awx/ui/src/screens/Inventory/SmartInventoryAdd/index.js b/awx/ui/src/screens/Inventory/SmartInventoryAdd/index.js
deleted file mode 100644
index 01155c332134..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventoryAdd/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './SmartInventoryAdd';
diff --git a/awx/ui/src/screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js b/awx/ui/src/screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js
deleted file mode 100644
index f738ed96c957..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.js
+++ /dev/null
@@ -1,182 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { Link, useHistory } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { Button, Label } from '@patternfly/react-core';
-
-import { Inventory } from 'types';
-import { InventoriesAPI, UnifiedJobsAPI } from 'api';
-import useRequest, { useDismissableError } from 'hooks/useRequest';
-
-import AlertModal from 'components/AlertModal';
-import { CardBody, CardActionsRow } from 'components/Card';
-import { VariablesDetail } from 'components/CodeEditor';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import DeleteButton from 'components/DeleteButton';
-import { DetailList, Detail, UserDateDetail } from 'components/DetailList';
-import ErrorDetail from 'components/ErrorDetail';
-import Sparkline from 'components/Sparkline';
-import InstanceGroupLabels from 'components/InstanceGroupLabels';
-
-function SmartInventoryDetail({ inventory }) {
- const history = useHistory();
- const {
- created,
- description,
- host_filter,
- id,
- modified,
- name,
- total_hosts,
- variables,
- summary_fields: {
- created_by,
- modified_by,
- organization,
- user_capabilities,
- },
- } = inventory;
-
- const {
- error: contentError,
- isLoading: hasContentLoading,
- request: fetchData,
- result: { recentJobs, instanceGroups },
- } = useRequest(
- useCallback(async () => {
- const params = {
- or__job__inventory: id,
- or__workflowjob__inventory: id,
- order_by: '-finished',
- page_size: 10,
- };
- const [{ data: jobData }, { data: igData }] = await Promise.all([
- UnifiedJobsAPI.read(params),
- InventoriesAPI.readInstanceGroups(id),
- ]);
- return {
- recentJobs: jobData.results,
- instanceGroups: igData.results,
- };
- }, [id]),
- {
- recentJobs: [],
- instanceGroups: [],
- }
- );
-
- useEffect(() => {
- fetchData();
- }, [fetchData]);
-
- const {
- error: deleteError,
- isLoading,
- request: handleDelete,
- } = useRequest(
- useCallback(async () => {
- await InventoriesAPI.destroy(id);
- history.push(`/inventories`);
- }, [id, history])
- );
-
- const { error, dismissError } = useDismissableError(deleteError);
-
- if (hasContentLoading) {
- return ;
- }
-
- if (contentError) {
- return ;
- }
-
- return (
- <>
-
-
-
- }
- isEmpty={recentJobs.length === 0}
- />
-
-
-
- {organization.name}
-
- }
- />
- {host_filter}}
- />
-
- }
- isEmpty={instanceGroups.length === 0}
- />
-
-
-
-
-
- {user_capabilities?.edit && (
-
- )}
- {user_capabilities?.delete && (
-
- {t`Delete`}
-
- )}
-
-
- {error && (
-
- {t`Failed to delete smart inventory.`}
-
-
- )}
- >
- );
-}
-
-SmartInventoryDetail.propTypes = {
- inventory: Inventory.isRequired,
-};
-
-export default SmartInventoryDetail;
diff --git a/awx/ui/src/screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.test.js b/awx/ui/src/screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.test.js
deleted file mode 100644
index e5d07fa5eb4b..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventoryDetail/SmartInventoryDetail.test.js
+++ /dev/null
@@ -1,187 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { InventoriesAPI, UnifiedJobsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import SmartInventoryDetail from './SmartInventoryDetail';
-
-import mockSmartInventory from '../shared/data.smart_inventory.json';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
-
- describe('User has edit permissions', () => {
- beforeEach(async () => {
- UnifiedJobsAPI.read.mockResolvedValue({
- data: {
- results: [
- {
- id: 1,
- name: 'job 1',
- type: 'job',
- status: 'successful',
- },
- ],
- },
- });
- InventoriesAPI.readInstanceGroups.mockResolvedValue({
- data: {
- results: [{ id: 1, name: 'mock instance group' }],
- },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('should render Details', async () => {
- function assertDetail(label, value) {
- expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
- expect(wrapper.find(`Detail[label="${label}"] dd`).text()).toBe(value);
- }
-
- assertDetail('Name', 'Smart Inv');
- assertDetail('Description', 'smart inv description');
- assertDetail('Type', 'Smart inventory');
- assertDetail('Organization', 'Default');
- assertDetail('Smart host filter', 'name__icontains=local');
- assertDetail('Instance groups', 'mock instance group');
- assertDetail('Total hosts', '2');
- expect(wrapper.find(`Detail[label="Activity"] Sparkline`)).toHaveLength(
- 1
- );
- const vars = wrapper.find('VariablesDetail');
- expect(vars).toHaveLength(1);
- expect(vars.prop('value')).toEqual(mockSmartInventory.variables);
- const dates = wrapper.find('UserDateDetail');
- expect(dates).toHaveLength(2);
- expect(dates.at(0).prop('date')).toEqual(mockSmartInventory.created);
- expect(dates.at(1).prop('date')).toEqual(mockSmartInventory.modified);
- });
-
- test('should show edit button for users with edit permission', () => {
- const editButton = wrapper.find('Button[aria-label="edit"]');
- expect(editButton.text()).toEqual('Edit');
- expect(editButton.prop('to')).toBe(
- `/inventories/smart_inventory/${mockSmartInventory.id}/edit`
- );
- });
-
- test('expected api calls are made on initial render', () => {
- expect(InventoriesAPI.readInstanceGroups).toHaveBeenCalledTimes(1);
- expect(UnifiedJobsAPI.read).toHaveBeenCalledTimes(1);
- });
-
- test('expected api call is made for delete', async () => {
- expect(InventoriesAPI.destroy).toHaveBeenCalledTimes(0);
- await act(async () => {
- wrapper.find('DeleteButton').invoke('onConfirm')();
- });
- expect(InventoriesAPI.destroy).toHaveBeenCalledTimes(1);
- });
-
- test('Error dialog shown for failed deletion', async () => {
- InventoriesAPI.destroy.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- wrapper.find('DeleteButton').invoke('onConfirm')();
- });
- await waitForElement(
- wrapper,
- 'Modal[title="Error!"]',
- (el) => el.length === 1
- );
- await act(async () => {
- wrapper.find('Modal[title="Error!"]').invoke('onClose')();
- });
- await waitForElement(
- wrapper,
- 'Modal[title="Error!"]',
- (el) => el.length === 0
- );
- });
-
- test('should not load Activity', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- const activity_detail = wrapper.find(`Detail[label="Activity"]`).at(0);
- expect(activity_detail.prop('isEmpty')).toEqual(true);
- });
-
- test('should not load Instance Groups', async () => {
- InventoriesAPI.readInstanceGroups.mockResolvedValue({
- data: {
- results: [],
- },
- });
-
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
- const instance_groups_detail = wrapper
- .find(`Detail[label="Instance groups"]`)
- .at(0);
- expect(instance_groups_detail.prop('isEmpty')).toEqual(true);
- });
- });
-
- describe('User has read-only permissions', () => {
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should hide edit button for users without edit permission', async () => {
- const readOnlySmartInv = { ...mockSmartInventory };
- readOnlySmartInv.summary_fields.user_capabilities.edit = false;
-
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('Button[aria-label="edit"]').length).toBe(0);
- });
-
- test('should show content error when jobs request fails', async () => {
- UnifiedJobsAPI.read.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
- expect(UnifiedJobsAPI.read).toHaveBeenCalledTimes(0);
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(UnifiedJobsAPI.read).toHaveBeenCalledTimes(1);
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- expect(wrapper.find('ContentError Title').text()).toEqual(
- 'Something went wrong...'
- );
- });
- });
-});
diff --git a/awx/ui/src/screens/Inventory/SmartInventoryDetail/index.js b/awx/ui/src/screens/Inventory/SmartInventoryDetail/index.js
deleted file mode 100644
index 48a9225181ef..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventoryDetail/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './SmartInventoryDetail';
diff --git a/awx/ui/src/screens/Inventory/SmartInventoryEdit/SmartInventoryEdit.js b/awx/ui/src/screens/Inventory/SmartInventoryEdit/SmartInventoryEdit.js
deleted file mode 100644
index 81e3ea480f28..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventoryEdit/SmartInventoryEdit.js
+++ /dev/null
@@ -1,109 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { useHistory } from 'react-router-dom';
-import { Inventory } from 'types';
-import useRequest from 'hooks/useRequest';
-import { InventoriesAPI } from 'api';
-import { CardBody } from 'components/Card';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import SmartInventoryForm from '../shared/SmartInventoryForm';
-import parseHostFilter from '../shared/utils';
-
-function SmartInventoryEdit({ inventory }) {
- const history = useHistory();
- const detailsUrl = `/inventories/smart_inventory/${inventory.id}/details`;
-
- const {
- error: contentError,
- isLoading: hasContentLoading,
- request: fetchInstanceGroups,
- result: initialInstanceGroups,
- } = useRequest(
- useCallback(async () => {
- const {
- data: { results },
- } = await InventoriesAPI.readInstanceGroups(inventory.id);
- return results;
- }, [inventory.id]),
- []
- );
-
- useEffect(() => {
- fetchInstanceGroups();
- }, [fetchInstanceGroups]);
-
- const {
- error: submitError,
- request: submitRequest,
- result: submitResult,
- } = useRequest(
- useCallback(
- async (values, groupsToAssociate, groupsToDisassociate) => {
- const { data } = await InventoriesAPI.update(inventory.id, values);
- await InventoriesAPI.orderInstanceGroups(
- inventory.id,
- groupsToAssociate,
- groupsToDisassociate
- );
- return data;
- },
- [inventory.id]
- )
- );
-
- useEffect(() => {
- if (submitResult) {
- history.push({
- pathname: detailsUrl,
- search: '',
- });
- }
- }, [submitResult, detailsUrl, history]);
-
- const handleSubmit = async (form) => {
- const modifiedForm = parseHostFilter(form);
- const { instance_groups, organization, ...remainingForm } = modifiedForm;
-
- await submitRequest(
- {
- organization: organization?.id,
- ...remainingForm,
- },
- instance_groups,
- initialInstanceGroups
- );
- };
-
- const handleCancel = () => {
- history.push({
- pathname: detailsUrl,
- search: '',
- });
- };
-
- if (hasContentLoading) {
- return ;
- }
-
- if (contentError) {
- return ;
- }
-
- return (
-
-
-
- );
-}
-
-SmartInventoryEdit.propTypes = {
- inventory: Inventory.isRequired,
-};
-
-export default SmartInventoryEdit;
diff --git a/awx/ui/src/screens/Inventory/SmartInventoryEdit/SmartInventoryEdit.test.js b/awx/ui/src/screens/Inventory/SmartInventoryEdit/SmartInventoryEdit.test.js
deleted file mode 100644
index 1c641c88a9d8..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventoryEdit/SmartInventoryEdit.test.js
+++ /dev/null
@@ -1,167 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { InventoriesAPI, OrganizationsAPI, InstanceGroupsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import SmartInventoryEdit from './SmartInventoryEdit';
-import mockSmartInventory from '../shared/data.smart_inventory.json';
-
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useParams: () => ({
- id: 2,
- }),
-}));
-jest.mock('../../../api');
-
-const mockSmartInv = {
- ...mockSmartInventory,
- organization: {
- id: mockSmartInventory.organization,
- },
-};
-
-describe('', () => {
- let wrapper;
-
- const history = createMemoryHistory({
- initialEntries: [`/inventories/smart_inventory/${mockSmartInv.id}/edit`],
- });
-
- beforeEach(async () => {
- OrganizationsAPI.read.mockResolvedValue({
- data: { results: [], count: 0 },
- });
- InstanceGroupsAPI.read.mockResolvedValue({
- data: { results: [], count: 0 },
- });
- InventoriesAPI.associateInstanceGroup.mockResolvedValue();
- InventoriesAPI.disassociateInstanceGroup.mockResolvedValue();
- InventoriesAPI.update.mockResolvedValue({ data: mockSmartInv });
- InventoriesAPI.readOptions.mockResolvedValue({
- data: { actions: { POST: true } },
- });
- InventoriesAPI.readInstanceGroups.mockResolvedValue({
- data: {
- count: 0,
- results: [
- { id: 10, name: 'instance-group-10' },
- { id: 20, name: 'instance-group-20' },
- ],
- },
- });
-
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('should render CodeEditor field', () => {
- expect(wrapper.find('CodeEditor').prop('value')).toEqual('---');
- });
-
- test('should fetch related instance groups on initial render', async () => {
- expect(InventoriesAPI.readInstanceGroups).toHaveBeenCalledTimes(1);
- });
-
- test('save button should be enabled for users with POST capability', () => {
- expect(wrapper.find('Button[aria-label="Save"]').prop('isDisabled')).toBe(
- false
- );
- });
-
- test('should post to the api when submit is clicked', async () => {
- expect(InventoriesAPI.update).toHaveBeenCalledTimes(0);
- expect(InventoriesAPI.associateInstanceGroup).toHaveBeenCalledTimes(0);
- expect(InventoriesAPI.disassociateInstanceGroup).toHaveBeenCalledTimes(0);
- await act(async () => {
- wrapper.find('SmartInventoryForm').invoke('onSubmit')({
- ...mockSmartInv,
- instance_groups: [
- { id: 10, name: 'instance-group-10' },
- { id: 30, name: 'instance-group-30' },
- ],
- });
- });
- expect(InventoriesAPI.update).toHaveBeenCalledTimes(1);
- expect(InventoriesAPI.orderInstanceGroups).toHaveBeenCalledTimes(1);
- });
-
- test('successful form submission should trigger redirect to details', async () => {
- expect(wrapper.find('FormSubmitError').length).toBe(0);
- expect(history.location.pathname).toEqual(
- '/inventories/smart_inventory/2/details'
- );
- });
-
- test('should navigate to inventory details when cancel is clicked', async () => {
- await act(async () => {
- wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
- });
- expect(history.location.pathname).toEqual(
- '/inventories/smart_inventory/2/details'
- );
- });
-
- test('unsuccessful form submission should show an error message', async () => {
- const error = {
- response: {
- data: { detail: 'An error occurred' },
- },
- };
- InventoriesAPI.update.mockImplementationOnce(() => Promise.reject(error));
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('FormSubmitError').length).toBe(0);
- await act(async () => {
- wrapper.find('SmartInventoryForm').invoke('onSubmit')({});
- });
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(1);
- });
-
- test('should throw content error', async () => {
- expect(wrapper.find('ContentError').length).toBe(0);
- InventoriesAPI.readInstanceGroups.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
- expect(wrapper.find('ContentError').length).toBe(1);
- });
-
- test('save button should be disabled for users without POST capability', async () => {
- InventoriesAPI.readOptions.mockResolvedValue({
- data: { actions: { POST: false } },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('Button[aria-label="Save"]').prop('isDisabled')).toBe(
- true
- );
- });
-});
diff --git a/awx/ui/src/screens/Inventory/SmartInventoryEdit/index.js b/awx/ui/src/screens/Inventory/SmartInventoryEdit/index.js
deleted file mode 100644
index 6fbd0d3bde44..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventoryEdit/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './SmartInventoryEdit';
diff --git a/awx/ui/src/screens/Inventory/SmartInventoryHost/SmartInventoryHost.js b/awx/ui/src/screens/Inventory/SmartInventoryHost/SmartInventoryHost.js
deleted file mode 100644
index 62de1ecf5cb2..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventoryHost/SmartInventoryHost.js
+++ /dev/null
@@ -1,95 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-
-import { t } from '@lingui/macro';
-import { Link, Redirect, Route, Switch, useRouteMatch } from 'react-router-dom';
-import { CaretLeftIcon } from '@patternfly/react-icons';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import RoutedTabs from 'components/RoutedTabs';
-import useRequest from 'hooks/useRequest';
-import { InventoriesAPI } from 'api';
-import SmartInventoryHostDetail from '../SmartInventoryHostDetail';
-
-function SmartInventoryHost({ inventory, setBreadcrumb }) {
- const { params, path, url } = useRouteMatch(
- '/inventories/smart_inventory/:id/hosts/:hostId'
- );
-
- const {
- result: host,
- error,
- isLoading,
- request: fetchHost,
- } = useRequest(
- useCallback(async () => {
- const response = await InventoriesAPI.readHostDetail(
- inventory.id,
- params.hostId
- );
- return response;
- }, [inventory.id, params.hostId]),
- null
- );
-
- useEffect(() => {
- fetchHost();
- }, [fetchHost]);
-
- useEffect(() => {
- if (inventory && host) {
- setBreadcrumb(inventory, host);
- }
- }, [inventory, host, setBreadcrumb]);
-
- if (error) {
- return ;
- }
-
- const tabsArray = [
- {
- name: (
- <>
-
- {t`Back to Hosts`}
- >
- ),
- link: `/inventories/smart_inventory/${inventory.id}/hosts`,
- id: 0,
- },
- {
- name: t`Details`,
- link: `${url}/details`,
- id: 1,
- },
- ];
-
- return (
- <>
-
-
- {isLoading && }
-
- {!isLoading && host && (
-
-
-
-
-
-
-
-
- {t`View smart inventory host details`}
-
-
-
-
- )}
- >
- );
-}
-
-export default SmartInventoryHost;
diff --git a/awx/ui/src/screens/Inventory/SmartInventoryHost/SmartInventoryHost.test.js b/awx/ui/src/screens/Inventory/SmartInventoryHost/SmartInventoryHost.test.js
deleted file mode 100644
index d3f01bd85e69..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventoryHost/SmartInventoryHost.test.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { InventoriesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import mockHost from '../shared/data.host.json';
-import SmartInventoryHost from './SmartInventoryHost';
-
-jest.mock('../../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useRouteMatch: () => ({
- params: { id: 1234, hostId: 2 },
- path: '/inventories/smart_inventory/:id/hosts/:hostId',
- url: '/inventories/smart_inventory/1234/hosts/2',
- }),
-}));
-
-const mockSmartInventory = {
- id: 1234,
- name: 'Mock Smart Inventory',
-};
-
-describe('', () => {
- let wrapper;
- let history;
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should render expected tabs', async () => {
- InventoriesAPI.readHostDetail.mockResolvedValue(mockHost);
- await act(async () => {
- wrapper = mountWithContexts(
- {}}
- />
- );
- });
-
- const expectedTabs = ['Back to Hosts', 'Details'];
-
- expect(wrapper.find('RoutedTabs li').length).toBe(2);
- wrapper.find('RoutedTabs li').forEach((tab, index) => {
- expect(tab.text()).toEqual(expectedTabs[index]);
- });
- });
-
- test('should show content error when api throws error on initial render', async () => {
- InventoriesAPI.readHostDetail.mockRejectedValueOnce(new Error());
- await act(async () => {
- wrapper = mountWithContexts(
- {}}
- />
- );
- });
-
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- expect(wrapper.find('ContentError Title').text()).toEqual(
- 'Something went wrong...'
- );
- });
-
- test('should show content error when user attempts to navigate to erroneous route', async () => {
- InventoriesAPI.readHostDetail.mockResolvedValue(mockHost);
- history = createMemoryHistory({
- initialEntries: ['/inventories/smart_inventory/1/hosts/1/foobar'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
- {}}
- />,
- { context: { router: { history } } }
- );
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- expect(wrapper.find('ContentError Title').text()).toEqual('Not Found');
- });
-});
diff --git a/awx/ui/src/screens/Inventory/SmartInventoryHost/index.js b/awx/ui/src/screens/Inventory/SmartInventoryHost/index.js
deleted file mode 100644
index 7e634beb10ce..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventoryHost/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './SmartInventoryHost';
diff --git a/awx/ui/src/screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js b/awx/ui/src/screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js
deleted file mode 100644
index 27af396135e3..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import React from 'react';
-import { Link } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { Host } from 'types';
-import { CardBody } from 'components/Card';
-import { Detail, DetailList, UserDateDetail } from 'components/DetailList';
-import Sparkline from 'components/Sparkline';
-import { VariablesDetail } from 'components/CodeEditor';
-
-function SmartInventoryHostDetail({ host }) {
- const {
- created,
- description,
- enabled,
- modified,
- name,
- variables,
- summary_fields: { inventory, recent_jobs, created_by, modified_by },
- } = host;
-
- const recentPlaybookJobs = recent_jobs?.map((job) => ({
- ...job,
- type: 'job',
- }));
-
- return (
-
-
-
- }
- isEmpty={recentPlaybookJobs?.length === 0}
- />
-
-
- {inventory?.name}
-
- }
- />
-
-
-
-
-
-
- );
-}
-
-SmartInventoryHostDetail.propTypes = {
- host: Host.isRequired,
-};
-
-export default SmartInventoryHostDetail;
diff --git a/awx/ui/src/screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.test.js b/awx/ui/src/screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.test.js
deleted file mode 100644
index 93a6092b8076..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventoryHostDetail/SmartInventoryHostDetail.test.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import SmartInventoryHostDetail from './SmartInventoryHostDetail';
-import mockHost from '../shared/data.host.json';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
-
- beforeAll(() => {
- wrapper = mountWithContexts();
- });
-
- test('should render Details', () => {
- function assertDetail(label, value) {
- expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
- expect(wrapper.find(`Detail[label="${label}"] dd`).text()).toBe(value);
- }
-
- assertDetail('Name', 'localhost');
- assertDetail('Description', 'localhost description');
- assertDetail('Inventory', 'Mikes Inventory');
- assertDetail('Enabled', 'On');
- assertDetail('Created', '10/28/2019, 9:26:54 PM');
- assertDetail('Last modified', '10/29/2019, 8:18:41 PM');
- expect(wrapper.find('Detail[label="Activity"] Sparkline')).toHaveLength(1);
- expect(wrapper.find('VariablesDetail')).toHaveLength(1);
- });
-
- test('should not load Activity', () => {
- wrapper = mountWithContexts(
-
- );
- const activity_detail = wrapper.find(`Detail[label="Activity"]`).at(0);
- expect(activity_detail.prop('isEmpty')).toEqual(true);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/SmartInventoryHostDetail/index.js b/awx/ui/src/screens/Inventory/SmartInventoryHostDetail/index.js
deleted file mode 100644
index 4c166ddc0167..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventoryHostDetail/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './SmartInventoryHostDetail';
diff --git a/awx/ui/src/screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js b/awx/ui/src/screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js
deleted file mode 100644
index 747e7bd0585d..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.js
+++ /dev/null
@@ -1,134 +0,0 @@
-import React, { useEffect, useCallback, useState } from 'react';
-import { useLocation } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import DataListToolbar from 'components/DataListToolbar';
-import PaginatedTable, {
- HeaderRow,
- HeaderCell,
-} from 'components/PaginatedTable';
-import useRequest from 'hooks/useRequest';
-import useSelected from 'hooks/useSelected';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import { InventoriesAPI } from 'api';
-import { Inventory } from 'types';
-import AdHocCommands from 'components/AdHocCommands/AdHocCommands';
-import SmartInventoryHostListItem from './SmartInventoryHostListItem';
-
-const QS_CONFIG = getQSConfig('host', {
- page: 1,
- page_size: 20,
- order_by: 'name',
-});
-
-function SmartInventoryHostList({ inventory }) {
- const location = useLocation();
- const [isAdHocLaunchLoading, setIsAdHocLaunchLoading] = useState(false);
- const {
- result: { hosts, count, moduleOptions },
- error: contentError,
- isLoading,
- request: fetchHosts,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
- const [
- {
- data: { results, count: hostCount },
- },
- adHocOptions,
- ] = await Promise.all([
- InventoriesAPI.readHosts(inventory.id, params),
- InventoriesAPI.readAdHocOptions(inventory.id),
- ]);
-
- return {
- hosts: results,
- count: hostCount,
- moduleOptions: adHocOptions.data.actions.GET.module_name.choices,
- };
- }, [location.search, inventory.id]),
- {
- hosts: [],
- count: 0,
- moduleOptions: [],
- }
- );
-
- const { selected, isAllSelected, handleSelect, clearSelected, selectAll } =
- useSelected(hosts);
-
- useEffect(() => {
- fetchHosts();
- }, [fetchHosts]);
-
- return (
- (
- 0}
- onLaunchLoading={setIsAdHocLaunchLoading}
- moduleOptions={moduleOptions}
- />,
- ]
- : []
- }
- />
- )}
- headerRow={
-
- {t`Name`}
- {t`Recent jobs`}
- {t`Inventory`}
-
- }
- renderRow={(host, index) => (
- row.id === host.id)}
- onSelect={() => handleSelect(host)}
- rowIndex={index}
- />
- )}
- />
- );
-}
-
-SmartInventoryHostList.propTypes = {
- inventory: Inventory.isRequired,
-};
-
-export default SmartInventoryHostList;
diff --git a/awx/ui/src/screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.test.js b/awx/ui/src/screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.test.js
deleted file mode 100644
index 0b8798183619..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventoryHosts/SmartInventoryHostList.test.js
+++ /dev/null
@@ -1,95 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { InventoriesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import SmartInventoryHostList from './SmartInventoryHostList';
-import mockInventory from '../shared/data.inventory.json';
-import mockHosts from '../shared/data.hosts.json';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
- const clonedInventory = {
- ...mockInventory,
- summary_fields: {
- ...mockInventory.summary_fields,
- user_capabilities: {
- ...mockInventory.summary_fields.user_capabilities,
- },
- },
- };
-
- beforeEach(async () => {
- InventoriesAPI.readHosts.mockResolvedValue({
- data: mockHosts,
- });
- InventoriesAPI.readAdHocOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {
- module_name: {
- choices: [
- ['command', 'command'],
- ['shell', 'shell'],
- ],
- },
- },
- POST: {},
- },
- },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('initially renders successfully', () => {
- expect(wrapper.find('SmartInventoryHostList').length).toBe(1);
- });
-
- test('should fetch hosts from api and render them in the list', () => {
- expect(InventoriesAPI.readHosts).toHaveBeenCalled();
- expect(wrapper.find('SmartInventoryHostListItem').length).toBe(3);
- });
-
- test('should select and deselect all items', async () => {
- expect.assertions(6);
- act(() => {
- wrapper.find('DataListToolbar').invoke('onSelectAll')(true);
- });
- wrapper.update();
- wrapper.find('.pf-c-table__check input').forEach((el) => {
- expect(el.props().checked).toEqual(true);
- });
- act(() => {
- wrapper.find('DataListToolbar').invoke('onSelectAll')(false);
- });
- wrapper.update();
- wrapper.find('.pf-c-table__check input').forEach((el) => {
- expect(el.props().checked).toEqual(false);
- });
- });
-
- test('should show content error when api throws an error', async () => {
- InventoriesAPI.readHosts.mockImplementation(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js b/awx/ui/src/screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js
deleted file mode 100644
index ae5fe8aab38a..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import React from 'react';
-import { Link } from 'react-router-dom';
-import { string, bool, func } from 'prop-types';
-
-import { t } from '@lingui/macro';
-import 'styled-components/macro';
-
-import { Tr, Td } from '@patternfly/react-table';
-import Sparkline from 'components/Sparkline';
-import { Host } from 'types';
-
-function SmartInventoryHostListItem({
- detailUrl,
- host,
- isSelected,
- onSelect,
- rowIndex,
-}) {
- const recentPlaybookJobs = host.summary_fields.recent_jobs.map((job) => ({
- ...job,
- type: 'job',
- }));
-
- return (
-
- |
-
-
- {host.name}
-
- |
-
-
- |
-
-
- {host.summary_fields.inventory.name}
-
- |
-
- );
-}
-
-SmartInventoryHostListItem.propTypes = {
- detailUrl: string.isRequired,
- host: Host.isRequired,
- isSelected: bool.isRequired,
- onSelect: func.isRequired,
-};
-
-export default SmartInventoryHostListItem;
diff --git a/awx/ui/src/screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.test.js b/awx/ui/src/screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.test.js
deleted file mode 100644
index b3d26782cabe..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventoryHosts/SmartInventoryHostListItem.test.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import SmartInventoryHostListItem from './SmartInventoryHostListItem';
-
-const mockHost = {
- id: 2,
- name: 'Host Two',
- url: '/api/v2/hosts/2',
- inventory: 1,
- summary_fields: {
- inventory: {
- id: 1,
- name: 'Inv 1',
- },
- user_capabilities: {
- edit: true,
- },
- recent_jobs: [],
- },
-};
-
-describe('', () => {
- let wrapper;
-
- beforeEach(() => {
- wrapper = mountWithContexts(
-
- );
- });
-
- test('should render expected row cells', () => {
- const cells = wrapper.find('Td');
- expect(cells).toHaveLength(4);
- expect(cells.at(1).text()).toEqual('Host Two');
- expect(cells.at(2).find('Sparkline').length).toEqual(1);
- expect(cells.at(3).text()).toEqual('Inv 1');
- });
-});
diff --git a/awx/ui/src/screens/Inventory/SmartInventoryHosts/SmartInventoryHosts.js b/awx/ui/src/screens/Inventory/SmartInventoryHosts/SmartInventoryHosts.js
deleted file mode 100644
index b1f461eabcc3..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventoryHosts/SmartInventoryHosts.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import React from 'react';
-import { Route, Switch } from 'react-router-dom';
-import { Inventory } from 'types';
-import SmartInventoryHostList from './SmartInventoryHostList';
-import SmartInventoryHost from '../SmartInventoryHost';
-
-function SmartInventoryHosts({ inventory, setBreadcrumb }) {
- return (
-
-
-
-
-
-
-
-
- );
-}
-
-SmartInventoryHosts.propTypes = {
- inventory: Inventory.isRequired,
-};
-
-export default SmartInventoryHosts;
diff --git a/awx/ui/src/screens/Inventory/SmartInventoryHosts/SmartInventoryHosts.test.js b/awx/ui/src/screens/Inventory/SmartInventoryHosts/SmartInventoryHosts.test.js
deleted file mode 100644
index f97b3c73d074..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventoryHosts/SmartInventoryHosts.test.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import SmartInventoryHosts from './SmartInventoryHosts';
-
-jest.mock('../../../api');
-jest.mock('./SmartInventoryHostList', () => {
- const SmartInventoryHostList = () => ;
- return {
- __esModule: true,
- default: SmartInventoryHostList,
- };
-});
-
-describe('', () => {
- test('should render smart inventory host list', () => {
- const history = createMemoryHistory({
- initialEntries: ['/inventories/smart_inventory/1/hosts'],
- });
- const match = {
- path: '/inventories/smart_inventory/:id/hosts',
- url: '/inventories/smart_inventory/1/hosts',
- isExact: true,
- };
- const wrapper = mountWithContexts(
- ,
- {
- context: { router: { history, route: { match } } },
- }
- );
- expect(wrapper.find('SmartInventoryHostList').length).toBe(1);
- expect(wrapper.find('SmartInventoryHostList').prop('inventory')).toEqual({
- id: 1,
- });
- jest.clearAllMocks();
- });
-
- test('should render smart inventory host details', async () => {
- let wrapper;
- const history = createMemoryHistory({
- initialEntries: ['/inventories/smart_inventory/1/hosts/2'],
- });
- const match = {
- path: '/inventories/smart_inventory/:id/hosts/:hostId',
- url: '/inventories/smart_inventory/1/hosts/2',
- isExact: true,
- };
- await act(async () => {
- wrapper = mountWithContexts(
- {}} />,
- {
- context: { router: { history, route: { match } } },
- }
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('SmartInventoryHost').length).toBe(1);
- jest.clearAllMocks();
- });
-});
diff --git a/awx/ui/src/screens/Inventory/SmartInventoryHosts/index.js b/awx/ui/src/screens/Inventory/SmartInventoryHosts/index.js
deleted file mode 100644
index 95af99ffe3fa..000000000000
--- a/awx/ui/src/screens/Inventory/SmartInventoryHosts/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './SmartInventoryHosts';
diff --git a/awx/ui/src/screens/Inventory/index.js b/awx/ui/src/screens/Inventory/index.js
deleted file mode 100644
index 83c495ab5c61..000000000000
--- a/awx/ui/src/screens/Inventory/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default } from './Inventories';
-export { default as parseHostFilter } from './shared/utils';
diff --git a/awx/ui/src/screens/Inventory/shared/Inventory.helptext.js b/awx/ui/src/screens/Inventory/shared/Inventory.helptext.js
deleted file mode 100644
index 685a47f01aa5..000000000000
--- a/awx/ui/src/screens/Inventory/shared/Inventory.helptext.js
+++ /dev/null
@@ -1,201 +0,0 @@
-/* eslint-disable react/destructuring-assignment */
-import React from 'react';
-import { t, Trans } from '@lingui/macro';
-import { Link } from 'react-router-dom';
-
-const ansibleDocUrls = {
- ec2: 'https://docs.ansible.com/ansible/latest/collections/amazon/aws/aws_ec2_inventory.html',
- azure_rm:
- 'https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_inventory.html',
- controller:
- 'https://docs.ansible.com/ansible/latest/collections/awx/awx/tower_inventory.html',
- gce: 'https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_compute_inventory.html',
- insights:
- 'https://docs.ansible.com/ansible/latest/collections/redhatinsights/insights/insights_inventory.html',
- openstack:
- 'https://docs.ansible.com/ansible/latest/collections/openstack/cloud/openstack_inventory.html',
- satellite6:
- 'https://docs.ansible.com/ansible/latest/collections/theforeman/foreman/foreman_inventory.html',
- rhv: 'https://docs.ansible.com/ansible/latest/collections/ovirt/ovirt/ovirt_inventory.html',
- vmware:
- 'https://docs.ansible.com/ansible/latest/collections/community/vmware/vmware_vm_inventory_inventory.html',
-};
-
-const getInventoryHelpTextStrings = () => ({
- labels: t`Optional labels that describe this inventory,
- such as 'dev' or 'test'. Labels can be used to group and filter
- inventories and completed jobs.`,
- variables: () => {
- const jsonExample = `
- {
- "somevar": "somevalue"
- "somepassword": "Magic"
- }
- `;
- const yamlExample = `
- ---
- somevar: somevalue
- somepassword: magic
- `;
-
- return (
- <>
-
- Variables must be in JSON or YAML syntax. Use the radio button to
- toggle between the two.
-
-
-
- JSON:
- {jsonExample}
-
- YAML:
- {yamlExample}
-
-
- View JSON examples at{' '}
-
- www.json.org
-
-
-
-
- View YAML examples at{' '}
-
- docs.ansible.com
-
-
- >
- );
- },
- subFormVerbosityFields: t`Control the level of output Ansible
- will produce for inventory source update jobs.`,
- subFormOptions: {
- overwrite: (
- <>
- {t`If checked, any hosts and groups that were
- previously present on the external source but are now removed
- will be removed from the inventory. Hosts and groups
- that were not managed by the inventory source will be promoted
- to the next manually created group or if there is no manually
- created group to promote them into, they will be left in the "all"
- default group for the inventory.`}
-
-
- {t`When not checked, local child
- hosts and groups not found on the external source will remain
- untouched by the inventory update process.`}
- >
- ),
- overwriteVariables: (
- <>
- {t`If checked, all variables for child groups
- and hosts will be removed and replaced by those found
- on the external source.`}
-
-
- {t`When not checked, a merge will be performed,
- combining local variables with those found on the
- external source.`}
- >
- ),
- updateOnLaunch: ({ value }) => (
- <>
-
- {t`Each time a job runs using this inventory,
- refresh the inventory from the selected source before
- executing job tasks.`}
-
-
- {value && (
-
- {t`If you want the Inventory Source to update on
- launch and on project update, click on Update on launch, and also go to`}
- {value.name}
- {t`and click on Update Revision on Launch`}
-
- )}
- >
- ),
- updateOnProjectUpdate: ({ value }) => (
- <>
-
- {t`After every project update where the SCM revision
- changes, refresh the inventory from the selected source
- before executing job tasks. This is intended for static content,
- like the Ansible inventory .ini file format.`}
-
-
- {value && (
-
- {t`If you want the Inventory Source to update on
- launch and on project update, click on Update on launch, and also go to`}
- {value.name}
- {t`and click on Update Revision on Launch`}
-
- )}
- >
- ),
- cachedTimeOut: t`Time in seconds to consider an inventory sync
- to be current. During job runs and callbacks the task system will
- evaluate the timestamp of the latest sync. If it is older than
- Cache Timeout, it is not considered current, and a new
- inventory sync will be performed.`,
- },
- enabledVariableField: t`Retrieve the enabled state from the given dict of host variables.
- The enabled variable may be specified using dot notation, e.g: 'foo.bar'`,
- enabledValue: t`This field is ignored unless an Enabled Variable is set. If the enabled variable matches this value, the host will be enabled on import.`,
- hostFilter: t`Regular expression where only matching host names will be imported. The filter is applied as a post-processing step after any inventory plugin filters are applied.`,
- sourceVars: (docsBaseUrl, source) => {
- const docsUrl = `${docsBaseUrl}/html/userguide/inventories.html#inventory-plugins`;
- let sourceType = '';
- if (source && source !== 'scm') {
- const type = ansibleDocUrls[source].split(/[/,.]/);
- sourceType = type[type.length - 2];
- }
- return (
- <>
-
- Variables used to configure the inventory source. For a detailed
- description of how to configure this plugin, see{' '}
-
- Inventory Plugins
- {' '}
- in the documentation and the{' '}
-
- {sourceType}
- {' '}
- plugin configuration guide.
-
-
-
- >
- );
- },
- sourcePath: t`The inventory file
- to be synced by this source. You can select from
- the dropdown or enter a file within the input.`,
- preventInstanceGroupFallback: t`If enabled, the inventory will prevent adding any organization instance groups to the list of preferred instances groups to run associated job templates on.
- Note: If this setting is enabled and you provided an empty list, the global instance groups will be applied.`,
- enabledOptions: (
- {t`Prevent Instance Group Fallback: If enabled, the inventory will prevent adding any organization instance groups to the list of preferred instances groups to run associated job templates on.`}
- ),
-});
-
-export default getInventoryHelpTextStrings;
diff --git a/awx/ui/src/screens/Inventory/shared/InventoryForm.js b/awx/ui/src/screens/Inventory/shared/InventoryForm.js
deleted file mode 100644
index 49c91fa73b3e..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventoryForm.js
+++ /dev/null
@@ -1,172 +0,0 @@
-import React, { useCallback, useState } from 'react';
-import { Formik, useField, useFormikContext } from 'formik';
-import { t } from '@lingui/macro';
-import { func, shape } from 'prop-types';
-import { Form, FormGroup } from '@patternfly/react-core';
-import { VariablesField } from 'components/CodeEditor';
-import Popover from 'components/Popover';
-import FormField, {
- CheckboxField,
- FormSubmitError,
-} from 'components/FormField';
-import FormActionGroup from 'components/FormActionGroup';
-import { required } from 'util/validators';
-import LabelSelect from 'components/LabelSelect';
-import InstanceGroupsLookup from 'components/Lookup/InstanceGroupsLookup';
-import OrganizationLookup from 'components/Lookup/OrganizationLookup';
-import ContentError from 'components/ContentError';
-import {
- FormColumnLayout,
- FormFullWidthLayout,
- FormCheckboxLayout,
-} from 'components/FormLayout';
-import getHelpText from './Inventory.helptext';
-
-function InventoryFormFields({ inventory }) {
- const helpText = getHelpText();
- const [contentError, setContentError] = useState(false);
- const { setFieldValue, setFieldTouched } = useFormikContext();
- const [organizationField, organizationMeta, organizationHelpers] =
- useField('organization');
- const [instanceGroupsField, , instanceGroupsHelpers] =
- useField('instanceGroups');
- const [labelsField, , labelsHelpers] = useField('labels');
- const handleOrganizationUpdate = useCallback(
- (value) => {
- setFieldValue('organization', value);
- setFieldTouched('organization', true, false);
- },
- [setFieldValue, setFieldTouched]
- );
-
- if (contentError) {
- return ;
- }
-
- return (
- <>
-
-
- organizationHelpers.setTouched()}
- onChange={handleOrganizationUpdate}
- value={organizationField.value}
- touched={organizationMeta.touched}
- error={organizationMeta.error}
- required
- autoPopulate={!inventory?.id}
- validate={required(t`Select a value for this field`)}
- />
- {
- instanceGroupsHelpers.setValue(value);
- }}
- fieldName="instanceGroups"
- />
-
- }
- fieldId="inventory-labels"
- >
- labelsHelpers.setValue(labels)}
- onError={setContentError}
- createText={t`Create`}
- />
-
-
-
-
-
-
-
-
- >
- );
-}
-
-function InventoryForm({
- inventory = {},
- onSubmit,
- onCancel,
- submitError,
- instanceGroups,
- ...rest
-}) {
- const initialValues = {
- name: inventory.name || '',
- description: inventory.description || '',
- variables: inventory.variables || '---',
- organization:
- (inventory.summary_fields && inventory.summary_fields.organization) ||
- null,
- instanceGroups: instanceGroups || [],
- labels: inventory?.summary_fields?.labels?.results || [],
- prevent_instance_group_fallback:
- inventory.prevent_instance_group_fallback || false,
- };
- return (
- {
- onSubmit(values);
- }}
- >
- {(formik) => (
-
- )}
-
- );
-}
-
-InventoryForm.propType = {
- handleSubmit: func.isRequired,
- handleCancel: func.isRequired,
- instanceGroups: shape(),
- inventory: shape(),
- submitError: shape(),
-};
-
-InventoryForm.defaultProps = {
- inventory: {},
- instanceGroups: [],
- submitError: null,
-};
-
-export default InventoryForm;
diff --git a/awx/ui/src/screens/Inventory/shared/InventoryForm.test.js b/awx/ui/src/screens/Inventory/shared/InventoryForm.test.js
deleted file mode 100644
index 1f0c73f6cc05..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventoryForm.test.js
+++ /dev/null
@@ -1,139 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { LabelsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import InventoryForm from './InventoryForm';
-
-jest.mock('../../../api');
-
-const inventory = {
- id: 1,
- type: 'inventory',
- url: '/api/v2/inventories/1/',
- summary_fields: {
- organization: {
- id: 1,
- name: 'Default',
- description: '',
- },
- user_capabilities: {
- edit: true,
- delete: true,
- copy: true,
- adhoc: true,
- },
- labels: {
- results: [
- { name: 'Sushi', id: 1 },
- { name: 'Major', id: 2 },
- ],
- },
- },
- created: '2019-10-04T16:56:48.025455Z',
- modified: '2019-10-04T16:56:48.025468Z',
- name: 'Inv no hosts',
- description: '',
- organization: 1,
- kind: '',
- host_filter: null,
- variables: '---',
- has_active_failures: false,
- total_hosts: 0,
- hosts_with_active_failures: 0,
- total_groups: 0,
- groups_with_active_failures: 0,
- has_inventory_sources: false,
- total_inventory_sources: 0,
- inventory_sources_with_failures: 0,
- pending_deletion: false,
-};
-
-const instanceGroups = [
- { name: 'Foo', id: 1 },
- { name: 'Bar', id: 2 },
-];
-describe('', () => {
- let wrapper;
- let onCancel;
- let onSubmit;
-
- beforeAll(async () => {
- onCancel = jest.fn();
- onSubmit = jest.fn();
- LabelsAPI.read.mockReturnValue({
- data: inventory.summary_fields.labels,
- });
-
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('Initially renders successfully', () => {
- expect(wrapper.length).toBe(1);
- });
-
- test('should display form fields properly', async () => {
- await waitForElement(wrapper, 'InventoryForm', (el) => el.length > 0);
-
- expect(wrapper.find('FormGroup[label="Name"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Description"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Organization"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Instance Groups"]').length).toBe(1);
- expect(wrapper.find('VariablesField[label="Variables"]').length).toBe(1);
- expect(wrapper.find('CodeEditor').prop('value')).toEqual('---');
- });
-
- test('should update form values', async () => {
- await act(async () => {
- wrapper.find('OrganizationLookup').invoke('onBlur')();
- wrapper.find('OrganizationLookup').invoke('onChange')({
- id: 3,
- name: 'organization',
- });
-
- wrapper.find('input#inventory-name').simulate('change', {
- target: { value: 'new Foo', name: 'name' },
- });
- });
- wrapper.update();
- expect(wrapper.find('OrganizationLookup').prop('value')).toEqual({
- id: 3,
- name: 'organization',
- });
- expect(wrapper.find('input#inventory-name').prop('value')).toEqual(
- 'new Foo'
- );
- });
-
- test('should call handleCancel when Cancel button is clicked', async () => {
- expect(onCancel).not.toHaveBeenCalled();
- wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
- expect(onCancel).toBeCalled();
- });
-
- test('should render LabelsSelect', async () => {
- const select = wrapper.find('LabelSelect');
- expect(select).toHaveLength(1);
- expect(select.prop('value')).toEqual(
- inventory.summary_fields.labels.results
- );
- });
-});
diff --git a/awx/ui/src/screens/Inventory/shared/InventoryGroupForm.js b/awx/ui/src/screens/Inventory/shared/InventoryGroupForm.js
deleted file mode 100644
index a7dc1af60814..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventoryGroupForm.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import React from 'react';
-
-import { Formik } from 'formik';
-import { Form, Card } from '@patternfly/react-core';
-import { t } from '@lingui/macro';
-
-import { CardBody } from 'components/Card';
-import FormField, { FormSubmitError } from 'components/FormField';
-import FormActionGroup from 'components/FormActionGroup/FormActionGroup';
-import { VariablesField } from 'components/CodeEditor';
-import { required } from 'util/validators';
-import { FormColumnLayout, FormFullWidthLayout } from 'components/FormLayout';
-
-function InventoryGroupForm({ error, group = {}, handleSubmit, handleCancel }) {
- const initialValues = {
- name: group.name || '',
- description: group.description || '',
- variables: group.variables || '---',
- };
-
- return (
-
-
-
- {(formik) => (
-
- )}
-
-
-
- );
-}
-
-export default InventoryGroupForm;
diff --git a/awx/ui/src/screens/Inventory/shared/InventoryGroupForm.test.js b/awx/ui/src/screens/Inventory/shared/InventoryGroupForm.test.js
deleted file mode 100644
index 9f7bfd0fdbbb..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventoryGroupForm.test.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import InventoryGroupForm from './InventoryGroupForm';
-
-const group = {
- id: 1,
- name: 'Foo',
- description: 'Bar',
- variables: 'ying: false',
-};
-describe('', () => {
- let wrapper;
- beforeEach(() => {
- wrapper = mountWithContexts(
-
- );
- });
- test('initially renders successfully', () => {
- expect(wrapper.length).toBe(1);
- });
- test('should render values for the fields that have them', () => {
- expect(wrapper.find("FormGroup[label='Name']").length).toBe(1);
- expect(wrapper.find("FormGroup[label='Description']").length).toBe(1);
- expect(wrapper.find("VariablesField[label='Variables']").length).toBe(1);
- });
- test('should throw error properly', () => {
- const newWrapper = mountWithContexts(
-
- );
- expect(newWrapper.find('FormSubmitError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/shared/InventoryGroupsDeleteModal.js b/awx/ui/src/screens/Inventory/shared/InventoryGroupsDeleteModal.js
deleted file mode 100644
index 35f7a4e6be0f..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventoryGroupsDeleteModal.js
+++ /dev/null
@@ -1,173 +0,0 @@
-import 'styled-components/macro';
-import React, { useState, useContext, useEffect } from 'react';
-import { useParams } from 'react-router-dom';
-import { func, bool, arrayOf } from 'prop-types';
-import { t, Plural } from '@lingui/macro';
-import { Button, Radio, DropdownItem } from '@patternfly/react-core';
-import styled from 'styled-components';
-import { KebabifiedContext } from 'contexts/Kebabified';
-import { GroupsAPI, InventoriesAPI } from 'api';
-import { Group } from 'types';
-import ErrorDetail from 'components/ErrorDetail';
-import AlertModal from 'components/AlertModal';
-
-const ListItem = styled.li`
- display: flex;
- font-weight: 600;
- color: var(--pf-global--danger-color--100);
-`;
-
-const InventoryGroupsDeleteModal = ({ onAfterDelete, isDisabled, groups }) => {
- const [radioOption, setRadioOption] = useState(null);
- const [isModalOpen, setIsModalOpen] = useState(false);
- const [isDeleteLoading, setIsDeleteLoading] = useState(false);
- const [deletionError, setDeletionError] = useState(null);
- const { id: inventoryId } = useParams();
- const { isKebabified, onKebabModalChange } = useContext(KebabifiedContext);
-
- useEffect(() => {
- if (isKebabified) {
- onKebabModalChange(isModalOpen);
- }
- }, [isKebabified, isModalOpen, onKebabModalChange]);
- const handleDelete = async (option) => {
- setIsDeleteLoading(true);
-
- try {
- /* eslint-disable no-await-in-loop */
- /* Delete groups sequentially to avoid api integrity errors */
- /* https://eslint.org/docs/rules/no-await-in-loop#when-not-to-use-it */
- for (let i = 0; i < groups.length; i++) {
- const group = groups[i];
- if (option === 'delete') {
- await GroupsAPI.destroy(+group.id);
- } else if (option === 'promote') {
- await InventoriesAPI.promoteGroup(inventoryId, +group.id);
- }
- }
- /* eslint-enable no-await-in-loop */
- } catch (error) {
- setDeletionError(error);
- } finally {
- setIsModalOpen(false);
- setIsDeleteLoading(false);
- onAfterDelete();
- }
- };
- return (
- <>
- {isKebabified ? (
- setIsModalOpen(true)}
- ouiaId="group-delete-dropdown-item"
- >
- {t`Delete`}
-
- ) : (
-
- )}
- {isModalOpen && (
-
- }
- onClose={() => setIsModalOpen(false)}
- actions={[
- ,
- ,
- ]}
- >
-
-
-
- {groups.map((group) => (
- {group.name}
- ))}
-
-
- setRadioOption('delete')}
- ouiaId="delete-all-radio-button"
- />
- setRadioOption('promote')}
- ouiaId="promote-radio-button"
- />
-
-
- )}
- {deletionError && (
- setDeletionError(null)}
- >
- {t`Failed to delete one or more groups.`}
-
-
- )}
- >
- );
-};
-
-InventoryGroupsDeleteModal.propTypes = {
- onAfterDelete: func.isRequired,
- groups: arrayOf(Group),
- isDisabled: bool.isRequired,
-};
-
-InventoryGroupsDeleteModal.defaultProps = {
- groups: [],
-};
-
-export default InventoryGroupsDeleteModal;
diff --git a/awx/ui/src/screens/Inventory/shared/InventoryGroupsDeleteModal.test.js b/awx/ui/src/screens/Inventory/shared/InventoryGroupsDeleteModal.test.js
deleted file mode 100644
index 2dc69343d915..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventoryGroupsDeleteModal.test.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { InventoriesAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import InventoryGroupsDeleteModal from './InventoryGroupsDeleteModal';
-
-jest.mock('../../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useParams: () => ({
- id: 1,
- }),
-}));
-describe('', () => {
- let wrapper;
- beforeEach(() => {
- act(() => {
- wrapper = mountWithContexts(
- {}}
- isDisabled={false}
- groups={[
- { id: 1, name: 'Foo' },
- { id: 2, name: 'Bar' },
- ]}
- />
- );
- });
- });
- afterEach(() => {
- jest.clearAllMocks();
- });
- test('should mount properly', async () => {
- expect(wrapper.find('Button[aria-label="Delete"]').length).toBe(1);
- act(() => wrapper.find('Button[aria-label="Delete"]').prop('onClick')());
- wrapper.update();
- expect(wrapper.find('AlertModal').length).toBe(1);
- });
-
- test('should close modal', () => {
- act(() => wrapper.find('Button[aria-label="Delete"]').prop('onClick')());
- wrapper.update();
- act(() => wrapper.find('ModalBoxCloseButton').prop('onClose')());
- wrapper.update();
- expect(wrapper.find('AlertModal').length).toBe(0);
- });
-
- test('should delete properly', async () => {
- act(() => wrapper.find('Button[aria-label="Delete"]').prop('onClick')({}));
- wrapper.update();
- act(() =>
- wrapper
- .find('Radio[label="Promote Child Groups and Hosts"]')
- .invoke('onChange')()
- );
- wrapper.update();
- expect(
- wrapper.find('Button[aria-label="Confirm Delete"]').prop('isDisabled')
- ).toBe(false);
- await act(() =>
- wrapper.find('Button[aria-label="Confirm Delete"]').prop('onClick')()
- );
- expect(InventoriesAPI.promoteGroup).toBeCalledWith(1, 1);
- });
-
- test('should throw deletion error ', async () => {
- InventoriesAPI.promoteGroup.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'post',
- url: '/api/v2/inventories/1/groups',
- },
- data: 'An error occurred',
- status: 403,
- },
- })
- );
- act(() => wrapper.find('Button[aria-label="Delete"]').prop('onClick')({}));
- wrapper.update();
- act(() =>
- wrapper
- .find('Radio[label="Promote Child Groups and Hosts"]')
- .invoke('onChange')()
- );
- wrapper.update();
- expect(
- wrapper.find('Button[aria-label="Confirm Delete"]').prop('isDisabled')
- ).toBe(false);
- await act(() =>
- wrapper.find('Button[aria-label="Confirm Delete"]').prop('onClick')()
- );
- expect(InventoriesAPI.promoteGroup).toBeCalledWith(1, 1);
- wrapper.update();
- expect(wrapper.find('ErrorDetail').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceForm.js b/awx/ui/src/screens/Inventory/shared/InventorySourceForm.js
deleted file mode 100644
index 7131ed16bf76..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceForm.js
+++ /dev/null
@@ -1,324 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-import { Formik, useField, useFormikContext } from 'formik';
-import { func, shape } from 'prop-types';
-import { t } from '@lingui/macro';
-import { Form, FormGroup, Title } from '@patternfly/react-core';
-import { InventorySourcesAPI } from 'api';
-import useRequest from 'hooks/useRequest';
-import { required } from 'util/validators';
-import AnsibleSelect from 'components/AnsibleSelect';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import FormActionGroup from 'components/FormActionGroup/FormActionGroup';
-import FormField, { FormSubmitError } from 'components/FormField';
-import { FormColumnLayout, SubFormLayout } from 'components/FormLayout';
-
-import { ExecutionEnvironmentLookup } from 'components/Lookup';
-import {
- AzureSubForm,
- EC2SubForm,
- GCESubForm,
- InsightsSubForm,
- OpenStackSubForm,
- SCMSubForm,
- SatelliteSubForm,
- ControllerSubForm,
- VMwareSubForm,
- VirtualizationSubForm,
-} from './InventorySourceSubForms';
-
-const buildSourceChoiceOptions = (options) => {
- const sourceChoices = options.actions.GET.source.choices.map(
- ([choice, label]) => ({ label, key: choice, value: choice })
- );
- return sourceChoices.filter(({ key }) => key !== 'file');
-};
-
-const InventorySourceFormFields = ({
- source,
- sourceOptions,
- organizationId,
-}) => {
- const { values, initialValues, resetForm, setFieldTouched, setFieldValue } =
- useFormikContext();
- const [sourceField, sourceMeta] = useField({
- name: 'source',
- validate: required(t`Set a value for this field`),
- });
- const [
- executionEnvironmentField,
- executionEnvironmentMeta,
- executionEnvironmentHelpers,
- ] = useField('execution_environment');
-
- const resetSubFormFields = (sourceType) => {
- if (sourceType === initialValues.source) {
- resetForm({
- values: {
- ...initialValues,
- name: values.name,
- description: values.description,
- source: sourceType,
- },
- });
- } else {
- const defaults = {
- credential: null,
- overwrite: false,
- overwrite_vars: false,
- source: sourceType,
- source_path: '',
- source_project: null,
- source_script: null,
- source_vars: '---\n',
- update_cache_timeout: 0,
- update_on_launch: false,
- verbosity: 1,
- enabled_var: '',
- enabled_value: '',
- host_filter: '',
- };
- Object.keys(defaults).forEach((label) => {
- setFieldValue(label, defaults[label]);
- setFieldTouched(label, false);
- });
- }
- };
-
- const handleExecutionEnvironmentUpdate = useCallback(
- (value) => {
- setFieldValue('execution_environment', value);
- setFieldTouched('execution_environment', true, false);
- },
- [setFieldValue, setFieldTouched]
- );
-
- return (
- <>
-
-
- executionEnvironmentHelpers.setTouched()}
- value={executionEnvironmentField.value}
- onChange={handleExecutionEnvironmentUpdate}
- globallyAvailable
- organizationId={organizationId}
- />
-
- {
- resetSubFormFields(value);
- }}
- />
-
- {!['', 'custom'].includes(sourceField.value) && (
-
-
- {t`Source details`}
-
-
- {
- {
- azure_rm: (
-
- ),
- ec2: ,
- gce: (
-
- ),
- insights: (
-
- ),
- openstack: (
-
- ),
- rhv: (
-
- ),
- satellite6: (
-
- ),
- scm: (
-
- ),
- controller: (
-
- ),
- vmware: (
-
- ),
- }[sourceField.value]
- }
-
-
- )}
- >
- );
-};
-
-const InventorySourceForm = ({
- onCancel,
- onSubmit,
- source,
- submitError = null,
- organizationId,
-}) => {
- const initialValues = {
- credential: source?.summary_fields?.credential || null,
- description: source?.description || '',
- name: source?.name || '',
- overwrite: source?.overwrite || false,
- overwrite_vars: source?.overwrite_vars || false,
- source: source?.source || '',
- source_path: source?.source_path || '',
- source_project: source?.summary_fields?.source_project || null,
- source_script: source?.summary_fields?.source_script || null,
- source_vars: source?.source_vars || '---\n',
- update_cache_timeout: source?.update_cache_timeout || 0,
- update_on_launch: source?.update_on_launch || false,
- verbosity: source?.verbosity || 1,
- enabled_var: source?.enabled_var || '',
- enabled_value: source?.enabled_value || '',
- host_filter: source?.host_filter || '',
- execution_environment:
- source?.summary_fields?.execution_environment || null,
- };
-
- const {
- isLoading: isSourceOptionsLoading,
- error: sourceOptionsError,
- request: fetchSourceOptions,
- result: sourceOptions,
- } = useRequest(
- useCallback(async () => {
- const { data } = await InventorySourcesAPI.readOptions();
- return data;
- }, []),
- null
- );
-
- useEffect(() => {
- fetchSourceOptions();
- }, [fetchSourceOptions]);
-
- if (sourceOptionsError) {
- return ;
- }
-
- if (!sourceOptions || isSourceOptionsLoading) {
- return ;
- }
-
- return (
- {
- onSubmit(values);
- }}
- >
- {(formik) => (
-
- )}
-
- );
-};
-
-InventorySourceForm.propTypes = {
- onCancel: func.isRequired,
- onSubmit: func.isRequired,
- submitError: shape({}),
-};
-
-InventorySourceForm.defaultProps = {
- submitError: null,
-};
-
-export default InventorySourceForm;
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceForm.test.js b/awx/ui/src/screens/Inventory/shared/InventorySourceForm.test.js
deleted file mode 100644
index edea0bb8036f..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceForm.test.js
+++ /dev/null
@@ -1,137 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { InventorySourcesAPI, ProjectsAPI, CredentialsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import InventorySourceForm from './InventorySourceForm';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
-
- describe('Successful form submission', () => {
- const onSubmit = jest.fn();
-
- beforeAll(async () => {
- CredentialsAPI.read.mockResolvedValue({
- data: { count: 0, results: [] },
- });
- ProjectsAPI.readInventories.mockResolvedValue({
- data: ['foo', 'bar'],
- });
- InventorySourcesAPI.readOptions = async () => ({
- data: {
- actions: {
- GET: {
- source: {
- choices: [
- ['file', 'File, Directory or Script'],
- ['scm', 'Sourced from a Project'],
- ['ec2', 'Amazon EC2'],
- ['gce', 'Google Compute Engine'],
- ['azure_rm', 'Microsoft Azure Resource Manager'],
- ['vmware', 'VMware vCenter'],
- ['satellite6', 'Red Hat Satellite 6'],
- ['openstack', 'OpenStack'],
- ['rhv', 'Red Hat Virtualization'],
- ['controller', 'Red Hat Ansible Automation Platform'],
- ],
- },
- },
- },
- },
- });
- await act(async () => {
- wrapper = mountWithContexts(
- {}} onSubmit={onSubmit} />
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('should initially display primary form fields', () => {
- expect(wrapper.find('FormGroup[label="Name"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Description"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Source"]')).toHaveLength(1);
- expect(
- wrapper.find('FormGroup[label="Ansible Environment"]')
- ).toHaveLength(0);
- expect(wrapper.find('ExecutionEnvironmentLookup')).toHaveLength(1);
- });
-
- test('should display subform when source dropdown has a value', async () => {
- await act(async () => {
- wrapper.find('AnsibleSelect#source').prop('onChange')(null, 'scm');
- });
- wrapper.update();
- expect(wrapper.find('Title').text()).toBe('Source details');
- });
-
- test('should show field error when form is invalid', async () => {
- expect(onSubmit).not.toHaveBeenCalled();
- await act(async () => {
- wrapper.find('CredentialLookup').invoke('onChange')({
- id: 1,
- name: 'mock cred',
- });
- wrapper.find('ProjectLookup').invoke('onChange')({
- id: 2,
- name: 'mock proj',
- });
- wrapper.find('Select#source_path').prop('onToggle')();
- wrapper.find('Select#source_path').prop('onSelect')(null, 'foo');
- wrapper.find('AnsibleSelect#verbosity').prop('onChange')(null, '2');
- wrapper.find('button[aria-label="Save"]').simulate('click');
- });
- wrapper.update();
- expect(wrapper.find('FormGroup[label="Name"] .pf-m-error')).toHaveLength(
- 1
- );
- expect(onSubmit).not.toHaveBeenCalled();
- });
-
- test('should call onSubmit when Save button is clicked', async () => {
- expect(onSubmit).not.toHaveBeenCalled();
- wrapper.find('input#name').simulate('change', {
- target: { value: 'new foo', name: 'name' },
- });
- await act(async () => {
- wrapper.find('button[aria-label="Save"]').simulate('click');
- });
- wrapper.update();
- expect(onSubmit).toHaveBeenCalled();
- });
- });
-
- test('calls "onCancel" when Cancel button is clicked', async () => {
- const onCancel = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
- {}} />
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(onCancel).not.toHaveBeenCalled();
- wrapper.find('button[aria-label="Cancel"]').prop('onClick')();
- expect(onCancel).toBeCalled();
- });
-
- test('should display ContentError on throw', async () => {
- InventorySourcesAPI.readOptions = jest.fn();
- InventorySourcesAPI.readOptions.mockRejectedValueOnce(new Error());
- await act(async () => {
- wrapper = mountWithContexts(
- {}} onSubmit={() => {}} />
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('ContentError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.js
deleted file mode 100644
index 1824e3aa80c4..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import React, { useCallback } from 'react';
-import { useField, useFormikContext } from 'formik';
-import { t } from '@lingui/macro';
-import getDocsBaseUrl from 'util/getDocsBaseUrl';
-import { useConfig } from 'contexts/Config';
-import CredentialLookup from 'components/Lookup/CredentialLookup';
-import { required } from 'util/validators';
-import {
- OptionsField,
- SourceVarsField,
- VerbosityField,
- EnabledVarField,
- EnabledValueField,
- HostFilterField,
-} from './SharedFields';
-import getHelpText from '../Inventory.helptext';
-
-const AzureSubForm = ({ autoPopulateCredential }) => {
- const helpText = getHelpText();
- const { setFieldValue, setFieldTouched } = useFormikContext();
- const [credentialField, credentialMeta, credentialHelpers] =
- useField('credential');
- const config = useConfig();
-
- const handleCredentialUpdate = useCallback(
- (value) => {
- setFieldValue('credential', value);
- setFieldTouched('credential', true, false);
- },
- [setFieldValue, setFieldTouched]
- );
- const docsBaseUrl = getDocsBaseUrl(config);
-
- return (
- <>
- credentialHelpers.setTouched()}
- onChange={handleCredentialUpdate}
- value={credentialField.value}
- required
- autoPopulate={autoPopulateCredential}
- validate={required(t`Select a value for this field`)}
- />
-
-
-
-
-
-
- >
- );
-};
-
-export default AzureSubForm;
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.test.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.test.js
deleted file mode 100644
index 9e120dae7ea6..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.test.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { CredentialsAPI } from 'api';
-import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
-import AzureSubForm from './AzureSubForm';
-
-jest.mock('../../../../api');
-
-const initialValues = {
- credential: null,
- overwrite: false,
- overwrite_vars: false,
- source_path: '',
- source_project: null,
- source_script: null,
- source_vars: '---\n',
- update_cache_timeout: 0,
- update_on_launch: true,
- verbosity: 1,
-};
-
-const mockSourceOptions = {
- actions: {
- POST: {},
- },
-};
-
-describe('', () => {
- let wrapper;
-
- beforeEach(async () => {
- CredentialsAPI.read.mockResolvedValue({
- data: { count: 0, results: [] },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('should render subform fields', () => {
- expect(wrapper.find('FormGroup[label="Credential"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
- expect(
- wrapper.find('FormGroup[label="Cache timeout (seconds)"]')
- ).toHaveLength(1);
- expect(
- wrapper.find('VariablesField[label="Source variables"]')
- ).toHaveLength(1);
- });
-
- test('should make expected api calls', () => {
- expect(CredentialsAPI.read).toHaveBeenCalledTimes(1);
- expect(CredentialsAPI.read).toHaveBeenCalledWith({
- credential_type__namespace: 'azure_rm',
- order_by: 'name',
- page: 1,
- page_size: 5,
- });
- });
-});
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.js
deleted file mode 100644
index afc56dd5b12b..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import React, { useCallback } from 'react';
-import { useField, useFormikContext } from 'formik';
-
-import { t } from '@lingui/macro';
-import getDocsBaseUrl from 'util/getDocsBaseUrl';
-import { useConfig } from 'contexts/Config';
-import CredentialLookup from 'components/Lookup/CredentialLookup';
-import { required } from 'util/validators';
-import {
- OptionsField,
- VerbosityField,
- EnabledVarField,
- EnabledValueField,
- HostFilterField,
- SourceVarsField,
-} from './SharedFields';
-import getHelpText from '../Inventory.helptext';
-
-const ControllerSubForm = ({ autoPopulateCredential }) => {
- const helpText = getHelpText();
- const { setFieldValue, setFieldTouched } = useFormikContext();
- const [credentialField, credentialMeta, credentialHelpers] =
- useField('credential');
- const config = useConfig();
- const handleCredentialUpdate = useCallback(
- (value) => {
- setFieldValue('credential', value);
- setFieldTouched('credential', true, false);
- },
- [setFieldValue, setFieldTouched]
- );
-
- return (
- <>
- credentialHelpers.setTouched()}
- onChange={handleCredentialUpdate}
- value={credentialField.value}
- required
- autoPopulate={autoPopulateCredential}
- validate={required(t`Select a value for this field`)}
- />
-
-
-
-
-
-
- >
- );
-};
-
-export default ControllerSubForm;
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.test.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.test.js
deleted file mode 100644
index 3d52a277b0a1..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/ControllerSubForm.test.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { CredentialsAPI } from 'api';
-import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
-import ControllerSubForm from './ControllerSubForm';
-
-jest.mock('../../../../api');
-
-const initialValues = {
- credential: null,
- overwrite: false,
- overwrite_vars: false,
- source_path: '',
- source_project: null,
- source_script: null,
- source_vars: '---\n',
- update_cache_timeout: 0,
- update_on_launch: true,
- verbosity: 1,
-};
-
-describe('', () => {
- let wrapper;
-
- beforeEach(async () => {
- CredentialsAPI.read.mockResolvedValue({
- data: { count: 0, results: [] },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('should render subform fields', () => {
- expect(wrapper.find('FormGroup[label="Credential"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
- expect(
- wrapper.find('FormGroup[label="Cache timeout (seconds)"]')
- ).toHaveLength(1);
- expect(
- wrapper.find('VariablesField[label="Source variables"]')
- ).toHaveLength(1);
- });
-
- test('should make expected api calls', () => {
- expect(CredentialsAPI.read).toHaveBeenCalledTimes(1);
- expect(CredentialsAPI.read).toHaveBeenCalledWith({
- credential_type__namespace: 'controller',
- order_by: 'name',
- page: 1,
- page_size: 5,
- });
- });
-});
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.js
deleted file mode 100644
index ef12223535e3..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import React, { useCallback } from 'react';
-import { useField, useFormikContext } from 'formik';
-import { t } from '@lingui/macro';
-import getDocsBaseUrl from 'util/getDocsBaseUrl';
-import { useConfig } from 'contexts/Config';
-import CredentialLookup from 'components/Lookup/CredentialLookup';
-import {
- OptionsField,
- SourceVarsField,
- VerbosityField,
- EnabledVarField,
- EnabledValueField,
- HostFilterField,
-} from './SharedFields';
-import getHelpText from '../Inventory.helptext';
-
-const EC2SubForm = () => {
- const helpText = getHelpText();
- const { setFieldValue, setFieldTouched } = useFormikContext();
- const [credentialField, credentialMeta] = useField('credential');
- const config = useConfig();
- const handleCredentialUpdate = useCallback(
- (value) => {
- setFieldValue('credential', value);
- setFieldTouched('credential', true, false);
- },
- [setFieldValue, setFieldTouched]
- );
- const docsBaseUrl = getDocsBaseUrl(config);
-
- return (
- <>
-
-
-
-
-
-
-
- >
- );
-};
-
-export default EC2SubForm;
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.test.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.test.js
deleted file mode 100644
index 113f75e0228e..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.test.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { CredentialsAPI } from 'api';
-import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
-import EC2SubForm from './EC2SubForm';
-
-jest.mock('../../../../api');
-
-const initialValues = {
- credential: null,
- overwrite: false,
- overwrite_vars: false,
- source_path: '',
- source_project: null,
- source_script: null,
- source_vars: '---\n',
- update_cache_timeout: 0,
- update_on_launch: true,
- verbosity: 1,
-};
-
-const mockSourceOptions = {
- actions: {
- POST: {},
- },
-};
-
-describe('', () => {
- let wrapper;
-
- beforeEach(async () => {
- CredentialsAPI.read.mockResolvedValue({
- data: { count: 0, results: [] },
- });
-
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('should render subform fields', () => {
- expect(wrapper.find('FormGroup[label="Credential"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
- expect(
- wrapper.find('FormGroup[label="Cache timeout (seconds)"]')
- ).toHaveLength(1);
- expect(
- wrapper.find('VariablesField[label="Source variables"]')
- ).toHaveLength(1);
- });
-
- test('should make expected api calls', () => {
- expect(CredentialsAPI.read).toHaveBeenCalledTimes(1);
- expect(CredentialsAPI.read).toHaveBeenCalledWith({
- credential_type__namespace: 'aws',
- order_by: 'name',
- page: 1,
- page_size: 5,
- });
- });
-});
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/GCESubForm.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/GCESubForm.js
deleted file mode 100644
index 3b2813ae4bcc..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/GCESubForm.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import React, { useCallback } from 'react';
-import { useField, useFormikContext } from 'formik';
-import { t } from '@lingui/macro';
-import getDocsBaseUrl from 'util/getDocsBaseUrl';
-import { useConfig } from 'contexts/Config';
-import CredentialLookup from 'components/Lookup/CredentialLookup';
-import { required } from 'util/validators';
-import {
- OptionsField,
- VerbosityField,
- EnabledVarField,
- EnabledValueField,
- HostFilterField,
- SourceVarsField,
-} from './SharedFields';
-import getHelpText from '../Inventory.helptext';
-
-const GCESubForm = ({ autoPopulateCredential }) => {
- const helpText = getHelpText();
- const { setFieldValue, setFieldTouched } = useFormikContext();
- const [credentialField, credentialMeta, credentialHelpers] =
- useField('credential');
- const config = useConfig();
- const handleCredentialUpdate = useCallback(
- (value) => {
- setFieldValue('credential', value);
- setFieldTouched('credential', true, false);
- },
- [setFieldValue, setFieldTouched]
- );
- const docsBaseUrl = getDocsBaseUrl(config);
-
- return (
- <>
- credentialHelpers.setTouched()}
- onChange={handleCredentialUpdate}
- value={credentialField.value}
- required
- autoPopulate={autoPopulateCredential}
- validate={required(t`Select a value for this field`)}
- />
-
-
-
-
-
-
- >
- );
-};
-
-export default GCESubForm;
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/GCESubForm.test.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/GCESubForm.test.js
deleted file mode 100644
index 2427215cf502..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/GCESubForm.test.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { CredentialsAPI } from 'api';
-import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
-import GCESubForm from './GCESubForm';
-
-jest.mock('../../../../api');
-
-const initialValues = {
- credential: null,
- overwrite: false,
- overwrite_vars: false,
- source_path: '',
- source_project: null,
- source_script: null,
- source_vars: '---\n',
- update_cache_timeout: 0,
- update_on_launch: true,
- verbosity: 1,
-};
-
-const mockSourceOptions = {
- actions: {
- POST: {},
- },
-};
-
-describe('', () => {
- let wrapper;
-
- beforeEach(async () => {
- CredentialsAPI.read.mockResolvedValue({
- data: { count: 0, results: [] },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('should render subform fields', () => {
- expect(wrapper.find('FormGroup[label="Credential"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
- expect(
- wrapper.find('FormGroup[label="Cache timeout (seconds)"]')
- ).toHaveLength(1);
- expect(
- wrapper.find('VariablesField[label="Source variables"]')
- ).toHaveLength(1);
- });
-
- test('should make expected api calls', () => {
- expect(CredentialsAPI.read).toHaveBeenCalledTimes(1);
- expect(CredentialsAPI.read).toHaveBeenCalledWith({
- credential_type__namespace: 'gce',
- order_by: 'name',
- page: 1,
- page_size: 5,
- });
- });
-});
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.js
deleted file mode 100644
index 0926d06774f0..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import React, { useCallback } from 'react';
-import { useField, useFormikContext } from 'formik';
-
-import { t } from '@lingui/macro';
-import getDocsBaseUrl from 'util/getDocsBaseUrl';
-import { useConfig } from 'contexts/Config';
-import CredentialLookup from 'components/Lookup/CredentialLookup';
-import { required } from 'util/validators';
-import {
- OptionsField,
- VerbosityField,
- EnabledVarField,
- EnabledValueField,
- HostFilterField,
- SourceVarsField,
-} from './SharedFields';
-import getHelpText from '../Inventory.helptext';
-
-const InsightsSubForm = ({ autoPopulateCredential }) => {
- const helpText = getHelpText();
- const { setFieldValue, setFieldTouched } = useFormikContext();
- const [credentialField, credentialMeta, credentialHelpers] =
- useField('credential');
- const config = useConfig();
- const handleCredentialUpdate = useCallback(
- (value) => {
- setFieldValue('credential', value);
- setFieldTouched('credential', true, false);
- },
- [setFieldValue, setFieldTouched]
- );
- const docsBaseUrl = getDocsBaseUrl(config);
-
- return (
- <>
- credentialHelpers.setTouched()}
- onChange={handleCredentialUpdate}
- value={credentialField.value}
- required
- autoPopulate={autoPopulateCredential}
- validate={required(t`Select a value for this field`)}
- />
-
-
-
-
-
-
- >
- );
-};
-
-export default InsightsSubForm;
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.test.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.test.js
deleted file mode 100644
index 3b2dd1e7417e..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/InsightsSubForm.test.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { CredentialsAPI } from 'api';
-import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
-import InsightsSubForm from './InsightsSubForm';
-
-jest.mock('../../../../api');
-
-const initialValues = {
- credential: null,
- overwrite: false,
- overwrite_vars: false,
- source_path: '',
- source_project: null,
- source_script: null,
- source_vars: '---\n',
- update_cache_timeout: 0,
- update_on_launch: true,
- verbosity: 1,
-};
-
-describe('', () => {
- let wrapper;
-
- beforeEach(async () => {
- CredentialsAPI.read.mockResolvedValue({
- data: { count: 0, results: [] },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('should render subform fields', () => {
- expect(wrapper.find('FormGroup[label="Credential"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
- expect(
- wrapper.find('FormGroup[label="Cache timeout (seconds)"]')
- ).toHaveLength(1);
- expect(
- wrapper.find('VariablesField[label="Source variables"]')
- ).toHaveLength(1);
- });
-
- test('should make expected api calls', () => {
- expect(CredentialsAPI.read).toHaveBeenCalledTimes(1);
- expect(CredentialsAPI.read).toHaveBeenCalledWith({
- credential_type__namespace: 'insights',
- order_by: 'name',
- page: 1,
- page_size: 5,
- });
- });
-});
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.js
deleted file mode 100644
index 3b1998edcc34..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import React, { useCallback } from 'react';
-import { useField, useFormikContext } from 'formik';
-import { t } from '@lingui/macro';
-import { useConfig } from 'contexts/Config';
-import getDocsBaseUrl from 'util/getDocsBaseUrl';
-import CredentialLookup from 'components/Lookup/CredentialLookup';
-import { required } from 'util/validators';
-import {
- OptionsField,
- SourceVarsField,
- VerbosityField,
- EnabledVarField,
- EnabledValueField,
- HostFilterField,
-} from './SharedFields';
-import getHelpText from '../Inventory.helptext';
-
-const OpenStackSubForm = ({ autoPopulateCredential }) => {
- const helpText = getHelpText();
- const { setFieldValue, setFieldTouched } = useFormikContext();
- const [credentialField, credentialMeta, credentialHelpers] =
- useField('credential');
- const config = useConfig();
-
- const handleCredentialUpdate = useCallback(
- (value) => {
- setFieldValue('credential', value);
- setFieldTouched('credential', true, false);
- },
- [setFieldValue, setFieldTouched]
- );
-
- return (
- <>
- credentialHelpers.setTouched()}
- onChange={handleCredentialUpdate}
- value={credentialField.value}
- required
- autoPopulate={autoPopulateCredential}
- validate={required(t`Select a value for this field`)}
- />
-
-
-
-
-
-
- >
- );
-};
-
-export default OpenStackSubForm;
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.test.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.test.js
deleted file mode 100644
index d5f9675f0380..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.test.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { CredentialsAPI } from 'api';
-import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
-import OpenStackSubForm from './OpenStackSubForm';
-
-jest.mock('../../../../api');
-
-const initialValues = {
- credential: null,
- overwrite: false,
- overwrite_vars: false,
- source_path: '',
- source_project: null,
- source_script: null,
- source_vars: '---\n',
- update_cache_timeout: 0,
- update_on_launch: true,
- verbosity: 1,
-};
-
-describe('', () => {
- let wrapper;
-
- beforeEach(async () => {
- CredentialsAPI.read.mockResolvedValue({
- data: { count: 0, results: [] },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- });
-
- afterAll(() => {
- jest.resetAllMocks();
- });
-
- test('should render subform fields', () => {
- expect(wrapper.find('FormGroup[label="Credential"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
- expect(
- wrapper.find('FormGroup[label="Cache timeout (seconds)"]')
- ).toHaveLength(1);
- expect(
- wrapper.find('VariablesField[label="Source variables"]')
- ).toHaveLength(1);
- });
-
- test('should make expected api calls', () => {
- expect(CredentialsAPI.read).toHaveBeenCalledTimes(1);
- expect(CredentialsAPI.read).toHaveBeenCalledWith({
- credential_type__namespace: 'openstack',
- order_by: 'name',
- page: 1,
- page_size: 5,
- });
- });
-});
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js
deleted file mode 100644
index 1b6d3e95bad7..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js
+++ /dev/null
@@ -1,154 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { useField, useFormikContext } from 'formik';
-import { t } from '@lingui/macro';
-import {
- FormGroup,
- SelectVariant,
- Select,
- SelectOption,
-} from '@patternfly/react-core';
-import { ProjectsAPI } from 'api';
-import useRequest from 'hooks/useRequest';
-import { required } from 'util/validators';
-import CredentialLookup from 'components/Lookup/CredentialLookup';
-import ProjectLookup from 'components/Lookup/ProjectLookup';
-import Popover from 'components/Popover';
-import {
- OptionsField,
- SourceVarsField,
- VerbosityField,
- EnabledVarField,
- EnabledValueField,
- HostFilterField,
-} from './SharedFields';
-import getHelpText from '../Inventory.helptext';
-
-const SCMSubForm = ({ autoPopulateProject }) => {
- const helpText = getHelpText();
- const [isOpen, setIsOpen] = useState(false);
- const [sourcePath, setSourcePath] = useState([]);
- const { setFieldValue, setFieldTouched } = useFormikContext();
- const [credentialField] = useField('credential');
-
- const [projectField, projectMeta, projectHelpers] =
- useField('source_project');
- const [sourcePathField, sourcePathMeta, sourcePathHelpers] = useField({
- name: 'source_path',
- validate: required(t`Select a value for this field`),
- });
-
- const { error: sourcePathError, request: fetchSourcePath } = useRequest(
- useCallback(async (projectId) => {
- const { data } = await ProjectsAPI.readInventories(projectId);
- setSourcePath([...data, '/ (project root)']);
- }, []),
- []
- );
-
- useEffect(() => {
- if (projectMeta.initialValue) {
- fetchSourcePath(projectMeta.initialValue.id);
- if (sourcePathField.value === '') {
- sourcePathHelpers.setValue('/ (project root)');
- }
- } // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [fetchSourcePath, projectMeta.initialValue]);
-
- const handleProjectUpdate = useCallback(
- (value) => {
- setFieldValue('source_project', value);
- setFieldTouched('source_project', true, false);
- if (sourcePathField.value) {
- setFieldValue('source_path', '');
- setFieldTouched('source_path', false);
- }
- if (value) {
- fetchSourcePath(value.id);
- }
- },
- [fetchSourcePath, setFieldValue, setFieldTouched, sourcePathField.value]
- );
-
- const handleCredentialUpdate = useCallback(
- (value) => {
- setFieldValue('credential', value);
- setFieldTouched('credential', true, false);
- },
- [setFieldValue, setFieldTouched]
- );
-
- return (
- <>
-
- projectHelpers.setTouched()}
- onChange={handleProjectUpdate}
- required
- autoPopulate={autoPopulateProject}
- fieldName="source_project"
- validate={required(t`Select a value for this field`)}
- />
- }
- >
-
-
-
-
-
-
-
-
- >
- );
-};
-
-export default SCMSubForm;
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.test.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.test.js
deleted file mode 100644
index ab40c9a6f516..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.test.js
+++ /dev/null
@@ -1,140 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { ProjectsAPI, CredentialsAPI } from 'api';
-import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
-import SCMSubForm from './SCMSubForm';
-
-jest.mock('../../../../api');
-
-const initialValues = {
- credential: null,
- overwrite: false,
- overwrite_vars: false,
- source_path: '',
- source_project: null,
- source_script: null,
- source_vars: '---\n',
- update_cache_timeout: 0,
- update_on_launch: true,
- verbosity: 1,
-};
-
-describe('', () => {
- let wrapper;
-
- beforeEach(async () => {
- CredentialsAPI.read.mockResolvedValue({
- data: { count: 0, results: [] },
- });
- ProjectsAPI.readInventories.mockResolvedValue({
- data: ['foo', 'bar'],
- });
- ProjectsAPI.read.mockResolvedValue({
- data: {
- count: 2,
- results: [
- {
- id: 1,
- name: 'mock proj one',
- },
- {
- id: 2,
- name: 'mock proj two',
- },
- ],
- },
- });
-
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('should render subform fields', () => {
- expect(wrapper.find('FormGroup[label="Credential"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Project"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Inventory file"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
- expect(
- wrapper.find('FormGroup[label="Cache timeout (seconds)"]')
- ).toHaveLength(1);
- expect(
- wrapper.find('VariablesField[label="Source variables"]')
- ).toHaveLength(1);
- });
-
- test('project lookup should fetch project source path list', async () => {
- expect(ProjectsAPI.readInventories).not.toHaveBeenCalled();
- await act(async () => {
- wrapper.find('ProjectLookup').invoke('onChange')({
- id: 2,
- name: 'mock proj two',
- });
- wrapper.find('ProjectLookup').invoke('onBlur')();
- });
- expect(ProjectsAPI.readInventories).toHaveBeenCalledWith(2);
- });
-
- test('changing source project should reset source path dropdown', async () => {
- expect(wrapper.find('Select#source_path').prop('selections')).toEqual('');
- await act(async () => {
- await wrapper.find('Select#source_path').prop('onToggle')();
- });
- wrapper.update();
- await act(async () => {
- await wrapper.find('Select#source_path').prop('onSelect')(null, 'bar');
- });
- wrapper.update();
- expect(wrapper.find('Select#source_path').prop('selections')).toEqual(
- 'bar'
- );
-
- await act(async () => {
- wrapper.find('ProjectLookup').invoke('onChange')({
- id: 1,
- name: 'mock proj one',
- });
- });
- wrapper.update();
- expect(wrapper.find('Select#source_path').prop('selections')).toEqual('');
- });
-
- test('should be able to create custom source path', async () => {
- const customInitialValues = {
- credential: { id: 1, name: 'Credential' },
- overwrite: false,
- overwrite_vars: false,
- source_path: '/path',
- source_project: { id: 1, name: 'Source project' },
- source_script: null,
- source_vars: '---\n',
- update_cache_timeout: 0,
- update_on_launch: true,
- verbosity: 1,
- };
- let customWrapper;
- await act(async () => {
- customWrapper = mountWithContexts(
-
-
-
- );
- });
-
- await act(async () => {
- customWrapper.find('Select').invoke('onSelect')({}, 'newPath');
- });
- customWrapper.update();
- expect(customWrapper.find('Select').prop('selections')).toBe('newPath');
- });
-});
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.js
deleted file mode 100644
index d376400aa623..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.js
+++ /dev/null
@@ -1,61 +0,0 @@
-import React, { useCallback } from 'react';
-import { useField, useFormikContext } from 'formik';
-import { t } from '@lingui/macro';
-import { useConfig } from 'contexts/Config';
-import getDocsBaseUrl from 'util/getDocsBaseUrl';
-import CredentialLookup from 'components/Lookup/CredentialLookup';
-import { required } from 'util/validators';
-import {
- OptionsField,
- SourceVarsField,
- VerbosityField,
- EnabledVarField,
- EnabledValueField,
- HostFilterField,
-} from './SharedFields';
-import getHelpText from '../Inventory.helptext';
-
-const SatelliteSubForm = ({ autoPopulateCredential }) => {
- const helpText = getHelpText();
- const { setFieldValue, setFieldTouched } = useFormikContext();
- const [credentialField, credentialMeta, credentialHelpers] =
- useField('credential');
- const config = useConfig();
- const handleCredentialUpdate = useCallback(
- (value) => {
- setFieldValue('credential', value);
- setFieldTouched('credential', true, false);
- },
- [setFieldValue, setFieldTouched]
- );
-
- return (
- <>
- credentialHelpers.setTouched()}
- onChange={handleCredentialUpdate}
- value={credentialField.value}
- required
- autoPopulate={autoPopulateCredential}
- validate={required(t`Select a value for this field`)}
- />
-
-
-
-
-
-
- >
- );
-};
-
-export default SatelliteSubForm;
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.test.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.test.js
deleted file mode 100644
index fe9215802821..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.test.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { CredentialsAPI } from 'api';
-import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
-import SatelliteSubForm from './SatelliteSubForm';
-
-jest.mock('../../../../api');
-
-const initialValues = {
- credential: null,
- overwrite: false,
- overwrite_vars: false,
- source_path: '',
- source_project: null,
- source_script: null,
- source_vars: '---\n',
- update_cache_timeout: 0,
- update_on_launch: true,
- verbosity: 1,
-};
-
-describe('', () => {
- let wrapper;
-
- beforeEach(async () => {
- CredentialsAPI.read.mockResolvedValue({
- data: { count: 0, results: [] },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('should render subform fields', () => {
- expect(wrapper.find('FormGroup[label="Credential"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
- expect(
- wrapper.find('FormGroup[label="Cache timeout (seconds)"]')
- ).toHaveLength(1);
- expect(
- wrapper.find('VariablesField[label="Source variables"]')
- ).toHaveLength(1);
- });
-
- test('should make expected api calls', () => {
- expect(CredentialsAPI.read).toHaveBeenCalledTimes(1);
- expect(CredentialsAPI.read).toHaveBeenCalledWith({
- credential_type__namespace: 'satellite6',
- order_by: 'name',
- page: 1,
- page_size: 5,
- });
- });
-});
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/SharedFields.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/SharedFields.js
deleted file mode 100644
index 147e54998883..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/SharedFields.js
+++ /dev/null
@@ -1,152 +0,0 @@
-import React, { useEffect } from 'react';
-
-import { t } from '@lingui/macro';
-import { useField } from 'formik';
-import { FormGroup } from '@patternfly/react-core';
-import { minMaxValue, regExp } from 'util/validators';
-import AnsibleSelect from 'components/AnsibleSelect';
-import { VariablesField } from 'components/CodeEditor';
-import FormField, { CheckboxField } from 'components/FormField';
-import { FormFullWidthLayout, FormCheckboxLayout } from 'components/FormLayout';
-import Popover from 'components/Popover';
-import getHelpText from '../Inventory.helptext';
-
-export const SourceVarsField = ({ popoverContent }) => {
- const helpText = getHelpText();
- return (
-
-
- {popoverContent}
- {helpText.variables()}
- >
- }
- />
-
- );
-};
-
-export const VerbosityField = () => {
- const helpText = getHelpText();
- const [field, meta, helpers] = useField('verbosity');
- const isValid = !(meta.touched && meta.error);
- const options = [
- { value: '0', key: '0', label: t`0 (Warning)` },
- { value: '1', key: '1', label: t`1 (Info)` },
- { value: '2', key: '2', label: t`2 (Debug)` },
- ];
-
- return (
- }
- >
- helpers.setValue(value)}
- />
-
- );
-};
-
-export const OptionsField = () => {
- const helpText = getHelpText();
- const [updateOnLaunchField] = useField('update_on_launch');
- const [, , updateCacheTimeoutHelper] = useField('update_cache_timeout');
- const [projectField] = useField('source_project');
-
- useEffect(() => {
- if (!updateOnLaunchField.value) {
- updateCacheTimeoutHelper.setValue(0);
- }
- }, [updateOnLaunchField.value]); // eslint-disable-line react-hooks/exhaustive-deps
-
- return (
- <>
-
-
-
-
-
-
-
-
-
- {updateOnLaunchField.value && (
-
- )}
- >
- );
-};
-
-export const EnabledVarField = () => {
- const helpText = getHelpText();
- return (
-
- );
-};
-
-export const EnabledValueField = () => {
- const helpText = getHelpText();
- return (
-
- );
-};
-
-export const HostFilterField = () => {
- const helpText = getHelpText();
- return (
-
- );
-};
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.js
deleted file mode 100644
index 90ceb0eeebab..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import React, { useCallback } from 'react';
-import { useField, useFormikContext } from 'formik';
-import { t } from '@lingui/macro';
-import { useConfig } from 'contexts/Config';
-import getDocsBaseUrl from 'util/getDocsBaseUrl';
-import CredentialLookup from 'components/Lookup/CredentialLookup';
-import { required } from 'util/validators';
-import {
- OptionsField,
- SourceVarsField,
- VerbosityField,
- EnabledVarField,
- EnabledValueField,
- HostFilterField,
-} from './SharedFields';
-import getHelpText from '../Inventory.helptext';
-
-const VMwareSubForm = ({ autoPopulateCredential }) => {
- const helpText = getHelpText();
- const { setFieldValue, setFieldTouched } = useFormikContext();
- const [credentialField, credentialMeta, credentialHelpers] =
- useField('credential');
- const config = useConfig();
- const handleCredentialUpdate = useCallback(
- (value) => {
- setFieldValue('credential', value);
- setFieldTouched('credential', true, false);
- },
- [setFieldValue, setFieldTouched]
- );
-
- const docsBaseUrl = getDocsBaseUrl(config);
-
- return (
- <>
- credentialHelpers.setTouched()}
- onChange={handleCredentialUpdate}
- value={credentialField.value}
- required
- autoPopulate={autoPopulateCredential}
- validate={required(t`Select a value for this field`)}
- />
-
-
-
-
-
-
- >
- );
-};
-
-export default VMwareSubForm;
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.test.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.test.js
deleted file mode 100644
index f9c4bb4cce26..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.test.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { CredentialsAPI } from 'api';
-import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
-import VMwareSubForm from './VMwareSubForm';
-
-jest.mock('../../../../api');
-
-const initialValues = {
- credential: null,
- overwrite: false,
- overwrite_vars: false,
- source_path: '',
- source_project: null,
- source_script: null,
- source_vars: '---\n',
- update_cache_timeout: 0,
- update_on_launch: true,
- verbosity: 1,
-};
-
-const mockSourceOptions = {
- actions: {
- POST: {},
- },
-};
-
-describe('', () => {
- let wrapper;
-
- beforeEach(async () => {
- CredentialsAPI.read.mockResolvedValue({
- data: { count: 0, results: [] },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('should render subform fields', () => {
- expect(wrapper.find('FormGroup[label="Credential"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
- expect(
- wrapper.find('FormGroup[label="Cache timeout (seconds)"]')
- ).toHaveLength(1);
- expect(
- wrapper.find('VariablesField[label="Source variables"]')
- ).toHaveLength(1);
- });
-
- test('should make expected api calls', () => {
- expect(CredentialsAPI.read).toHaveBeenCalledTimes(1);
- expect(CredentialsAPI.read).toHaveBeenCalledWith({
- credential_type__namespace: 'vmware',
- order_by: 'name',
- page: 1,
- page_size: 5,
- });
- });
-});
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.js
deleted file mode 100644
index ddf7e4152b37..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import React, { useCallback } from 'react';
-import { useField, useFormikContext } from 'formik';
-import { t } from '@lingui/macro';
-import { useConfig } from 'contexts/Config';
-import getDocsBaseUrl from 'util/getDocsBaseUrl';
-import CredentialLookup from 'components/Lookup/CredentialLookup';
-import { required } from 'util/validators';
-import {
- OptionsField,
- VerbosityField,
- EnabledVarField,
- EnabledValueField,
- HostFilterField,
- SourceVarsField,
-} from './SharedFields';
-import getHelpText from '../Inventory.helptext';
-
-const VirtualizationSubForm = ({ autoPopulateCredential }) => {
- const helpText = getHelpText();
- const { setFieldValue, setFieldTouched } = useFormikContext();
- const [credentialField, credentialMeta, credentialHelpers] =
- useField('credential');
- const config = useConfig();
-
- const handleCredentialUpdate = useCallback(
- (value) => {
- setFieldValue('credential', value);
- setFieldTouched('credential', true, false);
- },
- [setFieldValue, setFieldTouched]
- );
-
- const docsBaseUrl = getDocsBaseUrl(config);
- return (
- <>
- credentialHelpers.setTouched()}
- onChange={handleCredentialUpdate}
- value={credentialField.value}
- required
- autoPopulate={autoPopulateCredential}
- validate={required(t`Select a value for this field`)}
- />
-
-
-
-
-
-
- >
- );
-};
-
-export default VirtualizationSubForm;
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.test.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.test.js
deleted file mode 100644
index 6b289600a98c..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.test.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { CredentialsAPI } from 'api';
-import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
-import VirtualizationSubForm from './VirtualizationSubForm';
-
-jest.mock('../../../../api');
-
-const initialValues = {
- credential: null,
- overwrite: false,
- overwrite_vars: false,
- source_path: '',
- source_project: null,
- source_script: null,
- source_vars: '---\n',
- update_cache_timeout: 0,
- update_on_launch: true,
- verbosity: 1,
-};
-
-describe('', () => {
- let wrapper;
-
- beforeEach(async () => {
- CredentialsAPI.read.mockResolvedValue({
- data: { count: 0, results: [] },
- });
-
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('should render subform fields', () => {
- expect(wrapper.find('FormGroup[label="Credential"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Verbosity"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Update options"]')).toHaveLength(1);
- expect(
- wrapper.find('FormGroup[label="Cache timeout (seconds)"]')
- ).toHaveLength(1);
- expect(
- wrapper.find('VariablesField[label="Source variables"]')
- ).toHaveLength(1);
- });
-
- test('should make expected api calls', () => {
- expect(CredentialsAPI.read).toHaveBeenCalledTimes(1);
- expect(CredentialsAPI.read).toHaveBeenCalledWith({
- credential_type__namespace: 'rhv',
- order_by: 'name',
- page: 1,
- page_size: 5,
- });
- });
-});
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/index.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/index.js
deleted file mode 100644
index d9ab42201a4b..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/index.js
+++ /dev/null
@@ -1,10 +0,0 @@
-export { default as AzureSubForm } from './AzureSubForm';
-export { default as EC2SubForm } from './EC2SubForm';
-export { default as GCESubForm } from './GCESubForm';
-export { default as InsightsSubForm } from './InsightsSubForm';
-export { default as OpenStackSubForm } from './OpenStackSubForm';
-export { default as SCMSubForm } from './SCMSubForm';
-export { default as SatelliteSubForm } from './SatelliteSubForm';
-export { default as ControllerSubForm } from './ControllerSubForm';
-export { default as VMwareSubForm } from './VMwareSubForm';
-export { default as VirtualizationSubForm } from './VirtualizationSubForm';
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSyncButton.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSyncButton.js
deleted file mode 100644
index 5205b2252c63..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSyncButton.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import React, { useCallback } from 'react';
-
-import { t } from '@lingui/macro';
-import PropTypes from 'prop-types';
-import { Button, Tooltip } from '@patternfly/react-core';
-import { SyncIcon } from '@patternfly/react-icons';
-import useRequest, { useDismissableError } from 'hooks/useRequest';
-import AlertModal from 'components/AlertModal/AlertModal';
-import ErrorDetail from 'components/ErrorDetail/ErrorDetail';
-import { InventorySourcesAPI } from 'api';
-
-function InventorySourceSyncButton({ source, icon }) {
- const {
- isLoading: startSyncLoading,
- error: startSyncError,
- request: startSyncProcess,
- } = useRequest(
- useCallback(async () => {
- const {
- data: { status },
- } = await InventorySourcesAPI.createSyncStart(source.id);
-
- return status;
- }, [source.id]),
- {}
- );
-
- const { error: startError, dismissError: dismissStartError } =
- useDismissableError(startSyncError);
-
- return (
- <>
-
-
-
-
- {startError && (
-
- {t`Failed to sync inventory source.`}
-
-
- )}
- >
- );
-}
-
-InventorySourceSyncButton.defaultProps = {
- source: {},
- icon: true,
-};
-
-InventorySourceSyncButton.propTypes = {
- source: PropTypes.shape({}),
- icon: PropTypes.bool,
-};
-
-export default InventorySourceSyncButton;
diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSyncButton.test.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSyncButton.test.js
deleted file mode 100644
index 3b3d479dc231..000000000000
--- a/awx/ui/src/screens/Inventory/shared/InventorySourceSyncButton.test.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { InventorySourcesAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import InventorySourceSyncButton from './InventorySourceSyncButton';
-
-jest.mock('../../../api');
-
-const source = { id: 1, name: 'Foo', source: 'Source Bar' };
-const onSyncLoading = jest.fn();
-
-describe('', () => {
- let wrapper;
- beforeEach(() => {
- wrapper = mountWithContexts(
- {}}
- />
- );
- });
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should mount properly', () => {
- expect(wrapper.find('InventorySourceSyncButton').length).toBe(1);
- });
-
- test('should render start sync button', () => {
- expect(wrapper.find('SyncIcon').length).toBe(1);
- expect(
- wrapper.find('Button[aria-label="Start sync source"]').prop('isDisabled')
- ).toBe(false);
- });
-
- test('should start sync properly', async () => {
- InventorySourcesAPI.createSyncStart.mockResolvedValue({
- data: { status: 'pending' },
- });
-
- await act(async () =>
- wrapper.find('Button[aria-label="Start sync source"]').simulate('click')
- );
- expect(InventorySourcesAPI.createSyncStart).toBeCalledWith(1);
- });
-
- test('should throw error on sync start properly', async () => {
- InventorySourcesAPI.createSyncStart.mockRejectedValueOnce(
- new Error({
- response: {
- config: {
- method: 'post',
- url: '/api/v2/inventory_sources/update',
- },
- data: 'An error occurred',
- status: 403,
- },
- })
- );
-
- await act(async () =>
- wrapper.find('Button[aria-label="Start sync source"]').simulate('click')
- );
- wrapper.update();
- expect(wrapper.find('AlertModal').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Inventory/shared/SmartInventoryForm.js b/awx/ui/src/screens/Inventory/shared/SmartInventoryForm.js
deleted file mode 100644
index 17d5fc091f4a..000000000000
--- a/awx/ui/src/screens/Inventory/shared/SmartInventoryForm.js
+++ /dev/null
@@ -1,197 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-import { Formik, useField, useFormikContext } from 'formik';
-import { t } from '@lingui/macro';
-import { useLocation } from 'react-router-dom';
-import { func, shape, arrayOf } from 'prop-types';
-import { Form } from '@patternfly/react-core';
-import { InstanceGroup } from 'types';
-import { VariablesField } from 'components/CodeEditor';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import FormActionGroup from 'components/FormActionGroup';
-import FormField, { FormSubmitError } from 'components/FormField';
-import { FormColumnLayout, FormFullWidthLayout } from 'components/FormLayout';
-import {
- toHostFilter,
- toSearchParams,
-} from 'components/Lookup/shared/HostFilterUtils';
-import HostFilterLookup from 'components/Lookup/HostFilterLookup';
-import InstanceGroupsLookup from 'components/Lookup/InstanceGroupsLookup';
-import OrganizationLookup from 'components/Lookup/OrganizationLookup';
-import useRequest from 'hooks/useRequest';
-import { required } from 'util/validators';
-import { InventoriesAPI } from 'api';
-
-const SmartInventoryFormFields = ({ inventory }) => {
- const { setFieldValue, setFieldTouched } = useFormikContext();
- const [organizationField, organizationMeta, organizationHelpers] =
- useField('organization');
- const [instanceGroupsField, , instanceGroupsHelpers] =
- useField('instance_groups');
- const [hostFilterField, hostFilterMeta, hostFilterHelpers] = useField({
- name: 'host_filter',
- validate: required(null),
- });
- const handleOrganizationUpdate = useCallback(
- (value) => {
- setFieldValue('organization', value);
- setFieldTouched('organization', true, false);
- },
- [setFieldValue, setFieldTouched]
- );
-
- return (
- <>
-
-
- organizationHelpers.setTouched()}
- onChange={handleOrganizationUpdate}
- value={organizationField.value}
- required
- autoPopulate={!inventory?.id}
- validate={required(t`Select a value for this field`)}
- />
- {
- hostFilterHelpers.setValue(value);
- }}
- onBlur={() => hostFilterHelpers.setTouched()}
- isValid={!hostFilterMeta.touched || !hostFilterMeta.error}
- isDisabled={!organizationField.value}
- enableNegativeFiltering={false}
- enableRelatedFuzzyFiltering={false}
- />
- {
- instanceGroupsHelpers.setValue(value);
- }}
- tooltip={t`Select the Instance Groups for this Inventory to run on.`}
- />
-
-
-
- >
- );
-};
-
-function SmartInventoryForm({
- inventory,
- instanceGroups,
- onSubmit,
- onCancel,
- submitError,
-}) {
- const { search } = useLocation();
- const queryParams = new URLSearchParams(search);
- const hostFilterFromParams = queryParams.get('host_filter');
-
- function addHostFilter(string) {
- if (!string) return null;
- if (string.includes('ansible_facts') && !string.includes('host_filter')) {
- return string.replace('ansible_facts', 'host_filter=ansible_facts');
- }
- return string;
- }
-
- const initialValues = {
- description: inventory.description || '',
- host_filter:
- addHostFilter(inventory.host_filter) ||
- (hostFilterFromParams
- ? toHostFilter(toSearchParams(hostFilterFromParams))
- : ''),
- instance_groups: instanceGroups || [],
- kind: 'smart',
- name: inventory.name || '',
- organization: inventory.summary_fields?.organization || null,
- variables: inventory.variables || '---',
- };
-
- const {
- isLoading,
- error: optionsError,
- request: fetchOptions,
- result: options,
- } = useRequest(
- useCallback(async () => {
- const { data } = await InventoriesAPI.readOptions();
- return data;
- }, []),
- null
- );
-
- useEffect(() => {
- fetchOptions();
- }, [fetchOptions]);
-
- if (isLoading) {
- return ;
- }
-
- if (optionsError) {
- return ;
- }
-
- return (
- {
- onSubmit(values);
- }}
- >
- {(formik) => (
-
- )}
-
- );
-}
-
-SmartInventoryForm.propTypes = {
- instanceGroups: arrayOf(InstanceGroup),
- inventory: shape({}),
- onCancel: func.isRequired,
- onSubmit: func.isRequired,
- submitError: shape({}),
-};
-
-SmartInventoryForm.defaultProps = {
- instanceGroups: [],
- inventory: {},
- submitError: null,
-};
-
-export default SmartInventoryForm;
diff --git a/awx/ui/src/screens/Inventory/shared/SmartInventoryForm.test.js b/awx/ui/src/screens/Inventory/shared/SmartInventoryForm.test.js
deleted file mode 100644
index c42c38f8f65c..000000000000
--- a/awx/ui/src/screens/Inventory/shared/SmartInventoryForm.test.js
+++ /dev/null
@@ -1,222 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { InventoriesAPI, OrganizationsAPI, InstanceGroupsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import SmartInventoryForm from './SmartInventoryForm';
-
-jest.mock('../../../api');
-
-const mockFormValues = {
- kind: 'smart',
- name: 'new smart inventory',
- description: '',
- organization: { id: 1, name: 'mock organization' },
- host_filter:
- 'name__icontains=mock and name__icontains=foo and groups__name__icontains=mock group',
- instance_groups: [{ id: 123, name: 'mock instance group' }],
- variables: '---',
-};
-
-describe('', () => {
- let wrapper;
- const onSubmit = jest.fn();
-
- beforeAll(async () => {
- OrganizationsAPI.read.mockResolvedValue({
- data: { results: [], count: 0 },
- });
- InstanceGroupsAPI.read.mockResolvedValue({
- data: { results: [], count: 0 },
- });
- InventoriesAPI.readOptions.mockResolvedValue({
- data: { actions: { POST: true } },
- });
- await act(async () => {
- wrapper = mountWithContexts(
- {}} onSubmit={onSubmit} />
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- describe('when initialized by users with POST capability', () => {
- test('should enable save button', () => {
- expect(wrapper.find('Button[aria-label="Save"]').prop('isDisabled')).toBe(
- false
- );
- });
-
- test('should show expected form fields', () => {
- expect(wrapper.find('FormGroup[label="Name"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Description"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Organization"]')).toHaveLength(1);
- expect(wrapper.find('FormGroup[label="Smart host filter"]')).toHaveLength(
- 1
- );
- expect(wrapper.find('FormGroup[label="Instance Groups"]')).toHaveLength(
- 1
- );
- expect(wrapper.find('VariablesField[label="Variables"]')).toHaveLength(1);
- expect(wrapper.find('Button[aria-label="Save"]')).toHaveLength(1);
- expect(wrapper.find('Button[aria-label="Cancel"]')).toHaveLength(1);
- });
-
- test('should enable host filter field when organization field has a value', async () => {
- expect(wrapper.find('HostFilterLookup').prop('isDisabled')).toBe(true);
- await act(async () => {
- wrapper.find('OrganizationLookup').invoke('onBlur')();
- wrapper.find('OrganizationLookup').invoke('onChange')(
- mockFormValues.organization
- );
- });
- wrapper.update();
- expect(wrapper.find('HostFilterLookup').prop('isDisabled')).toBe(false);
- });
-
- test('should show error when form is saved without a host filter value', async () => {
- expect(wrapper.find('HostFilterLookup #host-filter-helper').length).toBe(
- 0
- );
- wrapper.find('input#name').simulate('change', {
- target: { value: mockFormValues.name, name: 'name' },
- });
- await act(async () => {
- wrapper.find('button[aria-label="Save"]').simulate('click');
- });
- wrapper.update();
- const hostFilterError = wrapper.find(
- 'HostFilterLookup #host-filter-helper'
- );
- expect(hostFilterError.length).toBe(1);
- expect(hostFilterError.text()).toContain('This field must not be blank');
- expect(onSubmit).not.toHaveBeenCalled();
- });
-
- test('should display filter chips when host filter has a value', async () => {
- await act(async () => {
- wrapper.find('HostFilterLookup').invoke('onBlur')();
- wrapper.find('HostFilterLookup').invoke('onChange')(
- mockFormValues.host_filter
- );
- });
- wrapper.update();
- const nameChipGroup = wrapper.find(
- 'HostFilterLookup ChipGroup[categoryName="Name"]'
- );
- const groupChipGroup = wrapper.find(
- 'HostFilterLookup ChipGroup[categoryName="Group"]'
- );
- expect(nameChipGroup.find('Chip').length).toBe(2);
- expect(groupChipGroup.find('Chip').length).toBe(1);
- });
-
- test('should display filter chips for advanced host filter', async () => {
- await act(async () => {
- wrapper.find('HostFilterLookup').invoke('onBlur')();
- wrapper.find('HostFilterLookup').invoke('onChange')(
- 'name__contains=f or name__contains=o'
- );
- });
- wrapper.update();
- const nameChipGroup = wrapper.find(
- 'HostFilterLookup ChipGroup[categoryName="name__contains"]'
- );
- expect(nameChipGroup.find('Chip').length).toBe(2);
- expect(nameChipGroup.find('Chip').at(0).prop('children')).toBe('f');
- expect(nameChipGroup.find('Chip').at(1).prop('children')).toBe('o');
- });
-
- test('should submit expected form values on save', async () => {
- await act(async () => {
- wrapper.find('InstanceGroupsLookup').invoke('onChange')(
- mockFormValues.instance_groups
- );
- });
- await act(async () => {
- wrapper.find('HostFilterLookup').invoke('onBlur')();
- wrapper.find('HostFilterLookup').invoke('onChange')(
- mockFormValues.host_filter
- );
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('button[aria-label="Save"]').simulate('click');
- });
- wrapper.update();
- expect(onSubmit).toHaveBeenCalledWith(mockFormValues);
- });
- });
-
- test('should pre-fill the host filter when query param present and not editing', async () => {
- InventoriesAPI.readOptions.mockResolvedValue({
- data: { actions: { POST: true } },
- });
- let newWrapper;
- const history = createMemoryHistory({
- initialEntries: [
- '/inventories/smart_inventory/add?host_filter=name__icontains%3Dfoo',
- ],
- });
- await act(async () => {
- newWrapper = mountWithContexts(
- {}} onSubmit={() => {}} />,
- {
- context: { router: { history } },
- }
- );
- });
- await waitForElement(newWrapper, 'ContentLoading', (el) => el.length === 0);
- newWrapper.update();
- const nameChipGroup = newWrapper.find(
- 'HostFilterLookup ChipGroup[categoryName="Name"]'
- );
- expect(nameChipGroup.find('Chip').length).toBe(1);
- });
-
- test('should throw content error when option request fails', async () => {
- let newWrapper;
- InventoriesAPI.readOptions.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- newWrapper = mountWithContexts(
- {}} onSubmit={() => {}} />
- );
- });
- expect(newWrapper.find('ContentError').length).toBe(0);
- newWrapper.update();
- expect(newWrapper.find('ContentError').length).toBe(1);
- jest.clearAllMocks();
- });
-
- test('should throw content error when option request fails', async () => {
- let newWrapper;
- const error = {
- response: {
- data: { detail: 'An error occurred' },
- },
- };
- await act(async () => {
- newWrapper = mountWithContexts(
- {}}
- onSubmit={() => {}}
- />
- );
- });
- expect(newWrapper.find('FormSubmitError').length).toBe(1);
- expect(newWrapper.find('SmartInventoryForm').prop('submitError')).toEqual(
- error
- );
- jest.clearAllMocks();
- });
-});
diff --git a/awx/ui/src/screens/Inventory/shared/data.host.json b/awx/ui/src/screens/Inventory/shared/data.host.json
deleted file mode 100644
index a4975ad01ba5..000000000000
--- a/awx/ui/src/screens/Inventory/shared/data.host.json
+++ /dev/null
@@ -1,86 +0,0 @@
-{
- "id": 2,
- "type": "host",
- "url": "/api/v2/hosts/2/",
- "related": {
- "created_by": "/api/v2/users/1/",
- "modified_by": "/api/v2/users/1/",
- "variable_data": "/api/v2/hosts/2/variable_data/",
- "groups": "/api/v2/hosts/2/groups/",
- "all_groups": "/api/v2/hosts/2/all_groups/",
- "job_events": "/api/v2/hosts/2/job_events/",
- "job_host_summaries": "/api/v2/hosts/2/job_host_summaries/",
- "activity_stream": "/api/v2/hosts/2/activity_stream/",
- "inventory_sources": "/api/v2/hosts/2/inventory_sources/",
- "smart_inventories": "/api/v2/hosts/2/smart_inventories/",
- "ad_hoc_commands": "/api/v2/hosts/2/ad_hoc_commands/",
- "ad_hoc_command_events": "/api/v2/hosts/2/ad_hoc_command_events/",
- "insights": "/api/v2/hosts/2/insights/",
- "ansible_facts": "/api/v2/hosts/2/ansible_facts/",
- "inventory": "/api/v2/inventories/3/",
- "last_job": "/api/v2/jobs/3/",
- "last_job_host_summary": "/api/v2/job_host_summaries/1/"
- },
- "summary_fields": {
- "inventory": {
- "id": 3,
- "name": "Mikes Inventory",
- "description": "",
- "has_active_failures": false,
- "total_hosts": 3,
- "hosts_with_active_failures": 0,
- "total_groups": 0,
- "groups_with_active_failures": 0,
- "has_inventory_sources": true,
- "total_inventory_sources": 1,
- "inventory_sources_with_failures": 0,
- "organization_id": 3,
- "kind": ""
- },
- "last_job": {
- "id": 3,
- "name": "Ping",
- "description": "",
- "finished": "2019-10-28T21:29:08.880572Z",
- "status": "successful",
- "failed": false,
- "job_template_id": 9,
- "job_template_name": "Ping"
- },
- "last_job_host_summary": {
- "id": 1,
- "failed": false
- },
- "user_capabilities": {
- "edit": true,
- "delete": true
- },
- "groups": {
- "count": 0,
- "results": []
- },
- "recent_jobs": [
- {
- "id": 3,
- "name": "Ping",
- "status": "successful",
- "finished": "2019-10-28T21:29:08.880572Z",
- "type": "job"
- }
- ]
- },
- "created": "2019-10-28T21:26:54.508081Z",
- "modified": "2019-10-29T20:18:41.915796Z",
- "name": "localhost",
- "description": "localhost description",
- "inventory": 3,
- "enabled": true,
- "instance_id": "",
- "variables": "---\nansible_connection: local",
- "has_active_failures": false,
- "has_inventory_sources": false,
- "last_job": 3,
- "last_job_host_summary": 1,
- "insights_system_id": null,
- "ansible_facts_modified": null
-}
\ No newline at end of file
diff --git a/awx/ui/src/screens/Inventory/shared/data.hostFacts.json b/awx/ui/src/screens/Inventory/shared/data.hostFacts.json
deleted file mode 100644
index 0675ffb4ef93..000000000000
--- a/awx/ui/src/screens/Inventory/shared/data.hostFacts.json
+++ /dev/null
@@ -1,1243 +0,0 @@
-{
- "ansible_lo": {
- "mtu": 65536,
- "ipv4": {
- "address": "127.0.0.1",
- "netmask": "255.0.0.0",
- "network": "127.0.0.0",
- "broadcast": "host"
- },
- "type": "loopback",
- "active": true,
- "device": "lo",
- "promisc": false,
- "features": {
- "rx_all": "off [fixed]",
- "rx_fcs": "off [fixed]",
- "highdma": "on [fixed]",
- "fcoe_mtu": "off [fixed]",
- "loopback": "on [fixed]",
- "busy_poll": "off [fixed]",
- "netns_local": "on [fixed]",
- "tx_lockless": "on [fixed]",
- "hw_tc_offload": "off [fixed]",
- "tx_gso_robust": "off [fixed]",
- "l2_fwd_offload": "off [fixed]",
- "ntuple_filters": "off [fixed]",
- "rx_vlan_filter": "off [fixed]",
- "scatter_gather": "on",
- "tx_gso_partial": "off [fixed]",
- "receive_hashing": "off [fixed]",
- "rx_checksumming": "on [fixed]",
- "rx_vlan_offload": "off [fixed]",
- "tx_checksumming": "on",
- "tx_nocache_copy": "off [fixed]",
- "tx_vlan_offload": "off [fixed]",
- "vlan_challenged": "on [fixed]",
- "tx_checksum_ipv4": "off [fixed]",
- "tx_checksum_ipv6": "off [fixed]",
- "tx_checksum_sctp": "on [fixed]",
- "tx_scatter_gather": "on [fixed]",
- "rx_vlan_stag_filter": "off [fixed]",
- "tx_gre_segmentation": "off [fixed]",
- "tx_tcp_segmentation": "on",
- "tx_checksum_fcoe_crc": "off [fixed]",
- "tx_fcoe_segmentation": "off [fixed]",
- "tx_sctp_segmentation": "on",
- "tx_tcp6_segmentation": "on",
- "large_receive_offload": "off [fixed]",
- "rx_vlan_stag_hw_parse": "off [fixed]",
- "tx_checksum_ip_generic": "on [fixed]",
- "tx_ipxip4_segmentation": "off [fixed]",
- "tx_ipxip6_segmentation": "off [fixed]",
- "tx_vlan_stag_hw_insert": "off [fixed]",
- "generic_receive_offload": "on",
- "tx_tcp_ecn_segmentation": "on",
- "tx_udp_tnl_segmentation": "off [fixed]",
- "tcp_segmentation_offload": "on",
- "tx_gre_csum_segmentation": "off [fixed]",
- "udp_fragmentation_offload": "on",
- "tx_scatter_gather_fraglist": "on [fixed]",
- "generic_segmentation_offload": "on",
- "tx_tcp_mangleid_segmentation": "on",
- "tx_udp_tnl_csum_segmentation": "off [fixed]"
- },
- "timestamping": [
- "rx_software",
- "software"
- ],
- "hw_timestamp_filters": []
- },
- "ansible_dns": {
- "options": {
- "ndots": "0"
- },
- "nameservers": [
- "127.0.0.11"
- ]
- },
- "ansible_env": {
- "_": "/usr/libexec/platform-python",
- "OS": " Operating System: Docker Desktop",
- "TZ": "UTC",
- "PWD": "/tmp/awx_13_r1ffeqze/project",
- "HOME": "/var/lib/awx",
- "LANG": "\"en-us\"",
- "PATH": "/var/lib/awx/venv/ansible/bin:/var/lib/awx/venv/awx/bin:/var/lib/awx/venv/awx/bin:/usr/local/n/versions/node/10.15.0/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
- "SHLVL": "4",
- "JOB_ID": "13",
- "LC_ALL": "en_US.UTF-8",
- "MFLAGS": "-w",
- "OLDPWD": "/awx_devel",
- "AWX_HOST": "https://towerhost",
- "HOSTNAME": "awx",
- "LANGUAGE": "en_US:en",
- "SDB_HOST": "0.0.0.0",
- "SDB_PORT": "7899",
- "MAKEFLAGS": "w",
- "MAKELEVEL": "2",
- "PYTHONPATH": "/var/lib/awx/venv/ansible/lib/python3.6/site-packages:/awx_devel/awx/lib:/var/lib/awx/venv/awx/lib/python3.6/site-packages/ansible_runner/callbacks",
- "CURRENT_UID": "501",
- "VIRTUAL_ENV": "/var/lib/awx/venv/ansible",
- "INVENTORY_ID": "1",
- "MAX_EVENT_RES": "700000",
- "ANSIBLE_LIBRARY": "/awx_devel/awx/plugins/library",
- "SDB_NOTIFY_HOST": "docker.for.mac.host.internal",
- "AWX_GROUP_QUEUES": "tower",
- "PROJECT_REVISION": "9e2cd25bfb26ba82f40cf31276e1942bf38b3a30",
- "ANSIBLE_VENV_PATH": "/var/lib/awx/venv/ansible",
- "ANSIBLE_ROLES_PATH": "/tmp/awx_13_r1ffeqze/requirements_roles:~/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles",
- "RUNNER_OMIT_EVENTS": "False",
- "SUPERVISOR_ENABLED": "1",
- "ANSIBLE_FORCE_COLOR": "True",
- "ANSIBLE_CACHE_PLUGIN": "jsonfile",
- "AWX_PRIVATE_DATA_DIR": "/tmp/awx_13_r1ffeqze",
- "SUPERVISOR_GROUP_NAME": "tower-processes",
- "SUPERVISOR_SERVER_URL": "unix:///tmp/supervisor.sock",
- "DJANGO_SETTINGS_MODULE": "awx.settings.development",
- "ANSIBLE_STDOUT_CALLBACK": "awx_display",
- "SUPERVISOR_PROCESS_NAME": "awx-dispatcher",
- "ANSIBLE_CALLBACK_PLUGINS": "/awx_devel/awx/plugins/callback:/var/lib/awx/venv/awx/lib/python3.6/site-packages/ansible_runner/callbacks",
- "ANSIBLE_COLLECTIONS_PATHS": "/tmp/awx_13_r1ffeqze/requirements_collections:~/.ansible/collections:/usr/share/ansible/collections",
- "ANSIBLE_HOST_KEY_CHECKING": "False",
- "RUNNER_ONLY_FAILED_EVENTS": "False",
- "ANSIBLE_RETRY_FILES_ENABLED": "False",
- "ANSIBLE_SSH_CONTROL_PATH_DIR": "/tmp/awx_13_r1ffeqze/cp",
- "ANSIBLE_CACHE_PLUGIN_CONNECTION": "/tmp/awx_13_r1ffeqze/artifacts/13/fact_cache",
- "DJANGO_LIVE_TEST_SERVER_ADDRESS": "localhost:9013-9199",
- "ANSIBLE_INVENTORY_UNPARSED_FAILED": "True",
- "ANSIBLE_PARAMIKO_RECORD_HOST_KEYS": "False"
- },
- "ansible_lsb": {},
- "ansible_eth0": {
- "mtu": 1500,
- "ipv4": {
- "address": "172.18.0.5",
- "netmask": "255.255.0.0",
- "network": "172.18.0.0",
- "broadcast": "172.18.255.255"
- },
- "type": "ether",
- "speed": 10000,
- "active": true,
- "device": "eth0",
- "promisc": false,
- "features": {
- "rx_all": "off [fixed]",
- "rx_fcs": "off [fixed]",
- "highdma": "on",
- "fcoe_mtu": "off [fixed]",
- "loopback": "off [fixed]",
- "busy_poll": "off [fixed]",
- "netns_local": "off [fixed]",
- "tx_lockless": "on [fixed]",
- "hw_tc_offload": "off [fixed]",
- "tx_gso_robust": "off [fixed]",
- "l2_fwd_offload": "off [fixed]",
- "ntuple_filters": "off [fixed]",
- "rx_vlan_filter": "off [fixed]",
- "scatter_gather": "on",
- "tx_gso_partial": "off [fixed]",
- "receive_hashing": "off [fixed]",
- "rx_checksumming": "on",
- "rx_vlan_offload": "on",
- "tx_checksumming": "on",
- "tx_nocache_copy": "off",
- "tx_vlan_offload": "on",
- "vlan_challenged": "off [fixed]",
- "tx_checksum_ipv4": "off [fixed]",
- "tx_checksum_ipv6": "off [fixed]",
- "tx_checksum_sctp": "on",
- "tx_scatter_gather": "on",
- "rx_vlan_stag_filter": "off [fixed]",
- "tx_gre_segmentation": "on",
- "tx_tcp_segmentation": "on",
- "tx_checksum_fcoe_crc": "off [fixed]",
- "tx_fcoe_segmentation": "off [fixed]",
- "tx_sctp_segmentation": "on",
- "tx_tcp6_segmentation": "on",
- "large_receive_offload": "off [fixed]",
- "rx_vlan_stag_hw_parse": "on",
- "tx_checksum_ip_generic": "on",
- "tx_ipxip4_segmentation": "on",
- "tx_ipxip6_segmentation": "on",
- "tx_vlan_stag_hw_insert": "on",
- "generic_receive_offload": "on",
- "tx_tcp_ecn_segmentation": "on",
- "tx_udp_tnl_segmentation": "on",
- "tcp_segmentation_offload": "on",
- "tx_gre_csum_segmentation": "on",
- "udp_fragmentation_offload": "on",
- "tx_scatter_gather_fraglist": "on",
- "generic_segmentation_offload": "on",
- "tx_tcp_mangleid_segmentation": "on",
- "tx_udp_tnl_csum_segmentation": "on"
- },
- "macaddress": "02:42:ac:12:00:05",
- "timestamping": [
- "rx_software",
- "software"
- ],
- "hw_timestamp_filters": []
- },
- "ansible_fips": false,
- "ansible_fqdn": "awx",
- "module_setup": true,
- "ansible_local": {},
- "ansible_tunl0": {
- "mtu": 1480,
- "type": "unknown",
- "active": false,
- "device": "tunl0",
- "promisc": false,
- "features": {
- "rx_all": "off [fixed]",
- "rx_fcs": "off [fixed]",
- "highdma": "on",
- "fcoe_mtu": "off [fixed]",
- "loopback": "off [fixed]",
- "busy_poll": "off [fixed]",
- "netns_local": "on [fixed]",
- "tx_lockless": "on [fixed]",
- "hw_tc_offload": "off [fixed]",
- "tx_gso_robust": "off [fixed]",
- "l2_fwd_offload": "off [fixed]",
- "ntuple_filters": "off [fixed]",
- "rx_vlan_filter": "off [fixed]",
- "scatter_gather": "on",
- "tx_gso_partial": "off [fixed]",
- "receive_hashing": "off [fixed]",
- "rx_checksumming": "off [fixed]",
- "rx_vlan_offload": "off [fixed]",
- "tx_checksumming": "on",
- "tx_nocache_copy": "off",
- "tx_vlan_offload": "off [fixed]",
- "vlan_challenged": "off [fixed]",
- "tx_checksum_ipv4": "off [fixed]",
- "tx_checksum_ipv6": "off [fixed]",
- "tx_checksum_sctp": "off [fixed]",
- "tx_scatter_gather": "on",
- "rx_vlan_stag_filter": "off [fixed]",
- "tx_gre_segmentation": "off [fixed]",
- "tx_tcp_segmentation": "on",
- "tx_checksum_fcoe_crc": "off [fixed]",
- "tx_fcoe_segmentation": "off [fixed]",
- "tx_sctp_segmentation": "on",
- "tx_tcp6_segmentation": "on",
- "large_receive_offload": "off [fixed]",
- "rx_vlan_stag_hw_parse": "off [fixed]",
- "tx_checksum_ip_generic": "on",
- "tx_ipxip4_segmentation": "off [fixed]",
- "tx_ipxip6_segmentation": "off [fixed]",
- "tx_vlan_stag_hw_insert": "off [fixed]",
- "generic_receive_offload": "on",
- "tx_tcp_ecn_segmentation": "on",
- "tx_udp_tnl_segmentation": "off [fixed]",
- "tcp_segmentation_offload": "on",
- "tx_gre_csum_segmentation": "off [fixed]",
- "udp_fragmentation_offload": "on",
- "tx_scatter_gather_fraglist": "on",
- "generic_segmentation_offload": "on",
- "tx_tcp_mangleid_segmentation": "on",
- "tx_udp_tnl_csum_segmentation": "off [fixed]"
- },
- "macaddress": "00:00:00:00",
- "timestamping": [
- "rx_software",
- "software"
- ],
- "hw_timestamp_filters": []
- },
- "gather_subset": [
- "all"
- ],
- "ansible_domain": "",
- "ansible_kernel": "4.9.184-linuxkit",
- "ansible_mounts": [
- {
- "uuid": "N/A",
- "mount": "/etc/resolv.conf",
- "device": "/dev/sda1",
- "fstype": "ext4",
- "options": "rw,nosuid,relatime,data=ordered,bind",
- "block_size": 4096,
- "block_used": 3938899,
- "inode_used": 479010,
- "size_total": 62725623808,
- "block_total": 15313873,
- "inode_total": 3907584,
- "size_available": 46591893504,
- "block_available": 11374974,
- "inode_available": 3428574
- },
- {
- "uuid": "N/A",
- "mount": "/etc/hostname",
- "device": "/dev/sda1",
- "fstype": "ext4",
- "options": "rw,nosuid,relatime,data=ordered,bind",
- "block_size": 4096,
- "block_used": 3938899,
- "inode_used": 479010,
- "size_total": 62725623808,
- "block_total": 15313873,
- "inode_total": 3907584,
- "size_available": 46591893504,
- "block_available": 11374974,
- "inode_available": 3428574
- },
- {
- "uuid": "N/A",
- "mount": "/etc/hosts",
- "device": "/dev/sda1",
- "fstype": "ext4",
- "options": "rw,nosuid,relatime,data=ordered,bind",
- "block_size": 4096,
- "block_used": 3938899,
- "inode_used": 479010,
- "size_total": 62725623808,
- "block_total": 15313873,
- "inode_total": 3907584,
- "size_available": 46591893504,
- "block_available": 11374974,
- "inode_available": 3428574
- }
- ],
- "ansible_python": {
- "type": "cpython",
- "version": {
- "major": 3,
- "micro": 8,
- "minor": 6,
- "serial": 0,
- "releaselevel": "final"
- },
- "executable": "/usr/libexec/platform-python",
- "version_info": [
- 3,
- 6,
- 8,
- "final",
- 0
- ],
- "has_sslcontext": true
- },
- "ansible_system": "Linux",
- "ansible_cmdline": {
- "root": "/dev/sr0",
- "text": true,
- "panic": "1",
- "console": "ttyS1",
- "vsyscall": "emulate",
- "BOOT_IMAGE": "/boot/kernel",
- "page_poison": "1"
- },
- "ansible_devices": {
- "sda": {
- "host": "",
- "size": "59.60 GB",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": "BHYVE SATA DISK",
- "vendor": "ATA",
- "holders": [],
- "sectors": "124999680",
- "virtual": 1,
- "removable": "0",
- "partitions": {
- "sda1": {
- "size": "59.60 GB",
- "uuid": null,
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "start": "2048",
- "holders": [],
- "sectors": "124997632",
- "sectorsize": 512
- }
- },
- "rotational": "1",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "deadline",
- "support_discard": "4096",
- "sas_device_handle": null
- },
- "sr0": {
- "host": "",
- "size": "453.38 MB",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": "BHYVE DVD-ROM",
- "vendor": "BHYVE",
- "holders": [],
- "sectors": "928528",
- "virtual": 1,
- "removable": "1",
- "partitions": {},
- "rotational": "1",
- "sectorsize": "2048",
- "sas_address": null,
- "scheduler_mode": "deadline",
- "support_discard": "0",
- "sas_device_handle": null
- },
- "sr1": {
- "host": "",
- "size": "86.00 KB",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": "BHYVE DVD-ROM",
- "vendor": "BHYVE",
- "holders": [],
- "sectors": "172",
- "virtual": 1,
- "removable": "1",
- "partitions": {},
- "rotational": "1",
- "sectorsize": "2048",
- "sas_address": null,
- "scheduler_mode": "deadline",
- "support_discard": "0",
- "sas_device_handle": null
- },
- "sr2": {
- "host": "",
- "size": "832.02 MB",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": "BHYVE DVD-ROM",
- "vendor": "BHYVE",
- "holders": [],
- "sectors": "1703968",
- "virtual": 1,
- "removable": "1",
- "partitions": {},
- "rotational": "1",
- "sectorsize": "2048",
- "sas_address": null,
- "scheduler_mode": "deadline",
- "support_discard": "0",
- "sas_device_handle": null
- },
- "nbd0": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd1": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd2": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd3": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd4": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd5": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd6": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd7": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd8": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd9": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "loop0": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "1",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "0",
- "sas_device_handle": null
- },
- "loop1": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "1",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "0",
- "sas_device_handle": null
- },
- "loop2": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "1",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "0",
- "sas_device_handle": null
- },
- "loop3": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "1",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "0",
- "sas_device_handle": null
- },
- "loop4": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "1",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "0",
- "sas_device_handle": null
- },
- "loop5": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "1",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "0",
- "sas_device_handle": null
- },
- "loop6": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "1",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "0",
- "sas_device_handle": null
- },
- "loop7": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "1",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "0",
- "sas_device_handle": null
- },
- "nbd10": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd11": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd12": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd13": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd14": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- },
- "nbd15": {
- "host": "",
- "size": "0.00 Bytes",
- "links": {
- "ids": [],
- "uuids": [],
- "labels": [],
- "masters": []
- },
- "model": null,
- "vendor": null,
- "holders": [],
- "sectors": "0",
- "virtual": 1,
- "removable": "0",
- "partitions": {},
- "rotational": "0",
- "sectorsize": "512",
- "sas_address": null,
- "scheduler_mode": "",
- "support_discard": "512",
- "sas_device_handle": null
- }
- },
- "ansible_hostnqn": "",
- "ansible_ip6tnl0": {
- "mtu": 1452,
- "type": "unknown",
- "active": false,
- "device": "ip6tnl0",
- "promisc": false,
- "features": {
- "rx_all": "off [fixed]",
- "rx_fcs": "off [fixed]",
- "highdma": "on",
- "fcoe_mtu": "off [fixed]",
- "loopback": "off [fixed]",
- "busy_poll": "off [fixed]",
- "netns_local": "on [fixed]",
- "tx_lockless": "on [fixed]",
- "hw_tc_offload": "off [fixed]",
- "tx_gso_robust": "off [fixed]",
- "l2_fwd_offload": "off [fixed]",
- "ntuple_filters": "off [fixed]",
- "rx_vlan_filter": "off [fixed]",
- "scatter_gather": "on",
- "tx_gso_partial": "off [fixed]",
- "receive_hashing": "off [fixed]",
- "rx_checksumming": "off [fixed]",
- "rx_vlan_offload": "off [fixed]",
- "tx_checksumming": "on",
- "tx_nocache_copy": "off",
- "tx_vlan_offload": "off [fixed]",
- "vlan_challenged": "off [fixed]",
- "tx_checksum_ipv4": "off [fixed]",
- "tx_checksum_ipv6": "off [fixed]",
- "tx_checksum_sctp": "off [fixed]",
- "tx_scatter_gather": "on",
- "rx_vlan_stag_filter": "off [fixed]",
- "tx_gre_segmentation": "off [fixed]",
- "tx_tcp_segmentation": "on",
- "tx_checksum_fcoe_crc": "off [fixed]",
- "tx_fcoe_segmentation": "off [fixed]",
- "tx_sctp_segmentation": "on",
- "tx_tcp6_segmentation": "on",
- "large_receive_offload": "off [fixed]",
- "rx_vlan_stag_hw_parse": "off [fixed]",
- "tx_checksum_ip_generic": "on",
- "tx_ipxip4_segmentation": "off [fixed]",
- "tx_ipxip6_segmentation": "off [fixed]",
- "tx_vlan_stag_hw_insert": "off [fixed]",
- "generic_receive_offload": "on",
- "tx_tcp_ecn_segmentation": "on",
- "tx_udp_tnl_segmentation": "off [fixed]",
- "tcp_segmentation_offload": "on",
- "tx_gre_csum_segmentation": "off [fixed]",
- "udp_fragmentation_offload": "on",
- "tx_scatter_gather_fraglist": "on",
- "generic_segmentation_offload": "on",
- "tx_tcp_mangleid_segmentation": "on",
- "tx_udp_tnl_csum_segmentation": "off [fixed]"
- },
- "macaddress": "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00",
- "timestamping": [
- "rx_software",
- "software"
- ],
- "hw_timestamp_filters": []
- },
- "ansible_machine": "x86_64",
- "ansible_pkg_mgr": "dnf",
- "ansible_selinux": {
- "status": "disabled"
- },
- "ansible_user_id": "awx",
- "ansible_apparmor": {
- "status": "disabled"
- },
- "ansible_hostname": "awx",
- "ansible_nodename": "awx",
- "ansible_user_dir": "/tmp",
- "ansible_user_gid": 0,
- "ansible_user_uid": 501,
- "ansible_bios_date": "03/14/2014",
- "ansible_date_time": {
- "tz": "UTC",
- "day": "11",
- "date": "2020-03-11",
- "hour": "18",
- "time": "18:17:15",
- "year": "2020",
- "epoch": "1583950635",
- "month": "03",
- "minute": "17",
- "second": "15",
- "iso8601": "2020-03-11T18:17:15Z",
- "weekday": "Wednesday",
- "tz_offset": "+0000",
- "weeknumber": "10",
- "iso8601_basic": "20200311T181715335527",
- "iso8601_micro": "2020-03-11T18:17:15.335587Z",
- "weekday_number": "3",
- "iso8601_basic_short": "20200311T181715"
- },
- "ansible_is_chroot": false,
- "ansible_iscsi_iqn": "",
- "ansible_memory_mb": {
- "real": {
- "free": 268,
- "used": 7705,
- "total": 7973
- },
- "swap": {
- "free": 1023,
- "used": 0,
- "total": 1023,
- "cached": 0
- },
- "nocache": {
- "free": 4740,
- "used": 3233
- }
- },
- "ansible_os_family": "RedHat",
- "ansible_processor": [
- "0",
- "GenuineIntel",
- "Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz",
- "1",
- "GenuineIntel",
- "Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz",
- "2",
- "GenuineIntel",
- "Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz",
- "3",
- "GenuineIntel",
- "Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz",
- "4",
- "GenuineIntel",
- "Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz",
- "5",
- "GenuineIntel",
- "Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz"
- ],
- "ansible_interfaces": [
- "ip6tnl0",
- "eth0",
- "lo",
- "tunl0"
- ],
- "ansible_machine_id": "a76ef18c33e144f09ee5370f751184fa",
- "ansible_memfree_mb": 268,
- "ansible_user_gecos": ",,,",
- "ansible_user_shell": "/bin/bash",
- "ansible_form_factor": "Unknown",
- "ansible_memtotal_mb": 7973,
- "ansible_service_mgr": "bwrap",
- "ansible_swapfree_mb": 1023,
- "ansible_architecture": "x86_64",
- "ansible_bios_version": "1.00",
- "ansible_default_ipv4": {
- "mtu": 1500,
- "type": "ether",
- "alias": "eth0",
- "address": "172.18.0.5",
- "gateway": "172.18.0.1",
- "netmask": "255.255.0.0",
- "network": "172.18.0.0",
- "broadcast": "172.18.255.255",
- "interface": "eth0",
- "macaddress": "02:42:ac:12:00:05"
- },
- "ansible_default_ipv6": {},
- "ansible_device_links": {
- "ids": {},
- "uuids": {},
- "labels": {},
- "masters": {}
- },
- "ansible_distribution": "CentOS",
- "ansible_proc_cmdline": {
- "root": "/dev/sr0",
- "text": true,
- "panic": "1",
- "console": [
- "ttyS0",
- "ttyS1"
- ],
- "vsyscall": "emulate",
- "BOOT_IMAGE": "/boot/kernel",
- "page_poison": "1"
- },
- "ansible_product_name": "BHYVE",
- "ansible_product_uuid": "NA",
- "ansible_real_user_id": 501,
- "ansible_swaptotal_mb": 1023,
- "ansible_real_group_id": 0,
- "ansible_system_vendor": "NA",
- "ansible_kernel_version": "#1 SMP Tue Jul 2 22:58:16 UTC 2019",
- "ansible_product_serial": "NA",
- "ansible_python_version": "3.6.8",
- "ansible_uptime_seconds": 1867065,
- "ansible_userspace_bits": "64",
- "_ansible_facts_gathered": true,
- "ansible_processor_cores": 1,
- "ansible_processor_count": 6,
- "ansible_processor_vcpus": 6,
- "ansible_product_version": "1.0",
- "ansible_effective_user_id": 501,
- "ansible_fibre_channel_wwn": [],
- "ansible_all_ipv4_addresses": [
- "172.18.0.5"
- ],
- "ansible_all_ipv6_addresses": [],
- "ansible_effective_group_id": 0,
- "ansible_system_capabilities": [
- ""
- ],
- "ansible_virtualization_role": "guest",
- "ansible_virtualization_type": "docker",
- "ansible_distribution_release": "Core",
- "ansible_distribution_version": "8.1",
- "discovered_interpreter_python": "/usr/libexec/platform-python",
- "ansible_distribution_file_path": "/etc/redhat-release",
- "ansible_selinux_python_present": true,
- "ansible_userspace_architecture": "x86_64",
- "ansible_distribution_file_parsed": true,
- "ansible_distribution_file_variety": "RedHat",
- "ansible_distribution_major_version": "8",
- "ansible_processor_threads_per_core": 1,
- "ansible_system_capabilities_enforced": "True"
-}
diff --git a/awx/ui/src/screens/Inventory/shared/data.hosts.json b/awx/ui/src/screens/Inventory/shared/data.hosts.json
deleted file mode 100644
index 25caa6b6f113..000000000000
--- a/awx/ui/src/screens/Inventory/shared/data.hosts.json
+++ /dev/null
@@ -1,393 +0,0 @@
-
-{
- "count": 3,
- "results": [
- {
- "id": 2,
- "type": "host",
- "url": "/api/v2/hosts/2/",
- "related": {
- "created_by": "/api/v2/users/10/",
- "modified_by": "/api/v2/users/19/",
- "variable_data": "/api/v2/hosts/2/variable_data/",
- "groups": "/api/v2/hosts/2/groups/",
- "all_groups": "/api/v2/hosts/2/all_groups/",
- "job_events": "/api/v2/hosts/2/job_events/",
- "job_host_summaries": "/api/v2/hosts/2/job_host_summaries/",
- "activity_stream": "/api/v2/hosts/2/activity_stream/",
- "inventory_sources": "/api/v2/hosts/2/inventory_sources/",
- "smart_inventories": "/api/v2/hosts/2/smart_inventories/",
- "ad_hoc_commands": "/api/v2/hosts/2/ad_hoc_commands/",
- "ad_hoc_command_events": "/api/v2/hosts/2/ad_hoc_command_events/",
- "insights": "/api/v2/hosts/2/insights/",
- "ansible_facts": "/api/v2/hosts/2/ansible_facts/",
- "inventory": "/api/v2/inventories/2/",
- "last_job": "/api/v2/jobs/236/",
- "last_job_host_summary": "/api/v2/job_host_summaries/2202/"
- },
- "summary_fields": {
- "inventory": {
- "id": 2,
- "name": " Inventory 1 Org 0",
- "description": "",
- "has_active_failures": false,
- "total_hosts": 33,
- "hosts_with_active_failures": 0,
- "total_groups": 4,
- "has_inventory_sources": false,
- "total_inventory_sources": 0,
- "inventory_sources_with_failures": 0,
- "organization_id": 2,
- "kind": ""
- },
- "last_job": {
- "id": 236,
- "name": " Job Template 1 Project 0",
- "description": "",
- "finished": "2020-02-26T03:15:21.471439Z",
- "status": "successful",
- "failed": false,
- "job_template_id": 18,
- "job_template_name": " Job Template 1 Project 0"
- },
- "last_job_host_summary": {
- "id": 2202,
- "failed": false
- },
- "created_by": {
- "id": 10,
- "username": "user-3",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 19,
- "username": "all",
- "first_name": "",
- "last_name": ""
- },
- "user_capabilities": {
- "edit": true,
- "delete": true
- },
- "groups": {
- "count": 2,
- "results": [
- {
- "id": 1,
- "name": " Group 1 Inventory 0"
- },
- {
- "id": 2,
- "name": " Group 2 Inventory 0"
- }
- ]
- },
- "recent_jobs": [
- {
- "id": 236,
- "name": " Job Template 1 Project 0",
- "status": "successful",
- "finished": "2020-02-26T03:15:21.471439Z"
- },
- {
- "id": 232,
- "name": " Job Template 1 Project 0",
- "status": "successful",
- "finished": "2020-02-25T21:20:33.593789Z"
- },
- {
- "id": 229,
- "name": " Job Template 1 Project 0",
- "status": "successful",
- "finished": "2020-02-25T16:19:46.364134Z"
- },
- {
- "id": 228,
- "name": " Job Template 1 Project 0",
- "status": "successful",
- "finished": "2020-02-25T16:18:54.138363Z"
- },
- {
- "id": 225,
- "name": " Job Template 1 Project 0",
- "status": "successful",
- "finished": "2020-02-25T15:55:32.247652Z"
- }
- ]
- },
- "created": "2020-02-24T15:10:58.922179Z",
- "modified": "2020-02-26T21:52:43.428530Z",
- "name": ".host-000001.group-00000.dummy",
- "description": "Bar",
- "inventory": 2,
- "enabled": false,
- "instance_id": "",
- "variables": "",
- "has_active_failures": false,
- "has_inventory_sources": false,
- "last_job": 236,
- "last_job_host_summary": 2202,
- "insights_system_id": null,
- "ansible_facts_modified": null
- },
- {
- "id": 3,
- "type": "host",
- "url": "/api/v2/hosts/3/",
- "related": {
- "created_by": "/api/v2/users/11/",
- "modified_by": "/api/v2/users/1/",
- "variable_data": "/api/v2/hosts/3/variable_data/",
- "groups": "/api/v2/hosts/3/groups/",
- "all_groups": "/api/v2/hosts/3/all_groups/",
- "job_events": "/api/v2/hosts/3/job_events/",
- "job_host_summaries": "/api/v2/hosts/3/job_host_summaries/",
- "activity_stream": "/api/v2/hosts/3/activity_stream/",
- "inventory_sources": "/api/v2/hosts/3/inventory_sources/",
- "smart_inventories": "/api/v2/hosts/3/smart_inventories/",
- "ad_hoc_commands": "/api/v2/hosts/3/ad_hoc_commands/",
- "ad_hoc_command_events": "/api/v2/hosts/3/ad_hoc_command_events/",
- "insights": "/api/v2/hosts/3/insights/",
- "ansible_facts": "/api/v2/hosts/3/ansible_facts/",
- "inventory": "/api/v2/inventories/2/",
- "last_job": "/api/v2/jobs/236/",
- "last_job_host_summary": "/api/v2/job_host_summaries/2195/"
- },
- "summary_fields": {
- "inventory": {
- "id": 2,
- "name": " Inventory 1 Org 0",
- "description": "",
- "has_active_failures": false,
- "total_hosts": 33,
- "hosts_with_active_failures": 0,
- "total_groups": 4,
- "has_inventory_sources": false,
- "total_inventory_sources": 0,
- "inventory_sources_with_failures": 0,
- "organization_id": 2,
- "kind": ""
- },
- "last_job": {
- "id": 236,
- "name": " Job Template 1 Project 0",
- "description": "",
- "finished": "2020-02-26T03:15:21.471439Z",
- "status": "successful",
- "failed": false,
- "job_template_id": 18,
- "job_template_name": " Job Template 1 Project 0"
- },
- "last_job_host_summary": {
- "id": 2195,
- "failed": false
- },
- "created_by": {
- "id": 11,
- "username": "user-4",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "user_capabilities": {
- "edit": true,
- "delete": true
- },
- "groups": {
- "count": 2,
- "results": [
- {
- "id": 1,
- "name": " Group 1 Inventory 0"
- },
- {
- "id": 2,
- "name": " Group 2 Inventory 0"
- }
- ]
- },
- "recent_jobs": [
- {
- "id": 236,
- "name": " Job Template 1 Project 0",
- "status": "successful",
- "finished": "2020-02-26T03:15:21.471439Z"
- },
- {
- "id": 232,
- "name": " Job Template 1 Project 0",
- "status": "successful",
- "finished": "2020-02-25T21:20:33.593789Z"
- },
- {
- "id": 229,
- "name": " Job Template 1 Project 0",
- "status": "successful",
- "finished": "2020-02-25T16:19:46.364134Z"
- },
- {
- "id": 228,
- "name": " Job Template 1 Project 0",
- "status": "successful",
- "finished": "2020-02-25T16:18:54.138363Z"
- },
- {
- "id": 225,
- "name": " Job Template 1 Project 0",
- "status": "successful",
- "finished": "2020-02-25T15:55:32.247652Z"
- }
- ]
- },
- "created": "2020-02-24T15:10:58.945113Z",
- "modified": "2020-02-27T03:43:43.635871Z",
- "name": ".host-000002.group-00000.dummy",
- "description": "Buzz",
- "inventory": 2,
- "enabled": false,
- "instance_id": "",
- "variables": "",
- "has_active_failures": false,
- "has_inventory_sources": false,
- "last_job": 236,
- "last_job_host_summary": 2195,
- "insights_system_id": null,
- "ansible_facts_modified": null
- },
- {
- "id": 4,
- "type": "host",
- "url": "/api/v2/hosts/4/",
- "related": {
- "created_by": "/api/v2/users/12/",
- "modified_by": "/api/v2/users/1/",
- "variable_data": "/api/v2/hosts/4/variable_data/",
- "groups": "/api/v2/hosts/4/groups/",
- "all_groups": "/api/v2/hosts/4/all_groups/",
- "job_events": "/api/v2/hosts/4/job_events/",
- "job_host_summaries": "/api/v2/hosts/4/job_host_summaries/",
- "activity_stream": "/api/v2/hosts/4/activity_stream/",
- "inventory_sources": "/api/v2/hosts/4/inventory_sources/",
- "smart_inventories": "/api/v2/hosts/4/smart_inventories/",
- "ad_hoc_commands": "/api/v2/hosts/4/ad_hoc_commands/",
- "ad_hoc_command_events": "/api/v2/hosts/4/ad_hoc_command_events/",
- "insights": "/api/v2/hosts/4/insights/",
- "ansible_facts": "/api/v2/hosts/4/ansible_facts/",
- "inventory": "/api/v2/inventories/2/",
- "last_job": "/api/v2/jobs/236/",
- "last_job_host_summary": "/api/v2/job_host_summaries/2192/"
- },
- "summary_fields": {
- "inventory": {
- "id": 2,
- "name": " Inventory 1 Org 0",
- "description": "",
- "has_active_failures": false,
- "total_hosts": 33,
- "hosts_with_active_failures": 0,
- "total_groups": 4,
- "has_inventory_sources": false,
- "total_inventory_sources": 0,
- "inventory_sources_with_failures": 0,
- "organization_id": 2,
- "kind": ""
- },
- "last_job": {
- "id": 236,
- "name": " Job Template 1 Project 0",
- "description": "",
- "finished": "2020-02-26T03:15:21.471439Z",
- "status": "successful",
- "failed": false,
- "job_template_id": 18,
- "job_template_name": " Job Template 1 Project 0"
- },
- "last_job_host_summary": {
- "id": 2192,
- "failed": false
- },
- "created_by": {
- "id": 12,
- "username": "user-5",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "user_capabilities": {
- "edit": true,
- "delete": true
- },
- "groups": {
- "count": 2,
- "results": [
- {
- "id": 1,
- "name": " Group 1 Inventory 0"
- },
- {
- "id": 2,
- "name": " Group 2 Inventory 0"
- }
- ]
- },
- "recent_jobs": [
- {
- "id": 236,
- "name": " Job Template 1 Project 0",
- "status": "successful",
- "finished": "2020-02-26T03:15:21.471439Z"
- },
- {
- "id": 232,
- "name": " Job Template 1 Project 0",
- "status": "successful",
- "finished": "2020-02-25T21:20:33.593789Z"
- },
- {
- "id": 229,
- "name": " Job Template 1 Project 0",
- "status": "successful",
- "finished": "2020-02-25T16:19:46.364134Z"
- },
- {
- "id": 228,
- "name": " Job Template 1 Project 0",
- "status": "successful",
- "finished": "2020-02-25T16:18:54.138363Z"
- },
- {
- "id": 225,
- "name": " Job Template 1 Project 0",
- "status": "successful",
- "finished": "2020-02-25T15:55:32.247652Z"
- }
- ]
- },
- "created": "2020-02-24T15:10:58.962312Z",
- "modified": "2020-02-27T03:43:45.528882Z",
- "name": ".host-000003.group-00000.dummy",
- "description": "BarFoo",
- "inventory": 2,
- "enabled": false,
- "instance_id": "",
- "variables": "",
- "has_active_failures": false,
- "has_inventory_sources": false,
- "last_job": 236,
- "last_job_host_summary": 2192,
- "insights_system_id": null,
- "ansible_facts_modified": null
- }
- ]
-}
diff --git a/awx/ui/src/screens/Inventory/shared/data.inventory.json b/awx/ui/src/screens/Inventory/shared/data.inventory.json
deleted file mode 100644
index 9c6a8eba9479..000000000000
--- a/awx/ui/src/screens/Inventory/shared/data.inventory.json
+++ /dev/null
@@ -1,96 +0,0 @@
-{
- "id": 1,
- "type": "inventory",
- "url": "/api/v2/inventories/1/",
- "related": {
- "named_url": "/api/v2/inventories/Mike's Inventory++Default/",
- "created_by": "/api/v2/users/1/",
- "modified_by": "/api/v2/users/1/",
- "hosts": "/api/v2/inventories/1/hosts/",
- "groups": "/api/v2/inventories/1/groups/",
- "root_groups": "/api/v2/inventories/1/root_groups/",
- "variable_data": "/api/v2/inventories/1/variable_data/",
- "script": "/api/v2/inventories/1/script/",
- "tree": "/api/v2/inventories/1/tree/",
- "inventory_sources": "/api/v2/inventories/1/inventory_sources/",
- "update_inventory_sources": "/api/v2/inventories/1/update_inventory_sources/",
- "activity_stream": "/api/v2/inventories/1/activity_stream/",
- "job_templates": "/api/v2/inventories/1/job_templates/",
- "ad_hoc_commands": "/api/v2/inventories/1/ad_hoc_commands/",
- "access_list": "/api/v2/inventories/1/access_list/",
- "object_roles": "/api/v2/inventories/1/object_roles/",
- "instance_groups": "/api/v2/inventories/1/instance_groups/",
- "copy": "/api/v2/inventories/1/copy/",
- "organization": "/api/v2/organizations/1/"
- },
- "summary_fields": {
- "organization": {
- "id": 1,
- "name": "Default",
- "description": ""
- },
- "created_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "object_roles": {
- "admin_role": {
- "description": "Can manage all aspects of the inventory",
- "name": "Admin",
- "id": 19
- },
- "update_role": {
- "description": "May update the inventory",
- "name": "Update",
- "id": 20
- },
- "adhoc_role": {
- "description": "May run ad hoc commands on the inventory",
- "name": "Ad Hoc",
- "id": 21
- },
- "use_role": {
- "description": "Can use the inventory in a job template",
- "name": "Use",
- "id": 22
- },
- "read_role": {
- "description": "May view settings for the inventory",
- "name": "Read",
- "id": 23
- }
- },
- "user_capabilities": {
- "edit": true,
- "delete": true,
- "copy": true,
- "adhoc": true
- }
- },
- "created": "2019-10-04T14:28:04.765571Z",
- "modified": "2019-10-04T14:28:04.765594Z",
- "name": "Mike's Inventory",
- "description": "",
- "organization": 1,
- "kind": "",
- "host_filter": null,
- "variables": "---",
- "has_active_failures": false,
- "total_hosts": 1,
- "hosts_with_active_failures": 0,
- "total_groups": 0,
- "groups_with_active_failures": 0,
- "has_inventory_sources": false,
- "total_inventory_sources": 0,
- "inventory_sources_with_failures": 0,
- "pending_deletion": false,
- "prevent_instance_group_fallback": false
-}
diff --git a/awx/ui/src/screens/Inventory/shared/data.inventory_source.json b/awx/ui/src/screens/Inventory/shared/data.inventory_source.json
deleted file mode 100644
index 7c829c56b7fe..000000000000
--- a/awx/ui/src/screens/Inventory/shared/data.inventory_source.json
+++ /dev/null
@@ -1,121 +0,0 @@
-{
- "id":123,
- "type":"inventory_source",
- "url":"/api/v2/inventory_sources/123/",
- "related":{
- "named_url":"/api/v2/inventory_sources/src++Demo Inventory++Default/",
- "created_by":"/api/v2/users/1/",
- "modified_by":"/api/v2/users/1/",
- "update":"/api/v2/inventory_sources/123/update/",
- "inventory_updates":"/api/v2/inventory_sources/123/inventory_updates/",
- "schedules":"/api/v2/inventory_sources/123/schedules/",
- "activity_stream":"/api/v2/inventory_sources/123/activity_stream/",
- "hosts":"/api/v2/inventory_sources/123/hosts/",
- "groups":"/api/v2/inventory_sources/123/groups/",
- "notification_templates_started":"/api/v2/inventory_sources/123/notification_templates_started/",
- "notification_templates_success":"/api/v2/inventory_sources/123/notification_templates_success/",
- "notification_templates_error":"/api/v2/inventory_sources/123/notification_templates_error/",
- "inventory":"/api/v2/inventories/1/",
- "source_project":"/api/v2/projects/8/",
- "credentials":"/api/v2/inventory_sources/123/credentials/"
- },
- "summary_fields":{
- "organization":{
- "id":1,
- "name":"Mock Org",
- "description":""
- },
- "inventory":{
- "id":2,
- "name":"Mock Inventory",
- "description":"",
- "has_active_failures":false,
- "total_hosts":1,
- "hosts_with_active_failures":0,
- "total_groups":2,
- "has_inventory_sources":true,
- "total_inventory_sources":5,
- "inventory_sources_with_failures":0,
- "organization_id":1,
- "kind":""
- },
- "execution_environment": {
- "id": 1,
- "name": "Default EE",
- "description": "",
- "image": "quay.io/ansible/awx-ee"
- },
- "source_project":{
- "id":8,
- "name":"Mock Project",
- "description":"",
- "status":"never updated",
- "scm_type":"git"
- },
- "source_script": {
- "name": "Mock Script",
- "description": ""
- },
- "created_by":{
- "id":1,
- "username":"admin",
- "first_name":"",
- "last_name":""
- },
- "modified_by":{
- "id":1,
- "username":"admin",
- "first_name":"",
- "last_name":""
- },
- "user_capabilities":{
- "edit":true,
- "delete":true,
- "start":true,
- "schedule":true
- },
- "credential": {
- "id": 8,
- "name": "mock cred",
- "description": "",
- "kind": "vmware",
- "cloud": true,
- "credential_type_id": 7
- },
- "credentials":[
- {
- "id": 8,
- "name": "mock cred",
- "description": "",
- "kind": "vmware",
- "cloud": true,
- "credential_type_id": 7
- }
- ]
- },
- "created":"2020-04-02T18:59:08.474167Z",
- "modified":"2020-04-02T19:52:23.924252Z",
- "name":"mock inv source",
- "description":"mock description",
- "source":"scm",
- "source_path": "foo",
- "source_script": "Mock Script",
- "source_vars":"---\nfoo: bar",
- "credential": 8,
- "overwrite":true,
- "overwrite_vars":true,
- "custom_virtualenv":"/var/lib/awx/venv/custom",
- "timeout":0,
- "verbosity":2,
- "last_job_run":null,
- "last_job_failed":false,
- "next_job_run":null,
- "status":"never updated",
- "inventory":1,
- "update_on_launch":true,
- "update_cache_timeout":2,
- "source_project":8,
- "last_update_failed": true,
- "last_updated":null,
- "execution_environment": 1
-}
\ No newline at end of file
diff --git a/awx/ui/src/screens/Inventory/shared/data.relatedGroups.json b/awx/ui/src/screens/Inventory/shared/data.relatedGroups.json
deleted file mode 100644
index 835ab95d792c..000000000000
--- a/awx/ui/src/screens/Inventory/shared/data.relatedGroups.json
+++ /dev/null
@@ -1,181 +0,0 @@
-{
- "count": 3,
- "results": [{
- "id": 2,
- "type": "group",
- "url": "/api/v2/groups/2/",
- "related": {
- "created_by": "/api/v2/users/10/",
- "modified_by": "/api/v2/users/14/",
- "variable_data": "/api/v2/groups/2/variable_data/",
- "hosts": "/api/v2/groups/2/hosts/",
- "potential_children": "/api/v2/groups/2/potential_children/",
- "children": "/api/v2/groups/2/children/",
- "all_hosts": "/api/v2/groups/2/all_hosts/",
- "job_events": "/api/v2/groups/2/job_events/",
- "job_host_summaries": "/api/v2/groups/2/job_host_summaries/",
- "activity_stream": "/api/v2/groups/2/activity_stream/",
- "inventory_sources": "/api/v2/groups/2/inventory_sources/",
- "ad_hoc_commands": "/api/v2/groups/2/ad_hoc_commands/",
- "inventory": "/api/v2/inventories/1/"
- },
- "summary_fields": {
- "inventory": {
- "id": 1,
- "name": " Inventory 1 Org 0",
- "description": "",
- "has_active_failures": false,
- "total_hosts": 33,
- "hosts_with_active_failures": 0,
- "total_groups": 4,
- "has_inventory_sources": false,
- "total_inventory_sources": 0,
- "inventory_sources_with_failures": 0,
- "organization_id": 1,
- "kind": ""
- },
- "created_by": {
- "id": 10,
- "username": "user-4",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 14,
- "username": "user-8",
- "first_name": "",
- "last_name": ""
- },
- "user_capabilities": {
- "edit": true,
- "delete": true,
- "copy": true
- }
- },
- "created": "2020-09-23T14:30:55.263148Z",
- "modified": "2020-09-23T14:30:55.263175Z",
- "name": " Group 2 Inventory 0",
- "description": "",
- "inventory": 1,
- "variables": ""
- },
- {
- "id": 3,
- "type": "group",
- "url": "/api/v2/groups/3/",
- "related": {
- "created_by": "/api/v2/users/11/",
- "modified_by": "/api/v2/users/15/",
- "variable_data": "/api/v2/groups/3/variable_data/",
- "hosts": "/api/v2/groups/3/hosts/",
- "potential_children": "/api/v2/groups/3/potential_children/",
- "children": "/api/v2/groups/3/children/",
- "all_hosts": "/api/v2/groups/3/all_hosts/",
- "job_events": "/api/v2/groups/3/job_events/",
- "job_host_summaries": "/api/v2/groups/3/job_host_summaries/",
- "activity_stream": "/api/v2/groups/3/activity_stream/",
- "inventory_sources": "/api/v2/groups/3/inventory_sources/",
- "ad_hoc_commands": "/api/v2/groups/3/ad_hoc_commands/",
- "inventory": "/api/v2/inventories/1/"
- },
- "summary_fields": {
- "inventory": {
- "id": 1,
- "name": " Inventory 1 Org 0",
- "description": "",
- "has_active_failures": false,
- "total_hosts": 33,
- "hosts_with_active_failures": 0,
- "total_groups": 4,
- "has_inventory_sources": false,
- "total_inventory_sources": 0,
- "inventory_sources_with_failures": 0,
- "organization_id": 1,
- "kind": ""
- },
- "created_by": {
- "id": 11,
- "username": "user-5",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 15,
- "username": "user-9",
- "first_name": "",
- "last_name": ""
- },
- "user_capabilities": {
- "edit": true,
- "delete": true,
- "copy": true
- }
- },
- "created": "2020-09-23T14:30:55.281583Z",
- "modified": "2020-09-23T14:30:55.281615Z",
- "name": " Group 3 Inventory 0",
- "description": "",
- "inventory": 1,
- "variables": ""
- },
- {
- "id": 4,
- "type": "group",
- "url": "/api/v2/groups/4/",
- "related": {
- "created_by": "/api/v2/users/12/",
- "modified_by": "/api/v2/users/16/",
- "variable_data": "/api/v2/groups/4/variable_data/",
- "hosts": "/api/v2/groups/4/hosts/",
- "potential_children": "/api/v2/groups/4/potential_children/",
- "children": "/api/v2/groups/4/children/",
- "all_hosts": "/api/v2/groups/4/all_hosts/",
- "job_events": "/api/v2/groups/4/job_events/",
- "job_host_summaries": "/api/v2/groups/4/job_host_summaries/",
- "activity_stream": "/api/v2/groups/4/activity_stream/",
- "inventory_sources": "/api/v2/groups/4/inventory_sources/",
- "ad_hoc_commands": "/api/v2/groups/4/ad_hoc_commands/",
- "inventory": "/api/v2/inventories/1/"
- },
- "summary_fields": {
- "inventory": {
- "id": 1,
- "name": " Inventory 1 Org 0",
- "description": "",
- "has_active_failures": false,
- "total_hosts": 33,
- "hosts_with_active_failures": 0,
- "total_groups": 4,
- "has_inventory_sources": false,
- "total_inventory_sources": 0,
- "inventory_sources_with_failures": 0,
- "organization_id": 1,
- "kind": ""
- },
- "created_by": {
- "id": 12,
- "username": "user-6",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 16,
- "username": "user-10",
- "first_name": "",
- "last_name": ""
- },
- "user_capabilities": {
- "edit": false,
- "delete": true,
- "copy": true
- }
- },
- "created": "2020-09-23T14:30:55.293574Z",
- "modified": "2020-09-23T14:30:55.293603Z",
- "name": " Group 4 Inventory 0",
- "description": "",
- "inventory": 1,
- "variables": ""
- }
- ]
-}
diff --git a/awx/ui/src/screens/Inventory/shared/data.smart_inventory.json b/awx/ui/src/screens/Inventory/shared/data.smart_inventory.json
deleted file mode 100644
index aa6c370269f0..000000000000
--- a/awx/ui/src/screens/Inventory/shared/data.smart_inventory.json
+++ /dev/null
@@ -1,94 +0,0 @@
-{
- "id": 2,
- "type": "inventory",
- "url": "/api/v2/inventories/2/",
- "related": {
- "created_by": "/api/v2/users/1/",
- "modified_by": "/api/v2/users/1/",
- "hosts": "/api/v2/inventories/2/hosts/",
- "groups": "/api/v2/inventories/2/groups/",
- "root_groups": "/api/v2/inventories/2/root_groups/",
- "variable_data": "/api/v2/inventories/2/variable_data/",
- "script": "/api/v2/inventories/2/script/",
- "tree": "/api/v2/inventories/2/tree/",
- "inventory_sources": "/api/v2/inventories/2/inventory_sources/",
- "update_inventory_sources": "/api/v2/inventories/2/update_inventory_sources/",
- "activity_stream": "/api/v2/inventories/2/activity_stream/",
- "job_templates": "/api/v2/inventories/2/job_templates/",
- "ad_hoc_commands": "/api/v2/inventories/2/ad_hoc_commands/",
- "access_list": "/api/v2/inventories/2/access_list/",
- "object_roles": "/api/v2/inventories/2/object_roles/",
- "instance_groups": "/api/v2/inventories/2/instance_groups/",
- "copy": "/api/v2/inventories/2/copy/",
- "organization": "/api/v2/organizations/1/"
- },
- "summary_fields": {
- "organization": {
- "id": 1,
- "name": "Default",
- "description": ""
- },
- "created_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "object_roles": {
- "admin_role": {
- "description": "Can manage all aspects of the inventory",
- "name": "Admin",
- "id": 27
- },
- "update_role": {
- "description": "May update the inventory",
- "name": "Update",
- "id": 28
- },
- "adhoc_role": {
- "description": "May run ad hoc commands on the inventory",
- "name": "Ad Hoc",
- "id": 29
- },
- "use_role": {
- "description": "Can use the inventory in a job template",
- "name": "Use",
- "id": 30
- },
- "read_role": {
- "description": "May view settings for the inventory",
- "name": "Read",
- "id": 31
- }
- },
- "user_capabilities": {
- "edit": true,
- "delete": true,
- "copy": true,
- "adhoc": true
- }
- },
- "created": "2019-10-04T15:29:11.542911Z",
- "modified": "2019-10-04T15:29:11.542924Z",
- "name": "Smart Inv",
- "description": "smart inv description",
- "organization": 1,
- "kind": "smart",
- "host_filter": "name__icontains=local",
- "variables": "",
- "has_active_failures": false,
- "total_hosts": 2,
- "hosts_with_active_failures": 0,
- "total_groups": 0,
- "groups_with_active_failures": 0,
- "has_inventory_sources": false,
- "total_inventory_sources": 0,
- "inventory_sources_with_failures": 0,
- "pending_deletion": false
-}
diff --git a/awx/ui/src/screens/Inventory/shared/index.js b/awx/ui/src/screens/Inventory/shared/index.js
deleted file mode 100644
index fda9943d93a6..000000000000
--- a/awx/ui/src/screens/Inventory/shared/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './InventoryForm';
diff --git a/awx/ui/src/screens/Inventory/shared/utils.js b/awx/ui/src/screens/Inventory/shared/utils.js
deleted file mode 100644
index c08710327f93..000000000000
--- a/awx/ui/src/screens/Inventory/shared/utils.js
+++ /dev/null
@@ -1,10 +0,0 @@
-const parseHostFilter = (value) => {
- if (value.host_filter && value.host_filter.includes('host_filter=')) {
- return {
- ...value,
- host_filter: value.host_filter.slice('host_filter='.length),
- };
- }
- return value;
-};
-export default parseHostFilter;
diff --git a/awx/ui/src/screens/Inventory/shared/utils.test.js b/awx/ui/src/screens/Inventory/shared/utils.test.js
deleted file mode 100644
index 4d659932f774..000000000000
--- a/awx/ui/src/screens/Inventory/shared/utils.test.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import parseHostFilter from './utils';
-
-describe('parseHostFilter', () => {
- test('parse host filter', () => {
- expect(
- parseHostFilter({
- host_filter:
- 'host_filter=ansible_facts__ansible_processor[]="GenuineIntel"',
- name: 'Foo',
- })
- ).toEqual({
- host_filter: 'ansible_facts__ansible_processor[]="GenuineIntel"',
- name: 'Foo',
- });
- });
- test('do not parse host filter', () => {
- expect(parseHostFilter({ name: 'Foo' })).toEqual({
- name: 'Foo',
- });
- });
-});
diff --git a/awx/ui/src/screens/Job/Job.helptext.js b/awx/ui/src/screens/Job/Job.helptext.js
deleted file mode 100644
index b2ccee92646e..000000000000
--- a/awx/ui/src/screens/Job/Job.helptext.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import React from 'react';
-import { t } from '@lingui/macro';
-
-const jobHelpText = () => ({
- jobType: t`For job templates, select run to execute the playbook. Select check to only check playbook syntax, test environment setup, and report problems without executing the playbook.`,
- inventory: t`Select the inventory containing the hosts you want this job to manage.`,
- project: t`The project containing the playbook this job will execute.`,
- project_source: t`The project from which this inventory update is sourced.`,
- executionEnvironment: t`The execution environment that will be used when launching this job template. The resolved execution environment can be overridden by explicitly assigning a different one to this job template.`,
- playbook: t`Select the playbook to be executed by this job.`,
- credentials: t`Select credentials for accessing the nodes this job will be ran against. You can only select one credential of each type. For machine credentials (SSH), checking "Prompt on launch" without selecting credentials will require you to select a machine credential at run time. If you select credentials and check "Prompt on launch", the selected credential(s) become the defaults that can be updated at run time.`,
- labels: t`Optional labels that describe this job template, such as 'dev' or 'test'. Labels can be used to group and filter job templates and completed jobs.`,
- variables: t`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. Refer to the documentation for example syntax.`,
- limit: t`Provide a host pattern to further constrain the list of hosts that will be managed or affected by the playbook. Multiple patterns are allowed. Refer to Ansible documentation for more information and examples on patterns.`,
- verbosity: t`Control the level of output ansible will produce as the playbook executes.`,
- jobSlicing: t`Divide the work done by this job template into the specified number of job slices, each running the same tasks against a portion of the inventory.`,
- timeout: t`The amount of time (in seconds) to run before the job is canceled. Defaults to 0 for no job timeout.`,
- instanceGroups: t`Select the Instance Groups for this Job Template to run on.`,
- jobTags: t`Tags are useful when you have a large playbook, and you want to run a specific part of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags.`,
- skipTags: t`Skip tags are useful when you have a large playbook, and you want to skip specific parts of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags.`,
- sourceControlBranch: t`Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch.`,
- projectUpdate: t`Project checkout results`,
- forks: (
-
- {t`The number of parallel or simultaneous processes to use while executing the playbook. An empty value, or a value less than 1 will use the Ansible default which is usually 5. The default number of forks can be overwritten with a change to`}{' '}
- ansible.cfg.{' '}
- {t`Refer to the Ansible documentation for details about the configuration file.`}
-
- ),
- module: (moduleName) =>
- moduleName ? (
- <>
- {t`These arguments are used with the specified module. You can find information about ${moduleName} by clicking `}{' '}
-
- {t`here.`}
-
- >
- ) : (
- t`These arguments are used with the specified module.`
- ),
-});
-
-export default jobHelpText;
diff --git a/awx/ui/src/screens/Job/Job.js b/awx/ui/src/screens/Job/Job.js
deleted file mode 100644
index 3e60c14c71a6..000000000000
--- a/awx/ui/src/screens/Job/Job.js
+++ /dev/null
@@ -1,225 +0,0 @@
-import React, { useEffect, useCallback, useRef } from 'react';
-import {
- Route,
- Switch,
- Redirect,
- Link,
- useParams,
- useRouteMatch,
-} from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { CaretLeftIcon } from '@patternfly/react-icons';
-import { Card, PageSection } from '@patternfly/react-core';
-import { InventorySourcesAPI } from 'api';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import RoutedTabs from 'components/RoutedTabs';
-import { getSearchableKeys } from 'components/PaginatedTable';
-import useRequest from 'hooks/useRequest';
-import { getJobModel } from 'util/jobs';
-import WorkflowOutputNavigation from 'components/WorkflowOutputNavigation';
-import JobDetail from './JobDetail';
-import JobOutput from './JobOutput';
-import { WorkflowOutput } from './WorkflowOutput';
-import useWsJob from './useWsJob';
-
-// maps the displayed url segments to actual api types
-export const JOB_URL_SEGMENT_MAP = {
- playbook: 'job',
- project: 'project_update',
- management: 'system_job',
- system: 'system_job',
- inventory: 'inventory_update',
- command: 'ad_hoc_command',
- workflow: 'workflow_job',
-};
-
-function Job({ setBreadcrumb }) {
- const { id, typeSegment } = useParams();
- const match = useRouteMatch();
-
- const type = JOB_URL_SEGMENT_MAP[typeSegment];
-
- const {
- isLoading,
- error,
- request: fetchJob,
- result: {
- jobDetail,
- eventRelatedSearchableKeys,
- eventSearchableKeys,
- inventorySourceChoices,
- relatedJobs,
- },
- } = useRequest(
- useCallback(async () => {
- let eventOptions = {};
- let relatedJobData = {};
- const { data: jobDetailData } = await getJobModel(type).readDetail(id);
- if (type !== 'workflow_job') {
- const { data: jobEventOptions } = await getJobModel(
- type
- ).readEventOptions(id);
- eventOptions = jobEventOptions;
- }
- if (jobDetailData.related.source_workflow_job) {
- const {
- data: { results },
- } = await getJobModel('workflow_job').readNodes(
- jobDetailData.summary_fields.source_workflow_job.id
- );
- relatedJobData = results;
- }
- if (
- jobDetailData?.summary_fields?.credentials?.find(
- (cred) => cred.kind === 'vault'
- )
- ) {
- const {
- data: { results },
- } = await getJobModel(type).readCredentials(jobDetailData.id);
-
- jobDetailData.summary_fields.credentials = results;
- }
-
- setBreadcrumb(jobDetailData);
- let choices;
- if (jobDetailData.type === 'inventory_update') {
- choices = await InventorySourcesAPI.readOptions();
- }
-
- return {
- inventorySourceChoices:
- choices?.data?.actions?.GET?.source?.choices || [],
- jobDetail: jobDetailData,
- relatedJobs: relatedJobData,
- eventRelatedSearchableKeys: (
- eventOptions?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- eventSearchableKeys: getSearchableKeys(eventOptions?.actions?.GET),
- };
- }, [id, type, setBreadcrumb]),
- {
- jobDetail: null,
- inventorySourceChoices: [],
- eventRelatedSearchableKeys: [],
- eventSearchableKeys: [],
- relatedJobs: [],
- }
- );
-
- useEffect(() => {
- fetchJob();
- }, [fetchJob]);
-
- const job = useWsJob(jobDetail);
- const ref = useRef(null);
- const tabsArray = [
- {
- name: (
- <>
-
- {t`Back to Jobs`}
- >
- ),
- link: `/jobs`,
- isBackButton: true,
- id: 99,
- },
- { name: t`Details`, link: `${match.url}/details`, id: 0 },
- { name: t`Output`, link: `${match.url}/output`, id: 1 },
- ];
- if (relatedJobs?.length > 0) {
- tabsArray.push({
- name: (
-
- ),
- link: undefined,
- id: 2,
- hasstyle: 'margin-left: auto',
- });
- }
-
- if (isLoading) {
- return (
-
-
-
-
-
- );
- }
-
- if (error) {
- return (
-
-
-
- {error.response?.status === 404 && (
-
- {t`The page you requested could not be found.`}{' '}
- {t`View all Jobs.`}
-
- )}
-
-
-
- );
- }
-
- return (
-
-
-
-
-
-
-
- {job && [
-
-
- ,
-
- {job.type === 'workflow_job' ? (
-
- ) : (
-
- )}
- ,
-
-
-
- {t`View Job Details`}
-
-
- ,
- ]}
-
-
-
-
- );
-}
-
-export default Job;
-export { Job as _Job };
diff --git a/awx/ui/src/screens/Job/Job.test.js b/awx/ui/src/screens/Job/Job.test.js
deleted file mode 100644
index d90839f0bba2..000000000000
--- a/awx/ui/src/screens/Job/Job.test.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-
-import Job from './Job';
-
-jest.mock('../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useParams: () => ({
- id: 1,
- typeSegment: 'project',
- }),
-}));
-
-describe('', () => {
- test('initially renders successfully', async () => {
- await act(async () => {
- await mountWithContexts( {}} />);
- });
- });
-});
diff --git a/awx/ui/src/screens/Job/JobDetail/JobDetail.js b/awx/ui/src/screens/Job/JobDetail/JobDetail.js
deleted file mode 100644
index d3435307cfe9..000000000000
--- a/awx/ui/src/screens/Job/JobDetail/JobDetail.js
+++ /dev/null
@@ -1,633 +0,0 @@
-import 'styled-components/macro';
-import React, { useState } from 'react';
-import { Link, useHistory } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { Button, Chip } from '@patternfly/react-core';
-import styled from 'styled-components';
-
-import { useConfig } from 'contexts/Config';
-import AlertModal from 'components/AlertModal';
-import {
- DeletedDetail,
- DetailList,
- Detail,
- UserDateDetail,
- LaunchedByDetail,
-} from 'components/DetailList';
-import { CardBody, CardActionsRow } from 'components/Card';
-import ChipGroup from 'components/ChipGroup';
-import CredentialChip from 'components/CredentialChip';
-import { VariablesDetail } from 'components/CodeEditor';
-import DeleteButton from 'components/DeleteButton';
-import ErrorDetail from 'components/ErrorDetail';
-import { LaunchButton, ReLaunchDropDown } from 'components/LaunchButton';
-import StatusLabel from 'components/StatusLabel';
-import JobCancelButton from 'components/JobCancelButton';
-import ExecutionEnvironmentDetail from 'components/ExecutionEnvironmentDetail';
-import { VERBOSITY } from 'components/VerbositySelectField';
-import { getJobModel, isJobRunning } from 'util/jobs';
-import { formatDateString } from 'util/dates';
-import { Job } from 'types';
-import getJobHelpText from '../Job.helptext';
-
-const StatusDetailValue = styled.div`
- align-items: center;
- display: inline-grid;
- grid-gap: 10px;
- grid-template-columns: auto auto;
-`;
-
-function JobDetail({ job, inventorySourceLabels }) {
- const jobHelpText = getJobHelpText();
- const { me } = useConfig();
- const {
- created_by,
- credential,
- credentials,
- instance_group: instanceGroup,
- inventory,
- inventory_source,
- source_project,
- job_template: jobTemplate,
- workflow_job_template: workflowJobTemplate,
- labels,
- project,
- project_update: projectUpdate,
- source_workflow_job,
- execution_environment: executionEnvironment,
- } = job.summary_fields;
- const { scm_branch: scmBranch } = job;
- const [errorMsg, setErrorMsg] = useState();
- const history = useHistory();
-
- const jobTypes = {
- project_update: t`Source Control Update`,
- inventory_update: t`Inventory Sync`,
- job: job.job_type === 'check' ? t`Playbook Check` : t`Playbook Run`,
- ad_hoc_command: t`Run Command`,
- system_job: t`Management Job`,
- workflow_job: t`Workflow Job`,
- };
-
- const scmTypes = {
- '': t`Manual`,
- git: t`Git`,
- svn: t`Subversion`,
- insights: t`Red Hat Insights`,
- archive: t`Remote Archive`,
- };
-
- const deleteJob = async () => {
- try {
- await getJobModel(job.type).destroy(job.id);
- history.push('/jobs');
- } catch (err) {
- setErrorMsg(err);
- }
- };
-
- const buildInstanceGroupLink = (item) => (
- {item.name}
- );
-
- const buildContainerGroupLink = (item) => (
- {item.name}
- );
-
- const renderInventoryDetail = () => {
- if (
- job.type !== 'project_update' &&
- job.type !== 'system_job' &&
- job.type !== 'workflow_job'
- ) {
- return inventory ? (
-
- {inventory.name}
-
- }
- />
- ) : (
-
- );
- }
- if (job.type === 'workflow_job') {
- return inventory ? (
-
- {inventory.name}
-
- }
- />
- ) : null;
- }
- return null;
- };
-
- const renderProjectDetail = () => {
- if (
- (job.type !== 'ad_hoc_command' &&
- job.type !== 'inventory_update' &&
- job.type !== 'system_job' &&
- job.type !== 'workflow_job') ||
- source_project
- ) {
- const projectDetailsLink = `/projects/${
- project ? project?.id : source_project?.id
- }/details`;
-
- const jobLink = `/jobs/project/${
- project ? projectUpdate?.id : job.source_project_update
- }`;
-
- let projectName = '';
- if (project?.name || source_project?.name) {
- projectName = project ? project.name : source_project.name;
- }
- return project || inventory_source ? (
- <>
- {projectName}}
- />
-
-
-
- ) : null
- }
- />
- >
- ) : (
-
- );
- }
- return null;
- };
- return (
-
-
-
-
- {job.status && }
- {job?.job_explanation !== job.status ? job.job_explanation : null}
-
- }
- />
-
- {job?.finished && (
-
- )}
- {jobTemplate && (
-
- {jobTemplate.name}
-
- }
- />
- )}
- {workflowJobTemplate && (
-
- {workflowJobTemplate.name}
-
- }
- />
- )}
- {source_workflow_job && (
-
- {source_workflow_job.id} - {source_workflow_job.name}
-
- }
- />
- )}
-
-
-
- {renderInventoryDetail()}
- {inventory_source && (
- <>
-
- {inventory_source.name}
-
- }
- />
- {!source_project && (
-
- string === job.source ? label : null
- )}
- isEmpty={inventorySourceLabels.length === 0}
- />
- )}
- >
- )}
- {renderProjectDetail()}
- {scmBranch && (
-
- )}
-
-
-
-
- {job.type !== 'workflow_job' && !isJobRunning(job.status) && (
-
- )}
-
- {job?.controller_node ? (
-
- ) : null}
- {instanceGroup && !instanceGroup?.is_container_group && (
-
- )}
- {instanceGroup && instanceGroup?.is_container_group && (
-
- )}
- {typeof job.job_slice_number === 'number' &&
- typeof job.job_slice_count === 'number' && (
-
- )}
- {job.type === 'workflow_job' && job.is_sliced_job && (
-
- )}
- {typeof job.forks === 'number' && (
-
- )}
- {typeof job.timeout === 'number' && (
-
- )}
- {credential && (
-
-
-
- }
- />
- )}
- {credentials && (
-
- {credentials.map((c) => (
-
- ))}
-
- }
- isEmpty={credentials.length === 0}
- />
- )}
- {labels && labels.count > 0 && (
-
- {labels.results.map((l) => (
-
- {l.name}
-
- ))}
-
- }
- />
- )}
- {job.job_tags && (
-
- {job.job_tags.split(',').map((jobTag) => (
-
- {jobTag}
-
- ))}
-
- }
- isEmpty={job.job_tags.length === 0}
- />
- )}
- {job.skip_tags && (
-
- {job.skip_tags.split(',').map((skipTag) => (
-
- {skipTag}
-
- ))}
-
- }
- isEmpty={job.skip_tags.length === 0}
- />
- )}
-
-
-
-
- {job.extra_vars && (
-
- )}
- {job.artifacts && (
-
- )}
-
-
- {job.type !== 'system_job' &&
- job.summary_fields.user_capabilities.start &&
- (job.status === 'failed' && job.type === 'job' ? (
-
- {({ handleRelaunch, isLaunching }) => (
-
- )}
-
- ) : (
-
- {({ handleRelaunch, isLaunching }) => (
-
- )}
-
- ))}
- {isJobRunning(job.status) &&
- (job.type === 'system_job'
- ? me.is_superuser
- : job?.summary_fields?.user_capabilities?.start) && (
-
- )}
- {!isJobRunning(job.status) &&
- job?.summary_fields?.user_capabilities?.delete && (
-
- {t`Delete`}
-
- )}
-
- {errorMsg && (
- setErrorMsg()}
- title={t`Job Delete Error`}
- >
-
-
- )}
-
- );
-}
-JobDetail.propTypes = {
- job: Job.isRequired,
-};
-
-export default JobDetail;
diff --git a/awx/ui/src/screens/Job/JobDetail/JobDetail.test.js b/awx/ui/src/screens/Job/JobDetail/JobDetail.test.js
deleted file mode 100644
index bfc577bf9a07..000000000000
--- a/awx/ui/src/screens/Job/JobDetail/JobDetail.test.js
+++ /dev/null
@@ -1,611 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { JobsAPI, ProjectUpdatesAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import JobDetail from './JobDetail';
-import mockJobData from '../shared/data.job.json';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
- function assertDetail(label, value) {
- expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
- expect(wrapper.find(`Detail[label="${label}"] dd`).text()).toBe(value);
- }
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should display details', () => {
- wrapper = mountWithContexts(
-
- );
-
- // StatusIcon adds visibly hidden accessibility text " successful "
- assertDetail('Job ID', '2');
- expect(wrapper.find(`Detail[label="Status"] dd`).text()).toContain(
- 'Successful'
- );
- expect(wrapper.find(`Detail[label="Status"] dd`).text()).toContain(
- 'Job explanation placeholder'
- );
- assertDetail('Started', '8/8/2019, 7:24:18 PM');
- assertDetail('Finished', '8/8/2019, 7:24:50 PM');
- assertDetail('Job Template', mockJobData.summary_fields.job_template.name);
- assertDetail('Source Workflow Job', `1234 - Test Source Workflow`);
- assertDetail('Job Type', 'Playbook Run');
- assertDetail('Launched By', mockJobData.summary_fields.created_by.username);
- assertDetail('Inventory', mockJobData.summary_fields.inventory.name);
- assertDetail('Project', mockJobData.summary_fields.project.name);
- assertDetail('Revision', mockJobData.scm_revision);
- assertDetail('Playbook', mockJobData.playbook);
- assertDetail('Verbosity', '0 (Normal)');
- assertDetail('Execution Node', mockJobData.execution_node);
- assertDetail(
- 'Instance Group',
- mockJobData.summary_fields.instance_group.name
- );
- assertDetail('Credentials', 'SSH: Demo Credential');
- assertDetail('Machine Credential', 'SSH: Machine cred');
- assertDetail('Source Control Branch', 'main');
-
- assertDetail(
- 'Execution Environment',
- mockJobData.summary_fields.execution_environment.name
- );
-
- assertDetail('Job Slice', '0/1');
- assertDetail('Forks', '42');
-
- const credentialChip = wrapper.find(
- `Detail[label="Credentials"] CredentialChip`
- );
- expect(credentialChip.prop('credential')).toEqual(
- mockJobData.summary_fields.credentials[0]
- );
-
- expect(
- wrapper
- .find('Detail[label="Job Tags"]')
- .containsAnyMatchingElements([a, b])
- ).toEqual(true);
-
- expect(
- wrapper
- .find('Detail[label="Skip Tags"]')
- .containsAnyMatchingElements([c, d])
- ).toEqual(true);
-
- const statusDetail = wrapper.find('Detail[label="Status"]');
- const statusLabel = statusDetail.find('StatusLabel');
- expect(statusLabel.prop('status')).toEqual('successful');
-
- const projectStatusDetail = wrapper.find(
- 'Detail[label="Project Update Status"]'
- );
- expect(projectStatusDetail.find('StatusLabel')).toHaveLength(1);
- const projectStatusLabel = statusDetail.find('StatusLabel');
- expect(projectStatusLabel.prop('status')).toEqual('successful');
- });
-
- test('should display Deleted for Inventory and Project for job type run', () => {
- const job = {
- ...mockJobData,
- summary_fields: {
- ...mockJobData.summary_fields,
- project: null,
- inventory: null,
- },
- project: null,
- inventory: null,
- };
-
- wrapper = mountWithContexts();
- expect(wrapper.find(`DeletedDetail[label="Project"]`).length).toBe(1);
- expect(wrapper.find(`DeletedDetail[label="Inventory"]`).length).toBe(1);
- });
-
- test('should not display finished date', () => {
- wrapper = mountWithContexts(
-
- );
- expect(wrapper.find(`Detail[label="Finished"]`).length).toBe(0);
- });
-
- test('should display module name and module arguments', () => {
- wrapper = mountWithContexts(
-
- );
- assertDetail('Module Name', 'command');
- assertDetail('Module Arguments', 'echo hello_world');
- assertDetail('Job Type', 'Run Command');
- expect(wrapper.find(`Detail[label="Project"]`).length).toBe(0);
- });
-
- test('should display source data', () => {
- wrapper = mountWithContexts(
-
- );
- assertDetail('Source', 'Sourced from Project');
- expect(wrapper.find(`Detail[label="Project"]`).length).toBe(0);
- });
-
- test('should show schedule that launched workflow job', async () => {
- wrapper = mountWithContexts(
-
- );
- const launchedByDetail = wrapper.find('Detail[label="Launched By"] dd');
- expect(launchedByDetail).toHaveLength(1);
- expect(launchedByDetail.text()).toBe('mock wf schedule');
- expect(
- launchedByDetail.find(
- 'a[href="/templates/workflow_job_template/888/schedules/999/details"]'
- )
- ).toHaveLength(1);
- });
-
- test('should hide "Launched By" detail for JT launched from a workflow launched by a schedule', () => {
- wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('Detail[label="Launched By"] dt')).toHaveLength(0);
- expect(wrapper.find('Detail[label="Launched By"] dd')).toHaveLength(0);
- });
-
- test('should properly delete job', async () => {
- wrapper = mountWithContexts();
- wrapper.find('button[aria-label="Delete"]').simulate('click');
- wrapper.update();
- const modal = wrapper.find('Modal[aria-label="Alert modal"]');
- expect(modal.length).toBe(1);
- modal.find('button[aria-label="Confirm Delete"]').simulate('click');
- expect(JobsAPI.destroy).toHaveBeenCalledTimes(1);
- });
-
- test('should display error modal when a job does not delete properly', async () => {
- ProjectUpdatesAPI.destroy.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'delete',
- url: '/api/v2/project_updates/1',
- },
- data: 'An error occurred',
- status: 404,
- },
- })
- );
- wrapper = mountWithContexts();
- wrapper.find('button[aria-label="Delete"]').simulate('click');
- const modal = wrapper.find('Modal[aria-label="Alert modal"]');
- expect(modal.length).toBe(1);
- await act(async () => {
- modal.find('button[aria-label="Confirm Delete"]').simulate('click');
- });
- wrapper.update();
-
- const errorModal = wrapper.find('ErrorDetail');
- expect(errorModal.length).toBe(1);
- });
-
- test('should display Playbook Check detail', () => {
- wrapper = mountWithContexts(
-
- );
- assertDetail('Job Type', 'Playbook Check');
- });
-
- test('should not show cancel job button, not super user', () => {
- const history = createMemoryHistory({
- initialEntries: ['/settings/miscellaneous_system/edit'],
- });
-
- wrapper = mountWithContexts(
- ,
- {
- context: {
- router: {
- history,
- },
- config: {
- me: {
- is_superuser: false,
- },
- },
- },
- }
- );
- expect(
- wrapper.find('Button[aria-label="Cancel Demo Job Template"]')
- ).toHaveLength(0);
- expect(wrapper.find(`Detail[label="Project"]`).length).toBe(0);
- });
-
- test('should not show cancel job button, job completed', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/settings/miscellaneous_system/edit'],
- });
-
- wrapper = mountWithContexts(
- ,
- {
- context: {
- router: {
- history,
- },
- config: {
- me: {
- is_superuser: true,
- },
- },
- },
- }
- );
- expect(
- wrapper.find('Button[aria-label="Cancel Demo Job Template"]')
- ).toHaveLength(0);
- });
-
- test('should show cancel button, pending, super user', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/settings/miscellaneous_system/edit'],
- });
-
- wrapper = mountWithContexts(
- ,
- {
- context: {
- router: {
- history,
- },
- config: {
- me: {
- is_superuser: true,
- },
- },
- },
- }
- );
- expect(
- wrapper.find('Button[aria-label="Cancel Demo Job Template"]')
- ).toHaveLength(1);
- });
-
- test('should show cancel button, pending, super project update, not super user', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/settings/miscellaneous_system/edit'],
- });
-
- wrapper = mountWithContexts(
- ,
- {
- context: {
- router: {
- history,
- },
- config: {
- me: {
- is_superuser: false,
- },
- },
- },
- }
- );
- expect(
- wrapper.find('Button[aria-label="Cancel Demo Job Template"]')
- ).toHaveLength(1);
- });
-
- test('should render workflow job details', () => {
- const workFlowJob = {
- id: 15,
- type: 'workflow_job',
- url: '/api/v2/workflow_jobs/15/',
- related: {
- created_by: '/api/v2/users/1/',
- modified_by: '/api/v2/users/1/',
- unified_job_template: '/api/v2/job_templates/9/',
- job_template: '/api/v2/job_templates/9/',
- workflow_nodes: '/api/v2/workflow_jobs/15/workflow_nodes/',
- labels: '/api/v2/workflow_jobs/15/labels/',
- activity_stream: '/api/v2/workflow_jobs/15/activity_stream/',
- relaunch: '/api/v2/workflow_jobs/15/relaunch/',
- cancel: '/api/v2/workflow_jobs/15/cancel/',
- },
- summary_fields: {
- organization: {
- id: 1,
- name: 'Default',
- description: '',
- },
- inventory: {
- id: 1,
- name: 'Demo Inventory',
- description: '',
- has_active_failures: false,
- total_hosts: 4,
- hosts_with_active_failures: 0,
- total_groups: 0,
- has_inventory_sources: false,
- total_inventory_sources: 0,
- inventory_sources_with_failures: 0,
- organization_id: 1,
- kind: '',
- },
- job_template: {
- id: 9,
- name: 'Sliced Job Template',
- description: '',
- },
- unified_job_template: {
- id: 9,
- name: 'Sliced Job Template',
- description: '',
- unified_job_type: 'job',
- },
- created_by: {
- id: 1,
- username: 'admin',
- first_name: '',
- last_name: '',
- },
- modified_by: {
- id: 1,
- username: 'admin',
- first_name: '',
- last_name: '',
- },
- user_capabilities: {
- delete: true,
- start: true,
- },
- labels: {
- count: 0,
- results: [],
- },
- },
- created: '2021-07-06T19:40:17.654030Z',
- modified: '2021-07-06T19:40:17.964699Z',
- name: 'Sliced Job Template',
- description: '',
- unified_job_template: 9,
- launch_type: 'manual',
- status: 'successful',
- failed: false,
- started: '2021-07-06T19:40:17.962019Z',
- finished: '2021-07-06T19:40:42.238563Z',
- canceled_on: null,
- elapsed: 24.277,
- job_explanation: '',
- launched_by: {
- id: 1,
- name: 'admin',
- type: 'user',
- url: '/api/v2/users/1/',
- },
- work_unit_id: null,
- workflow_job_template: null,
- extra_vars: '{}',
- allow_simultaneous: false,
- job_template: 9,
- is_sliced_job: true,
- inventory: 1,
- limit: '',
- scm_branch: '',
- webhook_service: '',
- webhook_credential: null,
- webhook_guid: '',
- };
- wrapper = mountWithContexts();
- assertDetail('Status', 'Successful');
- assertDetail('Started', '7/6/2021, 7:40:17 PM');
- assertDetail('Finished', '7/6/2021, 7:40:42 PM');
- assertDetail('Job Template', 'Sliced Job Template');
- assertDetail('Job Type', 'Workflow Job');
- assertDetail('Inventory', 'Demo Inventory');
- assertDetail('Job Slice Parent', 'True');
- });
-
- test('should not load Source', () => {
- wrapper = mountWithContexts(
-
- );
- const source_detail = wrapper.find(`Detail[label="Source"]`).at(0);
- expect(source_detail.prop('isEmpty')).toEqual(true);
- });
-
- test('should not load Credentials', () => {
- wrapper = mountWithContexts(
-
- );
- const credentials_detail = wrapper
- .find(`Detail[label="Credentials"]`)
- .at(0);
- expect(credentials_detail.prop('isEmpty')).toEqual(true);
- });
-
- test('should not load Job Tags', () => {
- wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('Detail[label="Job Tags"]').length).toBe(0);
- });
-
- test('should not load Skip Tags', () => {
- wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('Detail[label="Skip Tags"]').length).toBe(0);
- });
-});
diff --git a/awx/ui/src/screens/Job/JobDetail/index.js b/awx/ui/src/screens/Job/JobDetail/index.js
deleted file mode 100644
index daa4c97fa7e9..000000000000
--- a/awx/ui/src/screens/Job/JobDetail/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './JobDetail';
diff --git a/awx/ui/src/screens/Job/JobOutput/EmptyOutput.js b/awx/ui/src/screens/Job/JobOutput/EmptyOutput.js
deleted file mode 100644
index aaa92878ffd7..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/EmptyOutput.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import React, { useEffect } from 'react';
-import { Link, useParams } from 'react-router-dom';
-import 'styled-components/macro';
-import { t } from '@lingui/macro';
-import {
- SearchIcon,
- ExclamationCircleIcon as PFExclamationCircleIcon,
-} from '@patternfly/react-icons';
-import ContentEmpty from 'components/ContentEmpty';
-
-import styled from 'styled-components';
-
-const ExclamationCircleIcon = styled(PFExclamationCircleIcon)`
- color: var(--pf-global--danger-color--100);
-`;
-
-export default function EmptyOutput({
- hasQueryParams,
- isJobRunning,
- onUnmount,
- job,
-}) {
- let title;
- let message;
- let icon;
- const { typeSegment, id } = useParams();
-
- useEffect(() => onUnmount);
-
- if (hasQueryParams) {
- title = t`The search filter did not produce any results…`;
- message = t`Please try another search using the filter above`;
- icon = SearchIcon;
- } else if (isJobRunning) {
- title = t`Waiting for job output…`;
- } else if (job.status === 'failed') {
- title = t`This job failed and has no output.`;
- message = (
- <>
- {t`Return to `}{' '}
- {t`details.`}
-
- {job.job_explanation && (
- <>
- {t`Failure Explanation:`} {`${job.job_explanation}`}
- >
- )}
- >
- );
- icon = ExclamationCircleIcon;
- } else {
- title = t`No output found for this job.`;
- }
-
- return (
-
- );
-}
diff --git a/awx/ui/src/screens/Job/JobOutput/HostEventModal.js b/awx/ui/src/screens/Job/JobOutput/HostEventModal.js
deleted file mode 100644
index a7295c169258..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/HostEventModal.js
+++ /dev/null
@@ -1,201 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { Modal, Tab, Tabs, TabTitleText } from '@patternfly/react-core';
-import PropTypes from 'prop-types';
-import { t } from '@lingui/macro';
-import { encode } from 'html-entities';
-import StatusLabel from '../../../components/StatusLabel';
-import { DetailList, Detail } from '../../../components/DetailList';
-import ContentEmpty from '../../../components/ContentEmpty';
-import CodeEditor from '../../../components/CodeEditor';
-
-const processEventStatus = (event) => {
- let status = null;
- if (event.event === 'runner_on_unreachable') {
- status = 'unreachable';
- }
- // equiv to 'runner_on_error' && 'runner_on_failed'
- if (event.failed) {
- status = 'failed';
- }
- if (
- event.event === 'runner_on_ok' ||
- event.event === 'runner_on_async_ok' ||
- event.event === 'runner_item_on_ok'
- ) {
- status = 'ok';
- }
- // if 'ok' and 'changed' are both true, show 'changed'
- if (event.changed) {
- status = 'changed';
- }
- if (event.event === 'runner_on_skipped') {
- status = 'skipped';
- }
- return status;
-};
-
-const processCodeEditorValue = (value) => {
- let codeEditorValue;
- if (!value) {
- codeEditorValue = '';
- } else if (typeof value === 'string') {
- codeEditorValue = encode(value);
- } else if (Array.isArray(value)) {
- codeEditorValue = encode(value.join(' '));
- } else {
- codeEditorValue = value;
- }
- return codeEditorValue;
-};
-
-const getStdOutValue = (hostEvent) => {
- const taskAction = hostEvent?.event_data?.task_action;
- const res = hostEvent?.event_data?.res;
-
- let stdOut;
- if (taskAction === 'debug' && res.result && res.result.stdout) {
- stdOut = res.result.stdout;
- } else if (
- taskAction === 'yum' &&
- res.results &&
- Array.isArray(res.results)
- ) {
- stdOut = res.results.join('\n');
- } else if (res?.stdout) {
- stdOut = Array.isArray(res.stdout) ? res.stdout.join(' ') : res.stdout;
- }
- return stdOut;
-};
-
-function HostEventModal({ onClose, hostEvent = {}, isOpen = false }) {
- const [hostStatus, setHostStatus] = useState(null);
- const [activeTabKey, setActiveTabKey] = useState(0);
- useEffect(() => {
- setHostStatus(processEventStatus(hostEvent));
- }, [setHostStatus, hostEvent]);
-
- const handleTabClick = (event, tabIndex) => {
- setActiveTabKey(tabIndex);
- };
-
- const jsonObj = processCodeEditorValue(hostEvent?.event_data?.res);
- const stdErr = hostEvent?.event_data?.res?.stderr;
- const stdOut = getStdOutValue(hostEvent);
-
- return (
-
-
- {t`Details`}}
- >
-
-
- {hostEvent.summary_fields?.host?.description ? (
-
- ) : null}
- {hostStatus ? (
- }
- />
- ) : null}
-
-
-
-
-
-
- {t`JSON`}}
- aria-label={t`JSON tab`}
- ouiaId="json-tab"
- >
- {activeTabKey === 1 && jsonObj ? (
- {}}
- rows={20}
- hasErrors={false}
- />
- ) : (
-
- )}
-
- {stdOut?.length ? (
- {t`Output`}}
- aria-label={t`Output tab`}
- ouiaId="standard-out-tab"
- >
- {}}
- rows={20}
- hasErrors={false}
- />
-
- ) : null}
- {stdErr?.length ? (
- {t`Standard Error`}}
- aria-label={t`Standard error tab`}
- ouiaId="standard-error-tab"
- >
- {}}
- value={stdErr}
- hasErrors={false}
- rows={20}
- />
-
- ) : null}
-
-
- );
-}
-
-export default HostEventModal;
-
-HostEventModal.propTypes = {
- onClose: PropTypes.func.isRequired,
- hostEvent: PropTypes.shape({}),
- isOpen: PropTypes.bool,
-};
-
-HostEventModal.defaultProps = {
- hostEvent: null,
- isOpen: false,
-};
diff --git a/awx/ui/src/screens/Job/JobOutput/HostEventModal.test.js b/awx/ui/src/screens/Job/JobOutput/HostEventModal.test.js
deleted file mode 100644
index 0b877b4e4c75..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/HostEventModal.test.js
+++ /dev/null
@@ -1,407 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import HostEventModal from './HostEventModal';
-
-const hostEvent = {
- changed: true,
- event: 'runner_on_ok',
- event_data: {
- host: 'foo',
- play: 'all',
- playbook: 'run_command.yml',
- res: {
- ansible_loop_var: 'item',
- changed: true,
- item: '1',
- msg: 'This is a debug message: 1',
- stdout:
- ' total used free shared buff/cache available\nMem: 7973 3005 960 30 4007 4582\nSwap: 1023 0 1023',
- stderr: 'problems',
- cmd: ['free', '-m'],
- stderr_lines: [],
- stdout_lines: [
- ' total used free shared buff/cache available',
- 'Mem: 7973 3005 960 30 4007 4582',
- 'Swap: 1023 0 1023',
- ],
- },
- task: 'command',
- task_action: 'command',
- },
- event_display: 'Host OK',
- event_level: 3,
- failed: false,
- host: 1,
- host_name: 'foo',
- id: 123,
- job: 4,
- play: 'all',
- playbook: 'run_command.yml',
- stdout: `stdout: "[0;33mchanged: [localhost] => {"changed": true, "cmd": ["free", "-m"], "delta": "0:00:01.479609", "end": "2019-09-10 14:21:45.469533", "rc": 0, "start": "2019-09-10 14:21:43.989924", "stderr": "", "stderr_lines": [], "stdout": " total used free shared buff/cache available\nMem: 7973 3005 960 30 4007 4582\nSwap: 1023 0 1023", "stdout_lines": [" total used free shared buff/cache available", "Mem: 7973 3005 960 30 4007 4582", "Swap: 1023 0 1023"]}[0m"
- `,
- task: 'command',
- type: 'job_event',
- url: '/api/v2/job_events/123/',
- summary_fields: {
- host: {
- id: 1,
- name: 'foo',
- description: 'Bar',
- },
- },
-};
-
-const partialHostEvent = {
- changed: true,
- event: 'runner_on_ok',
- event_data: {
- host: 'foo',
- play: 'all',
- playbook: 'run_command.yml',
- res: {
- ansible_loop_var: 'item',
- changed: true,
- item: '1',
- msg: 'This is a debug message: 1',
- stdout:
- ' total used free shared buff/cache available\nMem: 7973 3005 960 30 4007 4582\nSwap: 1023 0 1023',
- stderr: 'problems',
- cmd: ['free', '-m'],
- stderr_lines: [],
- stdout_lines: [
- ' total used free shared buff/cache available',
- 'Mem: 7973 3005 960 30 4007 4582',
- 'Swap: 1023 0 1023',
- ],
- },
- task: 'command',
- task_action: 'command',
- },
- event_display: 'Host OK',
- event_level: 3,
- failed: false,
- host: 1,
- id: 123,
- job: 4,
- play: 'all',
- playbook: 'run_command.yml',
- stdout: `stdout: "[0;33mchanged: [localhost] => {"changed": true, "cmd": ["free", "-m"], "delta": "0:00:01.479609", "end": "2019-09-10 14:21:45.469533", "rc": 0, "start": "2019-09-10 14:21:43.989924", "stderr": "", "stderr_lines": [], "stdout": " total used free shared buff/cache available\nMem: 7973 3005 960 30 4007 4582\nSwap: 1023 0 1023", "stdout_lines": [" total used free shared buff/cache available", "Mem: 7973 3005 960 30 4007 4582", "Swap: 1023 0 1023"]}[0m"
- `,
- task: 'command',
- type: 'job_event',
- url: '/api/v2/job_events/123/',
-};
-
-/*
-Some libraries return a list of string in stdout
-Example: https://github.com/ansible-collections/cisco.ios/blob/main/plugins/modules/ios_command.py#L124-L128
-*/
-const hostEventWithArray = {
- changed: true,
- event: 'runner_on_ok',
- event_data: {
- host: 'foo',
- play: 'all',
- playbook: 'run_command.yml',
- res: {
- ansible_loop_var: 'item',
- changed: true,
- item: '1',
- msg: 'This is a debug message: 1',
- stdout: [
- ' total used free shared buff/cache available\nMem: 7973 3005 960 30 4007 4582\nSwap: 1023 0 1023',
- ],
- stderr: 'problems',
- cmd: ['free', '-m'],
- stderr_lines: [],
- stdout_lines: [
- ' total used free shared buff/cache available',
- 'Mem: 7973 3005 960 30 4007 4582',
- 'Swap: 1023 0 1023',
- ],
- },
- task: 'command',
- task_action: 'command',
- },
- event_display: 'Host OK',
- event_level: 3,
- failed: false,
- host: 1,
- host_name: 'foo',
- id: 123,
- job: 4,
- play: 'all',
- playbook: 'run_command.yml',
- stdout: `stdout: "[0;33mchanged: [localhost] => {"changed": true, "cmd": ["free", "-m"], "delta": "0:00:01.479609", "end": "2019-09-10 14:21:45.469533", "rc": 0, "start": "2019-09-10 14:21:43.989924", "stderr": "", "stderr_lines": [], "stdout": " total used free shared buff/cache available\nMem: 7973 3005 960 30 4007 4582\nSwap: 1023 0 1023", "stdout_lines": [" total used free shared buff/cache available", "Mem: 7973 3005 960 30 4007 4582", "Swap: 1023 0 1023"]}[0m"
- `,
- task: 'command',
- type: 'job_event',
- url: '/api/v2/job_events/123/',
- summary_fields: {
- host: {
- id: 1,
- name: 'foo',
- description: 'Bar',
- },
- },
-};
-
-/* eslint-disable no-useless-escape */
-const jsonValue = `{
- \"ansible_loop_var\": \"item\",
- \"changed\": true,
- \"item\": \"1\",
- \"msg\": \"This is a debug message: 1\",
- \"stdout\": \" total used free shared buff/cache available\\nMem: 7973 3005 960 30 4007 4582\\nSwap: 1023 0 1023\",
- \"stderr\": \"problems\",
- \"cmd\": [
- \"free\",
- \"-m\"
- ],
- \"stderr_lines\": [],
- \"stdout_lines\": [
- \" total used free shared buff/cache available\",
- \"Mem: 7973 3005 960 30 4007 4582\",
- \"Swap: 1023 0 1023\"
- ]
-}`;
-
-describe('HostEventModal', () => {
- test('initially renders successfully', () => {
- const wrapper = shallow(
- {}} />
- );
- expect(wrapper).toHaveLength(1);
- });
-
- test('renders successfully with partial data', () => {
- const wrapper = shallow(
- {}} />
- );
- expect(wrapper).toHaveLength(1);
- });
-
- test('should render all tabs', () => {
- const wrapper = shallow(
- {}} isOpen />
- );
-
- expect(wrapper.find('Tabs Tab').length).toEqual(4);
- });
-
- test('should initially show details tab', () => {
- const wrapper = shallow(
- {}} isOpen />
- );
- expect(wrapper.find('Tabs').prop('activeKey')).toEqual(0);
- expect(wrapper.find('Detail')).toHaveLength(6);
-
- function assertDetail(index, label, value) {
- const detail = wrapper.find('Detail').at(index);
- expect(detail.prop('label')).toEqual(label);
- expect(detail.prop('value')).toEqual(value);
- }
-
- const detail = wrapper.find('Detail').first();
- expect(detail.prop('value')).toEqual('foo');
- assertDetail(1, 'Description', 'Bar');
- assertDetail(2, 'Play', 'all');
- assertDetail(3, 'Task', 'command');
- assertDetail(4, 'Module', 'command');
- assertDetail(5, 'Command', hostEvent.event_data.res.cmd);
- });
-
- test('should display successful host status label', () => {
- const successfulHostEvent = { ...hostEvent, changed: false };
- const wrapper = mountWithContexts(
- {}}
- isOpen
- />
- );
- const icon = wrapper.find('StatusLabel');
- expect(icon.prop('status')).toBe('ok');
- });
-
- test('should display skipped host status label', () => {
- const skippedHostEvent = { ...hostEvent, event: 'runner_on_skipped' };
- const wrapper = mountWithContexts(
- {}} isOpen />
- );
-
- const icon = wrapper.find('StatusLabel');
- expect(icon.prop('status')).toBe('skipped');
- });
-
- test('should display unreachable host status label', () => {
- const unreachableHostEvent = {
- ...hostEvent,
- event: 'runner_on_unreachable',
- changed: false,
- };
- const wrapper = mountWithContexts(
- {}}
- isOpen
- />
- );
-
- const icon = wrapper.find('StatusLabel');
- expect(icon.prop('status')).toBe('unreachable');
- });
-
- test('should display failed host status label', () => {
- const unreachableHostEvent = {
- ...hostEvent,
- changed: false,
- failed: true,
- event: 'runner_on_failed',
- };
- const wrapper = mountWithContexts(
- {}}
- isOpen
- />
- );
-
- const icon = wrapper.find('StatusLabel');
- expect(icon.prop('status')).toBe('failed');
- });
-
- test('should display JSON tab content on tab click', () => {
- const wrapper = shallow(
- {}} isOpen />
- );
-
- const handleTabClick = wrapper.find('Tabs').prop('onSelect');
- handleTabClick(null, 1);
- wrapper.update();
-
- const codeEditor = wrapper.find('Tab[eventKey=1] CodeEditor');
- expect(codeEditor.prop('mode')).toBe('javascript');
- expect(codeEditor.prop('readOnly')).toBe(true);
- expect(codeEditor.prop('value')).toEqual(jsonValue);
- });
-
- test('should display Standard Out tab content on tab click', () => {
- const wrapper = shallow(
- {}} isOpen />
- );
-
- const handleTabClick = wrapper.find('Tabs').prop('onSelect');
- handleTabClick(null, 2);
- wrapper.update();
-
- const codeEditor = wrapper.find('Tab[eventKey=2] CodeEditor');
- expect(codeEditor.prop('mode')).toBe('javascript');
- expect(codeEditor.prop('readOnly')).toBe(true);
- expect(codeEditor.prop('value')).toEqual(hostEvent.event_data.res.stdout);
- });
-
- test('should display Standard Error tab content on tab click', () => {
- const hostEventError = {
- ...hostEvent,
- event_data: {
- res: {
- stderr: 'error content',
- },
- },
- };
- const wrapper = shallow(
- {}} isOpen />
- );
-
- const handleTabClick = wrapper.find('Tabs').prop('onSelect');
- handleTabClick(null, 3);
- wrapper.update();
-
- const codeEditor = wrapper.find('Tab[eventKey=3] CodeEditor');
- expect(codeEditor.prop('mode')).toBe('javascript');
- expect(codeEditor.prop('readOnly')).toBe(true);
- expect(codeEditor.prop('value')).toEqual('error content');
- });
-
- test('should pass onClose to Modal', () => {
- const onClose = jest.fn();
- const wrapper = shallow(
-
- );
-
- expect(wrapper.find('Modal').prop('onClose')).toEqual(onClose);
- });
-
- test('should render standard out of debug task', () => {
- const debugTaskAction = {
- ...hostEvent,
- event_data: {
- task_action: 'debug',
- res: {
- result: {
- stdout: 'foo bar',
- },
- },
- },
- };
- const wrapper = shallow(
- {}} isOpen />
- );
-
- const handleTabClick = wrapper.find('Tabs').prop('onSelect');
- handleTabClick(null, 2);
- wrapper.update();
-
- const codeEditor = wrapper.find('Tab[eventKey=2] CodeEditor');
- expect(codeEditor.prop('mode')).toBe('javascript');
- expect(codeEditor.prop('readOnly')).toBe(true);
- expect(codeEditor.prop('value')).toEqual('foo bar');
- });
-
- test('should render standard out of yum task', () => {
- const yumTaskAction = {
- ...hostEvent,
- event_data: {
- task_action: 'yum',
- res: {
- results: ['baz', 'bar'],
- },
- },
- };
- const wrapper = shallow(
- {}} isOpen />
- );
-
- const handleTabClick = wrapper.find('Tabs').prop('onSelect');
- handleTabClick(null, 2);
- wrapper.update();
-
- const codeEditor = wrapper.find('Tab[eventKey=2] CodeEditor');
- expect(codeEditor.prop('mode')).toBe('javascript');
- expect(codeEditor.prop('readOnly')).toBe(true);
- expect(codeEditor.prop('value')).toEqual('baz\nbar');
- });
-
- test('should display Standard Out array stdout content', () => {
- const wrapper = shallow(
- {}}
- isOpen
- />
- );
-
- const handleTabClick = wrapper.find('Tabs').prop('onSelect');
- handleTabClick(null, 2);
- wrapper.update();
-
- const codeEditor = wrapper.find('Tab[eventKey=2] CodeEditor');
- expect(codeEditor.prop('mode')).toBe('javascript');
- expect(codeEditor.prop('readOnly')).toBe(true);
- expect(codeEditor.prop('value')).toEqual(
- hostEventWithArray.event_data.res.stdout.join(' ')
- );
- });
-});
diff --git a/awx/ui/src/screens/Job/JobOutput/JobEvent.js b/awx/ui/src/screens/Job/JobOutput/JobEvent.js
deleted file mode 100644
index 87f6f54fbfc4..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/JobEvent.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import React, { useEffect } from 'react';
-import {
- JobEventLine,
- JobEventLineToggle,
- JobEventLineNumber,
- JobEventLineText,
- JobEventEllipsis,
-} from './shared';
-
-function JobEvent({
- style,
- lineTextHtml,
- isClickable,
- onJobEventClick,
- event,
- measure,
- isCollapsed,
- onToggleCollapsed,
- hasChildren,
- jobStatus,
-}) {
- const numOutputLines = lineTextHtml?.length || 0;
- useEffect(() => {
- const timeout = setTimeout(measure, 0);
- return () => {
- clearTimeout(timeout);
- };
- }, [numOutputLines, isCollapsed, measure, jobStatus]);
-
- let toggleLineIndex = -1;
- if (hasChildren) {
- lineTextHtml.forEach(({ html }, index) => {
- if (html) {
- toggleLineIndex = index;
- }
- });
- }
- return !event.stdout ? null : (
-
- {lineTextHtml.map(({ lineNumber, html }, index) => {
- if (lineNumber < 0) {
- return null;
- }
- const canToggle = index === toggleLineIndex && !event.isTracebackOnly;
- return (
-
-
-
- {!event.isTracebackOnly ? lineNumber : ''}
-
-
-
-
- );
- })}
-
- );
-}
-
-export default JobEvent;
diff --git a/awx/ui/src/screens/Job/JobOutput/JobEvent.test.js b/awx/ui/src/screens/Job/JobOutput/JobEvent.test.js
deleted file mode 100644
index f2a8cf8c8fe3..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/JobEvent.test.js
+++ /dev/null
@@ -1,99 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import JobEvent from './JobEvent';
-
-const mockOnPlayStartEvent = {
- created: '2019-07-11T18:11:22.005319Z',
- event: 'playbook_on_play_start',
- counter: 2,
- start_line: 0,
- end_line: 2,
- stdout:
- '\r\nPLAY [add hosts to inventory] **************************************************',
-};
-const mockRunnerOnOkEvent = {
- created: '2019-07-11T18:09:22.906001Z',
- event: 'runner_on_ok',
- counter: 5,
- start_line: 4,
- end_line: 5,
- stdout: '\u001b[0;32mok: [localhost]\u001b[0m',
-};
-
-const singleDigitTimestampEvent = {
- ...mockOnPlayStartEvent,
- created: '2019-07-11T08:01:02.906001Z',
-};
-
-const mockSingleDigitTimestampEventLineTextHtml = [
- { lineNumber: 0, html: '' },
- {
- lineNumber: 1,
- html: 'PLAY [add hosts to inventory] **************************************************08:01:02',
- },
-];
-
-const mockAnsiLineTextHtml = [
- {
- lineNumber: 4,
- html: 'ok: [localhost]',
- },
-];
-
-const mockOnPlayStartLineTextHtml = [
- { lineNumber: 0, html: '' },
- {
- lineNumber: 1,
- html: 'PLAY [add hosts to inventory] **************************************************18:11:22',
- },
-];
-
-describe('', () => {
- test('playbook event timestamps are rendered', () => {
- const wrapper1 = shallow(
-
- );
- const lineText1 = wrapper1.find('JobEventLineText');
- const html1 = lineText1.at(1).prop('dangerouslySetInnerHTML').__html;
- expect(html1.includes('18:11:22')).toBe(true);
-
- const wrapper2 = shallow(
-
- );
- const lineText2 = wrapper2.find('JobEventLineText');
- const html2 = lineText2.at(1).prop('dangerouslySetInnerHTML').__html;
- expect(html2.includes('08:01:02')).toBe(true);
- });
-
- test('ansi stdout colors are rendered as html', () => {
- const wrapper = shallow(
-
- );
- const lineText = wrapper.find('JobEventLineText');
- expect(
- lineText
- .prop('dangerouslySetInnerHTML')
- .__html.includes(
- 'ok: [localhost]'
- )
- ).toBe(true);
- });
-
- test("events without stdout aren't rendered", () => {
- const missingStdoutEvent = { ...mockOnPlayStartEvent };
- delete missingStdoutEvent.stdout;
- const wrapper = shallow(
-
- );
- expect(wrapper.find('JobEventLineText')).toHaveLength(0);
- });
-});
diff --git a/awx/ui/src/screens/Job/JobOutput/JobEventSkeleton.js b/awx/ui/src/screens/Job/JobOutput/JobEventSkeleton.js
deleted file mode 100644
index 2ecc66d856be..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/JobEventSkeleton.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import React, { useEffect } from 'react';
-import {
- JobEventLine,
- JobEventLineToggle,
- JobEventLineNumber,
- JobEventLineText,
-} from './shared';
-
-function JobEventSkeletonContent({ contentLength }) {
- return (
-
- {' '.repeat(contentLength)}
-
- );
-}
-
-function JobEventSkeleton({ counter, contentLength, style, measure }) {
- useEffect(() => {
- measure();
- }, [measure]);
-
- return (
- counter > 1 && (
-
-
-
-
-
-
-
- )
- );
-}
-
-export default JobEventSkeleton;
diff --git a/awx/ui/src/screens/Job/JobOutput/JobEventSkeleton.test.js b/awx/ui/src/screens/Job/JobOutput/JobEventSkeleton.test.js
deleted file mode 100644
index dac7d18d4c31..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/JobEventSkeleton.test.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import JobEventSkeleton from './JobEventSkeleton';
-
-const contentSelector = 'JobEventSkeletonContent';
-
-describe('', () => {
- test('initially renders successfully', () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.find(contentSelector).length).toEqual(1);
- });
-
- test('always skips first counter', () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.find(contentSelector).length).toEqual(0);
- });
-});
diff --git a/awx/ui/src/screens/Job/JobOutput/JobOutput.js b/awx/ui/src/screens/Job/JobOutput/JobOutput.js
deleted file mode 100644
index 12d541ffad51..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/JobOutput.js
+++ /dev/null
@@ -1,867 +0,0 @@
-/* eslint-disable react/jsx-no-useless-fragment */
-import React, { useCallback, useEffect, useRef, useState } from 'react';
-import { useHistory, useLocation } from 'react-router-dom';
-import { t } from '@lingui/macro';
-import styled from 'styled-components';
-import {
- AutoSizer,
- CellMeasurer,
- CellMeasurerCache,
- InfiniteLoader,
- List,
-} from 'react-virtualized';
-import { Button, Alert } from '@patternfly/react-core';
-
-import AlertModal from 'components/AlertModal';
-import { CardBody as _CardBody } from 'components/Card';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import ErrorDetail from 'components/ErrorDetail';
-import StatusLabel from 'components/StatusLabel';
-import { JobsAPI } from 'api';
-
-import { getJobModel, isJobRunning } from 'util/jobs';
-import useRequest, { useDismissableError } from 'hooks/useRequest';
-import useInterval from 'hooks/useInterval';
-import { parseQueryString, getQSConfig } from 'util/qs';
-import useIsMounted from 'hooks/useIsMounted';
-import JobEvent from './JobEvent';
-import JobEventSkeleton from './JobEventSkeleton';
-import PageControls from './PageControls';
-import HostEventModal from './HostEventModal';
-import JobOutputSearch from './JobOutputSearch';
-import EmptyOutput from './EmptyOutput';
-import { HostStatusBar, OutputToolbar } from './shared';
-import getLineTextHtml from './getLineTextHtml';
-import connectJobSocket, { closeWebSocket } from './connectJobSocket';
-import getEventRequestParams from './getEventRequestParams';
-import isHostEvent from './isHostEvent';
-import { prependTraceback } from './loadJobEvents';
-import useJobEvents from './useJobEvents';
-
-const QS_CONFIG = getQSConfig('job_output', {
- order_by: 'counter',
-});
-
-const CardBody = styled(_CardBody)`
- display: flex;
- flex-flow: column;
- height: calc(100vh - 267px);
-`;
-
-const HeaderTitle = styled.div`
- display: inline-flex;
- align-items: center;
- h1 {
- margin-right: 10px;
- font-weight: var(--pf-global--FontWeight--bold);
- }
-`;
-
-const OutputHeader = styled.div`
- display: flex;
- justify-content: space-between;
-`;
-
-const OutputWrapper = styled.div`
- background-color: #ffffff;
- display: flex;
- flex-direction: column;
- flex: 1 1 auto;
- font-family: monospace;
- font-size: 15px;
- outline: 1px solid #d7d7d7;
- ${({ cssMap }) =>
- Object.keys(cssMap).map(
- (className) => `.${className}{${cssMap[className]}}`
- )}
-`;
-
-const OutputFooter = styled.div`
- background-color: #ebebeb;
- border-right: 1px solid #d7d7d7;
- width: 75px;
- flex: 1;
-`;
-
-const cache = new CellMeasurerCache({
- fixedWidth: true,
- defaultHeight: 25,
-});
-
-function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
- const location = useLocation();
- const listRef = useRef(null);
- const previousWidth = useRef(0);
- const jobSocketCounter = useRef(0);
- const isMounted = useIsMounted();
- const scrollTop = useRef(0);
- const scrollHeight = useRef(0);
- const history = useHistory();
- const eventByUuidRequests = useRef([]);
- const eventsProcessedDelay = useRef(250);
-
- const fetchEventByUuid = async (uuid) => {
- let promise = eventByUuidRequests.current[uuid];
- if (!promise) {
- promise = getJobModel(job.type).readEvents(job.id, { uuid });
- eventByUuidRequests.current[uuid] = promise;
- }
- const { data } = await promise;
- eventByUuidRequests.current[uuid] = null;
- return data.results[0] || null;
- };
-
- const fetchChildrenSummary = () => JobsAPI.readChildrenSummary(job.id);
-
- const [jobStatus, setJobStatus] = useState(job.status ?? 'waiting');
- const [forceFlatMode, setForceFlatMode] = useState(false);
- const isFlatMode =
- isJobRunning(jobStatus) || location.search.length > 1 || job.type !== 'job';
- const [isTreeReady, setIsTreeReady] = useState(false);
- const [onReadyEvents, setOnReadyEvents] = useState([]);
-
- const {
- addEvents,
- toggleNodeIsCollapsed,
- toggleCollapseAll,
- getEventForRow,
- getNumCollapsedEvents,
- getCounterForRow,
- getEvent,
- clearLoadedEvents,
- rebuildEventsTree,
- isAllCollapsed,
- } = useJobEvents(
- {
- fetchEventByUuid,
- fetchChildrenSummary,
- setForceFlatMode,
- setJobTreeReady: () => setIsTreeReady(true),
- },
- job.id,
- isFlatMode || forceFlatMode
- );
- const [wsEvents, setWsEvents] = useState([]);
- const [cssMap, setCssMap] = useState({});
- const [remoteRowCount, setRemoteRowCount] = useState(0);
- const [contentError, setContentError] = useState(null);
- const [currentlyLoading, setCurrentlyLoading] = useState([]);
- const [hasContentLoading, setHasContentLoading] = useState(true);
- const [hostEvent, setHostEvent] = useState({});
- const [isHostModalOpen, setIsHostModalOpen] = useState(false);
- const [showCancelModal, setShowCancelModal] = useState(false);
- const [highestLoadedCounter, setHighestLoadedCounter] = useState(0);
- const [isFollowModeEnabled, setIsFollowModeEnabled] = useState(
- isJobRunning(job.status)
- );
- const [isMonitoringWebsocket, setIsMonitoringWebsocket] = useState(false);
- const [lastScrollPosition, setLastScrollPosition] = useState(0);
- const [showEventsRefresh, setShowEventsRefresh] = useState(false);
-
- useEffect(() => {
- if (!isTreeReady || !onReadyEvents.length) {
- return;
- }
- addEvents(onReadyEvents);
- setOnReadyEvents([]);
- if (isFollowModeEnabled) {
- setTimeout(() => {
- scrollToEnd();
- }, 0);
- }
- }, [isTreeReady, onReadyEvents]); // eslint-disable-line react-hooks/exhaustive-deps
-
- const totalNonCollapsedRows = Math.max(
- remoteRowCount - getNumCollapsedEvents(),
- 0
- );
-
- useInterval(
- () => {
- monitorJobSocketCounter();
- },
- isMonitoringWebsocket ? 5000 : null
- );
-
- useEffect(() => {
- const pendingRequests = Object.values(eventByUuidRequests.current || {});
- setHasContentLoading(true); // prevents "no content found" screen from flashing
- if (location.search) {
- setIsFollowModeEnabled(false);
- }
- Promise.allSettled(pendingRequests).then(() => {
- setRemoteRowCount(0);
- clearLoadedEvents();
- loadJobEvents();
- });
- }, [location.search]); // eslint-disable-line react-hooks/exhaustive-deps
-
- useEffect(() => {
- rebuildEventsTree();
- }, [isFlatMode]); // eslint-disable-line react-hooks/exhaustive-deps
-
- const pollForEventsProcessed = useCallback(async () => {
- const {
- data: { event_processing_finished },
- } = await getJobModel(job.type).readDetail(job.id);
- if (event_processing_finished) {
- setShowEventsRefresh(true);
- return;
- }
- const fiveMinutes = 1000 * 60 * 5;
- if (eventsProcessedDelay.current >= fiveMinutes) {
- return;
- }
- setTimeout(pollForEventsProcessed, eventsProcessedDelay.current);
- eventsProcessedDelay.current *= 2;
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [job.id, job.type, lastScrollPosition]);
-
- useEffect(() => {
- if (!isJobRunning(jobStatus)) {
- if (wsEvents.length) {
- pollForEventsProcessed();
- }
- return;
- }
- let batchTimeout;
- let batchedEvents = [];
- const addBatchedEvents = () => {
- let min;
- let max;
- let newCssMap;
- batchedEvents.forEach((event) => {
- if (!min || event.counter < min) {
- min = event.counter;
- }
- if (!max || event.counter > max) {
- max = event.counter;
- }
- const { lineCssMap } = getLineTextHtml(event);
- newCssMap = {
- ...newCssMap,
- ...lineCssMap,
- };
- });
- setWsEvents((oldWsEvents) => {
- const newEvents = [];
- batchedEvents.forEach((event) => {
- if (!oldWsEvents.find((e) => e.id === event.id)) {
- newEvents.push(event);
- }
- });
- const updated = oldWsEvents.concat(newEvents);
- jobSocketCounter.current = updated.length;
- if (!oldWsEvents.length && min > remoteRowCount + 1) {
- loadJobEvents(min);
- }
- return updated.sort((a, b) => a.counter - b.counter);
- });
- setCssMap((prevCssMap) => ({
- ...prevCssMap,
- ...newCssMap,
- }));
- if (max > jobSocketCounter.current) {
- jobSocketCounter.current = max;
- }
- batchedEvents = [];
- };
-
- connectJobSocket(job, (data) => {
- if (data.group_name === `${job.type}_events`) {
- batchedEvents.push(data);
- clearTimeout(batchTimeout);
- if (batchedEvents.length >= 10) {
- addBatchedEvents();
- } else {
- batchTimeout = setTimeout(addBatchedEvents, 500);
- }
- }
- if (data.group_name === 'jobs' && data.unified_job_id === job.id) {
- if (data.final_counter) {
- jobSocketCounter.current = data.final_counter;
- }
- if (data.status) {
- setJobStatus(data.status);
- }
- }
- });
- setIsMonitoringWebsocket(true);
-
- // eslint-disable-next-line consistent-return
- return function cleanup() {
- clearTimeout(batchTimeout);
- closeWebSocket();
- setIsMonitoringWebsocket(false);
- isMounted.current = false;
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [isJobRunning(jobStatus), pollForEventsProcessed]);
-
- useEffect(() => {
- if (isFollowModeEnabled) {
- scrollToEnd();
- }
- }, [wsEvents.length, isFollowModeEnabled]); // eslint-disable-line react-hooks/exhaustive-deps
-
- useEffect(() => {
- if (listRef.current?.recomputeRowHeights) {
- listRef.current.recomputeRowHeights();
- }
- }, [currentlyLoading, cssMap, remoteRowCount, wsEvents.length]);
-
- useEffect(() => {
- if (!jobStatus || isJobRunning(jobStatus)) {
- return;
- }
-
- if (isMonitoringWebsocket) {
- setIsMonitoringWebsocket(false);
- }
-
- if (isFollowModeEnabled) {
- setTimeout(() => setIsFollowModeEnabled(false), 1000);
- }
- }, [jobStatus]); // eslint-disable-line react-hooks/exhaustive-deps
-
- const {
- error: cancelError,
- isLoading: isCancelling,
- request: cancelJob,
- } = useRequest(
- useCallback(async () => {
- await getJobModel(job.type).cancel(job.id);
- }, [job.id, job.type]),
- {}
- );
-
- const { error: dismissableCancelError, dismissError: dismissCancelError } =
- useDismissableError(cancelError);
-
- const {
- request: deleteJob,
- isLoading: isDeleting,
- error: deleteError,
- } = useRequest(
- useCallback(async () => {
- await getJobModel(job.type).destroy(job.id);
-
- history.push('/jobs');
- }, [job.type, job.id, history])
- );
-
- const { error: dismissableDeleteError, dismissError: dismissDeleteError } =
- useDismissableError(deleteError);
-
- const monitorJobSocketCounter = () => {
- if (
- jobSocketCounter.current === remoteRowCount &&
- !isJobRunning(job.status)
- ) {
- setIsMonitoringWebsocket(false);
- }
- };
-
- const loadJobEvents = async (firstWsCounter = null) => {
- const [params, loadRange] = getEventRequestParams(job, 50, [1, 50]);
-
- if (isMounted.current) {
- setHasContentLoading(true);
- setCurrentlyLoading((prevCurrentlyLoading) =>
- prevCurrentlyLoading.concat(loadRange)
- );
- }
-
- if (isFlatMode) {
- params.not__stdout = '';
- }
- if (firstWsCounter) {
- params.counter__lt = firstWsCounter;
- }
- const qsParams = parseQueryString(QS_CONFIG, location.search);
- const eventPromise = getJobModel(job.type).readEvents(job.id, {
- ...params,
- ...qsParams,
- });
-
- try {
- const {
- data: { count, results: fetchedEvents = [] },
- } = await eventPromise;
-
- if (!isMounted.current) {
- return;
- }
- let newCssMap;
- let rowNumber = 0;
- const { events, countOffset } = prependTraceback(job, fetchedEvents);
- events.forEach((event) => {
- event.rowNumber = rowNumber;
- rowNumber++;
- const { lineCssMap } = getLineTextHtml(event);
- newCssMap = {
- ...newCssMap,
- ...lineCssMap,
- };
- });
- setCssMap((prevCssMap) => ({
- ...prevCssMap,
- ...newCssMap,
- }));
- const lastCounter = events[events.length - 1]?.counter || 50;
- if (isTreeReady) {
- addEvents(events);
- } else {
- setOnReadyEvents((prev) => prev.concat(events));
- }
- setHighestLoadedCounter(lastCounter);
- setRemoteRowCount(count + countOffset);
- } catch (err) {
- setContentError(err);
- } finally {
- if (isMounted.current) {
- setHasContentLoading(false);
- setCurrentlyLoading((prevCurrentlyLoading) =>
- prevCurrentlyLoading.filter((n) => !loadRange.includes(n))
- );
- loadRange.forEach((n) => {
- cache.clear(n);
- });
- }
- }
- };
-
- const isRowLoaded = ({ index }) => {
- let counter;
- try {
- counter = getCounterForRow(index);
- } catch (e) {
- console.error(e); // eslint-disable-line no-console
- return false;
- }
- if (getEvent(counter)) {
- return true;
- }
- if (index >= remoteRowCount && index < remoteRowCount + wsEvents.length) {
- return true;
- }
- return currentlyLoading.includes(counter);
- };
-
- const handleHostEventClick = (hostEventToOpen) => {
- setHostEvent(hostEventToOpen);
- setIsHostModalOpen(true);
- };
-
- const handleHostModalClose = () => {
- setIsHostModalOpen(false);
- };
-
- const rowRenderer = ({ index, parent, key, style }) => {
- let event;
- let node;
- try {
- const eventForRow = getEventForRow(index) || {};
- event = eventForRow.event;
- node = eventForRow.node;
- } catch (e) {
- event = null;
- }
- if (
- !event &&
- index >= remoteRowCount &&
- index < remoteRowCount + wsEvents.length
- ) {
- event = wsEvents[index - remoteRowCount];
- node = {
- eventIndex: event?.counter,
- isCollapsed: false,
- children: [],
- };
- }
- let actualLineTextHtml = [];
- if (event) {
- const { lineTextHtml } = getLineTextHtml(event);
- actualLineTextHtml = lineTextHtml;
- }
-
- return (
-
- {({ measure }) =>
- event ? (
- handleHostEventClick(event)}
- className="row"
- style={style}
- lineTextHtml={actualLineTextHtml}
- index={index}
- event={event}
- measure={measure}
- isCollapsed={node.isCollapsed}
- hasChildren={node.children.length}
- onToggleCollapsed={() => {
- toggleNodeIsCollapsed(event.uuid, !node.isCollapsed);
- }}
- jobStatus={jobStatus}
- />
- ) : (
-
- )
- }
-
- );
- };
-
- const loadMoreRows = async ({ startIndex, stopIndex }) => {
- if (!isMounted.current) {
- return;
- }
- if (startIndex === 0 && stopIndex === 0) {
- return;
- }
-
- if (isMounted.current) {
- setCurrentlyLoading((prevCurrentlyLoading) =>
- prevCurrentlyLoading.concat(loadRange)
- );
- }
-
- let range = [startIndex, stopIndex];
- if (!isFlatMode) {
- const diff = stopIndex - startIndex;
- const startCounter = getCounterForRow(startIndex);
- range = [startCounter, startCounter + diff];
- }
-
- const [requestParams, loadRange] = getEventRequestParams(
- job,
- remoteRowCount,
- range
- );
- const qs = parseQueryString(QS_CONFIG, location.search);
- const params = {
- ...requestParams,
- ...qs,
- };
- if (isFlatMode) {
- params.not__stdout = '';
- }
-
- const model = getJobModel(job.type);
-
- let response;
- try {
- response = await model.readEvents(job.id, params);
- } catch (error) {
- if (error.response.status === 404) {
- return;
- }
- throw error;
- }
- if (!isMounted.current) {
- return;
- }
- const events = response.data.results;
- const firstIndex = (params.page - 1) * params.page_size;
-
- let newCssMap;
- let rowNumber = firstIndex;
- events.forEach((event) => {
- event.rowNumber = rowNumber;
- rowNumber++;
- const { lineCssMap } = getLineTextHtml(event);
- newCssMap = {
- ...newCssMap,
- ...lineCssMap,
- };
- });
- setCssMap((prevCssMap) => ({
- ...prevCssMap,
- ...newCssMap,
- }));
-
- const lastCounter = events[events.length - 1]?.counter || 50;
- addEvents(events);
- if (lastCounter > highestLoadedCounter) {
- setHighestLoadedCounter(lastCounter);
- }
- setCurrentlyLoading((prevCurrentlyLoading) =>
- prevCurrentlyLoading.filter((n) => !loadRange.includes(n))
- );
- loadRange.forEach((n) => {
- cache.clear(n);
- });
- if (isFollowModeEnabled) {
- scrollToEnd();
- }
- };
-
- const scrollToRow = (rowIndex) => {
- setLastScrollPosition(rowIndex);
- if (listRef.current) {
- listRef.current.scrollToRow(rowIndex);
- }
- };
-
- const handleScrollPrevious = () => {
- const startIndex = listRef.current.Grid._renderedRowStartIndex;
- const stopIndex = listRef.current.Grid._renderedRowStopIndex;
- const scrollRange = stopIndex - startIndex;
- scrollToRow(Math.max(0, startIndex - scrollRange));
- setIsFollowModeEnabled(false);
- };
-
- const handleScrollNext = () => {
- const startIndex = listRef.current.Grid._renderedRowStartIndex;
- const stopIndex = listRef.current.Grid._renderedRowStopIndex;
- const scrollRange = stopIndex - startIndex;
- scrollToRow(stopIndex + scrollRange);
- };
-
- const handleScrollFirst = () => {
- scrollToRow(0);
- setIsFollowModeEnabled(false);
- };
-
- const scrollToEnd = useCallback(() => {
- scrollToRow(-1);
- let timeout;
- if (isFollowModeEnabled) {
- setTimeout(() => scrollToRow(-1), 100);
- }
- return () => clearTimeout(timeout);
- }, [isFollowModeEnabled]);
-
- const handleScrollLast = () => {
- scrollToEnd();
- setIsFollowModeEnabled(true);
- };
-
- const handleResize = ({ width }) => {
- if (width !== previousWidth) {
- cache.clearAll();
- if (listRef.current?.recomputeRowHeights) {
- listRef.current.recomputeRowHeights();
- }
- }
- previousWidth.current = width;
- };
-
- const handleScroll = (e) => {
- if (
- isFollowModeEnabled &&
- scrollTop.current > e.scrollTop &&
- scrollHeight.current === e.scrollHeight
- ) {
- setIsFollowModeEnabled(false);
- }
- scrollTop.current = e.scrollTop;
- scrollHeight.current = e.scrollHeight;
- if (e.scrollTop + e.clientHeight >= e.scrollHeight) {
- setIsFollowModeEnabled(true);
- }
- };
-
- const handleExpandCollapseAll = () => {
- toggleCollapseAll(!isAllCollapsed);
- };
-
- if (contentError) {
- return ;
- }
-
- return (
- <>
-
- {isHostModalOpen && (
-
- )}
-
-
- {job.name}
-
-
- setShowCancelModal(true)}
- onDelete={deleteJob}
- isDeleteDisabled={isDeleting}
- />
-
-
-
- {showEventsRefresh ? (
-
- {t`Events processing complete.`}{' '}
-
- >
- }
- />
- ) : null}
-
-
-
- {({ onRowsRendered, registerChild }) => {
- if (
- !hasContentLoading &&
- remoteRowCount + wsEvents.length === 0
- ) {
- return (
- 1}
- isJobRunning={isJobRunning(jobStatus)}
- onUnmount={() => {
- if (listRef.current?.recomputeRowHeights) {
- listRef.current.recomputeRowHeights();
- }
- }}
- />
- );
- }
- return (
-
- {({ width, height }) => (
- <>
- {hasContentLoading ? (
-
-
-
- ) : (
- {
- registerChild(ref);
- listRef.current = ref;
- }}
- deferredMeasurementCache={cache}
- height={height || 1}
- onRowsRendered={onRowsRendered}
- rowCount={totalNonCollapsedRows + wsEvents.length}
- rowHeight={cache.rowHeight}
- rowRenderer={rowRenderer}
- width={width || 1}
- overscanRowCount={20}
- onScroll={handleScroll}
- />
- )}
- >
- )}
-
- );
- }}
-
-
-
-
- {showCancelModal && isJobRunning(job.status) && (
- setShowCancelModal(false)}
- title={t`Cancel Job`}
- label={t`Cancel Job`}
- actions={[
- ,
- ,
- ]}
- >
- {t`Are you sure you want to submit the request to cancel this job?`}
-
- )}
- {dismissableDeleteError && (
-
-
-
- )}
- {dismissableCancelError && (
-
-
-
- )}
- >
- );
-}
-
-export default JobOutput;
diff --git a/awx/ui/src/screens/Job/JobOutput/JobOutput.test.js b/awx/ui/src/screens/Job/JobOutput/JobOutput.test.js
deleted file mode 100644
index 423595d0f96b..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/JobOutput.test.js
+++ /dev/null
@@ -1,153 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { JobsAPI, JobEventsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import JobOutput from './JobOutput';
-import mockJobData from '../shared/data.job.json';
-import mockJobEventsData from './data.job_events.json';
-
-jest.mock('../../../api');
-
-const applyJobEventMock = (mockJobEvents) => {
- const mockReadEvents = async (jobId, params) => {
- const [...results] = mockJobEvents.results;
- if (params.order_by && params.order_by.includes('-')) {
- results.reverse();
- }
- return {
- data: {
- results,
- count: mockJobEvents.count,
- },
- };
- };
- JobsAPI.readEvents = jest.fn().mockImplementation(mockReadEvents);
- JobsAPI.readChildrenSummary = jest.fn().mockResolvedValue({
- data: {
- 1: [0, 100],
- },
- });
-};
-
-describe('', () => {
- let wrapper;
- const mockJob = mockJobData;
-
- beforeEach(() => {
- applyJobEventMock(mockJobEventsData);
- Object.defineProperty(HTMLElement.prototype, 'offsetHeight', {
- configurable: true,
- value: 200,
- });
- Object.defineProperty(HTMLElement.prototype, 'offsetWidth', {
- configurable: true,
- value: 100,
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should make expected api call for delete', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'JobEvent', (el) => el.length > 0);
- await act(async () =>
- wrapper.find('button[aria-label="Delete"]').simulate('click')
- );
- await waitForElement(
- wrapper,
- 'Modal',
- (el) => el.props().isOpen === true && el.props().title === 'Delete Job'
- );
- await act(async () =>
- wrapper
- .find('Modal button[aria-label="Confirm Delete"]')
- .simulate('click')
- );
- expect(JobsAPI.destroy).toHaveBeenCalledTimes(1);
- });
-
- test('should show error dialog for failed deletion', async () => {
- JobsAPI.destroy.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'delete',
- url: `/api/v2/jobs/${mockJob.id}`,
- },
- data: 'An error occurred',
- status: 403,
- },
- })
- );
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'JobEvent', (el) => el.length > 0);
- await act(async () => {
- wrapper.find('DeleteButton').invoke('onConfirm')();
- });
- await waitForElement(
- wrapper,
- 'Modal[title="Job Delete Error"]',
- (el) => el.length === 1
- );
- await act(async () => {
- wrapper.find('Modal[title="Job Delete Error"]').invoke('onClose')();
- });
- await waitForElement(
- wrapper,
- 'Modal[title="Job Delete Error"]',
- (el) => el.length === 0
- );
- expect(JobsAPI.destroy).toHaveBeenCalledTimes(1);
- });
-
- test('filter should be enabled after job finishes', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'JobEvent', (el) => el.length > 0);
- expect(wrapper.find('Search').props().isDisabled).toBe(false);
- });
-
- test('filter should be disabled while job is running', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'JobEvent', (el) => el.length > 0);
- expect(wrapper.find('Search').props().isDisabled).toBe(true);
- });
-
- test('should throw error', async () => {
- JobsAPI.readEvents = () => Promise.reject(new Error());
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
- test('should show failed empty output screen', async () => {
- JobsAPI.readEvents.mockResolvedValue({
- data: {
- count: 0,
- next: null,
- previous: null,
- results: [],
- },
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'EmptyOutput', (el) => el.length === 1);
- });
-});
diff --git a/awx/ui/src/screens/Job/JobOutput/JobOutputSearch.js b/awx/ui/src/screens/Job/JobOutput/JobOutputSearch.js
deleted file mode 100644
index 5b72eace029d..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/JobOutputSearch.js
+++ /dev/null
@@ -1,191 +0,0 @@
-import React from 'react';
-import { useHistory, useLocation } from 'react-router-dom';
-import styled from 'styled-components';
-import { t } from '@lingui/macro';
-import {
- Toolbar,
- ToolbarContent,
- ToolbarItem,
- ToolbarToggleGroup,
- Tooltip,
- Button,
-} from '@patternfly/react-core';
-import { SearchIcon } from '@patternfly/react-icons';
-import Search from 'components/Search';
-import {
- parseQueryString,
- mergeParams,
- removeParams,
- updateQueryString,
-} from 'util/qs';
-import { isJobRunning } from 'util/jobs';
-
-const SearchToolbarContent = styled(ToolbarContent)`
- padding-left: 0px !important;
- padding-right: 0px !important;
-`;
-
-function JobOutputSearch({
- qsConfig,
- job,
- eventRelatedSearchableKeys,
- eventSearchableKeys,
- scrollToEnd,
- isFollowModeEnabled,
- setIsFollowModeEnabled,
-}) {
- const location = useLocation();
- const history = useHistory();
-
- const handleSearch = (key, value) => {
- const params = parseQueryString(qsConfig, location.search);
- const qs = updateQueryString(
- qsConfig,
- location.search,
- mergeParams(params, { [key]: value })
- );
- pushHistoryState(qs);
- };
-
- const handleReplaceSearch = (key, value) => {
- const qs = updateQueryString(qsConfig, location.search, {
- [key]: value,
- });
- pushHistoryState(qs);
- };
-
- const handleRemoveSearchTerm = (key, value) => {
- const oldParams = parseQueryString(qsConfig, location.search);
- const updatedParams = removeParams(qsConfig, oldParams, {
- [key]: value,
- });
- const qs = updateQueryString(qsConfig, location.search, updatedParams);
- pushHistoryState(qs);
- };
-
- const handleRemoveAllSearchTerms = () => {
- const oldParams = parseQueryString(qsConfig, location.search);
- Object.keys(oldParams).forEach((key) => {
- oldParams[key] = null;
- });
- const qs = updateQueryString(qsConfig, location.search, oldParams);
- pushHistoryState(qs);
- };
-
- const pushHistoryState = (qs) => {
- const { pathname } = history.location;
- history.push(qs ? `${pathname}?${qs}` : pathname);
- };
-
- const handleFollowToggle = () => {
- if (isFollowModeEnabled) {
- setIsFollowModeEnabled(false);
- } else {
- setIsFollowModeEnabled(true);
- scrollToEnd();
- }
- };
-
- const columns = [
- {
- name: t`Stdout`,
- key: 'stdout__icontains',
- isDefault: true,
- },
- ];
-
- if (job.type !== 'system_job' && job.type !== 'inventory_update') {
- columns.push({
- name: t`Event`,
- key: 'or__event',
- options: [
- ['debug', t`Debug`],
- ['deprecated', t`Deprecated`],
- ['error', t`Error`],
- ['runner_on_file_diff', t`File Difference`],
- ['playbook_on_setup', t`Gathering Facts`],
- ['runner_on_async_failed', t`Host Async Failure`],
- ['runner_on_async_ok', t`Host Async OK`],
- ['runner_on_failed', t`Host Failed`],
- ['runner_on_error', t`Host Failure`],
- ['runner_on_ok', t`Host OK`],
- ['runner_on_async_poll', t`Host Polling`],
- ['runner_retry', t`Host Retry`],
- ['runner_on_skipped', t`Host Skipped`],
- ['runner_on_start', t`Host Started`],
- ['runner_on_unreachable', t`Host Unreachable`],
- ['playbook_on_include', t`Including File`],
- ['runner_item_on_failed', t`Item Failed`],
- ['runner_item_on_ok', t`Item OK`],
- ['runner_item_on_skipped', t`Item Skipped`],
- ['playbook_on_no_hosts_matched', t`No Hosts Matched`],
- ['playbook_on_no_hosts_remaining', t`No Hosts Remaining`],
- ['runner_on_no_hosts', t`No Hosts Remaining`],
- ['playbook_on_play_start', t`Play Started`],
- ['playbook_on_stats', t`Playbook Complete`],
- ['playbook_on_start', t`Playbook Started`],
- ['playbook_on_notify', t`Running Handlers`],
- ['system_warning', t`System Warning`],
- ['playbook_on_task_start', t`Task Started`],
- ['playbook_on_vars_prompt', t`Variables Prompted`],
- ['verbose', t`Verbose`],
- ['warning', t`Warning`],
- ],
- });
- }
- columns.push({ name: t`Advanced`, key: 'advanced' });
- const isDisabled = isJobRunning(job.status);
-
- return (
-
-
- } breakpoint="lg">
-
- {isDisabled ? (
-
- {}}
- onRemove={handleRemoveSearchTerm}
- isDisabled
- />
-
- ) : (
- {}}
- onRemove={handleRemoveSearchTerm}
- />
- )}
-
-
- {isJobRunning(job.status) ? (
-
- ) : null}
-
-
- );
-}
-
-export default JobOutputSearch;
diff --git a/awx/ui/src/screens/Job/JobOutput/JobOutputSearch.test.js b/awx/ui/src/screens/Job/JobOutput/JobOutputSearch.test.js
deleted file mode 100644
index 366222ac9dbb..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/JobOutputSearch.test.js
+++ /dev/null
@@ -1,102 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import JobOutputSearch from './JobOutputSearch';
-
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- history: () => ({
- location: '/jobs/playbook/1/output',
- }),
-}));
-
-describe('JobOutputSearch', () => {
- test('should update url query params', async () => {
- const searchBtn = 'button[aria-label="Search submit button"]';
- const searchTextInput = 'input[aria-label="Search text input"]';
- const history = createMemoryHistory({
- initialEntries: ['/jobs/playbook/1/output'],
- });
-
- const wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
-
- await act(async () => {
- wrapper.find(searchTextInput).instance().value = '99';
- wrapper.find(searchTextInput).simulate('change');
- });
- wrapper.update();
- await act(async () => {
- wrapper.find(searchBtn).simulate('click');
- });
- expect(wrapper.find('Search').prop('columns')).toHaveLength(3);
- expect(wrapper.find('Search').prop('columns')[0].name).toBe('Stdout');
- expect(wrapper.find('Search').prop('columns')[1].name).toBe('Event');
- expect(wrapper.find('Search').prop('columns')[2].name).toBe('Advanced');
- expect(history.location.search).toEqual('?stdout__icontains=99');
- });
- test('Should not have Event key in search drop down for system job', () => {
- const history = createMemoryHistory({
- initialEntries: ['/jobs/playbook/1/output'],
- });
-
- const wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
- expect(wrapper.find('Search').prop('columns')).toHaveLength(2);
- expect(wrapper.find('Search').prop('columns')[0].name).toBe('Stdout');
- expect(wrapper.find('Search').prop('columns')[1].name).toBe('Advanced');
- });
-
- test('Should not have Event key in search drop down for inventory update job', () => {
- const history = createMemoryHistory({
- initialEntries: ['/jobs/playbook/1/output'],
- });
-
- const wrapper = mountWithContexts(
- ,
- {
- context: { router: { history } },
- }
- );
-
- expect(wrapper.find('Search').prop('columns')).toHaveLength(2);
- expect(wrapper.find('Search').prop('columns')[0].name).toBe('Stdout');
- expect(wrapper.find('Search').prop('columns')[1].name).toBe('Advanced');
- });
-});
diff --git a/awx/ui/src/screens/Job/JobOutput/PageControls.js b/awx/ui/src/screens/Job/JobOutput/PageControls.js
deleted file mode 100644
index 766ec9204ffb..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/PageControls.js
+++ /dev/null
@@ -1,97 +0,0 @@
-import React from 'react';
-
-import 'styled-components/macro';
-import { t } from '@lingui/macro';
-import { Button } from '@patternfly/react-core';
-import {
- AngleDoubleUpIcon,
- AngleDoubleDownIcon,
- AngleUpIcon,
- AngleDownIcon,
- AngleRightIcon,
-} from '@patternfly/react-icons';
-import styled from 'styled-components';
-
-const ControllsWrapper = styled.div`
- display: flex;
- height: 35px;
- border: 1px solid #d7d7d7;
- width: 100%;
- justify-content: space-between;
-`;
-
-const ScrollWrapper = styled.div`
- display: flex;
- justify-content: flex-end;
-`;
-const ExpandCollapseWrapper = styled.div`
- display: flex;
- justify-content: flex-start;
- & > Button {
- padding-left: 8px;
- }
-`;
-
-const PageControls = ({
- onScrollFirst,
- onScrollLast,
- onScrollNext,
- onScrollPrevious,
- toggleExpandCollapseAll,
- isAllCollapsed,
- isFlatMode,
- isTemplateJob,
-}) => (
-
-
- {!isFlatMode && isTemplateJob && (
-
- )}
-
-
-
-
-
-
-
-
-);
-
-export default PageControls;
diff --git a/awx/ui/src/screens/Job/JobOutput/PageControls.test.js b/awx/ui/src/screens/Job/JobOutput/PageControls.test.js
deleted file mode 100644
index 65c5a8b684ae..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/PageControls.test.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import PageControls from './PageControls';
-
-let wrapper;
-let AngleDoubleUpIcon;
-let AngleDoubleDownIcon;
-let AngleUpIcon;
-let AngleDownIcon;
-
-const findChildren = () => {
- AngleDoubleUpIcon = wrapper.find('AngleDoubleUpIcon');
- AngleDoubleDownIcon = wrapper.find('AngleDoubleDownIcon');
- AngleUpIcon = wrapper.find('AngleUpIcon');
- AngleDownIcon = wrapper.find('AngleDownIcon');
-};
-
-describe('PageControls', () => {
- test('should render successfully', () => {
- wrapper = mountWithContexts();
- expect(wrapper).toHaveLength(1);
- });
-
- test('should render menu control icons', () => {
- wrapper = mountWithContexts();
- findChildren();
- expect(AngleDoubleUpIcon.length).toBe(1);
- expect(AngleDoubleDownIcon.length).toBe(1);
- expect(AngleUpIcon.length).toBe(1);
- expect(AngleDownIcon.length).toBe(1);
- });
-
- test('should render expand/collapse all', () => {
- wrapper = mountWithContexts(
-
- );
- const expandCollapse = wrapper.find('PageControls__ExpandCollapseWrapper');
- expect(expandCollapse).toHaveLength(1);
- expect(expandCollapse.find('AngleDownIcon')).toHaveLength(1);
- expect(expandCollapse.find('AngleRightIcon')).toHaveLength(0);
- });
-
- test('should render correct expand/collapse angle icon', () => {
- wrapper = mountWithContexts(
-
- );
-
- const expandCollapse = wrapper.find('PageControls__ExpandCollapseWrapper');
- expect(expandCollapse).toHaveLength(1);
- expect(expandCollapse.find('AngleDownIcon')).toHaveLength(0);
- expect(expandCollapse.find('AngleRightIcon')).toHaveLength(1);
- });
-
- test('Should not render expand/collapse all', () => {
- wrapper = mountWithContexts(
-
- );
-
- const expandCollapse = wrapper.find('PageControls__ExpandCollapseWrapper');
- expect(expandCollapse.find('AngleDownIcon')).toHaveLength(0);
- expect(expandCollapse.find('AngleRightIcon')).toHaveLength(0);
- });
-});
diff --git a/awx/ui/src/screens/Job/JobOutput/connectJobSocket.js b/awx/ui/src/screens/Job/JobOutput/connectJobSocket.js
deleted file mode 100644
index 238e890805bb..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/connectJobSocket.js
+++ /dev/null
@@ -1,50 +0,0 @@
-let ws;
-
-export default function connectJobSocket({ type, id }, onMessage) {
- ws = new WebSocket(
- `${window.location.protocol === 'http:' ? 'ws:' : 'wss:'}//${
- window.location.host
- }${window.location.pathname}websocket/`
- );
-
- ws.onopen = () => {
- const xrftoken = `; ${document.cookie}`
- .split('; csrftoken=')
- .pop()
- .split(';')
- .shift();
- const eventGroup = `${type}_events`;
- ws.send(
- JSON.stringify({
- xrftoken,
- groups: { jobs: ['summary', 'status_changed'], [eventGroup]: [id] },
- })
- );
- };
-
- ws.onmessage = (e) => {
- onMessage(JSON.parse(e.data));
- };
-
- ws.onclose = (e) => {
- if (e.code !== 1000) {
- // eslint-disable-next-line no-console
- console.debug('Socket closed. Reconnecting...', e);
- setTimeout(() => {
- connectJobSocket({ type, id }, onMessage);
- }, 1000);
- }
- };
-
- ws.onerror = (err) => {
- // eslint-disable-next-line no-console
- console.debug('Socket error: ', err, 'Disconnecting...');
- ws.close();
- };
-}
-
-export function closeWebSocket() {
- if (ws) {
- ws.close();
- }
-}
diff --git a/awx/ui/src/screens/Job/JobOutput/data.filtered_job_events.json b/awx/ui/src/screens/Job/JobOutput/data.filtered_job_events.json
deleted file mode 100644
index 7fed1b8f2a4d..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/data.filtered_job_events.json
+++ /dev/null
@@ -1,3457 +0,0 @@
-{
- "count": 95,
- "next": "/api/v2/jobs/3/job_events/?order_by=start_line&page=2&page_size=50&stdout__icontains=99",
- "previous": null,
- "results": [
- {
- "id": 158,
- "type": "job_event",
- "url": "/api/v2/job_events/158/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/158/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:35.154973Z",
- "modified": "2021-01-25T19:59:35.186637Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 103,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 99",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "99",
- "ansible_loop_var": "item",
- "_ansible_item_label": "99"
- },
- "uuid": "51b43ae5-ebf3-4ec5-8eef-5d2bf5cd8538"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "51b43ae5-ebf3-4ec5-8eef-5d2bf5cd8538",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=99) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 99\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 298,
- "end_line": 301,
- "verbosity": 0
- },
- {
- "id": 261,
- "type": "job_event",
- "url": "/api/v2/job_events/261/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/261/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:36.201489Z",
- "modified": "2021-01-25T19:59:36.296056Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 203,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 199",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "199",
- "ansible_loop_var": "item",
- "_ansible_item_label": "199"
- },
- "uuid": "f9546a1f-b088-47c0-8def-c213f42c1c61"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "f9546a1f-b088-47c0-8def-c213f42c1c61",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=199) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 199\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 598,
- "end_line": 601,
- "verbosity": 0
- },
- {
- "id": 364,
- "type": "job_event",
- "url": "/api/v2/job_events/364/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/364/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:37.478217Z",
- "modified": "2021-01-25T19:59:37.576551Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 303,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 299",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "299",
- "ansible_loop_var": "item",
- "_ansible_item_label": "299"
- },
- "uuid": "496345b6-f8e8-4f09-8be2-14b7ed484c54"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "496345b6-f8e8-4f09-8be2-14b7ed484c54",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=299) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 299\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 898,
- "end_line": 901,
- "verbosity": 0
- },
- {
- "id": 457,
- "type": "job_event",
- "url": "/api/v2/job_events/457/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/457/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:38.963716Z",
- "modified": "2021-01-25T19:59:39.010339Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 403,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 399",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "399",
- "ansible_loop_var": "item",
- "_ansible_item_label": "399"
- },
- "uuid": "eaacb137-d989-4ba0-934f-bd524960d60d"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "eaacb137-d989-4ba0-934f-bd524960d60d",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=399) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 399\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 1198,
- "end_line": 1201,
- "verbosity": 0
- },
- {
- "id": 562,
- "type": "job_event",
- "url": "/api/v2/job_events/562/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/562/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:40.006197Z",
- "modified": "2021-01-25T19:59:40.094331Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 503,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 499",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "499",
- "ansible_loop_var": "item",
- "_ansible_item_label": "499"
- },
- "uuid": "355def14-416a-47ae-a3e9-98bdae551279"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "355def14-416a-47ae-a3e9-98bdae551279",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=499) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 499\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 1498,
- "end_line": 1501,
- "verbosity": 0
- },
- {
- "id": 655,
- "type": "job_event",
- "url": "/api/v2/job_events/655/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/655/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:41.250536Z",
- "modified": "2021-01-25T19:59:41.297211Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 603,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 599",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "599",
- "ansible_loop_var": "item",
- "_ansible_item_label": "599"
- },
- "uuid": "774264d8-fc30-49b6-99f4-3859e7f7b654"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "774264d8-fc30-49b6-99f4-3859e7f7b654",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=599) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 599\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 1798,
- "end_line": 1801,
- "verbosity": 0
- },
- {
- "id": 764,
- "type": "job_event",
- "url": "/api/v2/job_events/764/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/764/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:42.579121Z",
- "modified": "2021-01-25T19:59:42.688693Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 703,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 699",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "699",
- "ansible_loop_var": "item",
- "_ansible_item_label": "699"
- },
- "uuid": "15647ecd-0fa9-4148-91fa-798b3892d3a6"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "15647ecd-0fa9-4148-91fa-798b3892d3a6",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=699) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 699\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 2098,
- "end_line": 2101,
- "verbosity": 0
- },
- {
- "id": 859,
- "type": "job_event",
- "url": "/api/v2/job_events/859/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/859/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:43.609896Z",
- "modified": "2021-01-25T19:59:43.698752Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 803,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 799",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "799",
- "ansible_loop_var": "item",
- "_ansible_item_label": "799"
- },
- "uuid": "82e7e226-961f-4000-a8f4-3281002b22f0"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "82e7e226-961f-4000-a8f4-3281002b22f0",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=799) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 799\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 2398,
- "end_line": 2401,
- "verbosity": 0
- },
- {
- "id": 953,
- "type": "job_event",
- "url": "/api/v2/job_events/953/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/953/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:44.691259Z",
- "modified": "2021-01-25T19:59:44.717190Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 903,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 899",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "899",
- "ansible_loop_var": "item",
- "_ansible_item_label": "899"
- },
- "uuid": "a4ce202b-6c13-42b8-a409-8e36b9db7493"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "a4ce202b-6c13-42b8-a409-8e36b9db7493",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=899) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 899\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 2698,
- "end_line": 2701,
- "verbosity": 0
- },
- {
- "id": 1057,
- "type": "job_event",
- "url": "/api/v2/job_events/1057/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/1057/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:45.950889Z",
- "modified": "2021-01-25T19:59:46.019625Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 994,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 990",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "990",
- "ansible_loop_var": "item",
- "_ansible_item_label": "990"
- },
- "uuid": "5e2f09ab-10c9-4967-82be-6ffce7174fe8"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "5e2f09ab-10c9-4967-82be-6ffce7174fe8",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=990) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 990\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 2971,
- "end_line": 2974,
- "verbosity": 0
- },
- {
- "id": 1054,
- "type": "job_event",
- "url": "/api/v2/job_events/1054/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/1054/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:45.961448Z",
- "modified": "2021-01-25T19:59:46.003176Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 995,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 991",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "991",
- "ansible_loop_var": "item",
- "_ansible_item_label": "991"
- },
- "uuid": "27ed9c1a-6bf6-4758-b3e2-b0bdad56dd1d"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "27ed9c1a-6bf6-4758-b3e2-b0bdad56dd1d",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=991) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 991\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 2974,
- "end_line": 2977,
- "verbosity": 0
- },
- {
- "id": 1042,
- "type": "job_event",
- "url": "/api/v2/job_events/1042/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/1042/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:45.962508Z",
- "modified": "2021-01-25T19:59:45.982840Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 996,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 992",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "992",
- "ansible_loop_var": "item",
- "_ansible_item_label": "992"
- },
- "uuid": "950bfc13-16a8-4d01-8ba7-935b72b5396b"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "950bfc13-16a8-4d01-8ba7-935b72b5396b",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=992) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 992\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 2977,
- "end_line": 2980,
- "verbosity": 0
- },
- {
- "id": 1051,
- "type": "job_event",
- "url": "/api/v2/job_events/1051/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/1051/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:45.966562Z",
- "modified": "2021-01-25T19:59:45.992345Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 997,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 993",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "993",
- "ansible_loop_var": "item",
- "_ansible_item_label": "993"
- },
- "uuid": "9188a445-0402-4ec3-8b41-8f174d66d496"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "9188a445-0402-4ec3-8b41-8f174d66d496",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=993) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 993\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 2980,
- "end_line": 2983,
- "verbosity": 0
- },
- {
- "id": 1058,
- "type": "job_event",
- "url": "/api/v2/job_events/1058/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/1058/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:45.971693Z",
- "modified": "2021-01-25T19:59:46.019625Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 998,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 994",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "994",
- "ansible_loop_var": "item",
- "_ansible_item_label": "994"
- },
- "uuid": "d980c933-7fe6-41c3-afe6-6bec01aaabc2"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "d980c933-7fe6-41c3-afe6-6bec01aaabc2",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=994) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 994\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 2983,
- "end_line": 2986,
- "verbosity": 0
- },
- {
- "id": 1055,
- "type": "job_event",
- "url": "/api/v2/job_events/1055/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/1055/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:45.976062Z",
- "modified": "2021-01-25T19:59:46.003176Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 999,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 995",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "995",
- "ansible_loop_var": "item",
- "_ansible_item_label": "995"
- },
- "uuid": "caf521fb-ab23-4b2b-92d2-753a608bbdfd"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "caf521fb-ab23-4b2b-92d2-753a608bbdfd",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=995) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 995\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 2986,
- "end_line": 2989,
- "verbosity": 0
- },
- {
- "id": 1043,
- "type": "job_event",
- "url": "/api/v2/job_events/1043/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/1043/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:45.980970Z",
- "modified": "2021-01-25T19:59:45.982840Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 1000,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 996",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "996",
- "ansible_loop_var": "item",
- "_ansible_item_label": "996"
- },
- "uuid": "48d0ae47-a580-4e35-adf5-ea492af7ae4a"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "48d0ae47-a580-4e35-adf5-ea492af7ae4a",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=996) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 996\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 2989,
- "end_line": 2992,
- "verbosity": 0
- },
- {
- "id": 1052,
- "type": "job_event",
- "url": "/api/v2/job_events/1052/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/1052/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:45.988193Z",
- "modified": "2021-01-25T19:59:45.992345Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 1001,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 997",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "997",
- "ansible_loop_var": "item",
- "_ansible_item_label": "997"
- },
- "uuid": "1c0a5c35-aa2f-4252-8cd5-0929fe3b26dc"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "1c0a5c35-aa2f-4252-8cd5-0929fe3b26dc",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=997) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 997\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 2992,
- "end_line": 2995,
- "verbosity": 0
- },
- {
- "id": 1059,
- "type": "job_event",
- "url": "/api/v2/job_events/1059/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/1059/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:45.999787Z",
- "modified": "2021-01-25T19:59:46.019625Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 1002,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 998",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "998",
- "ansible_loop_var": "item",
- "_ansible_item_label": "998"
- },
- "uuid": "0ae19129-04ed-4bc4-8b35-382062cbda03"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "0ae19129-04ed-4bc4-8b35-382062cbda03",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=998) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 998\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 2995,
- "end_line": 2998,
- "verbosity": 0
- },
- {
- "id": 1056,
- "type": "job_event",
- "url": "/api/v2/job_events/1056/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/1056/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:46.000661Z",
- "modified": "2021-01-25T19:59:46.003176Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 1003,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 999",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "999",
- "ansible_loop_var": "item",
- "_ansible_item_label": "999"
- },
- "uuid": "0e0f43ba-e765-4a8d-a97b-d1b8155c2dba"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "0e0f43ba-e765-4a8d-a97b-d1b8155c2dba",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=999) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 999\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 2998,
- "end_line": 3001,
- "verbosity": 0
- },
- {
- "id": 1163,
- "type": "job_event",
- "url": "/api/v2/job_events/1163/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/1163/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:47.092035Z",
- "modified": "2021-01-25T19:59:47.135202Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 1103,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 1099",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "1099",
- "ansible_loop_var": "item",
- "_ansible_item_label": "1099"
- },
- "uuid": "cdd8eca6-81af-451a-8746-ca46946534f2"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "cdd8eca6-81af-451a-8746-ca46946534f2",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=1099) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 1099\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 3298,
- "end_line": 3301,
- "verbosity": 0
- },
- {
- "id": 1259,
- "type": "job_event",
- "url": "/api/v2/job_events/1259/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/1259/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:48.157141Z",
- "modified": "2021-01-25T19:59:48.232791Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 1203,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 1199",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "1199",
- "ansible_loop_var": "item",
- "_ansible_item_label": "1199"
- },
- "uuid": "fe7e2db0-484d-495d-ab06-4e6a784b9986"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "fe7e2db0-484d-495d-ab06-4e6a784b9986",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=1199) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 1199\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 3598,
- "end_line": 3601,
- "verbosity": 0
- },
- {
- "id": 1359,
- "type": "job_event",
- "url": "/api/v2/job_events/1359/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/1359/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:49.357156Z",
- "modified": "2021-01-25T19:59:49.404647Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 1303,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 1299",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "1299",
- "ansible_loop_var": "item",
- "_ansible_item_label": "1299"
- },
- "uuid": "3010eea7-30f6-4b2d-b852-d41350c36b6d"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "3010eea7-30f6-4b2d-b852-d41350c36b6d",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=1299) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 1299\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 3898,
- "end_line": 3901,
- "verbosity": 0
- },
- {
- "id": 1459,
- "type": "job_event",
- "url": "/api/v2/job_events/1459/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/1459/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:50.780261Z",
- "modified": "2021-01-25T19:59:50.926274Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 1403,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 1399",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "1399",
- "ansible_loop_var": "item",
- "_ansible_item_label": "1399"
- },
- "uuid": "cfaeddd1-0a80-4b0e-bbd0-1fb9440b84e8"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "cfaeddd1-0a80-4b0e-bbd0-1fb9440b84e8",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=1399) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 1399\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 4198,
- "end_line": 4201,
- "verbosity": 0
- },
- {
- "id": 1559,
- "type": "job_event",
- "url": "/api/v2/job_events/1559/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/1559/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:51.864216Z",
- "modified": "2021-01-25T19:59:51.991617Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 1503,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 1499",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "1499",
- "ansible_loop_var": "item",
- "_ansible_item_label": "1499"
- },
- "uuid": "671e121d-fdf0-443a-b6c3-401d27d79b5d"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "671e121d-fdf0-443a-b6c3-401d27d79b5d",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=1499) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 1499\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 4498,
- "end_line": 4501,
- "verbosity": 0
- },
- {
- "id": 1656,
- "type": "job_event",
- "url": "/api/v2/job_events/1656/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/1656/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:53.060776Z",
- "modified": "2021-01-25T19:59:53.118670Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 1603,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 1599",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "1599",
- "ansible_loop_var": "item",
- "_ansible_item_label": "1599"
- },
- "uuid": "b3235894-e653-48d1-bb6a-232214fdf623"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "b3235894-e653-48d1-bb6a-232214fdf623",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=1599) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 1599\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 4798,
- "end_line": 4801,
- "verbosity": 0
- },
- {
- "id": 1762,
- "type": "job_event",
- "url": "/api/v2/job_events/1762/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/1762/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:54.283641Z",
- "modified": "2021-01-25T19:59:54.353053Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 1703,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 1699",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "1699",
- "ansible_loop_var": "item",
- "_ansible_item_label": "1699"
- },
- "uuid": "ffb5aee1-325d-40f9-91b5-1357e8b65025"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "ffb5aee1-325d-40f9-91b5-1357e8b65025",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=1699) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 1699\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 5098,
- "end_line": 5101,
- "verbosity": 0
- },
- {
- "id": 1863,
- "type": "job_event",
- "url": "/api/v2/job_events/1863/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/1863/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:55.734279Z",
- "modified": "2021-01-25T19:59:55.860839Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 1803,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 1799",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "1799",
- "ansible_loop_var": "item",
- "_ansible_item_label": "1799"
- },
- "uuid": "be5cff27-d68e-4474-94f2-cc38c7cdbfc8"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "be5cff27-d68e-4474-94f2-cc38c7cdbfc8",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=1799) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 1799\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 5398,
- "end_line": 5401,
- "verbosity": 0
- },
- {
- "id": 1960,
- "type": "job_event",
- "url": "/api/v2/job_events/1960/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/1960/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:57.193270Z",
- "modified": "2021-01-25T19:59:57.287536Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 1903,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 1899",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "1899",
- "ansible_loop_var": "item",
- "_ansible_item_label": "1899"
- },
- "uuid": "766481f0-4728-438f-9cd0-895255ac253e"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "766481f0-4728-438f-9cd0-895255ac253e",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=1899) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 1899\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 5698,
- "end_line": 5701,
- "verbosity": 0
- },
- {
- "id": 2043,
- "type": "job_event",
- "url": "/api/v2/job_events/2043/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/2043/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:58.374962Z",
- "modified": "2021-01-25T19:59:58.425961Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 1994,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 1990",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "1990",
- "ansible_loop_var": "item",
- "_ansible_item_label": "1990"
- },
- "uuid": "0360a8c3-d54f-438d-913e-beeaf771cb95"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "0360a8c3-d54f-438d-913e-beeaf771cb95",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=1990) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 1990\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 5971,
- "end_line": 5974,
- "verbosity": 0
- },
- {
- "id": 2038,
- "type": "job_event",
- "url": "/api/v2/job_events/2038/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/2038/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:58.405637Z",
- "modified": "2021-01-25T19:59:58.409803Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 1995,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 1991",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "1991",
- "ansible_loop_var": "item",
- "_ansible_item_label": "1991"
- },
- "uuid": "c2f23fc8-86b5-4ded-8381-75ff6678bb20"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "c2f23fc8-86b5-4ded-8381-75ff6678bb20",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=1991) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 1991\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 5974,
- "end_line": 5977,
- "verbosity": 0
- },
- {
- "id": 2051,
- "type": "job_event",
- "url": "/api/v2/job_events/2051/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/2051/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:58.409971Z",
- "modified": "2021-01-25T19:59:58.415030Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 1996,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 1992",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "1992",
- "ansible_loop_var": "item",
- "_ansible_item_label": "1992"
- },
- "uuid": "5e650677-9801-49b3-b1e6-66f5fdd030a4"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "5e650677-9801-49b3-b1e6-66f5fdd030a4",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=1992) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 1992\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 5977,
- "end_line": 5980,
- "verbosity": 0
- },
- {
- "id": 2054,
- "type": "job_event",
- "url": "/api/v2/job_events/2054/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/2054/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:58.412000Z",
- "modified": "2021-01-25T19:59:58.450714Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 1997,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 1993",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "1993",
- "ansible_loop_var": "item",
- "_ansible_item_label": "1993"
- },
- "uuid": "b9588b00-0362-40ed-9a67-ad145374d7d6"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "b9588b00-0362-40ed-9a67-ad145374d7d6",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=1993) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 1993\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 5980,
- "end_line": 5983,
- "verbosity": 0
- },
- {
- "id": 2045,
- "type": "job_event",
- "url": "/api/v2/job_events/2045/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/2045/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:58.415443Z",
- "modified": "2021-01-25T19:59:58.425961Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 1998,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 1994",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "1994",
- "ansible_loop_var": "item",
- "_ansible_item_label": "1994"
- },
- "uuid": "6297a5a1-7cc6-486d-b3bc-c3a40a503c20"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "6297a5a1-7cc6-486d-b3bc-c3a40a503c20",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=1994) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 1994\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 5983,
- "end_line": 5986,
- "verbosity": 0
- },
- {
- "id": 2055,
- "type": "job_event",
- "url": "/api/v2/job_events/2055/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/2055/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:58.437385Z",
- "modified": "2021-01-25T19:59:58.450714Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 1999,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 1995",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "1995",
- "ansible_loop_var": "item",
- "_ansible_item_label": "1995"
- },
- "uuid": "bd4dbb3c-24e7-427c-90a4-d1caf3fdaae1"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "bd4dbb3c-24e7-427c-90a4-d1caf3fdaae1",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=1995) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 1995\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 5986,
- "end_line": 5989,
- "verbosity": 0
- },
- {
- "id": 2056,
- "type": "job_event",
- "url": "/api/v2/job_events/2056/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/2056/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:58.450193Z",
- "modified": "2021-01-25T19:59:58.635719Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 2000,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 1996",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "1996",
- "ansible_loop_var": "item",
- "_ansible_item_label": "1996"
- },
- "uuid": "a5fed339-1e57-4851-9af5-7e0d15c6ddda"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "a5fed339-1e57-4851-9af5-7e0d15c6ddda",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=1996) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 1996\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 5989,
- "end_line": 5992,
- "verbosity": 0
- },
- {
- "id": 2057,
- "type": "job_event",
- "url": "/api/v2/job_events/2057/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/2057/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:58.466953Z",
- "modified": "2021-01-25T19:59:58.635719Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 2001,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 1997",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "1997",
- "ansible_loop_var": "item",
- "_ansible_item_label": "1997"
- },
- "uuid": "735fbfd6-c07d-48c4-9332-ad69fdfca878"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "735fbfd6-c07d-48c4-9332-ad69fdfca878",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=1997) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 1997\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 5992,
- "end_line": 5995,
- "verbosity": 0
- },
- {
- "id": 2058,
- "type": "job_event",
- "url": "/api/v2/job_events/2058/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/2058/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:58.485294Z",
- "modified": "2021-01-25T19:59:58.635719Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 2002,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 1998",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "1998",
- "ansible_loop_var": "item",
- "_ansible_item_label": "1998"
- },
- "uuid": "f95a63b5-ff5d-403d-b25e-c5263520742b"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "f95a63b5-ff5d-403d-b25e-c5263520742b",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=1998) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 1998\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 5995,
- "end_line": 5998,
- "verbosity": 0
- },
- {
- "id": 2059,
- "type": "job_event",
- "url": "/api/v2/job_events/2059/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/2059/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:58.506554Z",
- "modified": "2021-01-25T19:59:58.635719Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 2003,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 1999",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "1999",
- "ansible_loop_var": "item",
- "_ansible_item_label": "1999"
- },
- "uuid": "e73e7536-6b84-4092-9b8f-59f79b45d466"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "e73e7536-6b84-4092-9b8f-59f79b45d466",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=1999) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 1999\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 5998,
- "end_line": 6001,
- "verbosity": 0
- },
- {
- "id": 2158,
- "type": "job_event",
- "url": "/api/v2/job_events/2158/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/2158/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T19:59:59.894830Z",
- "modified": "2021-01-25T20:00:00.015101Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 2103,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 2099",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "2099",
- "ansible_loop_var": "item",
- "_ansible_item_label": "2099"
- },
- "uuid": "d17641fc-8c1f-469f-a27a-52bb24e651ee"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "d17641fc-8c1f-469f-a27a-52bb24e651ee",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=2099) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 2099\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 6298,
- "end_line": 6301,
- "verbosity": 0
- },
- {
- "id": 2261,
- "type": "job_event",
- "url": "/api/v2/job_events/2261/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/2261/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T20:00:01.283631Z",
- "modified": "2021-01-25T20:00:01.364514Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 2203,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 2199",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "2199",
- "ansible_loop_var": "item",
- "_ansible_item_label": "2199"
- },
- "uuid": "80413f69-7b48-448f-a4f4-24f53b0657a8"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "80413f69-7b48-448f-a4f4-24f53b0657a8",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=2199) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 2199\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 6598,
- "end_line": 6601,
- "verbosity": 0
- },
- {
- "id": 2352,
- "type": "job_event",
- "url": "/api/v2/job_events/2352/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/2352/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T20:00:02.545472Z",
- "modified": "2021-01-25T20:00:02.549716Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 2303,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 2299",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "2299",
- "ansible_loop_var": "item",
- "_ansible_item_label": "2299"
- },
- "uuid": "707c37e5-1773-4110-b218-5db807292507"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "707c37e5-1773-4110-b218-5db807292507",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=2299) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 2299\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 6898,
- "end_line": 6901,
- "verbosity": 0
- },
- {
- "id": 2455,
- "type": "job_event",
- "url": "/api/v2/job_events/2455/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/2455/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T20:00:03.783615Z",
- "modified": "2021-01-25T20:00:03.867873Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 2403,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 2399",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "2399",
- "ansible_loop_var": "item",
- "_ansible_item_label": "2399"
- },
- "uuid": "b303a4c4-c0e6-4c6b-8058-37d6eab0d3b9"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "b303a4c4-c0e6-4c6b-8058-37d6eab0d3b9",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=2399) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 2399\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 7198,
- "end_line": 7201,
- "verbosity": 0
- },
- {
- "id": 2562,
- "type": "job_event",
- "url": "/api/v2/job_events/2562/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/2562/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T20:00:05.268054Z",
- "modified": "2021-01-25T20:00:05.323916Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 2503,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 2499",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "2499",
- "ansible_loop_var": "item",
- "_ansible_item_label": "2499"
- },
- "uuid": "a57db0ba-9185-44bf-b843-498829ddc05c"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "a57db0ba-9185-44bf-b843-498829ddc05c",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=2499) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 2499\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 7498,
- "end_line": 7501,
- "verbosity": 0
- },
- {
- "id": 2659,
- "type": "job_event",
- "url": "/api/v2/job_events/2659/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/2659/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T20:00:06.905923Z",
- "modified": "2021-01-25T20:00:06.928694Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 2603,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 2599",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "2599",
- "ansible_loop_var": "item",
- "_ansible_item_label": "2599"
- },
- "uuid": "ea65ded3-b7ca-457d-8286-90565a4660c0"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "ea65ded3-b7ca-457d-8286-90565a4660c0",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=2599) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 2599\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 7798,
- "end_line": 7801,
- "verbosity": 0
- },
- {
- "id": 2761,
- "type": "job_event",
- "url": "/api/v2/job_events/2761/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/2761/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T20:00:08.420576Z",
- "modified": "2021-01-25T20:00:08.472109Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 2703,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 2699",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "2699",
- "ansible_loop_var": "item",
- "_ansible_item_label": "2699"
- },
- "uuid": "d075210b-f84b-4327-bf39-3827c9c98a8d"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "d075210b-f84b-4327-bf39-3827c9c98a8d",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=2699) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 2699\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 8098,
- "end_line": 8101,
- "verbosity": 0
- },
- {
- "id": 2858,
- "type": "job_event",
- "url": "/api/v2/job_events/2858/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/2858/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T20:00:09.663555Z",
- "modified": "2021-01-25T20:00:09.711445Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 2803,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 2799",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "2799",
- "ansible_loop_var": "item",
- "_ansible_item_label": "2799"
- },
- "uuid": "71f49527-11cd-48e5-9668-0818e7b8ab16"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "71f49527-11cd-48e5-9668-0818e7b8ab16",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=2799) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 2799\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 8398,
- "end_line": 8401,
- "verbosity": 0
- },
- {
- "id": 2959,
- "type": "job_event",
- "url": "/api/v2/job_events/2959/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/2959/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T20:00:10.903733Z",
- "modified": "2021-01-25T20:00:11.024868Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 2903,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 2899",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "2899",
- "ansible_loop_var": "item",
- "_ansible_item_label": "2899"
- },
- "uuid": "46be2034-ec12-4a3c-b64b-557a1daf7db5"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "46be2034-ec12-4a3c-b64b-557a1daf7db5",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=2899) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 2899\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 8698,
- "end_line": 8701,
- "verbosity": 0
- },
- {
- "id": 3050,
- "type": "job_event",
- "url": "/api/v2/job_events/3050/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/3050/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T20:00:12.118441Z",
- "modified": "2021-01-25T20:00:12.134911Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 2994,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 2990",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "2990",
- "ansible_loop_var": "item",
- "_ansible_item_label": "2990"
- },
- "uuid": "095649f8-d185-49c9-82e3-aaa81d7ff644"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "095649f8-d185-49c9-82e3-aaa81d7ff644",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=2990) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 2990\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 8971,
- "end_line": 8974,
- "verbosity": 0
- },
- {
- "id": 3051,
- "type": "job_event",
- "url": "/api/v2/job_events/3051/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/3051/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T20:00:12.128037Z",
- "modified": "2021-01-25T20:00:12.134911Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 2995,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 2991",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "2991",
- "ansible_loop_var": "item",
- "_ansible_item_label": "2991"
- },
- "uuid": "2a946083-b268-4a0e-83c4-b88a03136531"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "2a946083-b268-4a0e-83c4-b88a03136531",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=2991) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 2991\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 8974,
- "end_line": 8977,
- "verbosity": 0
- },
- {
- "id": 3052,
- "type": "job_event",
- "url": "/api/v2/job_events/3052/",
- "related": {
- "job": "/api/v2/jobs/3/",
- "children": "/api/v2/job_events/3052/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": { "id": 1, "name": "localhost", "description": "" },
- "job": {
- "id": 3,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 71.24,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-25T20:00:12.139642Z",
- "modified": "2021-01-25T20:00:12.298717Z",
- "job": 3,
- "event": "runner_item_on_ok",
- "counter": 2996,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "99069b79-d726-488c-9dd4-acab97997db7",
- "play": "all",
- "play_uuid": "0242ac12-0004-ad5f-d7aa-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_3_5eu0an6_/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 2992",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "2992",
- "ansible_loop_var": "item",
- "_ansible_item_label": "2992"
- },
- "uuid": "720db8bb-3813-4f43-a6ae-c2502aecd0ac"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "720db8bb-3813-4f43-a6ae-c2502aecd0ac",
- "parent_uuid": "0242ac12-0004-ad5f-d7aa-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=2992) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 2992\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 8977,
- "end_line": 8980,
- "verbosity": 0
- }
- ]
-}
diff --git a/awx/ui/src/screens/Job/JobOutput/data.job_events.json b/awx/ui/src/screens/Job/JobOutput/data.job_events.json
deleted file mode 100644
index df90698fb534..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/data.job_events.json
+++ /dev/null
@@ -1,8172 +0,0 @@
-{
- "count": 100,
- "next": null,
- "previous": null,
- "results": [
- {
- "id": 11181,
- "type": "job_event",
- "url": "/api/v2/job_events/11181/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11181/children/"
- },
- "summary_fields": {
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:13.908757Z",
- "modified": "2021-01-28T16:17:13.912569Z",
- "job": 8,
- "event": "playbook_on_start",
- "counter": 1,
- "event_display": "Playbook Started",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7"
- },
- "event_level": 0,
- "failed": false,
- "changed": false,
- "uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "parent_uuid": "",
- "host": null,
- "host_name": "",
- "playbook": "chatty_tasks.yml",
- "play": "",
- "task": "",
- "role": "",
- "stdout": "",
- "start_line": 0,
- "end_line": 0,
- "verbosity": 0
- },
- {
- "id": 11182,
- "type": "job_event",
- "url": "/api/v2/job_events/11182/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11182/children/"
- },
- "summary_fields": {
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:13.914855Z",
- "modified": "2021-01-28T16:17:13.919137Z",
- "job": 8,
- "event": "playbook_on_play_start",
- "counter": 2,
- "event_display": "Play Started (all)",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "name": "all",
- "pattern": "all",
- "uuid": "0242ac12-0004-20a0-8d35-000000000006"
- },
- "event_level": 1,
- "failed": false,
- "changed": false,
- "uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "parent_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "host": null,
- "host_name": "",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "",
- "role": "",
- "stdout": "\r\nPLAY [all] *********************************************************************",
- "start_line": 0,
- "end_line": 2,
- "verbosity": 0
- },
- {
- "id": 11183,
- "type": "job_event",
- "url": "/api/v2/job_events/11183/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11183/children/"
- },
- "summary_fields": {
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:13.936809Z",
- "modified": "2021-01-28T16:17:13.939410Z",
- "job": 8,
- "event": "playbook_on_task_start",
- "counter": 3,
- "event_display": "Task Started (debug)",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "name": "debug",
- "is_conditional": false,
- "uuid": "0242ac12-0004-20a0-8d35-000000000008"
- },
- "event_level": 2,
- "failed": false,
- "changed": false,
- "uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "host": null,
- "host_name": "",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\r\nTASK [debug] *******************************************************************",
- "start_line": 2,
- "end_line": 4,
- "verbosity": 0
- },
- {
- "id": 11184,
- "type": "job_event",
- "url": "/api/v2/job_events/11184/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11184/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:13.939176Z",
- "modified": "2021-01-28T16:17:13.943973Z",
- "job": 8,
- "event": "runner_on_start",
- "counter": 4,
- "event_display": "Host Started",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "uuid": "d4bc464d-1402-4bb3-b5b8-36ef23c06d52"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "d4bc464d-1402-4bb3-b5b8-36ef23c06d52",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "",
- "start_line": 4,
- "end_line": 4,
- "verbosity": 0
- },
- {
- "id": 11186,
- "type": "job_event",
- "url": "/api/v2/job_events/11186/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11186/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.359276Z",
- "modified": "2021-01-28T16:17:14.367680Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 5,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 1",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "1",
- "ansible_loop_var": "item",
- "_ansible_item_label": "1"
- },
- "uuid": "deba55ca-d702-4f7a-b3f5-67b9b4111194"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "deba55ca-d702-4f7a-b3f5-67b9b4111194",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=1) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 1\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 4,
- "end_line": 7,
- "verbosity": 0
- },
- {
- "id": 11185,
- "type": "job_event",
- "url": "/api/v2/job_events/11185/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11185/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.370749Z",
- "modified": "2021-01-28T16:17:14.374644Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 6,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 2",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "2",
- "ansible_loop_var": "item",
- "_ansible_item_label": "2"
- },
- "uuid": "6c16b0b2-519a-4111-8c65-4a85e643f7df"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "6c16b0b2-519a-4111-8c65-4a85e643f7df",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=2) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 2\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 7,
- "end_line": 10,
- "verbosity": 0
- },
- {
- "id": 11187,
- "type": "job_event",
- "url": "/api/v2/job_events/11187/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11187/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.394328Z",
- "modified": "2021-01-28T16:17:14.402966Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 7,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 3",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "3",
- "ansible_loop_var": "item",
- "_ansible_item_label": "3"
- },
- "uuid": "aebe86cc-db98-4cb5-8620-38dd814f69cd"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "aebe86cc-db98-4cb5-8620-38dd814f69cd",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=3) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 3\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 10,
- "end_line": 13,
- "verbosity": 0
- },
- {
- "id": 11188,
- "type": "job_event",
- "url": "/api/v2/job_events/11188/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11188/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.415964Z",
- "modified": "2021-01-28T16:17:14.420170Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 8,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 4",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "4",
- "ansible_loop_var": "item",
- "_ansible_item_label": "4"
- },
- "uuid": "9d90b79b-d41e-4d8b-a5d4-14cacc3baa74"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "9d90b79b-d41e-4d8b-a5d4-14cacc3baa74",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=4) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 4\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 13,
- "end_line": 16,
- "verbosity": 0
- },
- {
- "id": 11193,
- "type": "job_event",
- "url": "/api/v2/job_events/11193/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11193/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.447391Z",
- "modified": "2021-01-28T16:17:14.620068Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 9,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 5",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "5",
- "ansible_loop_var": "item",
- "_ansible_item_label": "5"
- },
- "uuid": "3dc09abd-a2ed-4e27-9c41-aa055e911635"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "3dc09abd-a2ed-4e27-9c41-aa055e911635",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=5) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 5\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 16,
- "end_line": 19,
- "verbosity": 0
- },
- {
- "id": 11189,
- "type": "job_event",
- "url": "/api/v2/job_events/11189/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11189/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.473838Z",
- "modified": "2021-01-28T16:17:14.543706Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 10,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 6",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "6",
- "ansible_loop_var": "item",
- "_ansible_item_label": "6"
- },
- "uuid": "074206dd-9cc0-4121-88da-84d845aca5cd"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "074206dd-9cc0-4121-88da-84d845aca5cd",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=6) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 6\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 19,
- "end_line": 22,
- "verbosity": 0
- },
- {
- "id": 11191,
- "type": "job_event",
- "url": "/api/v2/job_events/11191/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11191/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.496577Z",
- "modified": "2021-01-28T16:17:14.596578Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 11,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 7",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "7",
- "ansible_loop_var": "item",
- "_ansible_item_label": "7"
- },
- "uuid": "05d4c436-248d-421e-855c-399ec0269c3c"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "05d4c436-248d-421e-855c-399ec0269c3c",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=7) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 7\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 22,
- "end_line": 25,
- "verbosity": 0
- },
- {
- "id": 11194,
- "type": "job_event",
- "url": "/api/v2/job_events/11194/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11194/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.520143Z",
- "modified": "2021-01-28T16:17:14.620068Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 12,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 8",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "8",
- "ansible_loop_var": "item",
- "_ansible_item_label": "8"
- },
- "uuid": "d48dbc71-3aa3-4ca2-8ec9-be2159367b7d"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "d48dbc71-3aa3-4ca2-8ec9-be2159367b7d",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=8) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 8\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 25,
- "end_line": 28,
- "verbosity": 0
- },
- {
- "id": 11190,
- "type": "job_event",
- "url": "/api/v2/job_events/11190/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11190/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.537247Z",
- "modified": "2021-01-28T16:17:14.543706Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 13,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 9",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "9",
- "ansible_loop_var": "item",
- "_ansible_item_label": "9"
- },
- "uuid": "43eae334-fdde-46a8-9766-a875f25db157"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "43eae334-fdde-46a8-9766-a875f25db157",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=9) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 9\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 28,
- "end_line": 31,
- "verbosity": 0
- },
- {
- "id": 11196,
- "type": "job_event",
- "url": "/api/v2/job_events/11196/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11196/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.559117Z",
- "modified": "2021-01-28T16:17:14.644701Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 14,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 10",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "10",
- "ansible_loop_var": "item",
- "_ansible_item_label": "10"
- },
- "uuid": "bf32dcd2-a9c6-4aaa-83af-f60d2c90517c"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "bf32dcd2-a9c6-4aaa-83af-f60d2c90517c",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=10) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 10\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 31,
- "end_line": 34,
- "verbosity": 0
- },
- {
- "id": 11192,
- "type": "job_event",
- "url": "/api/v2/job_events/11192/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11192/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.591599Z",
- "modified": "2021-01-28T16:17:14.596578Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 15,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 11",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "11",
- "ansible_loop_var": "item",
- "_ansible_item_label": "11"
- },
- "uuid": "9a3c0707-7e67-4ebe-abf8-7b8eddb9cccc"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "9a3c0707-7e67-4ebe-abf8-7b8eddb9cccc",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=11) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 11\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 34,
- "end_line": 37,
- "verbosity": 0
- },
- {
- "id": 11195,
- "type": "job_event",
- "url": "/api/v2/job_events/11195/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11195/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.612670Z",
- "modified": "2021-01-28T16:17:14.620068Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 16,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 12",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "12",
- "ansible_loop_var": "item",
- "_ansible_item_label": "12"
- },
- "uuid": "f92efc58-64aa-41b7-88f5-e0d7727b4f5f"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "f92efc58-64aa-41b7-88f5-e0d7727b4f5f",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=12) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 12\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 37,
- "end_line": 40,
- "verbosity": 0
- },
- {
- "id": 11197,
- "type": "job_event",
- "url": "/api/v2/job_events/11197/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11197/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.635395Z",
- "modified": "2021-01-28T16:17:14.644701Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 17,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 13",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "13",
- "ansible_loop_var": "item",
- "_ansible_item_label": "13"
- },
- "uuid": "8597d3e4-0515-4137-bf5e-64ad42237918"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "8597d3e4-0515-4137-bf5e-64ad42237918",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=13) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 13\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 40,
- "end_line": 43,
- "verbosity": 0
- },
- {
- "id": 11198,
- "type": "job_event",
- "url": "/api/v2/job_events/11198/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11198/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.661725Z",
- "modified": "2021-01-28T16:17:14.807060Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 18,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 14",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "14",
- "ansible_loop_var": "item",
- "_ansible_item_label": "14"
- },
- "uuid": "d9ed3304-99fe-4882-b175-fc6216455f68"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "d9ed3304-99fe-4882-b175-fc6216455f68",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=14) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 14\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 43,
- "end_line": 46,
- "verbosity": 0
- },
- {
- "id": 11199,
- "type": "job_event",
- "url": "/api/v2/job_events/11199/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11199/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.693482Z",
- "modified": "2021-01-28T16:17:14.807060Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 19,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 15",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "15",
- "ansible_loop_var": "item",
- "_ansible_item_label": "15"
- },
- "uuid": "1904559c-71f4-4966-aa17-adf6d926e6ae"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "1904559c-71f4-4966-aa17-adf6d926e6ae",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=15) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 15\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 46,
- "end_line": 49,
- "verbosity": 0
- },
- {
- "id": 11200,
- "type": "job_event",
- "url": "/api/v2/job_events/11200/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11200/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.700143Z",
- "modified": "2021-01-28T16:17:14.807060Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 20,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 16",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "16",
- "ansible_loop_var": "item",
- "_ansible_item_label": "16"
- },
- "uuid": "ef18487a-6868-48f1-9cda-bf0a71f52232"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "ef18487a-6868-48f1-9cda-bf0a71f52232",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=16) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 16\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 49,
- "end_line": 52,
- "verbosity": 0
- },
- {
- "id": 11201,
- "type": "job_event",
- "url": "/api/v2/job_events/11201/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11201/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.716934Z",
- "modified": "2021-01-28T16:17:14.807060Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 21,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 17",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "17",
- "ansible_loop_var": "item",
- "_ansible_item_label": "17"
- },
- "uuid": "d7e9ceb1-0abd-4876-a8b2-c195dac60e72"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "d7e9ceb1-0abd-4876-a8b2-c195dac60e72",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=17) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 17\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 52,
- "end_line": 55,
- "verbosity": 0
- },
- {
- "id": 11204,
- "type": "job_event",
- "url": "/api/v2/job_events/11204/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11204/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.748138Z",
- "modified": "2021-01-28T16:17:14.836829Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 22,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 18",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "18",
- "ansible_loop_var": "item",
- "_ansible_item_label": "18"
- },
- "uuid": "c84218b4-3412-40fb-a713-6e86433377b6"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "c84218b4-3412-40fb-a713-6e86433377b6",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=18) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 18\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 55,
- "end_line": 58,
- "verbosity": 0
- },
- {
- "id": 11202,
- "type": "job_event",
- "url": "/api/v2/job_events/11202/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11202/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.763518Z",
- "modified": "2021-01-28T16:17:14.807060Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 23,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 19",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "19",
- "ansible_loop_var": "item",
- "_ansible_item_label": "19"
- },
- "uuid": "c4b322ae-6f95-4cfc-88d1-189a4b2d342d"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "c4b322ae-6f95-4cfc-88d1-189a4b2d342d",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=19) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 19\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 58,
- "end_line": 61,
- "verbosity": 0
- },
- {
- "id": 11209,
- "type": "job_event",
- "url": "/api/v2/job_events/11209/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11209/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.774866Z",
- "modified": "2021-01-28T16:17:14.888588Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 24,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 20",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "20",
- "ansible_loop_var": "item",
- "_ansible_item_label": "20"
- },
- "uuid": "f1940fe3-501c-416b-ad51-a90d9a17969a"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "f1940fe3-501c-416b-ad51-a90d9a17969a",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=20) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 20\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 61,
- "end_line": 64,
- "verbosity": 0
- },
- {
- "id": 11205,
- "type": "job_event",
- "url": "/api/v2/job_events/11205/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11205/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.786440Z",
- "modified": "2021-01-28T16:17:14.836829Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 25,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 21",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "21",
- "ansible_loop_var": "item",
- "_ansible_item_label": "21"
- },
- "uuid": "54eee051-1c7a-4baa-8a4e-d92d717a1bd2"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "54eee051-1c7a-4baa-8a4e-d92d717a1bd2",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=21) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 21\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 64,
- "end_line": 67,
- "verbosity": 0
- },
- {
- "id": 11207,
- "type": "job_event",
- "url": "/api/v2/job_events/11207/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11207/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.795702Z",
- "modified": "2021-01-28T16:17:14.860501Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 26,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 22",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "22",
- "ansible_loop_var": "item",
- "_ansible_item_label": "22"
- },
- "uuid": "72dfb0b6-38bd-46a7-a2d0-a022c7b67286"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "72dfb0b6-38bd-46a7-a2d0-a022c7b67286",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=22) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 22\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 67,
- "end_line": 70,
- "verbosity": 0
- },
- {
- "id": 11203,
- "type": "job_event",
- "url": "/api/v2/job_events/11203/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11203/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.803609Z",
- "modified": "2021-01-28T16:17:14.807060Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 27,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 23",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "23",
- "ansible_loop_var": "item",
- "_ansible_item_label": "23"
- },
- "uuid": "854169e6-6348-4aa7-8fd5-cc9948236cf2"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "854169e6-6348-4aa7-8fd5-cc9948236cf2",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=23) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 23\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 70,
- "end_line": 73,
- "verbosity": 0
- },
- {
- "id": 11210,
- "type": "job_event",
- "url": "/api/v2/job_events/11210/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11210/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.815935Z",
- "modified": "2021-01-28T16:17:14.888588Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 28,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 24",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "24",
- "ansible_loop_var": "item",
- "_ansible_item_label": "24"
- },
- "uuid": "a703751d-eef2-491a-a979-f3789163a032"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "a703751d-eef2-491a-a979-f3789163a032",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=24) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 24\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 73,
- "end_line": 76,
- "verbosity": 0
- },
- {
- "id": 11206,
- "type": "job_event",
- "url": "/api/v2/job_events/11206/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11206/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.828072Z",
- "modified": "2021-01-28T16:17:14.836829Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 29,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 25",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "25",
- "ansible_loop_var": "item",
- "_ansible_item_label": "25"
- },
- "uuid": "732e43a8-e773-4ad4-a0cb-c73c132e709e"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "732e43a8-e773-4ad4-a0cb-c73c132e709e",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=25) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 25\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 76,
- "end_line": 79,
- "verbosity": 0
- },
- {
- "id": 11208,
- "type": "job_event",
- "url": "/api/v2/job_events/11208/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11208/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.855142Z",
- "modified": "2021-01-28T16:17:14.860501Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 30,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 26",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "26",
- "ansible_loop_var": "item",
- "_ansible_item_label": "26"
- },
- "uuid": "0f8ddc1b-7010-445f-b307-bde3bb587927"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "0f8ddc1b-7010-445f-b307-bde3bb587927",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=26) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 26\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 79,
- "end_line": 82,
- "verbosity": 0
- },
- {
- "id": 11211,
- "type": "job_event",
- "url": "/api/v2/job_events/11211/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11211/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.880929Z",
- "modified": "2021-01-28T16:17:14.888588Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 31,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 27",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "27",
- "ansible_loop_var": "item",
- "_ansible_item_label": "27"
- },
- "uuid": "c49e007d-75ac-4578-a263-94e6aa733c19"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "c49e007d-75ac-4578-a263-94e6aa733c19",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=27) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 27\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 82,
- "end_line": 85,
- "verbosity": 0
- },
- {
- "id": 11215,
- "type": "job_event",
- "url": "/api/v2/job_events/11215/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11215/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.909748Z",
- "modified": "2021-01-28T16:17:15.091182Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 32,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 28",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "28",
- "ansible_loop_var": "item",
- "_ansible_item_label": "28"
- },
- "uuid": "4691b7af-3a1b-4bec-aec2-298fe8a86a03"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "4691b7af-3a1b-4bec-aec2-298fe8a86a03",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=28) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 28\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 85,
- "end_line": 88,
- "verbosity": 0
- },
- {
- "id": 11216,
- "type": "job_event",
- "url": "/api/v2/job_events/11216/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11216/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.930843Z",
- "modified": "2021-01-28T16:17:15.091182Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 33,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 29",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "29",
- "ansible_loop_var": "item",
- "_ansible_item_label": "29"
- },
- "uuid": "72741a4c-94cc-4154-9516-7ccfb18db931"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "72741a4c-94cc-4154-9516-7ccfb18db931",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=29) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 29\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 88,
- "end_line": 91,
- "verbosity": 0
- },
- {
- "id": 11217,
- "type": "job_event",
- "url": "/api/v2/job_events/11217/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11217/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.964814Z",
- "modified": "2021-01-28T16:17:15.091182Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 34,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 30",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "30",
- "ansible_loop_var": "item",
- "_ansible_item_label": "30"
- },
- "uuid": "a4bba188-4b24-4750-a1c2-d7a512937322"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "a4bba188-4b24-4750-a1c2-d7a512937322",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=30) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 30\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 91,
- "end_line": 94,
- "verbosity": 0
- },
- {
- "id": 11212,
- "type": "job_event",
- "url": "/api/v2/job_events/11212/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11212/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.977160Z",
- "modified": "2021-01-28T16:17:15.070662Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 35,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 31",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "31",
- "ansible_loop_var": "item",
- "_ansible_item_label": "31"
- },
- "uuid": "b141e52e-8fc8-4797-8435-e4a39189e1a6"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "b141e52e-8fc8-4797-8435-e4a39189e1a6",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=31) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 31\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 94,
- "end_line": 97,
- "verbosity": 0
- },
- {
- "id": 11218,
- "type": "job_event",
- "url": "/api/v2/job_events/11218/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11218/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:14.998965Z",
- "modified": "2021-01-28T16:17:15.091182Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 36,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 32",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "32",
- "ansible_loop_var": "item",
- "_ansible_item_label": "32"
- },
- "uuid": "00a1196c-8368-4fbc-8b4e-276c70320a1c"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "00a1196c-8368-4fbc-8b4e-276c70320a1c",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=32) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 32\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 97,
- "end_line": 100,
- "verbosity": 0
- },
- {
- "id": 11221,
- "type": "job_event",
- "url": "/api/v2/job_events/11221/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11221/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.011475Z",
- "modified": "2021-01-28T16:17:15.118212Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 37,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 33",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "33",
- "ansible_loop_var": "item",
- "_ansible_item_label": "33"
- },
- "uuid": "6ef701e1-922e-4899-9bf3-e76ce94dd99b"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "6ef701e1-922e-4899-9bf3-e76ce94dd99b",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=33) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 33\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 100,
- "end_line": 103,
- "verbosity": 0
- },
- {
- "id": 11213,
- "type": "job_event",
- "url": "/api/v2/job_events/11213/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11213/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.020982Z",
- "modified": "2021-01-28T16:17:15.070662Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 38,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 34",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "34",
- "ansible_loop_var": "item",
- "_ansible_item_label": "34"
- },
- "uuid": "1f2908e0-345f-4267-8903-5935b7725724"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "1f2908e0-345f-4267-8903-5935b7725724",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=34) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 34\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 103,
- "end_line": 106,
- "verbosity": 0
- },
- {
- "id": 11223,
- "type": "job_event",
- "url": "/api/v2/job_events/11223/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11223/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.030420Z",
- "modified": "2021-01-28T16:17:15.120116Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 39,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 35",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "35",
- "ansible_loop_var": "item",
- "_ansible_item_label": "35"
- },
- "uuid": "ad3adecf-45d1-4ee4-acb6-1c4357fb865b"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "ad3adecf-45d1-4ee4-acb6-1c4357fb865b",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=35) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 35\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 106,
- "end_line": 109,
- "verbosity": 0
- },
- {
- "id": 11219,
- "type": "job_event",
- "url": "/api/v2/job_events/11219/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11219/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.041426Z",
- "modified": "2021-01-28T16:17:15.091182Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 40,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 36",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "36",
- "ansible_loop_var": "item",
- "_ansible_item_label": "36"
- },
- "uuid": "5dc857dd-1f8a-4de7-9304-42724e92d5b8"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "5dc857dd-1f8a-4de7-9304-42724e92d5b8",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=36) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 36\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 109,
- "end_line": 112,
- "verbosity": 0
- },
- {
- "id": 11222,
- "type": "job_event",
- "url": "/api/v2/job_events/11222/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11222/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.054496Z",
- "modified": "2021-01-28T16:17:15.118212Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 41,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 37",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "37",
- "ansible_loop_var": "item",
- "_ansible_item_label": "37"
- },
- "uuid": "25480ca3-97fa-4686-a282-ab2f0b7b42b2"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "25480ca3-97fa-4686-a282-ab2f0b7b42b2",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=37) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 37\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 112,
- "end_line": 115,
- "verbosity": 0
- },
- {
- "id": 11214,
- "type": "job_event",
- "url": "/api/v2/job_events/11214/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11214/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.066146Z",
- "modified": "2021-01-28T16:17:15.070662Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 42,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 38",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "38",
- "ansible_loop_var": "item",
- "_ansible_item_label": "38"
- },
- "uuid": "5a92c663-141d-4350-9bed-9305ca63c746"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "5a92c663-141d-4350-9bed-9305ca63c746",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=38) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 38\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 115,
- "end_line": 118,
- "verbosity": 0
- },
- {
- "id": 11225,
- "type": "job_event",
- "url": "/api/v2/job_events/11225/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11225/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.073557Z",
- "modified": "2021-01-28T16:17:15.120116Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 43,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 39",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "39",
- "ansible_loop_var": "item",
- "_ansible_item_label": "39"
- },
- "uuid": "bd391aab-6fd1-4773-96ca-9bfd581b1e79"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "bd391aab-6fd1-4773-96ca-9bfd581b1e79",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=39) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 39\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 118,
- "end_line": 121,
- "verbosity": 0
- },
- {
- "id": 11220,
- "type": "job_event",
- "url": "/api/v2/job_events/11220/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11220/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.087067Z",
- "modified": "2021-01-28T16:17:15.091182Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 44,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 40",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "40",
- "ansible_loop_var": "item",
- "_ansible_item_label": "40"
- },
- "uuid": "af87371e-45d6-432a-ad96-5a319ce7a2a4"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "af87371e-45d6-432a-ad96-5a319ce7a2a4",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=40) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 40\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 121,
- "end_line": 124,
- "verbosity": 0
- },
- {
- "id": 11224,
- "type": "job_event",
- "url": "/api/v2/job_events/11224/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11224/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.100089Z",
- "modified": "2021-01-28T16:17:15.118212Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 45,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 41",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "41",
- "ansible_loop_var": "item",
- "_ansible_item_label": "41"
- },
- "uuid": "c964a9d1-3d48-436f-9f6d-ffbe86da540d"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "c964a9d1-3d48-436f-9f6d-ffbe86da540d",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=41) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 41\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 124,
- "end_line": 127,
- "verbosity": 0
- },
- {
- "id": 11226,
- "type": "job_event",
- "url": "/api/v2/job_events/11226/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11226/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.109339Z",
- "modified": "2021-01-28T16:17:15.120116Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 46,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 42",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "42",
- "ansible_loop_var": "item",
- "_ansible_item_label": "42"
- },
- "uuid": "004adef6-12ab-4fa6-a1f0-43084bba9d85"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "004adef6-12ab-4fa6-a1f0-43084bba9d85",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=42) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 42\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 127,
- "end_line": 130,
- "verbosity": 0
- },
- {
- "id": 11227,
- "type": "job_event",
- "url": "/api/v2/job_events/11227/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11227/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.129119Z",
- "modified": "2021-01-28T16:17:15.293300Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 47,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 43",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "43",
- "ansible_loop_var": "item",
- "_ansible_item_label": "43"
- },
- "uuid": "1eb89f38-4f6a-4af1-929c-f257271095c2"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "1eb89f38-4f6a-4af1-929c-f257271095c2",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=43) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 43\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 130,
- "end_line": 133,
- "verbosity": 0
- },
- {
- "id": 11228,
- "type": "job_event",
- "url": "/api/v2/job_events/11228/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11228/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.142654Z",
- "modified": "2021-01-28T16:17:15.293300Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 48,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 44",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "44",
- "ansible_loop_var": "item",
- "_ansible_item_label": "44"
- },
- "uuid": "64de39f5-967d-4860-8e7c-60e905b3477d"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "64de39f5-967d-4860-8e7c-60e905b3477d",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=44) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 44\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 133,
- "end_line": 136,
- "verbosity": 0
- },
- {
- "id": 11229,
- "type": "job_event",
- "url": "/api/v2/job_events/11229/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11229/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.166060Z",
- "modified": "2021-01-28T16:17:15.293300Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 49,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 45",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "45",
- "ansible_loop_var": "item",
- "_ansible_item_label": "45"
- },
- "uuid": "733bad3e-c0d4-4ce0-8fa0-2e4235eb4d13"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "733bad3e-c0d4-4ce0-8fa0-2e4235eb4d13",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=45) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 45\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 136,
- "end_line": 139,
- "verbosity": 0
- },
- {
- "id": 11230,
- "type": "job_event",
- "url": "/api/v2/job_events/11230/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11230/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.187782Z",
- "modified": "2021-01-28T16:17:15.293300Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 50,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 46",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "46",
- "ansible_loop_var": "item",
- "_ansible_item_label": "46"
- },
- "uuid": "fd9df3a9-1c5b-4b81-b16c-f43a7f80b803"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "fd9df3a9-1c5b-4b81-b16c-f43a7f80b803",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=46) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 46\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 139,
- "end_line": 142,
- "verbosity": 0
- },
- {
- "id": 11231,
- "type": "job_event",
- "url": "/api/v2/job_events/11231/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11231/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.190284Z",
- "modified": "2021-01-28T16:17:15.293300Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 51,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 47",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "47",
- "ansible_loop_var": "item",
- "_ansible_item_label": "47"
- },
- "uuid": "3b58e007-a910-45f0-a15c-74614074d7aa"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "3b58e007-a910-45f0-a15c-74614074d7aa",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=47) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 47\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 142,
- "end_line": 145,
- "verbosity": 0
- },
- {
- "id": 11232,
- "type": "job_event",
- "url": "/api/v2/job_events/11232/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11232/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.208194Z",
- "modified": "2021-01-28T16:17:15.293300Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 52,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 48",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "48",
- "ansible_loop_var": "item",
- "_ansible_item_label": "48"
- },
- "uuid": "53687193-6117-42f6-aa23-01362a81451c"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "53687193-6117-42f6-aa23-01362a81451c",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=48) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 48\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 145,
- "end_line": 148,
- "verbosity": 0
- },
- {
- "id": 11233,
- "type": "job_event",
- "url": "/api/v2/job_events/11233/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11233/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.223816Z",
- "modified": "2021-01-28T16:17:15.293300Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 53,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 49",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "49",
- "ansible_loop_var": "item",
- "_ansible_item_label": "49"
- },
- "uuid": "b9c8360d-16f7-4e00-b32e-5f3246b1d53b"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "b9c8360d-16f7-4e00-b32e-5f3246b1d53b",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=49) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 49\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 148,
- "end_line": 151,
- "verbosity": 0
- },
- {
- "id": 11239,
- "type": "job_event",
- "url": "/api/v2/job_events/11239/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11239/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.239700Z",
- "modified": "2021-01-28T16:17:15.367780Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 54,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 50",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "50",
- "ansible_loop_var": "item",
- "_ansible_item_label": "50"
- },
- "uuid": "56428c4a-f882-4907-a248-ebd4d2da7bc6"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "56428c4a-f882-4907-a248-ebd4d2da7bc6",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=50) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 50\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 151,
- "end_line": 154,
- "verbosity": 0
- },
- {
- "id": 11234,
- "type": "job_event",
- "url": "/api/v2/job_events/11234/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11234/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.258205Z",
- "modified": "2021-01-28T16:17:15.293300Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 55,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 51",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "51",
- "ansible_loop_var": "item",
- "_ansible_item_label": "51"
- },
- "uuid": "05c59e38-8290-4396-bc7b-dd5b127d8a65"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "05c59e38-8290-4396-bc7b-dd5b127d8a65",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=51) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 51\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 154,
- "end_line": 157,
- "verbosity": 0
- },
- {
- "id": 11236,
- "type": "job_event",
- "url": "/api/v2/job_events/11236/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11236/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.264651Z",
- "modified": "2021-01-28T16:17:15.344259Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 56,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 52",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "52",
- "ansible_loop_var": "item",
- "_ansible_item_label": "52"
- },
- "uuid": "9c6721c2-b777-4771-b0f6-ebbd877cfa29"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "9c6721c2-b777-4771-b0f6-ebbd877cfa29",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=52) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 52\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 157,
- "end_line": 160,
- "verbosity": 0
- },
- {
- "id": 11240,
- "type": "job_event",
- "url": "/api/v2/job_events/11240/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11240/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.277008Z",
- "modified": "2021-01-28T16:17:15.367780Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 57,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 53",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "53",
- "ansible_loop_var": "item",
- "_ansible_item_label": "53"
- },
- "uuid": "95f91cf0-a935-4f1a-949c-ac7b1ef1656d"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "95f91cf0-a935-4f1a-949c-ac7b1ef1656d",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=53) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 53\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 160,
- "end_line": 163,
- "verbosity": 0
- },
- {
- "id": 11235,
- "type": "job_event",
- "url": "/api/v2/job_events/11235/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11235/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.288250Z",
- "modified": "2021-01-28T16:17:15.293300Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 58,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 54",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "54",
- "ansible_loop_var": "item",
- "_ansible_item_label": "54"
- },
- "uuid": "b48d7e63-986d-4d80-be7d-383893007675"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "b48d7e63-986d-4d80-be7d-383893007675",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=54) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 54\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 163,
- "end_line": 166,
- "verbosity": 0
- },
- {
- "id": 11237,
- "type": "job_event",
- "url": "/api/v2/job_events/11237/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11237/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.311707Z",
- "modified": "2021-01-28T16:17:15.344259Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 59,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 55",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "55",
- "ansible_loop_var": "item",
- "_ansible_item_label": "55"
- },
- "uuid": "c19f8947-b2e9-4d77-8675-472b48266518"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "c19f8947-b2e9-4d77-8675-472b48266518",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=55) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 55\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 166,
- "end_line": 169,
- "verbosity": 0
- },
- {
- "id": 11241,
- "type": "job_event",
- "url": "/api/v2/job_events/11241/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11241/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.319871Z",
- "modified": "2021-01-28T16:17:15.367780Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 60,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 56",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "56",
- "ansible_loop_var": "item",
- "_ansible_item_label": "56"
- },
- "uuid": "ef0d848f-bd6b-4edc-9c8f-05c4f85711be"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "ef0d848f-bd6b-4edc-9c8f-05c4f85711be",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=56) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 56\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 169,
- "end_line": 172,
- "verbosity": 0
- },
- {
- "id": 11243,
- "type": "job_event",
- "url": "/api/v2/job_events/11243/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11243/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.324224Z",
- "modified": "2021-01-28T16:17:15.402983Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 61,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 57",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "57",
- "ansible_loop_var": "item",
- "_ansible_item_label": "57"
- },
- "uuid": "580c7760-f667-4587-9920-5bdbb3368c17"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "580c7760-f667-4587-9920-5bdbb3368c17",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=57) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 57\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 172,
- "end_line": 175,
- "verbosity": 0
- },
- {
- "id": 11238,
- "type": "job_event",
- "url": "/api/v2/job_events/11238/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11238/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.338862Z",
- "modified": "2021-01-28T16:17:15.344259Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 62,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 58",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "58",
- "ansible_loop_var": "item",
- "_ansible_item_label": "58"
- },
- "uuid": "7ad13b4e-6fbf-4c94-b374-d7eeb38cf825"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "7ad13b4e-6fbf-4c94-b374-d7eeb38cf825",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=58) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 58\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 175,
- "end_line": 178,
- "verbosity": 0
- },
- {
- "id": 11242,
- "type": "job_event",
- "url": "/api/v2/job_events/11242/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11242/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.355083Z",
- "modified": "2021-01-28T16:17:15.367780Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 63,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 59",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "59",
- "ansible_loop_var": "item",
- "_ansible_item_label": "59"
- },
- "uuid": "4c35d0b5-c9d0-4e68-85e0-423b6b910690"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "4c35d0b5-c9d0-4e68-85e0-423b6b910690",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=59) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 59\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 178,
- "end_line": 181,
- "verbosity": 0
- },
- {
- "id": 11244,
- "type": "job_event",
- "url": "/api/v2/job_events/11244/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11244/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.382672Z",
- "modified": "2021-01-28T16:17:15.402983Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 64,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 60",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "60",
- "ansible_loop_var": "item",
- "_ansible_item_label": "60"
- },
- "uuid": "15060a82-0203-40c4-81e1-a0825eb10e95"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "15060a82-0203-40c4-81e1-a0825eb10e95",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=60) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 60\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 181,
- "end_line": 184,
- "verbosity": 0
- },
- {
- "id": 11245,
- "type": "job_event",
- "url": "/api/v2/job_events/11245/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11245/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.389899Z",
- "modified": "2021-01-28T16:17:15.402983Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 65,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 61",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "61",
- "ansible_loop_var": "item",
- "_ansible_item_label": "61"
- },
- "uuid": "f5084c64-685b-4737-93d8-a892519c9be8"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "f5084c64-685b-4737-93d8-a892519c9be8",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=61) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 61\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 184,
- "end_line": 187,
- "verbosity": 0
- },
- {
- "id": 11246,
- "type": "job_event",
- "url": "/api/v2/job_events/11246/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11246/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.398489Z",
- "modified": "2021-01-28T16:17:15.402983Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 66,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 62",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "62",
- "ansible_loop_var": "item",
- "_ansible_item_label": "62"
- },
- "uuid": "aeecd944-c4c7-47fb-9803-eab5399e39f3"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "aeecd944-c4c7-47fb-9803-eab5399e39f3",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=62) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 62\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 187,
- "end_line": 190,
- "verbosity": 0
- },
- {
- "id": 11247,
- "type": "job_event",
- "url": "/api/v2/job_events/11247/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11247/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.409578Z",
- "modified": "2021-01-28T16:17:15.550420Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 67,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 63",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "63",
- "ansible_loop_var": "item",
- "_ansible_item_label": "63"
- },
- "uuid": "3501c35c-d3d6-4c65-9fde-cdec9fbed5dd"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "3501c35c-d3d6-4c65-9fde-cdec9fbed5dd",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=63) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 63\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 190,
- "end_line": 193,
- "verbosity": 0
- },
- {
- "id": 11248,
- "type": "job_event",
- "url": "/api/v2/job_events/11248/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11248/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.416912Z",
- "modified": "2021-01-28T16:17:15.550420Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 68,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 64",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "64",
- "ansible_loop_var": "item",
- "_ansible_item_label": "64"
- },
- "uuid": "1435eb01-5d47-409d-b1c9-b750dc74683f"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "1435eb01-5d47-409d-b1c9-b750dc74683f",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=64) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 64\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 193,
- "end_line": 196,
- "verbosity": 0
- },
- {
- "id": 11249,
- "type": "job_event",
- "url": "/api/v2/job_events/11249/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11249/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.423699Z",
- "modified": "2021-01-28T16:17:15.550420Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 69,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 65",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "65",
- "ansible_loop_var": "item",
- "_ansible_item_label": "65"
- },
- "uuid": "d72f5a04-9036-4934-aca2-315f82731b23"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "d72f5a04-9036-4934-aca2-315f82731b23",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=65) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 65\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 196,
- "end_line": 199,
- "verbosity": 0
- },
- {
- "id": 11250,
- "type": "job_event",
- "url": "/api/v2/job_events/11250/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11250/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.446275Z",
- "modified": "2021-01-28T16:17:15.550420Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 70,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 66",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "66",
- "ansible_loop_var": "item",
- "_ansible_item_label": "66"
- },
- "uuid": "301ec331-deac-4143-b513-f6a09b1dd15d"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "301ec331-deac-4143-b513-f6a09b1dd15d",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=66) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 66\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 199,
- "end_line": 202,
- "verbosity": 0
- },
- {
- "id": 11251,
- "type": "job_event",
- "url": "/api/v2/job_events/11251/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11251/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.465117Z",
- "modified": "2021-01-28T16:17:15.550420Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 71,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 67",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "67",
- "ansible_loop_var": "item",
- "_ansible_item_label": "67"
- },
- "uuid": "14cf593a-5fa0-44b3-990c-62ef1bba8116"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "14cf593a-5fa0-44b3-990c-62ef1bba8116",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=67) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 67\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 202,
- "end_line": 205,
- "verbosity": 0
- },
- {
- "id": 11252,
- "type": "job_event",
- "url": "/api/v2/job_events/11252/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11252/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.479014Z",
- "modified": "2021-01-28T16:17:15.550420Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 72,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 68",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "68",
- "ansible_loop_var": "item",
- "_ansible_item_label": "68"
- },
- "uuid": "b85c1f52-7105-41ee-948e-932aa7b4c45f"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "b85c1f52-7105-41ee-948e-932aa7b4c45f",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=68) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 68\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 205,
- "end_line": 208,
- "verbosity": 0
- },
- {
- "id": 11255,
- "type": "job_event",
- "url": "/api/v2/job_events/11255/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11255/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.495141Z",
- "modified": "2021-01-28T16:17:15.595460Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 73,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 69",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "69",
- "ansible_loop_var": "item",
- "_ansible_item_label": "69"
- },
- "uuid": "a57c8dd7-2a10-4095-8905-0f184db59070"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "a57c8dd7-2a10-4095-8905-0f184db59070",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=69) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 69\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 208,
- "end_line": 211,
- "verbosity": 0
- },
- {
- "id": 11253,
- "type": "job_event",
- "url": "/api/v2/job_events/11253/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11253/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.515128Z",
- "modified": "2021-01-28T16:17:15.550420Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 74,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 70",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "70",
- "ansible_loop_var": "item",
- "_ansible_item_label": "70"
- },
- "uuid": "f7e0d839-440d-4d82-a6b9-c7c710de0075"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "f7e0d839-440d-4d82-a6b9-c7c710de0075",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=70) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 70\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 211,
- "end_line": 214,
- "verbosity": 0
- },
- {
- "id": 11262,
- "type": "job_event",
- "url": "/api/v2/job_events/11262/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11262/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.523839Z",
- "modified": "2021-01-28T16:17:15.626222Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 75,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 71",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "71",
- "ansible_loop_var": "item",
- "_ansible_item_label": "71"
- },
- "uuid": "463b89c6-5474-4cc8-a644-6fc6e8ad854f"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "463b89c6-5474-4cc8-a644-6fc6e8ad854f",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=71) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 71\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 214,
- "end_line": 217,
- "verbosity": 0
- },
- {
- "id": 11256,
- "type": "job_event",
- "url": "/api/v2/job_events/11256/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11256/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.532962Z",
- "modified": "2021-01-28T16:17:15.595460Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 76,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 72",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "72",
- "ansible_loop_var": "item",
- "_ansible_item_label": "72"
- },
- "uuid": "621d62b0-d17c-497b-8e94-7f5ad5a693db"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "621d62b0-d17c-497b-8e94-7f5ad5a693db",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=72) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 72\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 217,
- "end_line": 220,
- "verbosity": 0
- },
- {
- "id": 11259,
- "type": "job_event",
- "url": "/api/v2/job_events/11259/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11259/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.540592Z",
- "modified": "2021-01-28T16:17:15.608478Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 77,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 73",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "73",
- "ansible_loop_var": "item",
- "_ansible_item_label": "73"
- },
- "uuid": "1120ebd3-73dc-48c1-9494-32db35bc6329"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "1120ebd3-73dc-48c1-9494-32db35bc6329",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=73) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 73\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 220,
- "end_line": 223,
- "verbosity": 0
- },
- {
- "id": 11254,
- "type": "job_event",
- "url": "/api/v2/job_events/11254/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11254/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.546889Z",
- "modified": "2021-01-28T16:17:15.550420Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 78,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 74",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "74",
- "ansible_loop_var": "item",
- "_ansible_item_label": "74"
- },
- "uuid": "137f2774-8b29-43b7-bf59-b0a05608cdba"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "137f2774-8b29-43b7-bf59-b0a05608cdba",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=74) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 74\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 223,
- "end_line": 226,
- "verbosity": 0
- },
- {
- "id": 11263,
- "type": "job_event",
- "url": "/api/v2/job_events/11263/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11263/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.557229Z",
- "modified": "2021-01-28T16:17:15.626222Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 79,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 75",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "75",
- "ansible_loop_var": "item",
- "_ansible_item_label": "75"
- },
- "uuid": "ddf422a6-3282-44a6-8828-774fc80ee769"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "ddf422a6-3282-44a6-8828-774fc80ee769",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=75) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 75\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 226,
- "end_line": 229,
- "verbosity": 0
- },
- {
- "id": 11257,
- "type": "job_event",
- "url": "/api/v2/job_events/11257/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11257/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.564956Z",
- "modified": "2021-01-28T16:17:15.595460Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 80,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 76",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "76",
- "ansible_loop_var": "item",
- "_ansible_item_label": "76"
- },
- "uuid": "c787ae14-2a8d-45d7-b09a-da0dfc2aec19"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "c787ae14-2a8d-45d7-b09a-da0dfc2aec19",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=76) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 76\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 229,
- "end_line": 232,
- "verbosity": 0
- },
- {
- "id": 11260,
- "type": "job_event",
- "url": "/api/v2/job_events/11260/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11260/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.573282Z",
- "modified": "2021-01-28T16:17:15.608478Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 81,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 77",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "77",
- "ansible_loop_var": "item",
- "_ansible_item_label": "77"
- },
- "uuid": "06d26c46-04b8-49e3-8847-145fb3d1ec95"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "06d26c46-04b8-49e3-8847-145fb3d1ec95",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=77) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 77\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 232,
- "end_line": 235,
- "verbosity": 0
- },
- {
- "id": 11264,
- "type": "job_event",
- "url": "/api/v2/job_events/11264/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11264/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.581281Z",
- "modified": "2021-01-28T16:17:15.626222Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 82,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 78",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "78",
- "ansible_loop_var": "item",
- "_ansible_item_label": "78"
- },
- "uuid": "8c6a52f7-64c3-4146-9adc-0a050b3624e1"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "8c6a52f7-64c3-4146-9adc-0a050b3624e1",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=78) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 78\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 235,
- "end_line": 238,
- "verbosity": 0
- },
- {
- "id": 11258,
- "type": "job_event",
- "url": "/api/v2/job_events/11258/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11258/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.591277Z",
- "modified": "2021-01-28T16:17:15.595460Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 83,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 79",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "79",
- "ansible_loop_var": "item",
- "_ansible_item_label": "79"
- },
- "uuid": "3a4e3794-0959-475f-86d3-b2668e5de29e"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "3a4e3794-0959-475f-86d3-b2668e5de29e",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=79) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 79\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 238,
- "end_line": 241,
- "verbosity": 0
- },
- {
- "id": 11261,
- "type": "job_event",
- "url": "/api/v2/job_events/11261/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11261/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.602932Z",
- "modified": "2021-01-28T16:17:15.608478Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 84,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 80",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "80",
- "ansible_loop_var": "item",
- "_ansible_item_label": "80"
- },
- "uuid": "86f3c122-36af-468c-be14-5c231cadbfbf"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "86f3c122-36af-468c-be14-5c231cadbfbf",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=80) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 80\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 241,
- "end_line": 244,
- "verbosity": 0
- },
- {
- "id": 11265,
- "type": "job_event",
- "url": "/api/v2/job_events/11265/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11265/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.614756Z",
- "modified": "2021-01-28T16:17:15.626222Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 85,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 81",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "81",
- "ansible_loop_var": "item",
- "_ansible_item_label": "81"
- },
- "uuid": "eb3f8b55-afc5-4120-902b-e43fd857abb6"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "eb3f8b55-afc5-4120-902b-e43fd857abb6",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=81) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 81\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 244,
- "end_line": 247,
- "verbosity": 0
- },
- {
- "id": 11276,
- "type": "job_event",
- "url": "/api/v2/job_events/11276/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11276/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.627821Z",
- "modified": "2021-01-28T16:17:17.133034Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 86,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 82",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "82",
- "ansible_loop_var": "item",
- "_ansible_item_label": "82"
- },
- "uuid": "9c2900b1-f681-4c94-91c2-3c04e6482be6"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "9c2900b1-f681-4c94-91c2-3c04e6482be6",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=82) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 82\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 247,
- "end_line": 250,
- "verbosity": 0
- },
- {
- "id": 11273,
- "type": "job_event",
- "url": "/api/v2/job_events/11273/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11273/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.640647Z",
- "modified": "2021-01-28T16:17:16.774628Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 87,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 83",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "83",
- "ansible_loop_var": "item",
- "_ansible_item_label": "83"
- },
- "uuid": "9a469b7a-6d32-4822-acad-4f5c749f6613"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "9a469b7a-6d32-4822-acad-4f5c749f6613",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=83) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 83\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 250,
- "end_line": 253,
- "verbosity": 0
- },
- {
- "id": 11270,
- "type": "job_event",
- "url": "/api/v2/job_events/11270/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11270/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.653184Z",
- "modified": "2021-01-28T16:17:16.773081Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 88,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 84",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "84",
- "ansible_loop_var": "item",
- "_ansible_item_label": "84"
- },
- "uuid": "8f47c848-e82e-48cc-94cd-8ab35285fd26"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "8f47c848-e82e-48cc-94cd-8ab35285fd26",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=84) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 84\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 253,
- "end_line": 256,
- "verbosity": 0
- },
- {
- "id": 11266,
- "type": "job_event",
- "url": "/api/v2/job_events/11266/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11266/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.662214Z",
- "modified": "2021-01-28T16:17:15.853862Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 89,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 85",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "85",
- "ansible_loop_var": "item",
- "_ansible_item_label": "85"
- },
- "uuid": "9fc25ea6-dc68-4805-b0b1-2066f2e62d88"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "9fc25ea6-dc68-4805-b0b1-2066f2e62d88",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=85) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 85\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 256,
- "end_line": 259,
- "verbosity": 0
- },
- {
- "id": 11277,
- "type": "job_event",
- "url": "/api/v2/job_events/11277/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11277/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.676022Z",
- "modified": "2021-01-28T16:17:17.133034Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 90,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 86",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "86",
- "ansible_loop_var": "item",
- "_ansible_item_label": "86"
- },
- "uuid": "90bcf0b3-2284-4e82-9b5e-c138fb266c74"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "90bcf0b3-2284-4e82-9b5e-c138fb266c74",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=86) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 86\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 259,
- "end_line": 262,
- "verbosity": 0
- },
- {
- "id": 11278,
- "type": "job_event",
- "url": "/api/v2/job_events/11278/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11278/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.687264Z",
- "modified": "2021-01-28T16:17:17.133034Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 91,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 87",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "87",
- "ansible_loop_var": "item",
- "_ansible_item_label": "87"
- },
- "uuid": "bb68d101-bf92-41e1-873e-c9326274c29b"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "bb68d101-bf92-41e1-873e-c9326274c29b",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=87) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 87\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 262,
- "end_line": 265,
- "verbosity": 0
- },
- {
- "id": 11267,
- "type": "job_event",
- "url": "/api/v2/job_events/11267/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11267/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.695268Z",
- "modified": "2021-01-28T16:17:15.853862Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 92,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 88",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "88",
- "ansible_loop_var": "item",
- "_ansible_item_label": "88"
- },
- "uuid": "c64759bf-9998-4a35-911f-4daa96054c17"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "c64759bf-9998-4a35-911f-4daa96054c17",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=88) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 88\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 265,
- "end_line": 268,
- "verbosity": 0
- },
- {
- "id": 11279,
- "type": "job_event",
- "url": "/api/v2/job_events/11279/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11279/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.707388Z",
- "modified": "2021-01-28T16:17:17.133034Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 93,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 89",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "89",
- "ansible_loop_var": "item",
- "_ansible_item_label": "89"
- },
- "uuid": "574c8e2c-70f6-4489-9927-98a02e75789f"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "574c8e2c-70f6-4489-9927-98a02e75789f",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=89) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 89\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 268,
- "end_line": 271,
- "verbosity": 0
- },
- {
- "id": 11271,
- "type": "job_event",
- "url": "/api/v2/job_events/11271/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11271/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.717306Z",
- "modified": "2021-01-28T16:17:16.773081Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 94,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 90",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "90",
- "ansible_loop_var": "item",
- "_ansible_item_label": "90"
- },
- "uuid": "1d8fc847-30fb-40fe-affe-2c47eaa64258"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "1d8fc847-30fb-40fe-affe-2c47eaa64258",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=90) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 90\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 271,
- "end_line": 274,
- "verbosity": 0
- },
- {
- "id": 11274,
- "type": "job_event",
- "url": "/api/v2/job_events/11274/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11274/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.724962Z",
- "modified": "2021-01-28T16:17:16.774628Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 95,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 91",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "91",
- "ansible_loop_var": "item",
- "_ansible_item_label": "91"
- },
- "uuid": "cdf99527-31c5-42b1-9556-ca6d7f924bd0"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "cdf99527-31c5-42b1-9556-ca6d7f924bd0",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=91) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 91\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 274,
- "end_line": 277,
- "verbosity": 0
- },
- {
- "id": 11268,
- "type": "job_event",
- "url": "/api/v2/job_events/11268/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11268/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.737328Z",
- "modified": "2021-01-28T16:17:15.853862Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 96,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 92",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "92",
- "ansible_loop_var": "item",
- "_ansible_item_label": "92"
- },
- "uuid": "757726ee-aa7a-49b3-ac14-0001bcb86a81"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "757726ee-aa7a-49b3-ac14-0001bcb86a81",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=92) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 92\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 277,
- "end_line": 280,
- "verbosity": 0
- },
- {
- "id": 11280,
- "type": "job_event",
- "url": "/api/v2/job_events/11280/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11280/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.742581Z",
- "modified": "2021-01-28T16:17:17.133034Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 97,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 93",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "93",
- "ansible_loop_var": "item",
- "_ansible_item_label": "93"
- },
- "uuid": "c880cc54-1437-4962-9929-9bb2c9bf2cb8"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "c880cc54-1437-4962-9929-9bb2c9bf2cb8",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=93) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 93\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 280,
- "end_line": 283,
- "verbosity": 0
- },
- {
- "id": 11272,
- "type": "job_event",
- "url": "/api/v2/job_events/11272/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11272/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.749132Z",
- "modified": "2021-01-28T16:17:16.773081Z",
- "job": 8,
- "event": "runner_item_on_ok",
- "counter": 98,
- "event_display": "Item OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "res": {
- "msg": "This is a debug message: 94",
- "_ansible_verbose_always": true,
- "_ansible_no_log": false,
- "changed": false,
- "item": "94",
- "ansible_loop_var": "item",
- "_ansible_item_label": "94"
- },
- "uuid": "672f20bd-fa2d-4c2e-9875-3c9ff1dc25ea"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "672f20bd-fa2d-4c2e-9875-3c9ff1dc25ea",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "\u001b[0;32mok: [localhost] => (item=94) => {\u001b[0m\r\n\u001b[0;32m \"msg\": \"This is a debug message: 94\"\u001b[0m\r\n\u001b[0;32m}\u001b[0m",
- "start_line": 283,
- "end_line": 286,
- "verbosity": 0
- },
- {
- "id": 11269,
- "type": "job_event",
- "url": "/api/v2/job_events/11269/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11269/children/"
- },
- "summary_fields": {
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.763782Z",
- "modified": "2021-01-28T16:17:15.853862Z",
- "job": 8,
- "event": "playbook_on_stats",
- "counter": 100,
- "event_display": "Playbook Complete",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "changed": {},
- "dark": {},
- "failures": {},
- "ignored": {},
- "ok": {
- "localhost": 1
- },
- "processed": {
- "localhost": 1
- },
- "rescued": {},
- "skipped": {},
- "artifact_data": {},
- "uuid": "3ff2cd93-c05f-4418-8707-3e3fb8a517f1"
- },
- "event_level": 1,
- "failed": false,
- "changed": false,
- "uuid": "3ff2cd93-c05f-4418-8707-3e3fb8a517f1",
- "parent_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "host": null,
- "host_name": "",
- "playbook": "chatty_tasks.yml",
- "play": "",
- "task": "",
- "role": "",
- "stdout": "\r\nPLAY RECAP *********************************************************************\r\n\u001b[0;32mlocalhost\u001b[0m : \u001b[0;32mok=1 \u001b[0m changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 \r\n",
- "start_line": 286,
- "end_line": 290,
- "verbosity": 0
- },
- {
- "id": 11275,
- "type": "job_event",
- "url": "/api/v2/job_events/11275/",
- "related": {
- "job": "/api/v2/jobs/8/",
- "children": "/api/v2/job_events/11275/children/",
- "host": "/api/v2/hosts/1/"
- },
- "summary_fields": {
- "host": {
- "id": 1,
- "name": "localhost",
- "description": ""
- },
- "job": {
- "id": 8,
- "name": "chatty",
- "description": "",
- "status": "successful",
- "failed": false,
- "elapsed": 6.263,
- "type": "job",
- "job_template_id": 7,
- "job_template_name": "chatty"
- },
- "role": {}
- },
- "created": "2021-01-28T16:17:15.755721Z",
- "modified": "2021-01-28T16:17:16.774628Z",
- "job": 8,
- "event": "runner_on_ok",
- "counter": 99,
- "event_display": "Host OK",
- "event_data": {
- "playbook": "chatty_tasks.yml",
- "playbook_uuid": "a60e6549-f49b-401f-b2f0-b03557d3d1d7",
- "play": "all",
- "play_uuid": "0242ac12-0004-20a0-8d35-000000000006",
- "play_pattern": "all",
- "task": "debug",
- "task_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "task_action": "debug",
- "task_args": "",
- "task_path": "/tmp/awx_8_2sz9q_jd/project/chatty_tasks.yml:8",
- "host": "localhost",
- "remote_addr": "localhost",
- "res": {
- "results": [
- {
- "msg": "This is a debug message: 1",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "1",
- "ansible_loop_var": "item",
- "_ansible_item_label": "1"
- },
- {
- "msg": "This is a debug message: 2",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "2",
- "ansible_loop_var": "item",
- "_ansible_item_label": "2"
- },
- {
- "msg": "This is a debug message: 3",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "3",
- "ansible_loop_var": "item",
- "_ansible_item_label": "3"
- },
- {
- "msg": "This is a debug message: 4",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "4",
- "ansible_loop_var": "item",
- "_ansible_item_label": "4"
- },
- {
- "msg": "This is a debug message: 5",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "5",
- "ansible_loop_var": "item",
- "_ansible_item_label": "5"
- },
- {
- "msg": "This is a debug message: 6",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "6",
- "ansible_loop_var": "item",
- "_ansible_item_label": "6"
- },
- {
- "msg": "This is a debug message: 7",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "7",
- "ansible_loop_var": "item",
- "_ansible_item_label": "7"
- },
- {
- "msg": "This is a debug message: 8",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "8",
- "ansible_loop_var": "item",
- "_ansible_item_label": "8"
- },
- {
- "msg": "This is a debug message: 9",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "9",
- "ansible_loop_var": "item",
- "_ansible_item_label": "9"
- },
- {
- "msg": "This is a debug message: 10",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "10",
- "ansible_loop_var": "item",
- "_ansible_item_label": "10"
- },
- {
- "msg": "This is a debug message: 11",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "11",
- "ansible_loop_var": "item",
- "_ansible_item_label": "11"
- },
- {
- "msg": "This is a debug message: 12",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "12",
- "ansible_loop_var": "item",
- "_ansible_item_label": "12"
- },
- {
- "msg": "This is a debug message: 13",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "13",
- "ansible_loop_var": "item",
- "_ansible_item_label": "13"
- },
- {
- "msg": "This is a debug message: 14",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "14",
- "ansible_loop_var": "item",
- "_ansible_item_label": "14"
- },
- {
- "msg": "This is a debug message: 15",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "15",
- "ansible_loop_var": "item",
- "_ansible_item_label": "15"
- },
- {
- "msg": "This is a debug message: 16",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "16",
- "ansible_loop_var": "item",
- "_ansible_item_label": "16"
- },
- {
- "msg": "This is a debug message: 17",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "17",
- "ansible_loop_var": "item",
- "_ansible_item_label": "17"
- },
- {
- "msg": "This is a debug message: 18",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "18",
- "ansible_loop_var": "item",
- "_ansible_item_label": "18"
- },
- {
- "msg": "This is a debug message: 19",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "19",
- "ansible_loop_var": "item",
- "_ansible_item_label": "19"
- },
- {
- "msg": "This is a debug message: 20",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "20",
- "ansible_loop_var": "item",
- "_ansible_item_label": "20"
- },
- {
- "msg": "This is a debug message: 21",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "21",
- "ansible_loop_var": "item",
- "_ansible_item_label": "21"
- },
- {
- "msg": "This is a debug message: 22",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "22",
- "ansible_loop_var": "item",
- "_ansible_item_label": "22"
- },
- {
- "msg": "This is a debug message: 23",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "23",
- "ansible_loop_var": "item",
- "_ansible_item_label": "23"
- },
- {
- "msg": "This is a debug message: 24",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "24",
- "ansible_loop_var": "item",
- "_ansible_item_label": "24"
- },
- {
- "msg": "This is a debug message: 25",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "25",
- "ansible_loop_var": "item",
- "_ansible_item_label": "25"
- },
- {
- "msg": "This is a debug message: 26",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "26",
- "ansible_loop_var": "item",
- "_ansible_item_label": "26"
- },
- {
- "msg": "This is a debug message: 27",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "27",
- "ansible_loop_var": "item",
- "_ansible_item_label": "27"
- },
- {
- "msg": "This is a debug message: 28",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "28",
- "ansible_loop_var": "item",
- "_ansible_item_label": "28"
- },
- {
- "msg": "This is a debug message: 29",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "29",
- "ansible_loop_var": "item",
- "_ansible_item_label": "29"
- },
- {
- "msg": "This is a debug message: 30",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "30",
- "ansible_loop_var": "item",
- "_ansible_item_label": "30"
- },
- {
- "msg": "This is a debug message: 31",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "31",
- "ansible_loop_var": "item",
- "_ansible_item_label": "31"
- },
- {
- "msg": "This is a debug message: 32",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "32",
- "ansible_loop_var": "item",
- "_ansible_item_label": "32"
- },
- {
- "msg": "This is a debug message: 33",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "33",
- "ansible_loop_var": "item",
- "_ansible_item_label": "33"
- },
- {
- "msg": "This is a debug message: 34",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "34",
- "ansible_loop_var": "item",
- "_ansible_item_label": "34"
- },
- {
- "msg": "This is a debug message: 35",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "35",
- "ansible_loop_var": "item",
- "_ansible_item_label": "35"
- },
- {
- "msg": "This is a debug message: 36",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "36",
- "ansible_loop_var": "item",
- "_ansible_item_label": "36"
- },
- {
- "msg": "This is a debug message: 37",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "37",
- "ansible_loop_var": "item",
- "_ansible_item_label": "37"
- },
- {
- "msg": "This is a debug message: 38",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "38",
- "ansible_loop_var": "item",
- "_ansible_item_label": "38"
- },
- {
- "msg": "This is a debug message: 39",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "39",
- "ansible_loop_var": "item",
- "_ansible_item_label": "39"
- },
- {
- "msg": "This is a debug message: 40",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "40",
- "ansible_loop_var": "item",
- "_ansible_item_label": "40"
- },
- {
- "msg": "This is a debug message: 41",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "41",
- "ansible_loop_var": "item",
- "_ansible_item_label": "41"
- },
- {
- "msg": "This is a debug message: 42",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "42",
- "ansible_loop_var": "item",
- "_ansible_item_label": "42"
- },
- {
- "msg": "This is a debug message: 43",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "43",
- "ansible_loop_var": "item",
- "_ansible_item_label": "43"
- },
- {
- "msg": "This is a debug message: 44",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "44",
- "ansible_loop_var": "item",
- "_ansible_item_label": "44"
- },
- {
- "msg": "This is a debug message: 45",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "45",
- "ansible_loop_var": "item",
- "_ansible_item_label": "45"
- },
- {
- "msg": "This is a debug message: 46",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "46",
- "ansible_loop_var": "item",
- "_ansible_item_label": "46"
- },
- {
- "msg": "This is a debug message: 47",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "47",
- "ansible_loop_var": "item",
- "_ansible_item_label": "47"
- },
- {
- "msg": "This is a debug message: 48",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "48",
- "ansible_loop_var": "item",
- "_ansible_item_label": "48"
- },
- {
- "msg": "This is a debug message: 49",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "49",
- "ansible_loop_var": "item",
- "_ansible_item_label": "49"
- },
- {
- "msg": "This is a debug message: 50",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "50",
- "ansible_loop_var": "item",
- "_ansible_item_label": "50"
- },
- {
- "msg": "This is a debug message: 51",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "51",
- "ansible_loop_var": "item",
- "_ansible_item_label": "51"
- },
- {
- "msg": "This is a debug message: 52",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "52",
- "ansible_loop_var": "item",
- "_ansible_item_label": "52"
- },
- {
- "msg": "This is a debug message: 53",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "53",
- "ansible_loop_var": "item",
- "_ansible_item_label": "53"
- },
- {
- "msg": "This is a debug message: 54",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "54",
- "ansible_loop_var": "item",
- "_ansible_item_label": "54"
- },
- {
- "msg": "This is a debug message: 55",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "55",
- "ansible_loop_var": "item",
- "_ansible_item_label": "55"
- },
- {
- "msg": "This is a debug message: 56",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "56",
- "ansible_loop_var": "item",
- "_ansible_item_label": "56"
- },
- {
- "msg": "This is a debug message: 57",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "57",
- "ansible_loop_var": "item",
- "_ansible_item_label": "57"
- },
- {
- "msg": "This is a debug message: 58",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "58",
- "ansible_loop_var": "item",
- "_ansible_item_label": "58"
- },
- {
- "msg": "This is a debug message: 59",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "59",
- "ansible_loop_var": "item",
- "_ansible_item_label": "59"
- },
- {
- "msg": "This is a debug message: 60",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "60",
- "ansible_loop_var": "item",
- "_ansible_item_label": "60"
- },
- {
- "msg": "This is a debug message: 61",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "61",
- "ansible_loop_var": "item",
- "_ansible_item_label": "61"
- },
- {
- "msg": "This is a debug message: 62",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "62",
- "ansible_loop_var": "item",
- "_ansible_item_label": "62"
- },
- {
- "msg": "This is a debug message: 63",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "63",
- "ansible_loop_var": "item",
- "_ansible_item_label": "63"
- },
- {
- "msg": "This is a debug message: 64",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "64",
- "ansible_loop_var": "item",
- "_ansible_item_label": "64"
- },
- {
- "msg": "This is a debug message: 65",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "65",
- "ansible_loop_var": "item",
- "_ansible_item_label": "65"
- },
- {
- "msg": "This is a debug message: 66",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "66",
- "ansible_loop_var": "item",
- "_ansible_item_label": "66"
- },
- {
- "msg": "This is a debug message: 67",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "67",
- "ansible_loop_var": "item",
- "_ansible_item_label": "67"
- },
- {
- "msg": "This is a debug message: 68",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "68",
- "ansible_loop_var": "item",
- "_ansible_item_label": "68"
- },
- {
- "msg": "This is a debug message: 69",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "69",
- "ansible_loop_var": "item",
- "_ansible_item_label": "69"
- },
- {
- "msg": "This is a debug message: 70",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "70",
- "ansible_loop_var": "item",
- "_ansible_item_label": "70"
- },
- {
- "msg": "This is a debug message: 71",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "71",
- "ansible_loop_var": "item",
- "_ansible_item_label": "71"
- },
- {
- "msg": "This is a debug message: 72",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "72",
- "ansible_loop_var": "item",
- "_ansible_item_label": "72"
- },
- {
- "msg": "This is a debug message: 73",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "73",
- "ansible_loop_var": "item",
- "_ansible_item_label": "73"
- },
- {
- "msg": "This is a debug message: 74",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "74",
- "ansible_loop_var": "item",
- "_ansible_item_label": "74"
- },
- {
- "msg": "This is a debug message: 75",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "75",
- "ansible_loop_var": "item",
- "_ansible_item_label": "75"
- },
- {
- "msg": "This is a debug message: 76",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "76",
- "ansible_loop_var": "item",
- "_ansible_item_label": "76"
- },
- {
- "msg": "This is a debug message: 77",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "77",
- "ansible_loop_var": "item",
- "_ansible_item_label": "77"
- },
- {
- "msg": "This is a debug message: 78",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "78",
- "ansible_loop_var": "item",
- "_ansible_item_label": "78"
- },
- {
- "msg": "This is a debug message: 79",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "79",
- "ansible_loop_var": "item",
- "_ansible_item_label": "79"
- },
- {
- "msg": "This is a debug message: 80",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "80",
- "ansible_loop_var": "item",
- "_ansible_item_label": "80"
- },
- {
- "msg": "This is a debug message: 81",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "81",
- "ansible_loop_var": "item",
- "_ansible_item_label": "81"
- },
- {
- "msg": "This is a debug message: 82",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "82",
- "ansible_loop_var": "item",
- "_ansible_item_label": "82"
- },
- {
- "msg": "This is a debug message: 83",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "83",
- "ansible_loop_var": "item",
- "_ansible_item_label": "83"
- },
- {
- "msg": "This is a debug message: 84",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "84",
- "ansible_loop_var": "item",
- "_ansible_item_label": "84"
- },
- {
- "msg": "This is a debug message: 85",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "85",
- "ansible_loop_var": "item",
- "_ansible_item_label": "85"
- },
- {
- "msg": "This is a debug message: 86",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "86",
- "ansible_loop_var": "item",
- "_ansible_item_label": "86"
- },
- {
- "msg": "This is a debug message: 87",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "87",
- "ansible_loop_var": "item",
- "_ansible_item_label": "87"
- },
- {
- "msg": "This is a debug message: 88",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "88",
- "ansible_loop_var": "item",
- "_ansible_item_label": "88"
- },
- {
- "msg": "This is a debug message: 89",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "89",
- "ansible_loop_var": "item",
- "_ansible_item_label": "89"
- },
- {
- "msg": "This is a debug message: 90",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "90",
- "ansible_loop_var": "item",
- "_ansible_item_label": "90"
- },
- {
- "msg": "This is a debug message: 91",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "91",
- "ansible_loop_var": "item",
- "_ansible_item_label": "91"
- },
- {
- "msg": "This is a debug message: 92",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "92",
- "ansible_loop_var": "item",
- "_ansible_item_label": "92"
- },
- {
- "msg": "This is a debug message: 93",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "93",
- "ansible_loop_var": "item",
- "_ansible_item_label": "93"
- },
- {
- "msg": "This is a debug message: 94",
- "_ansible_verbose_always": true,
- "failed": false,
- "_ansible_no_log": false,
- "changed": false,
- "item": "94",
- "ansible_loop_var": "item",
- "_ansible_item_label": "94"
- }
- ],
- "msg": "All items completed",
- "changed": false
- },
- "start": "2021-01-28T16:17:13.938977",
- "end": "2021-01-28T16:17:15.755413",
- "duration": 1.816436,
- "event_loop": "sequence",
- "uuid": "c1e7d3ff-9a53-4ae1-aecf-c1faa4a411c0"
- },
- "event_level": 3,
- "failed": false,
- "changed": false,
- "uuid": "c1e7d3ff-9a53-4ae1-aecf-c1faa4a411c0",
- "parent_uuid": "0242ac12-0004-20a0-8d35-000000000008",
- "host": 1,
- "host_name": "localhost",
- "playbook": "chatty_tasks.yml",
- "play": "all",
- "task": "debug",
- "role": "",
- "stdout": "",
- "start_line": 286,
- "end_line": 286,
- "verbosity": 0
- }
- ]
-}
diff --git a/awx/ui/src/screens/Job/JobOutput/getEventRequestParams.js b/awx/ui/src/screens/Job/JobOutput/getEventRequestParams.js
deleted file mode 100644
index 2555f598ec3c..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/getEventRequestParams.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import getRowRangePageSize from './shared/jobOutputUtils';
-
-export default function getEventRequestParams(
- job,
- remoteRowCount,
- requestRange
-) {
- const [startIndex, stopIndex] = requestRange;
- const { page, pageSize, firstIndex } = getRowRangePageSize(
- startIndex,
- stopIndex
- );
- const loadRange = range(
- firstIndex + 1,
- Math.min(firstIndex + pageSize, remoteRowCount)
- );
-
- return [{ page, page_size: pageSize }, loadRange];
-}
-
-export function range(low, high) {
- const numbers = [];
- for (let n = low; n <= high; n++) {
- numbers.push(n);
- }
- return numbers;
-}
diff --git a/awx/ui/src/screens/Job/JobOutput/getEventRequestParams.test.js b/awx/ui/src/screens/Job/JobOutput/getEventRequestParams.test.js
deleted file mode 100644
index 7ccecc7e37f0..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/getEventRequestParams.test.js
+++ /dev/null
@@ -1,57 +0,0 @@
-import getEventRequestParams, { range } from './getEventRequestParams';
-
-describe('getEventRequestParams', () => {
- const job = {
- status: 'successful',
- };
-
- it('should return first page', () => {
- const [params, loadRange] = getEventRequestParams(job, 50, [1, 50]);
-
- expect(params).toEqual({
- page: 1,
- page_size: 50,
- });
- expect(loadRange).toEqual(range(1, 50));
- });
-
- it('should return second page', () => {
- const [params, loadRange] = getEventRequestParams(job, 1000, [51, 100]);
-
- expect(params).toEqual({
- page: 2,
- page_size: 50,
- });
- expect(loadRange).toEqual(range(51, 100));
- });
-
- it('should return page for first portion of requested range', () => {
- const [params, loadRange] = getEventRequestParams(job, 1000, [75, 125]);
-
- expect(params).toEqual({
- page: 2,
- page_size: 50,
- });
- expect(loadRange).toEqual(range(51, 100));
- });
-
- it('should return smaller page for shorter range', () => {
- const [params, loadRange] = getEventRequestParams(job, 1000, [120, 125]);
-
- expect(params).toEqual({
- page: 21,
- page_size: 6,
- });
- expect(loadRange).toEqual(range(121, 126));
- });
-
- it('should return last event only', () => {
- const [params, loadRange] = getEventRequestParams(job, 72, [72, 72]);
-
- expect(params).toEqual({
- page: 72,
- page_size: 1,
- });
- expect(loadRange).toEqual(range(72, 72));
- });
-});
diff --git a/awx/ui/src/screens/Job/JobOutput/getLineTextHtml.js b/awx/ui/src/screens/Job/JobOutput/getLineTextHtml.js
deleted file mode 100644
index 4a96924f59df..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/getLineTextHtml.js
+++ /dev/null
@@ -1,107 +0,0 @@
-import Ansi from 'ansi-to-html';
-import hasAnsi from 'has-ansi';
-import { encode } from 'html-entities';
-
-const EVENT_START_TASK = 'playbook_on_task_start';
-const EVENT_START_PLAY = 'playbook_on_play_start';
-const EVENT_STATS_PLAY = 'playbook_on_stats';
-const TIME_EVENTS = [EVENT_START_TASK, EVENT_START_PLAY, EVENT_STATS_PLAY];
-
-const ansi = new Ansi({
- stream: true,
- colors: {
- 0: '#000',
- 1: '#A30000',
- 2: '#486B00',
- 3: '#795600',
- 4: '#00A',
- 5: '#A0A',
- 6: '#004368',
- 7: '#AAA',
- 8: '#555',
- 9: '#F55',
- 10: '#5F5',
- 11: '#FF5',
- 12: '#55F',
- 13: '#F5F',
- 14: '#5FF',
- 15: '#FFF',
- },
-});
-
-function getTimestamp({ created }) {
- const date = new Date(created);
-
- const dateHours = date.getHours();
- const dateMinutes = date.getMinutes();
- const dateSeconds = date.getSeconds();
-
- const stampHours = dateHours < 10 ? `0${dateHours}` : dateHours;
- const stampMinutes = dateMinutes < 10 ? `0${dateMinutes}` : dateMinutes;
- const stampSeconds = dateSeconds < 10 ? `0${dateSeconds}` : dateSeconds;
-
- return `${stampHours}:${stampMinutes}:${stampSeconds}`;
-}
-
-function createStyleAttrHash(styleAttr) {
- let hash = 0;
- for (let i = 0; i < styleAttr.length; i++) {
- hash = (hash << 5) - hash; // eslint-disable-line no-bitwise
- hash += styleAttr.charCodeAt(i);
- hash &= hash; // eslint-disable-line no-bitwise
- }
- return `${hash}`;
-}
-
-const styleAttrPattern = new RegExp('style="[^"]*"', 'g');
-
-function replaceStyleAttrs(html) {
- const allStyleAttrs = [...new Set(html.match(styleAttrPattern))];
- const cssMap = {};
- let result = html;
- for (let i = 0; i < allStyleAttrs.length; i++) {
- const styleAttr = allStyleAttrs[i];
- const cssClassName = `output-${createStyleAttrHash(styleAttr)}`;
-
- cssMap[cssClassName] = styleAttr.replace('style="', '').slice(0, -1);
- result = result.split(styleAttr).join(`class="${cssClassName}"`);
- }
- return { cssMap, result };
-}
-
-export default function getLineTextHtml({
- created,
- event,
- start_line: startLine,
- stdout,
-}) {
- const sanitized = encode(stdout);
- let lineCssMap = {};
- const lineTextHtml = [];
-
- sanitized.split('\r\n').forEach((lineText, index) => {
- let html;
- if (hasAnsi(lineText)) {
- const { cssMap, result } = replaceStyleAttrs(ansi.toHtml(lineText));
- html = result;
- lineCssMap = { ...lineCssMap, ...cssMap };
- } else {
- html = lineText;
- }
-
- if (index === 1 && TIME_EVENTS.includes(event)) {
- const time = getTimestamp({ created });
- html += `${time}`;
- }
-
- lineTextHtml.push({
- lineNumber: startLine + index,
- html,
- });
- });
-
- return {
- lineCssMap,
- lineTextHtml,
- };
-}
diff --git a/awx/ui/src/screens/Job/JobOutput/index.js b/awx/ui/src/screens/Job/JobOutput/index.js
deleted file mode 100644
index 26d1b68b8259..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './JobOutput';
diff --git a/awx/ui/src/screens/Job/JobOutput/isHostEvent.js b/awx/ui/src/screens/Job/JobOutput/isHostEvent.js
deleted file mode 100644
index 30922b626fd9..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/isHostEvent.js
+++ /dev/null
@@ -1,16 +0,0 @@
-export default function isHostEvent(jobEvent) {
- const { event, event_data, host, type } = jobEvent;
- let isHost;
- if (typeof host === 'number' || (event_data && event_data.res)) {
- isHost = true;
- } else if (
- type === 'project_update_event' &&
- event !== 'runner_on_skipped' &&
- event_data.host
- ) {
- isHost = true;
- } else {
- isHost = false;
- }
- return isHost;
-}
diff --git a/awx/ui/src/screens/Job/JobOutput/loadJobEvents.js b/awx/ui/src/screens/Job/JobOutput/loadJobEvents.js
deleted file mode 100644
index 1dd59c607fa5..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/loadJobEvents.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import { getJobModel } from 'util/jobs';
-
-export async function fetchCount(job, params) {
- const {
- data: { results: lastEvents = [] },
- } = await getJobModel(job.type).readEvents(job.id, {
- ...params,
- order_by: '-counter',
- limit: 1,
- });
- return lastEvents.length >= 1 ? lastEvents[0].counter : 0;
-}
-
-export function prependTraceback(job, events) {
- let countOffset = 0;
- if (!job?.result_traceback) {
- return {
- events,
- countOffset,
- };
- }
-
- const tracebackEvent = {
- counter: 1,
- created: null,
- event: null,
- type: null,
- stdout: job?.result_traceback,
- start_line: 0,
- };
- const firstIndex = events.findIndex((jobEvent) => jobEvent.counter === 1);
- if (firstIndex > -1) {
- if (!events[firstIndex].stdout) {
- events[firstIndex].isTracebackOnly = true;
- }
- const stdoutLines = events[firstIndex].stdout?.split('\r\n') || [];
- stdoutLines[0] = tracebackEvent.stdout;
- events[firstIndex].stdout = stdoutLines.join('\r\n');
- } else {
- countOffset += 1;
- events.unshift(tracebackEvent);
- }
-
- return {
- events,
- countOffset,
- };
-}
diff --git a/awx/ui/src/screens/Job/JobOutput/shared/HostStatusBar.js b/awx/ui/src/screens/Job/JobOutput/shared/HostStatusBar.js
deleted file mode 100644
index b4702dd3c76c..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/shared/HostStatusBar.js
+++ /dev/null
@@ -1,87 +0,0 @@
-import React from 'react';
-import styled from 'styled-components';
-
-import { t } from '@lingui/macro';
-import { Badge, Tooltip } from '@patternfly/react-core';
-
-const BarWrapper = styled.div`
- background-color: #d7d7d7;
- display: flex;
- height: 5px;
- margin-top: 16px;
- width: 100%;
-`;
-
-const BarSegment = styled.div`
- background-color: ${(props) => props.color || 'inherit'};
- flex-grow: ${(props) => props.count || 0};
-`;
-BarSegment.displayName = 'BarSegment';
-
-const TooltipContent = styled.div`
- align-items: center;
- display: flex;
-
- span.pf-c-badge {
- margin-left: 10px;
- }
-`;
-
-const HostStatusBar = ({ counts = {} }) => {
- const noData = Object.keys(counts).length === 0;
- const hostStatus = {
- ok: {
- color: '#4CB140',
- label: t`OK`,
- },
- skipped: {
- color: '#73BCF7',
- label: t`Skipped`,
- },
- changed: {
- color: '#F0AB00',
- label: t`Changed`,
- },
- failures: {
- color: '#C9190B',
- label: t`Failed`,
- },
- dark: {
- color: '#8F4700',
- label: t`Unreachable`,
- },
- };
-
- const barSegments = Object.keys(hostStatus).map((key) => {
- const count = counts[key] || 0;
- return (
-
- {hostStatus[key].label}
- {count}
-
- }
- >
-
-
- );
- });
-
- if (noData) {
- return (
-
-
-
-
-
- );
- }
-
- return {barSegments};
-};
-
-export default HostStatusBar;
diff --git a/awx/ui/src/screens/Job/JobOutput/shared/HostStatusBar.test.js b/awx/ui/src/screens/Job/JobOutput/shared/HostStatusBar.test.js
deleted file mode 100644
index 623557126f6c..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/shared/HostStatusBar.test.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
-import { HostStatusBar } from '.';
-
-describe('', () => {
- let wrapper;
- const mockCounts = {
- ok: 5,
- skipped: 1,
- };
-
- beforeEach(() => {
- wrapper = mountWithContexts();
- });
-
- test('initially renders without crashing', () => {
- expect(wrapper.length).toBe(1);
- });
-
- test('should render five bar segments', () => {
- expect(wrapper.find('BarSegment').length).toBe(5);
- });
-
- test('tooltips should display host status and count', () => {
- const tooltips = wrapper.find('TooltipContent');
- const expectedContent = [
- { label: 'OK', count: 5 },
- { label: 'Skipped', count: 1 },
- { label: 'Changed', count: 0 },
- { label: 'Failed', count: 0 },
- { label: 'Unreachable', count: 0 },
- ];
-
- tooltips.forEach((tooltip, index) => {
- expect(tooltip.text()).toEqual(
- `${expectedContent[index].label}${expectedContent[index].count}`
- );
- });
- });
-
- test('empty host counts should display tooltip and one bar segment', () => {
- wrapper = mountWithContexts();
- expect(wrapper.find('BarSegment').length).toBe(1);
- expect(wrapper.find('Tooltip').prop('content')).toEqual(
- 'Host status information for this job is unavailable.'
- );
- });
-});
diff --git a/awx/ui/src/screens/Job/JobOutput/shared/JobEventEllipsis.js b/awx/ui/src/screens/Job/JobOutput/shared/JobEventEllipsis.js
deleted file mode 100644
index bfff0c8f2770..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/shared/JobEventEllipsis.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import React from 'react';
-import styled from 'styled-components';
-
-const Wrapper = styled.div`
- border-radius: 1em;
- background-color: var(--pf-global--BackgroundColor--light-200);
- font-size: 0.6rem;
- width: max-content;
- padding: 0em 1em;
- margin-left: auto;
- margin-right: -0.3em;
-`;
-
-export default function JobEventEllipsis({ isCollapsed }) {
- if (!isCollapsed) {
- return null;
- }
-
- return ...;
-}
diff --git a/awx/ui/src/screens/Job/JobOutput/shared/JobEventLine.js b/awx/ui/src/screens/Job/JobOutput/shared/JobEventLine.js
deleted file mode 100644
index a227cdfad032..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/shared/JobEventLine.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import styled from 'styled-components';
-
-export default styled.div`
- display: flex;
-
- &:hover {
- cursor: ${(props) => (props.isClickable ? 'pointer' : 'default')};
- }
-
- &--hidden {
- display: none;
- }
- ${({ isFirst }) => (isFirst ? 'padding-top: 10px;' : '')}
-`;
diff --git a/awx/ui/src/screens/Job/JobOutput/shared/JobEventLineNumber.js b/awx/ui/src/screens/Job/JobOutput/shared/JobEventLineNumber.js
deleted file mode 100644
index e724ddb59b9a..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/shared/JobEventLineNumber.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import styled from 'styled-components';
-
-const JobEventLineNumber = styled.div`
- color: #161b1f;
- background-color: #ebebeb;
- flex: 0 0 45px;
- text-align: right;
- vertical-align: top;
- padding-right: 5px;
- border-right: 1px solid #d7d7d7;
- user-select: none;
-`;
-
-JobEventLineNumber.displayName = 'JobEventLineNumber';
-
-export default JobEventLineNumber;
diff --git a/awx/ui/src/screens/Job/JobOutput/shared/JobEventLineText.js b/awx/ui/src/screens/Job/JobOutput/shared/JobEventLineText.js
deleted file mode 100644
index 50c7996deeb1..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/shared/JobEventLineText.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import styled from 'styled-components';
-
-const JobEventLineText = styled.div`
- padding: 0 15px;
- white-space: pre-wrap;
- word-break: break-all;
- word-wrap: break-word;
-
- .time {
- font-size: 14px;
- font-weight: 600;
- user-select: none;
- background-color: #ebebeb;
- border-radius: 12px;
- padding: 2px 10px;
- margin-left: 15px;
- }
-
- .content {
- background: var(--pf-global--disabled-color--200);
- background: linear-gradient(
- to right,
- #f5f5f5 10%,
- #e8e8e8 18%,
- #f5f5f5 33%
- );
- border-radius: 5px;
- }
-`;
-
-JobEventLineText.displayName = 'JobEventLineText';
-
-export default JobEventLineText;
diff --git a/awx/ui/src/screens/Job/JobOutput/shared/JobEventLineToggle.js b/awx/ui/src/screens/Job/JobOutput/shared/JobEventLineToggle.js
deleted file mode 100644
index 41633b118eae..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/shared/JobEventLineToggle.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import React from 'react';
-import styled from 'styled-components';
-import { t } from '@lingui/macro';
-import { AngleDownIcon, AngleRightIcon } from '@patternfly/react-icons';
-
-const Wrapper = styled.div`
- background-color: #ebebeb;
- color: #646972;
- display: flex;
- flex: 0 0 30px;
- font-size: 18px;
- justify-content: center;
- line-height: 12px;
- user-select: none;
-`;
-
-const Button = styled.button`
- align-self: flex-start;
- border: 0;
- padding: 2px;
- background: transparent;
- line-height: 1;
-`;
-
-export default function JobEventLineToggle({
- canToggle,
- isCollapsed,
- onToggle,
-}) {
- if (!canToggle) {
- return ;
- }
- return (
-
-
-
- );
-}
diff --git a/awx/ui/src/screens/Job/JobOutput/shared/OutputToolbar.js b/awx/ui/src/screens/Job/JobOutput/shared/OutputToolbar.js
deleted file mode 100644
index ee16e6c671b9..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/shared/OutputToolbar.js
+++ /dev/null
@@ -1,243 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import styled from 'styled-components';
-import { DateTime, Duration } from 'luxon';
-import { t } from '@lingui/macro';
-import { bool, shape, func } from 'prop-types';
-import {
- DownloadIcon,
- RocketIcon,
- TrashAltIcon,
-} from '@patternfly/react-icons';
-import { Badge as PFBadge, Button, Tooltip } from '@patternfly/react-core';
-import DeleteButton from 'components/DeleteButton';
-import { LaunchButton, ReLaunchDropDown } from 'components/LaunchButton';
-import { useConfig } from 'contexts/Config';
-
-import JobCancelButton from 'components/JobCancelButton';
-
-const BadgeGroup = styled.div`
- margin-left: 20px;
- height: 18px;
- display: inline-flex;
-`;
-
-const Badge = styled(PFBadge)`
- align-items: center;
- display: flex;
- justify-content: center;
- margin-left: 10px;
- ${(props) =>
- props.color
- ? `
- background-color: ${props.color}
- color: white;
- `
- : null}
-`;
-
-const Wrapper = styled.div`
- align-items: center;
- display: flex;
- flex-flow: row wrap;
- font-size: 14px;
-`;
-const calculateElapsed = (started) => {
- const now = DateTime.now();
- const duration = now
- .diff(DateTime.fromISO(`${started}`), [
- 'milliseconds',
- 'seconds',
- 'minutes',
- 'hours',
- ])
- .toObject();
-
- return Duration.fromObject({ ...duration }).toFormat('hh:mm:ss');
-};
-
-const OUTPUT_NO_COUNT_JOB_TYPES = [
- 'ad_hoc_command',
- 'system_job',
- 'inventory_update',
-];
-
-const OutputToolbar = ({ job, onDelete, isDeleteDisabled, jobStatus }) => {
- const [activeJobElapsedTime, setActiveJobElapsedTime] = useState('00:00:00');
- const hideCounts = OUTPUT_NO_COUNT_JOB_TYPES.includes(job.type);
-
- const playCount = job?.playbook_counts?.play_count;
- const taskCount = job?.playbook_counts?.task_count;
- const darkCount = job?.host_status_counts?.dark;
- const failureCount = job?.host_status_counts?.failures;
- const totalHostCount = job?.host_status_counts
- ? Object.keys(job.host_status_counts || {}).reduce(
- (sum, key) => sum + job.host_status_counts[key],
- 0
- )
- : 0;
- const { me } = useConfig();
-
- useEffect(() => {
- let secTimer;
- if (job.finished) {
- return () => clearInterval(secTimer);
- }
-
- secTimer = setInterval(() => {
- const elapsedTime = calculateElapsed(job.started);
- setActiveJobElapsedTime(elapsedTime);
- }, 1000);
-
- return () => clearInterval(secTimer);
- }, [job.started, job.finished]);
-
- return (
-
- {!hideCounts && (
- <>
- {playCount > 0 && (
-
- {t`Plays`}
- {playCount}
-
- )}
- {taskCount > 0 && (
-
- {t`Tasks`}
- {taskCount}
-
- )}
- {totalHostCount > 0 && (
-
- {t`Hosts`}
- {totalHostCount}
-
- )}
- {darkCount > 0 && (
-
- {t`Unreachable`}
-
-
- {darkCount}
-
-
-
- )}
- {failureCount > 0 && (
-
- {t`Failed`}
-
-
- {failureCount}
-
-
-
- )}
- >
- )}
-
-
- {t`Elapsed`}
-
-
- {job.finished
- ? Duration.fromObject({ seconds: job.elapsed }).toFormat(
- 'hh:mm:ss'
- )
- : activeJobElapsedTime}
-
-
-
- {['pending', 'waiting', 'running'].includes(jobStatus) &&
- (job.type === 'system_job'
- ? me.is_superuser
- : job?.summary_fields?.user_capabilities?.start) && (
-
- )}
- {job.summary_fields.user_capabilities?.start && (
-
- {job.status === 'failed' && job.type === 'job' ? (
-
- {({ handleRelaunch, isLaunching }) => (
-
- )}
-
- ) : (
-
- {({ handleRelaunch, isLaunching }) => (
-
- )}
-
- )}
-
- )}
-
- {job.related?.stdout && (
-
-
-
-
-
- )}
- {job.summary_fields.user_capabilities.delete &&
- ['new', 'successful', 'failed', 'error', 'canceled'].includes(
- jobStatus
- ) && (
-
-
-
-
-
- )}
-
- );
-};
-
-OutputToolbar.propTypes = {
- isDeleteDisabled: bool,
- job: shape({}).isRequired,
- onDelete: func.isRequired,
-};
-
-OutputToolbar.defaultProps = {
- isDeleteDisabled: false,
-};
-
-export default OutputToolbar;
diff --git a/awx/ui/src/screens/Job/JobOutput/shared/OutputToolbar.test.js b/awx/ui/src/screens/Job/JobOutput/shared/OutputToolbar.test.js
deleted file mode 100644
index a1cbc4cafb2e..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/shared/OutputToolbar.test.js
+++ /dev/null
@@ -1,123 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
-import { OutputToolbar } from '.';
-import mockJobData from '../../shared/data.job.json';
-
-describe('', () => {
- let wrapper;
-
- beforeEach(() => {
- wrapper = mountWithContexts(
- {}}
- />
- );
- });
-
- test('initially renders without crashing', () => {
- expect(wrapper.length).toBe(1);
- });
-
- test('should hide badge counts based on job type', () => {
- wrapper = mountWithContexts(
- {}}
- />
- );
- expect(wrapper.find('div[aria-label="Play Count"]').length).toBe(0);
- expect(wrapper.find('div[aria-label="Task Count"]').length).toBe(0);
- expect(wrapper.find('div[aria-label="Host Count"]').length).toBe(0);
- expect(
- wrapper.find('div[aria-label="Unreachable Host Count"]').length
- ).toBe(0);
- expect(wrapper.find('div[aria-label="Failed Host Count"]').length).toBe(0);
- expect(wrapper.find('div[aria-label="Elapsed Time"]').length).toBe(1);
- });
-
- test('should hide badge if count is equal to zero', () => {
- wrapper = mountWithContexts(
- {}}
- />
- );
-
- expect(wrapper.find('div[aria-label="Play Count"]').length).toBe(0);
- expect(wrapper.find('div[aria-label="Task Count"]').length).toBe(0);
- expect(wrapper.find('div[aria-label="Host Count"]').length).toBe(0);
- expect(
- wrapper.find('div[aria-label="Unreachable Host Count"]').length
- ).toBe(0);
- expect(wrapper.find('div[aria-label="Failed Host Count"]').length).toBe(0);
- });
-
- test('should display elapsed time as HH:MM:SS', () => {
- wrapper = mountWithContexts(
- {}}
- />
- );
-
- expect(wrapper.find('div[aria-label="Elapsed Time"] Badge').text()).toBe(
- '76:11:05'
- );
- });
-
- test('should hide relaunch button based on user capabilities', () => {
- expect(wrapper.find('LaunchButton').length).toBe(1);
- wrapper = mountWithContexts(
- {}}
- />
- );
- expect(wrapper.find('LaunchButton').length).toBe(0);
- });
-
- test('should hide delete button based on user capabilities', () => {
- expect(wrapper.find('DeleteButton').length).toBe(1);
- wrapper = mountWithContexts(
- {}}
- />
- );
- expect(wrapper.find('DeleteButton').length).toBe(0);
- });
-});
diff --git a/awx/ui/src/screens/Job/JobOutput/shared/index.js b/awx/ui/src/screens/Job/JobOutput/shared/index.js
deleted file mode 100644
index b73346355db8..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/shared/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export { default as HostStatusBar } from './HostStatusBar';
-export { default as JobEventLine } from './JobEventLine';
-export { default as JobEventLineToggle } from './JobEventLineToggle';
-export { default as JobEventLineNumber } from './JobEventLineNumber';
-export { default as JobEventLineText } from './JobEventLineText';
-export { default as OutputToolbar } from './OutputToolbar';
-export { default as JobEventEllipsis } from './JobEventEllipsis';
diff --git a/awx/ui/src/screens/Job/JobOutput/shared/jobOutputUtils.js b/awx/ui/src/screens/Job/JobOutput/shared/jobOutputUtils.js
deleted file mode 100644
index a600f7b898a2..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/shared/jobOutputUtils.js
+++ /dev/null
@@ -1,29 +0,0 @@
-export default function getRowRangePageSize(startIndex, stopIndex) {
- let page;
- let pageSize;
-
- if (startIndex === stopIndex) {
- page = startIndex;
- pageSize = 1;
- } else if (stopIndex >= startIndex + 50) {
- page = Math.floor(startIndex / 50) + 1;
- pageSize = 50;
- } else {
- for (let i = stopIndex - startIndex + 1; i <= 50; i++) {
- if (
- Math.floor(startIndex / i) === Math.floor(stopIndex / i) ||
- i === 50
- ) {
- page = Math.floor(startIndex / i) + 1;
- pageSize = i;
- break;
- }
- }
- }
-
- return {
- page,
- pageSize,
- firstIndex: (page - 1) * pageSize,
- };
-}
diff --git a/awx/ui/src/screens/Job/JobOutput/shared/jobOutputUtils.test.js b/awx/ui/src/screens/Job/JobOutput/shared/jobOutputUtils.test.js
deleted file mode 100644
index 7360fe0a0319..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/shared/jobOutputUtils.test.js
+++ /dev/null
@@ -1,51 +0,0 @@
-import getRowRangePageSize from './jobOutputUtils';
-
-describe('getRowRangePageSize', () => {
- test('handles range of 1', () => {
- expect(getRowRangePageSize(1, 1)).toEqual({
- page: 1,
- pageSize: 1,
- firstIndex: 0,
- });
- });
-
- test('handles range of 1 at a higher number', () => {
- expect(getRowRangePageSize(72, 72)).toEqual({
- page: 72,
- pageSize: 1,
- firstIndex: 71,
- });
- });
-
- test('handles range larger than 50 rows', () => {
- expect(getRowRangePageSize(55, 125)).toEqual({
- page: 2,
- pageSize: 50,
- firstIndex: 50,
- });
- });
-
- test('handles small range', () => {
- expect(getRowRangePageSize(47, 53)).toEqual({
- page: 6,
- pageSize: 9,
- firstIndex: 45,
- });
- });
-
- test('handles perfect range', () => {
- expect(getRowRangePageSize(5, 9)).toEqual({
- page: 2,
- pageSize: 5,
- firstIndex: 5,
- });
- });
-
- test('handles range with 0 startIndex', () => {
- expect(getRowRangePageSize(0, 50)).toEqual({
- page: 1,
- pageSize: 50,
- firstIndex: 0,
- });
- });
-});
diff --git a/awx/ui/src/screens/Job/JobOutput/useJobEvents.js b/awx/ui/src/screens/Job/JobOutput/useJobEvents.js
deleted file mode 100644
index 7566c1a915d2..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/useJobEvents.js
+++ /dev/null
@@ -1,535 +0,0 @@
-/* eslint-disable react/destructuring-assignment */
-import { useState, useEffect, useReducer } from 'react';
-
-const initialState = {
- // array of root level nodes (no parent_uuid)
- tree: [],
- // all events indexed by counter value
- events: {},
- // counter value indexed by uuid
- uuidMap: {},
- // events with parent events that aren't yet loaded.
- // arrays indexed by parent uuid
- eventsWithoutParents: {},
- // object in the form { counter: {rowNumber: n, numChildren: m}} for parent nodes
- childrenSummary: {},
- // parent_uuid's for "meta" events that need to be injected into the tree to
- // maintain tree integrity
- metaEventParentUuid: {},
- isAllCollapsed: false,
-};
-export const ADD_EVENTS = 'ADD_EVENTS';
-export const TOGGLE_NODE_COLLAPSED = 'TOGGLE_NODE_COLLAPSED';
-export const CLEAR_EVENTS = 'CLEAR_EVENTS';
-export const REBUILD_TREE = 'REBUILD_TREE';
-export const TOGGLE_COLLAPSE_ALL = 'TOGGLE_COLLAPSE_ALL';
-export const SET_CHILDREN_SUMMARY = 'SET_CHILDREN_SUMMARY';
-
-export default function useJobEvents(callbacks, jobId, isFlatMode) {
- const [actionQueue, setActionQueue] = useState([]);
- const enqueueAction = (action) => {
- setActionQueue((queue) => queue.concat(action));
- };
- const reducer = jobEventsReducer(callbacks, isFlatMode, enqueueAction);
- const [state, dispatch] = useReducer(reducer, initialState);
- useEffect(() => {
- setActionQueue((queue) => {
- const action = queue[0];
- if (!action) {
- return queue;
- }
- try {
- dispatch(action);
- } catch (e) {
- console.error(e); // eslint-disable-line no-console
- }
- return queue.slice(1);
- });
- }, [actionQueue]);
-
- useEffect(() => {
- if (isFlatMode) {
- callbacks.setJobTreeReady();
- return;
- }
-
- callbacks
- .fetchChildrenSummary()
- .then((result) => {
- const { event_processing_finished, is_tree } = result.data;
- if (event_processing_finished === false || is_tree === false) {
- callbacks.setForceFlatMode(true);
- callbacks.setJobTreeReady();
- return;
- }
- enqueueAction({
- type: SET_CHILDREN_SUMMARY,
- childrenSummary: result.data.children_summary,
- metaEventParentUuid: result.data.meta_event_nested_uuid,
- });
- })
- .catch(() => {
- callbacks.setForceFlatMode(true);
- callbacks.setJobTreeReady();
- });
- }, [jobId, isFlatMode]); // eslint-disable-line react-hooks/exhaustive-deps
-
- return {
- addEvents: (events) => dispatch({ type: ADD_EVENTS, events }),
- getNodeByUuid: (uuid) => getNodeByUuid(state, uuid),
- toggleNodeIsCollapsed: (uuid, isCollapsed) =>
- dispatch({ type: TOGGLE_NODE_COLLAPSED, uuid, isCollapsed }),
- toggleCollapseAll: (isCollapsed) =>
- dispatch({ type: TOGGLE_COLLAPSE_ALL, isCollapsed }),
- getEventForRow: (rowIndex) => getEventForRow(state, rowIndex),
- getNodeForRow: (rowIndex) => getNodeForRow(state, rowIndex),
- getTotalNumChildren: (uuid) => {
- const node = getNodeByUuid(state, uuid);
- return getTotalNumChildren(node, state.childrenSummary);
- },
- getNumCollapsedEvents: () =>
- state.tree.reduce(
- (sum, node) =>
- sum + getNumCollapsedChildren(node, state.childrenSummary),
- 0
- ),
- getCounterForRow: (rowIndex) => getCounterForRow(state, rowIndex),
- getEvent: (eventIndex) => getEvent(state, eventIndex),
- clearLoadedEvents: () => dispatch({ type: CLEAR_EVENTS }),
- rebuildEventsTree: () => dispatch({ type: REBUILD_TREE }),
- isAllCollapsed: state.isAllCollapsed,
- };
-}
-
-export function jobEventsReducer(callbacks, isFlatMode, enqueueAction) {
- return (state, action) => {
- switch (action.type) {
- case ADD_EVENTS:
- return addEvents(state, action.events);
- case TOGGLE_COLLAPSE_ALL:
- return toggleCollapseAll(state, action.isCollapsed);
- case TOGGLE_NODE_COLLAPSED:
- return toggleNodeIsCollapsed(state, action.uuid);
- case CLEAR_EVENTS:
- return initialState;
- case REBUILD_TREE:
- return rebuildTree(state);
- case SET_CHILDREN_SUMMARY:
- callbacks.setJobTreeReady();
- return {
- ...state,
- childrenSummary: action.childrenSummary || {},
- metaEventParentUuid: action.metaEventParentUuid || {},
- };
- default:
- throw new Error(`Unrecognized action: ${action.type}`);
- }
- };
-
- function addEvents(origState, newEvents) {
- let state = {
- ...origState,
- events: { ...origState.events },
- tree: [...origState.tree],
- };
- const parentsToFetch = {};
- newEvents.forEach((event) => {
- if (
- typeof event.rowNumber !== 'number' ||
- Number.isNaN(event.rowNumber)
- ) {
- throw new Error('Cannot add event; missing rowNumber');
- }
- const eventIndex = event.counter;
- if (!event.parent_uuid && state.metaEventParentUuid[eventIndex]) {
- event.parent_uuid = state.metaEventParentUuid[eventIndex];
- }
- if (state.events[eventIndex]) {
- state.events[eventIndex] = event;
- state = _gatherEventsForNewParent(state, event.uuid);
- return;
- }
- if (!event.parent_uuid || isFlatMode) {
- state = _addRootLevelEvent(state, event);
- return;
- }
-
- let isParentFound;
- [state, isParentFound] = _addNestedLevelEvent(state, event);
- if (!isParentFound) {
- parentsToFetch[event.parent_uuid] = true;
- state = _addEventWithoutParent(state, event);
- }
- });
-
- Object.keys(parentsToFetch).forEach(async (uuid) => {
- const parent = await callbacks.fetchEventByUuid(uuid);
-
- if (!state.childrenSummary || !state.childrenSummary[parent.counter]) {
- // eslint-disable-next-line no-console
- console.error('No row number found for ', parent.counter);
- return;
- }
- parent.rowNumber = state.childrenSummary[parent.counter].rowNumber;
-
- enqueueAction({
- type: ADD_EVENTS,
- events: [parent],
- });
- });
-
- return state;
- }
-
- function _addRootLevelEvent(state, event) {
- const eventIndex = event.counter;
- const newNode = {
- eventIndex,
- isCollapsed: state.isAllCollapsed,
- children: [],
- };
- const index = state.tree.findIndex((node) => node.eventIndex > eventIndex);
- const updatedTree = [...state.tree];
- if (index === -1) {
- updatedTree.push(newNode);
- } else {
- updatedTree.splice(index, 0, newNode);
- }
- return _gatherEventsForNewParent(
- {
- ...state,
- events: { ...state.events, [eventIndex]: event },
- tree: updatedTree,
- uuidMap: {
- ...state.uuidMap,
- [event.uuid]: eventIndex,
- },
- },
- event.uuid
- );
- }
-
- function _addNestedLevelEvent(state, event) {
- const eventIndex = event.counter;
- const parent = getNodeByUuid(state, event.parent_uuid);
- if (!parent) {
- return [state, false];
- }
- const newNode = {
- eventIndex,
- isCollapsed: state.isAllCollapsed,
- children: [],
- };
- const index = parent.children.findIndex(
- (node) => node.eventIndex >= eventIndex
- );
- if (index === -1) {
- state = updateNodeByUuid(state, event.parent_uuid, (node) => {
- node.children.push(newNode);
- return node;
- });
- } else {
- state = updateNodeByUuid(state, event.parent_uuid, (node) => {
- node.children.splice(index, 0, newNode);
- return node;
- });
- }
- state = _gatherEventsForNewParent(
- {
- ...state,
- events: {
- ...state.events,
- [eventIndex]: event,
- },
- uuidMap: {
- ...state.uuidMap,
- [event.uuid]: eventIndex,
- },
- },
- event.uuid
- );
-
- return [state, true];
- }
-
- function _addEventWithoutParent(state, event) {
- const parentUuid = event.parent_uuid;
- let eventsList;
- if (!state.eventsWithoutParents[parentUuid]) {
- eventsList = [event];
- } else {
- eventsList = state.eventsWithoutParents[parentUuid].concat(event);
- }
-
- return {
- ...state,
- eventsWithoutParents: {
- ...state.eventsWithoutParents,
- [parentUuid]: eventsList,
- },
- };
- }
-
- function _gatherEventsForNewParent(state, parentUuid) {
- if (!state.eventsWithoutParents[parentUuid]) {
- return state;
- }
-
- const { [parentUuid]: newEvents, ...remaining } =
- state.eventsWithoutParents;
- return addEvents(
- {
- ...state,
- eventsWithoutParents: remaining,
- },
- newEvents
- );
- }
-
- function rebuildTree(state) {
- const events = Object.values(state.events);
- return addEvents(initialState, events);
- }
-}
-
-function getEventForRow(state, rowIndex) {
- const { node } = _getNodeForRow(state, rowIndex, state.tree);
- if (node) {
- return {
- node,
- event: state.events[node.eventIndex],
- };
- }
- return null;
-}
-
-function getNodeForRow(state, rowToFind, childrenSummary) {
- const { node } = _getNodeForRow(
- state,
- rowToFind,
- state.tree,
- childrenSummary
- );
- return node;
-}
-
-function getCounterForRow(state, rowToFind) {
- const { node, expectedCounter } = _getNodeForRow(
- state,
- rowToFind,
- state.tree
- );
-
- if (node) {
- const event = state.events[node.eventIndex];
- return event.counter;
- }
- return expectedCounter;
-}
-
-function _getNodeForRow(state, rowToFind, nodes) {
- for (let i = 0; i < nodes.length; i++) {
- const node = nodes[i];
- const event = state.events[node.eventIndex];
- if (event.rowNumber === rowToFind) {
- return { node };
- }
- const totalNodeDescendants = getTotalNumChildren(
- node,
- state.childrenSummary
- );
- const numCollapsedChildren = getNumCollapsedChildren(
- node,
- state.childrenSummary
- );
- const nodeChildren = totalNodeDescendants - numCollapsedChildren;
- if (event.rowNumber + nodeChildren >= rowToFind) {
- // requested row is in children/descendants
- return _getNodeInChildren(state, node, rowToFind);
- }
- rowToFind += numCollapsedChildren;
-
- const nextNode = nodes[i + 1];
- if (!nextNode) {
- continue;
- }
- const nextEvent = state.events[nextNode.eventIndex];
- const lastChild = _getLastDescendantNode([node]);
- if (nextEvent.rowNumber > rowToFind) {
- // requested row is not loaded; return best guess at counter number
- const lastChildEvent = state.events[lastChild.eventIndex];
- const rowDiff = rowToFind - lastChildEvent.rowNumber;
- return {
- node: null,
- expectedCounter: lastChild.eventIndex + rowDiff,
- };
- }
- }
-
- const lastDescendant = _getLastDescendantNode(nodes);
- if (!lastDescendant) {
- return { node: null, expectedCounter: rowToFind };
- }
-
- const lastDescendantEvent = state.events[lastDescendant.eventIndex];
- const rowDiff = rowToFind - lastDescendantEvent.rowNumber;
- return {
- node: null,
- expectedCounter: lastDescendant.eventIndex + rowDiff,
- };
-}
-
-function _getNodeInChildren(state, node, rowToFind) {
- const event = state.events[node.eventIndex];
- const firstChild = state.events[node.children[0]?.eventIndex];
- if (!firstChild || rowToFind < firstChild.rowNumber) {
- const rowDiff = rowToFind - event.rowNumber;
- return {
- node: null,
- expectedCounter: event.counter + rowDiff,
- };
- }
- return _getNodeForRow(state, rowToFind, node.children);
-}
-
-function _getLastDescendantNode(nodes) {
- let lastDescendant = nodes[nodes.length - 1];
- let children = lastDescendant?.children || [];
- while (children.length) {
- lastDescendant = children[children.length - 1];
- children = lastDescendant.children;
- }
- return lastDescendant;
-}
-
-function getTotalNumChildren(node, childrenSummary) {
- if (childrenSummary[node.eventIndex]) {
- return childrenSummary[node.eventIndex].numChildren;
- }
-
- let estimatedNumChildren = node.children.length;
- node.children.forEach((child) => {
- estimatedNumChildren += getTotalNumChildren(child, childrenSummary);
- });
- return estimatedNumChildren;
-}
-
-function getNumCollapsedChildren(node, childrenSummary) {
- if (node.isCollapsed) {
- return getTotalNumChildren(node, childrenSummary);
- }
- let sum = 0;
- node.children.forEach((child) => {
- sum += getNumCollapsedChildren(child, childrenSummary);
- });
- return sum;
-}
-
-function toggleNodeIsCollapsed(state, eventUuid) {
- return {
- ...updateNodeByUuid(state, eventUuid, (node) => ({
- ...node,
- isCollapsed: !node.isCollapsed,
- })),
- isAllCollapsed: false,
- };
-}
-
-function toggleCollapseAll(state, isAllCollapsed) {
- const newTree = state.tree.map((node) =>
- _toggleNestedNodes(state.events, node, isAllCollapsed)
- );
- return { ...state, tree: newTree, isAllCollapsed };
-}
-
-function _toggleNestedNodes(events, node, isCollapsed) {
- const {
- parent_uuid,
- event_data: { playbook_uuid },
- uuid,
- } = events[node.eventIndex];
-
- const eventShouldNotCollapse = uuid === playbook_uuid || !parent_uuid?.length;
-
- const children = node.children?.map((nestedNode) =>
- _toggleNestedNodes(events, nestedNode, isCollapsed)
- );
-
- return {
- ...node,
- isCollapsed: eventShouldNotCollapse ? false : isCollapsed,
- children,
- };
-}
-
-function updateNodeByUuid(state, uuid, update) {
- if (!state.uuidMap[uuid]) {
- throw new Error(`Cannot update node; Event UUID not found ${uuid}`);
- }
- const index = state.uuidMap[uuid];
- return {
- ...state,
- tree: _updateNodeByIndex(index, state.tree, update),
- };
-}
-
-function _updateNodeByIndex(target, nodeArray, update) {
- const nextIndex = nodeArray.findIndex((node) => node.eventIndex > target);
- const targetIndex = nextIndex === -1 ? nodeArray.length - 1 : nextIndex - 1;
- let updatedNode;
- if (nodeArray[targetIndex].eventIndex === target) {
- updatedNode = update({
- ...nodeArray[targetIndex],
- children: [...nodeArray[targetIndex].children],
- });
- } else {
- updatedNode = {
- ...nodeArray[targetIndex],
- children: _updateNodeByIndex(
- target,
- nodeArray[targetIndex].children,
- update
- ),
- };
- }
- return [
- ...nodeArray.slice(0, targetIndex),
- updatedNode,
- ...nodeArray.slice(targetIndex + 1),
- ];
-}
-
-function getNodeByUuid(state, uuid) {
- if (!state.uuidMap[uuid]) {
- return null;
- }
-
- const index = state.uuidMap[uuid];
- return _getNodeByIndex(state.tree, index);
-}
-
-function _getNodeByIndex(arr, index) {
- if (!arr.length) {
- return null;
- }
- const i = arr.findIndex((node) => node.eventIndex >= index);
- if (i === -1) {
- return _getNodeByIndex(arr[arr.length - 1].children, index);
- }
- if (arr[i].eventIndex === index) {
- return arr[i];
- }
- if (!arr[i - 1]) {
- return null;
- }
- return _getNodeByIndex(arr[i - 1].children, index);
-}
-
-function getEvent(state, eventIndex) {
- const event = state.events[eventIndex];
- if (event) {
- return event;
- }
-
- return null;
-}
diff --git a/awx/ui/src/screens/Job/JobOutput/useJobEvents.test.js b/awx/ui/src/screens/Job/JobOutput/useJobEvents.test.js
deleted file mode 100644
index dd34ed4c8566..000000000000
--- a/awx/ui/src/screens/Job/JobOutput/useJobEvents.test.js
+++ /dev/null
@@ -1,1563 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { shallow, mount } from 'enzyme';
-import useJobEvents, {
- jobEventsReducer,
- ADD_EVENTS,
- TOGGLE_NODE_COLLAPSED,
-} from './useJobEvents';
-
-function Child() {
- return ;
-}
-function HookTest({
- fetchEventByUuid = () => {},
- fetchChildrenSummary = () => {},
- setForceFlatMode = () => {},
- setJobTreeReady = () => {},
- jobId = 1,
- isFlatMode = false,
-}) {
- const hookFuncs = useJobEvents(
- {
- fetchEventByUuid,
- fetchChildrenSummary,
- setForceFlatMode,
- setJobTreeReady,
- },
- jobId,
- isFlatMode
- );
- return ;
-}
-
-const eventsList = [
- {
- id: 101,
- counter: 1,
- rowNumber: 0,
- uuid: 'abc-001',
- event_level: 0,
- parent_uuid: '',
- },
- {
- id: 102,
- counter: 2,
- rowNumber: 1,
- uuid: 'abc-002',
- event_level: 1,
- parent_uuid: 'abc-001',
- },
- {
- id: 103,
- counter: 3,
- rowNumber: 2,
- uuid: 'abc-003',
- event_level: 2,
- parent_uuid: 'abc-002',
- },
- {
- id: 104,
- counter: 4,
- rowNumber: 3,
- uuid: 'abc-004',
- event_level: 2,
- parent_uuid: 'abc-002',
- },
- {
- id: 105,
- counter: 5,
- rowNumber: 4,
- uuid: 'abc-005',
- event_level: 2,
- parent_uuid: 'abc-002',
- },
- {
- id: 106,
- counter: 6,
- rowNumber: 5,
- uuid: 'abc-006',
- event_level: 1,
- parent_uuid: 'abc-001',
- },
- {
- id: 107,
- counter: 7,
- rowNumber: 6,
- uuid: 'abc-007',
- event_level: 2,
- parent_uuid: 'abc-006',
- },
- {
- id: 108,
- counter: 8,
- rowNumber: 7,
- uuid: 'abc-008',
- event_level: 2,
- parent_uuid: 'abc-006',
- },
- {
- id: 109,
- counter: 9,
- rowNumber: 8,
- uuid: 'abc-009',
- event_level: 2,
- parent_uuid: 'abc-006',
- },
-];
-const basicEvents = {
- 1: eventsList[0],
- 2: eventsList[1],
- 3: eventsList[2],
- 4: eventsList[3],
- 5: eventsList[4],
- 6: eventsList[5],
- 7: eventsList[6],
- 8: eventsList[7],
- 9: eventsList[8],
-};
-const basicTree = [
- {
- eventIndex: 1,
- isCollapsed: false,
- children: [
- {
- eventIndex: 2,
- isCollapsed: false,
- children: [
- { eventIndex: 3, isCollapsed: false, children: [] },
- { eventIndex: 4, isCollapsed: false, children: [] },
- { eventIndex: 5, isCollapsed: false, children: [] },
- ],
- },
- {
- eventIndex: 6,
- isCollapsed: false,
- children: [
- { eventIndex: 7, isCollapsed: false, children: [] },
- { eventIndex: 8, isCollapsed: false, children: [] },
- { eventIndex: 9, isCollapsed: false, children: [] },
- ],
- },
- ],
- },
-];
-
-describe('useJobEvents', () => {
- let callbacks;
- let reducer;
- let emptyState;
- let enqueueAction;
-
- beforeEach(() => {
- callbacks = {
- fetchEventByUuid: jest.fn(),
- fetchChildrenSummary: jest.fn(),
- setForceFlatMode: jest.fn(),
- setJobTreeReady: jest.fn(),
- };
- enqueueAction = jest.fn();
- reducer = jobEventsReducer(callbacks, false, enqueueAction);
- emptyState = {
- tree: [],
- events: {},
- uuidMap: {},
- eventsWithoutParents: {},
- childrenSummary: {},
- metaEventParentUuid: {},
- eventGaps: [],
- isAllCollapsed: false,
- };
- });
-
- afterAll(() => {
- jest.resetAllMocks();
- });
-
- describe('addEvents', () => {
- test('should build initial tree', () => {
- const state = reducer(emptyState, {
- type: ADD_EVENTS,
- events: eventsList,
- });
-
- expect(state.events).toEqual(basicEvents);
- expect(state.tree).toEqual(basicTree);
- expect(state.uuidMap).toEqual({
- 'abc-001': 1,
- 'abc-002': 2,
- 'abc-003': 3,
- 'abc-004': 4,
- 'abc-005': 5,
- 'abc-006': 6,
- 'abc-007': 7,
- 'abc-008': 8,
- 'abc-009': 9,
- });
- });
-
- test('should append new events', () => {
- const newEvents = [
- {
- id: 110,
- counter: 10,
- rowNumber: 9,
- uuid: 'abc-010',
- event_level: 2,
- parent_uuid: 'abc-006',
- },
- {
- id: 111,
- counter: 11,
- rowNumber: 10,
- uuid: 'abc-011',
- event_level: 1,
- parent_uuid: 'abc-001',
- },
- {
- id: 112,
- counter: 12,
- rowNumber: 11,
- uuid: 'abc-012',
- event_level: 0,
- parent_uuid: '',
- },
- ];
- const state = reducer(emptyState, {
- type: ADD_EVENTS,
- events: eventsList,
- });
- const { events, tree } = reducer(state, {
- type: ADD_EVENTS,
- events: newEvents,
- });
-
- expect(events).toEqual({
- 1: eventsList[0],
- 2: eventsList[1],
- 3: eventsList[2],
- 4: eventsList[3],
- 5: eventsList[4],
- 6: eventsList[5],
- 7: eventsList[6],
- 8: eventsList[7],
- 9: eventsList[8],
- 10: newEvents[0],
- 11: newEvents[1],
- 12: newEvents[2],
- });
- expect(tree).toEqual([
- {
- eventIndex: 1,
- isCollapsed: false,
- children: [
- {
- eventIndex: 2,
- isCollapsed: false,
- children: [
- { eventIndex: 3, isCollapsed: false, children: [] },
- { eventIndex: 4, isCollapsed: false, children: [] },
- { eventIndex: 5, isCollapsed: false, children: [] },
- ],
- },
- {
- eventIndex: 6,
- isCollapsed: false,
- children: [
- { eventIndex: 7, isCollapsed: false, children: [] },
- { eventIndex: 8, isCollapsed: false, children: [] },
- { eventIndex: 9, isCollapsed: false, children: [] },
- { eventIndex: 10, isCollapsed: false, children: [] },
- ],
- },
- {
- eventIndex: 11,
- isCollapsed: false,
- children: [],
- },
- ],
- },
- { eventIndex: 12, isCollapsed: false, children: [] },
- ]);
- });
-
- test('should not mutate original state', () => {
- const state = reducer(emptyState, {
- type: ADD_EVENTS,
- events: [eventsList[0], eventsList[1]],
- });
- window.debug = true;
- reducer(state, {
- type: ADD_EVENTS,
- events: [eventsList[2], eventsList[5]],
- });
-
- expect(state.events).toEqual({ 1: eventsList[0], 2: eventsList[1] });
- expect(state.tree).toEqual([
- {
- eventIndex: 1,
- isCollapsed: false,
- children: [
- {
- eventIndex: 2,
- isCollapsed: false,
- children: [],
- },
- ],
- },
- ]);
- expect(state.uuidMap).toEqual({
- 'abc-001': 1,
- 'abc-002': 2,
- });
- });
-
- test('should not duplicate events in events tree', () => {
- const state = reducer(emptyState, {
- type: ADD_EVENTS,
- events: eventsList,
- });
- const newNode = {
- id: 110,
- counter: 10,
- rowNumber: 9,
- uuid: 'abc-010',
- event_level: 2,
- parent_uuid: 'abc-006',
- };
- reducer(state, {
- type: ADD_EVENTS,
- events: [newNode],
- });
- const { events, tree } = reducer(state, {
- type: ADD_EVENTS,
- events: [newNode],
- });
-
- expect(events).toEqual({
- 1: eventsList[0],
- 2: eventsList[1],
- 3: eventsList[2],
- 4: eventsList[3],
- 5: eventsList[4],
- 6: eventsList[5],
- 7: eventsList[6],
- 8: eventsList[7],
- 9: eventsList[8],
- 10: newNode,
- });
- expect(tree).toEqual([
- {
- eventIndex: 1,
- isCollapsed: false,
- children: [
- {
- eventIndex: 2,
- isCollapsed: false,
- children: [
- { eventIndex: 3, isCollapsed: false, children: [] },
- { eventIndex: 4, isCollapsed: false, children: [] },
- { eventIndex: 5, isCollapsed: false, children: [] },
- ],
- },
- {
- eventIndex: 6,
- isCollapsed: false,
- children: [
- { eventIndex: 7, isCollapsed: false, children: [] },
- { eventIndex: 8, isCollapsed: false, children: [] },
- { eventIndex: 9, isCollapsed: false, children: [] },
- { eventIndex: 10, isCollapsed: false, children: [] },
- ],
- },
- ],
- },
- ]);
- });
-
- test('should fetch parent for events with missing parent', async () => {
- callbacks.fetchEventByUuid.mockResolvedValue({
- counter: 10,
- });
- const state = reducer(
- {
- ...emptyState,
- childrenSummary: {
- 10: [9, 2],
- },
- },
- {
- type: ADD_EVENTS,
- events: eventsList,
- }
- );
-
- const newEvents = [
- {
- id: 112,
- counter: 12,
- rowNumber: 11,
- uuid: 'abc-012',
- event_level: 2,
- parent_uuid: 'abc-010',
- },
- ];
- reducer(state, { type: ADD_EVENTS, events: newEvents });
-
- expect(callbacks.fetchEventByUuid).toHaveBeenCalledWith('abc-010');
- });
-
- test('should batch parent fetches by uuid', () => {
- callbacks.fetchEventByUuid.mockResolvedValue({
- counter: 10,
- });
- const state = reducer(
- {
- ...emptyState,
- childrenSummary: {
- 10: [9, 2],
- },
- },
- {
- type: ADD_EVENTS,
- events: eventsList,
- }
- );
-
- const newEvents = [
- {
- id: 112,
- counter: 12,
- rowNumber: 11,
- uuid: 'abc-012',
- event_level: 2,
- parent_uuid: 'abc-010',
- },
- {
- id: 113,
- counter: 13,
- rowNumber: 12,
- uuid: 'abc-013',
- event_level: 2,
- parent_uuid: 'abc-010',
- },
- ];
- reducer(state, { type: ADD_EVENTS, events: newEvents });
-
- expect(callbacks.fetchEventByUuid).toHaveBeenCalledTimes(1);
- expect(callbacks.fetchEventByUuid).toHaveBeenCalledWith('abc-010');
- });
-
- test('should fetch multiple parent fetches by uuid', () => {
- callbacks.fetchEventByUuid.mockResolvedValue({
- counter: 10,
- });
- const state = reducer(
- {
- ...emptyState,
- childrenSummary: {
- 10: [9, 1],
- },
- },
- {
- type: ADD_EVENTS,
- events: eventsList,
- }
- );
-
- const newEvents = [
- {
- id: 114,
- counter: 14,
- rowNumber: 13,
- uuid: 'abc-014',
- event_level: 2,
- parent_uuid: 'abc-012',
- },
- {
- id: 115,
- counter: 15,
- rowNumber: 14,
- uuid: 'abc-015',
- event_level: 1,
- parent_uuid: 'abc-011',
- },
- ];
- reducer(state, { type: ADD_EVENTS, events: newEvents });
-
- expect(callbacks.fetchEventByUuid).toHaveBeenCalledTimes(2);
- expect(callbacks.fetchEventByUuid).toHaveBeenCalledWith('abc-012');
- expect(callbacks.fetchEventByUuid).toHaveBeenCalledWith('abc-011');
- });
-
- test('should set eventsWithoutParents while fetching parent events', () => {
- callbacks.fetchEventByUuid.mockResolvedValue({
- counter: 10,
- });
- const state = reducer(
- {
- ...emptyState,
- childrenSummary: {
- 10: [9, 2],
- },
- },
- {
- type: ADD_EVENTS,
- events: eventsList,
- }
- );
-
- const newEvents = [
- {
- id: 112,
- counter: 12,
- rowNumber: 11,
- uuid: 'abc-012',
- event_level: 2,
- parent_uuid: 'abc-010',
- },
- ];
- const { eventsWithoutParents, tree } = reducer(state, {
- type: ADD_EVENTS,
- events: newEvents,
- });
-
- expect(eventsWithoutParents).toEqual({
- 'abc-010': [newEvents[0]],
- });
- expect(tree).toEqual(basicTree);
- });
-
- test('should check for eventsWithoutParents belonging to new nodes', () => {
- const childEvent = {
- id: 112,
- counter: 12,
- rowNumber: 11,
- uuid: 'abc-012',
- event_level: 1,
- parent_uuid: 'abc-010',
- };
- const initialState = {
- ...emptyState,
- eventsWithoutParents: {
- 'abc-010': [childEvent],
- },
- };
- const parentEvent = {
- id: 110,
- counter: 10,
- rowNumber: 9,
- uuid: 'abc-010',
- event_level: 0,
- parent_uuid: '',
- };
-
- const { tree, events, eventsWithoutParents } = reducer(initialState, {
- type: ADD_EVENTS,
- events: [parentEvent],
- });
-
- expect(tree).toEqual([
- {
- eventIndex: 10,
- isCollapsed: false,
- children: [
- {
- eventIndex: 12,
- isCollapsed: false,
- children: [],
- },
- ],
- },
- ]);
- expect(events).toEqual({
- 10: parentEvent,
- 12: childEvent,
- });
- expect(eventsWithoutParents).toEqual({});
- });
-
- test('should fetch parent of parent and compile them together', () => {
- callbacks.fetchEventByUuid.mockResolvedValueOnce({
- counter: 2,
- });
- callbacks.fetchEventByUuid.mockResolvedValueOnce({
- counter: 1,
- });
- const event3 = {
- id: 103,
- counter: 3,
- rowNumber: 2,
- uuid: 'abc-003',
- event_level: 2,
- parent_uuid: 'abc-002',
- };
- const state = reducer(
- {
- ...emptyState,
- childrenSummary: {
- 1: [0, 3],
- 2: [1, 2],
- },
- },
- {
- type: ADD_EVENTS,
- events: [event3],
- }
- );
- expect(callbacks.fetchEventByUuid).toHaveBeenCalledWith('abc-002');
-
- const event2 = {
- id: 102,
- counter: 2,
- rowNumber: 1,
- uuid: 'abc-002',
- event_level: 1,
- parent_uuid: 'abc-001',
- };
- const state2 = reducer(state, {
- type: ADD_EVENTS,
- events: [event2],
- });
- expect(callbacks.fetchEventByUuid).toHaveBeenCalledWith('abc-001');
- expect(state2.events).toEqual({});
- expect(state2.tree).toEqual([]);
- expect(state2.eventsWithoutParents).toEqual({
- 'abc-001': [event2],
- 'abc-002': [event3],
- });
-
- const event1 = {
- id: 101,
- counter: 1,
- rowNumber: 0,
- uuid: 'abc-001',
- event_level: 0,
- parent_uuid: '',
- };
- const state3 = reducer(state2, {
- type: ADD_EVENTS,
- events: [event1],
- });
- expect(state3.events).toEqual({
- 1: event1,
- 2: event2,
- 3: event3,
- });
- expect(state3.tree).toEqual([
- {
- eventIndex: 1,
- isCollapsed: false,
- children: [
- {
- eventIndex: 2,
- isCollapsed: false,
- children: [
- {
- eventIndex: 3,
- isCollapsed: false,
- children: [],
- },
- ],
- },
- ],
- },
- ]);
- expect(state3.eventsWithoutParents).toEqual({});
- });
-
- test('should add root level node in middle of array', () => {
- const events = [
- {
- id: 101,
- counter: 1,
- rowNumber: 0,
- uuid: 'abc-001',
- event_level: 0,
- parent_uuid: '',
- },
- {
- id: 102,
- counter: 2,
- rowNumber: 1,
- uuid: 'abc-002',
- event_level: 0,
- parent_uuid: '',
- },
- {
- id: 103,
- counter: 3,
- rowNumber: 2,
- uuid: 'abc-003',
- event_level: 0,
- parent_uuid: '',
- },
- ];
- const state = reducer(emptyState, {
- type: ADD_EVENTS,
- events: [events[0]],
- });
- const state2 = reducer(state, {
- type: ADD_EVENTS,
- events: [events[2]],
- });
- const state3 = reducer(state2, {
- type: ADD_EVENTS,
- events: [events[1]],
- });
-
- expect(state3.tree[0].eventIndex).toEqual(1);
- expect(state3.tree[1].eventIndex).toEqual(2);
- expect(state3.tree[2].eventIndex).toEqual(3);
- });
-
- test('should add child nodes in middle of array', () => {
- const state = reducer(emptyState, {
- type: ADD_EVENTS,
- events: [...eventsList.slice(0, 3), ...eventsList.slice(4)],
- });
- const state2 = reducer(state, {
- type: ADD_EVENTS,
- events: [eventsList[3]],
- });
-
- expect(state2.tree).toEqual([
- {
- eventIndex: 1,
- isCollapsed: false,
- children: [
- {
- eventIndex: 2,
- isCollapsed: false,
- children: [
- { eventIndex: 3, isCollapsed: false, children: [] },
- { eventIndex: 4, isCollapsed: false, children: [] },
- { eventIndex: 5, isCollapsed: false, children: [] },
- ],
- },
- {
- eventIndex: 6,
- isCollapsed: false,
- children: [
- { eventIndex: 7, isCollapsed: false, children: [] },
- { eventIndex: 8, isCollapsed: false, children: [] },
- { eventIndex: 9, isCollapsed: false, children: [] },
- ],
- },
- ],
- },
- ]);
- });
-
- test('should build in flat mode', () => {
- const flatReducer = jobEventsReducer(callbacks, true, enqueueAction);
- const state = flatReducer(emptyState, {
- type: ADD_EVENTS,
- events: eventsList,
- });
-
- expect(state.events).toEqual(basicEvents);
- expect(state.tree).toEqual([
- { eventIndex: 1, isCollapsed: false, children: [] },
- { eventIndex: 2, isCollapsed: false, children: [] },
- { eventIndex: 3, isCollapsed: false, children: [] },
- { eventIndex: 4, isCollapsed: false, children: [] },
- { eventIndex: 5, isCollapsed: false, children: [] },
- { eventIndex: 6, isCollapsed: false, children: [] },
- { eventIndex: 7, isCollapsed: false, children: [] },
- { eventIndex: 8, isCollapsed: false, children: [] },
- { eventIndex: 9, isCollapsed: false, children: [] },
- ]);
- expect(state.uuidMap).toEqual({
- 'abc-001': 1,
- 'abc-002': 2,
- 'abc-003': 3,
- 'abc-004': 4,
- 'abc-005': 5,
- 'abc-006': 6,
- 'abc-007': 7,
- 'abc-008': 8,
- 'abc-009': 9,
- });
- });
-
- test('should nest "meta" event based on given parent uuid', () => {
- const state = reducer(
- {
- ...emptyState,
- childrenSummary: {
- 2: { rowNumber: 1, numChildren: 3 },
- },
- metaEventParentUuid: {
- 4: 'abc-002',
- },
- },
- {
- type: ADD_EVENTS,
- events: [...eventsList.slice(0, 3)],
- }
- );
- const state2 = reducer(state, {
- type: ADD_EVENTS,
- events: [
- {
- counter: 4,
- rowNumber: 3,
- parent_uuid: '',
- },
- ],
- });
-
- expect(state2.tree).toEqual([
- {
- eventIndex: 1,
- isCollapsed: false,
- children: [
- {
- eventIndex: 2,
- isCollapsed: false,
- children: [
- { eventIndex: 3, isCollapsed: false, children: [] },
- { eventIndex: 4, isCollapsed: false, children: [] },
- ],
- },
- ],
- },
- ]);
- });
- });
-
- describe('getNodeByUuid', () => {
- let wrapper;
- beforeEach(() => {
- wrapper = shallow();
- wrapper.find('#test').prop('addEvents')(eventsList);
- });
-
- test('should get a root node', () => {
- const node = wrapper.find('#test').prop('getNodeByUuid')('abc-001');
- expect(node.eventIndex).toEqual(1);
- expect(node.isCollapsed).toEqual(false);
- expect(node.children).toHaveLength(2);
- });
-
- test('should get 2nd level node', () => {
- const node = wrapper.find('#test').prop('getNodeByUuid')('abc-002');
- expect(node.eventIndex).toEqual(2);
- expect(node.isCollapsed).toEqual(false);
- expect(node.children).toHaveLength(3);
- });
-
- test('should get 3rd level node', () => {
- const node = wrapper.find('#test').prop('getNodeByUuid')('abc-008');
- expect(node.eventIndex).toEqual(8);
- expect(node.isCollapsed).toEqual(false);
- expect(node.children).toHaveLength(0);
- });
-
- test('should return null if node not found', () => {
- const node = wrapper.find('#test').prop('getNodeByUuid')('abc-028');
- expect(node).toEqual(null);
- });
- });
-
- describe('toggleNodeIsCollapsed', () => {
- test('should collapse node', () => {
- const state = reducer(emptyState, {
- type: ADD_EVENTS,
- events: eventsList,
- });
- const { tree } = reducer(state, {
- type: TOGGLE_NODE_COLLAPSED,
- uuid: 'abc-001',
- });
-
- expect(tree).toEqual([
- {
- ...basicTree[0],
- isCollapsed: true,
- },
- ]);
- });
-
- test('should expand node', () => {
- const state = reducer(emptyState, {
- type: ADD_EVENTS,
- events: eventsList,
- });
- const { tree } = reducer(
- {
- ...state,
- tree: [
- {
- ...state.tree[0],
- isCollapsed: true,
- },
- ],
- },
- {
- type: TOGGLE_NODE_COLLAPSED,
- uuid: 'abc-001',
- }
- );
-
- expect(tree).toEqual(basicTree);
- });
- });
-
- describe('getNodeForRow', () => {
- let wrapper;
- beforeEach(() => {
- wrapper = shallow();
- wrapper.find('#test').prop('addEvents')(eventsList);
- });
-
- test('should get root node', () => {
- const node = wrapper.find('#test').prop('getNodeForRow')(0);
-
- expect(node.eventIndex).toEqual(1);
- expect(node.isCollapsed).toEqual(false);
- expect(node.children).toHaveLength(2);
- });
-
- test('should get 2nd level node', () => {
- const node = wrapper.find('#test').prop('getNodeForRow')(1);
-
- expect(node.eventIndex).toEqual(2);
- expect(node.isCollapsed).toEqual(false);
- expect(node.children).toHaveLength(3);
- });
-
- test('should get 3rd level node', () => {
- const node = wrapper.find('#test').prop('getNodeForRow')(7);
-
- expect(node.eventIndex).toEqual(8);
- expect(node.isCollapsed).toEqual(false);
- expect(node.children).toHaveLength(0);
- });
-
- test('should get last child node', () => {
- const node = wrapper.find('#test').prop('getNodeForRow')(4);
-
- expect(node.eventIndex).toEqual(5);
- expect(node.isCollapsed).toEqual(false);
- expect(node.children).toHaveLength(0);
- });
-
- test('should get a second root-level node', () => {
- const lastNode = {
- id: 110,
- counter: 10,
- rowNumber: 9,
- uuid: 'abc-010',
- event_level: 0,
- parent_uuid: '',
- };
- wrapper.find('#test').prop('addEvents')([lastNode]);
-
- const node = wrapper.find('#test').prop('getNodeForRow')(9);
-
- expect(node).toEqual({
- eventIndex: 10,
- isCollapsed: false,
- children: [],
- });
- });
-
- test('should return null if no node matches index', () => {
- const node = wrapper.find('#test').prop('getNodeForRow')(10);
-
- expect(node).toEqual(null);
- });
-
- test('should return null if no nodes loaded', () => {
- wrapper = shallow();
- const node = wrapper.find('#test').prop('getNodeForRow')(5);
-
- expect(node).toEqual(null);
- });
-
- test('should return collapsed node', () => {
- wrapper.find('#test').prop('toggleNodeIsCollapsed')('abc-002');
-
- const node = wrapper.find('#test').prop('getNodeForRow')(1);
-
- expect(node.eventIndex).toEqual(2);
- expect(node.isCollapsed).toBe(true);
- });
-
- test('should skip nodes with collapsed parent', () => {
- wrapper.find('#test').prop('toggleNodeIsCollapsed')('abc-002');
-
- const node = wrapper.find('#test').prop('getNodeForRow')(2);
- expect(node.eventIndex).toEqual(6);
- expect(node.isCollapsed).toBe(false);
-
- const node2 = wrapper.find('#test').prop('getNodeForRow')(4);
- expect(node2.eventIndex).toEqual(8);
- expect(node2.isCollapsed).toBe(false);
- });
-
- test('should skip deeply-nested collapsed nodes', () => {
- wrapper = shallow();
- wrapper.find('#test').prop('addEvents')([
- { id: 101, counter: 1, rowNumber: 0, uuid: 'abc-001', event_level: 0 },
- {
- id: 102,
- counter: 2,
- rowNumber: 1,
- uuid: 'abc-002',
- event_level: 1,
- parent_uuid: 'abc-001',
- },
- {
- id: 103,
- counter: 3,
- rowNumber: 2,
- uuid: 'abc-003',
- event_level: 2,
- parent_uuid: 'abc-002',
- },
- {
- id: 104,
- counter: 4,
- rowNumber: 3,
- uuid: 'abc-004',
- event_level: 2,
- parent_uuid: 'abc-002',
- },
- {
- id: 105,
- counter: 5,
- rowNumber: 4,
- uuid: 'abc-005',
- event_level: 3,
- parent_uuid: 'abc-004',
- },
- {
- id: 106,
- counter: 6,
- rowNumber: 5,
- uuid: 'abc-006',
- event_level: 3,
- parent_uuid: 'abc-004',
- },
- {
- id: 107,
- counter: 7,
- rowNumber: 6,
- uuid: 'abc-007',
- event_level: 2,
- parent_uuid: 'abc-002',
- },
- {
- id: 108,
- counter: 8,
- rowNumber: 7,
- uuid: 'abc-008',
- event_level: 1,
- parent_uuid: 'abc-001',
- },
- {
- id: 109,
- counter: 9,
- rowNumber: 8,
- uuid: 'abc-009',
- event_level: 2,
- parent_uuid: 'abc-008',
- },
- {
- id: 110,
- counter: 10,
- rowNumber: 9,
- uuid: 'abc-010',
- event_level: 2,
- parent_uuid: 'abc-008',
- },
- ]);
- wrapper.update();
- wrapper.find('#test').prop('toggleNodeIsCollapsed')('abc-004');
- wrapper.update();
-
- const node = wrapper.find('#test').prop('getNodeForRow')(5);
- expect(node.eventIndex).toEqual(8);
- expect(node.isCollapsed).toBe(false);
- });
-
- test('should skip full sub-tree of collapsed node', () => {
- wrapper = shallow();
- wrapper.find('#test').prop('addEvents')([
- { id: 101, counter: 1, rowNumber: 0, uuid: 'abc-001', event_level: 0 },
- {
- id: 102,
- counter: 2,
- rowNumber: 1,
- uuid: 'abc-002',
- event_level: 1,
- parent_uuid: 'abc-001',
- },
- {
- id: 103,
- counter: 3,
- rowNumber: 2,
- uuid: 'abc-003',
- event_level: 2,
- parent_uuid: 'abc-002',
- },
- {
- id: 104,
- counter: 4,
- rowNumber: 3,
- uuid: 'abc-004',
- event_level: 2,
- parent_uuid: 'abc-002',
- },
- {
- id: 105,
- counter: 5,
- rowNumber: 4,
- uuid: 'abc-005',
- event_level: 3,
- parent_uuid: 'abc-004',
- },
- {
- id: 106,
- counter: 6,
- rowNumber: 5,
- uuid: 'abc-006',
- event_level: 3,
- parent_uuid: 'abc-004',
- },
- {
- id: 107,
- counter: 7,
- rowNumber: 6,
- uuid: 'abc-007',
- event_level: 2,
- parent_uuid: 'abc-002',
- },
- {
- id: 108,
- counter: 8,
- rowNumber: 7,
- uuid: 'abc-008',
- event_level: 1,
- parent_uuid: 'abc-001',
- },
- {
- id: 109,
- counter: 9,
- rowNumber: 8,
- uuid: 'abc-009',
- event_level: 2,
- parent_uuid: 'abc-008',
- },
- {
- id: 110,
- counter: 10,
- rowNumber: 9,
- uuid: 'abc-010',
- event_level: 2,
- parent_uuid: 'abc-008',
- },
- ]);
- wrapper.find('#test').prop('toggleNodeIsCollapsed')('abc-002');
-
- const node = wrapper.find('#test').prop('getNodeForRow')(3);
- expect(node.eventIndex).toEqual(9);
- expect(node.isCollapsed).toBe(false);
- });
-
- test('should get node after gap in loaded children', async () => {
- const fetchChildrenSummary = jest.fn();
- fetchChildrenSummary.mockResolvedValue({
- data: {
- children_summary: {
- 1: { rowNumber: 0, numChildren: 52 },
- 2: { rowNumber: 1, numChildren: 3 },
- 6: { rowNumber: 5, numChildren: 47 },
- },
- meta_event_nested_uuid: {},
- },
- });
-
- wrapper = mount();
- const laterEvents = [
- {
- id: 151,
- counter: 51,
- rowNumber: 50,
- uuid: 'abc-051',
- event_level: 2,
- parent_uuid: 'abc-006',
- },
- {
- id: 152,
- counter: 52,
- rowNumber: 51,
- uuid: 'abc-052',
- event_level: 2,
- parent_uuid: 'abc-006',
- },
- {
- id: 153,
- counter: 53,
- rowNumber: 52,
- uuid: 'abc-052',
- event_level: 2,
- parent_uuid: 'abc-006',
- },
- ];
- await act(async () => {
- wrapper.find('#test').prop('addEvents')(eventsList);
- wrapper.find('#test').prop('addEvents')(laterEvents);
- });
- wrapper.update();
- wrapper.update();
-
- const node = wrapper.find('#test').prop('getNodeForRow')(51);
- expect(node).toEqual({
- eventIndex: 52,
- isCollapsed: false,
- children: [],
- });
- });
-
- test('should skip gaps in counter', () => {
- const nextNode = {
- id: 112,
- counter: 12,
- rowNumber: 9,
- uuid: 'abc-012',
- event_level: 0,
- parent_uuid: '',
- };
- wrapper.find('#test').prop('addEvents')([nextNode]);
-
- const node = wrapper.find('#test').prop('getNodeForRow')(9);
-
- expect(node).toEqual({
- eventIndex: 12,
- isCollapsed: false,
- children: [],
- });
- });
- });
-
- describe('getNumCollapsedEvents', () => {
- let wrapper;
- beforeEach(() => {
- wrapper = shallow();
- wrapper.find('#test').prop('addEvents')(eventsList);
- });
-
- test('should return number of collapsed events', () => {
- expect(wrapper.find('#test').prop('getNumCollapsedEvents')()).toEqual(0);
-
- wrapper.find('#test').prop('toggleNodeIsCollapsed')('abc-002');
- expect(wrapper.find('#test').prop('getNumCollapsedEvents')()).toEqual(3);
- });
- });
-
- describe('getEventforRow', () => {
- let wrapper;
- beforeEach(() => {
- wrapper = shallow();
- wrapper.find('#test').prop('addEvents')(eventsList);
- });
-
- test('should get event & node', () => {
- const { event, node } = wrapper.find('#test').prop('getEventForRow')(5);
- expect(event).toEqual(eventsList[5]);
- expect(node).toEqual({
- eventIndex: 6,
- isCollapsed: false,
- children: [
- { eventIndex: 7, isCollapsed: false, children: [] },
- { eventIndex: 8, isCollapsed: false, children: [] },
- { eventIndex: 9, isCollapsed: false, children: [] },
- ],
- });
- });
- });
-
- describe('getEvent', () => {
- let wrapper;
- beforeEach(() => {
- wrapper = shallow();
- wrapper.find('#test').prop('addEvents')(eventsList);
- });
-
- test('should get event object', () => {
- const event = wrapper.find('#test').prop('getEvent')(7);
- expect(event).toEqual(eventsList[6]);
- });
- });
-
- describe('getTotalNumChildren', () => {
- let wrapper;
-
- test('should not make call to get child events, because there are none for this job type', () => {
- wrapper = shallow();
- wrapper.find('#test').prop('addEvents')(eventsList);
- expect(callbacks.fetchChildrenSummary).not.toBeCalled();
- });
-
- test('should get basic number of children', () => {
- wrapper = shallow();
- wrapper.find('#test').prop('addEvents')(eventsList);
- expect(
- wrapper.find('#test').prop('getTotalNumChildren')('abc-002')
- ).toEqual(3);
- });
-
- test('should get total number of nested children', () => {
- wrapper = shallow();
- wrapper.find('#test').prop('addEvents')(eventsList);
- expect(
- wrapper.find('#test').prop('getTotalNumChildren')('abc-001')
- ).toEqual(8);
- });
- });
-
- describe('getCounterForRow', () => {
- test('should return exact counter when no nodes are collapsed', () => {
- const wrapper = shallow();
- wrapper.find('#test').prop('addEvents')(eventsList);
- const getCounterForRow = wrapper.find('#test').prop('getCounterForRow');
- expect(getCounterForRow(8)).toEqual(9);
- });
-
- test('should return estimated counter when node not loaded', () => {
- const wrapper = shallow();
- wrapper.find('#test').prop('addEvents')(eventsList);
- const getCounterForRow = wrapper.find('#test').prop('getCounterForRow');
- expect(getCounterForRow(12)).toEqual(13);
- });
-
- test('should return estimated counter when node is non-loaded child', async () => {
- callbacks.fetchChildrenSummary.mockResolvedValue({
- data: {
- 1: { rowNumber: 0, numChildren: 28 },
- 2: { rowNumber: 1, numChildren: 3 },
- 6: { rowNumber: 5, numChidren: 23 },
- },
- });
- const wrapper = mount();
- wrapper.update();
- await act(async () => {
- wrapper.find('#test').prop('addEvents')(eventsList);
- wrapper.find('#test').prop('addEvents')([
- {
- id: 130,
- counter: 30,
- rowNumber: 29,
- uuid: 'abc-030',
- event_level: 1,
- parent_uuid: 'abc-001',
- },
- ]);
- });
- wrapper.update();
-
- const getCounterForRow = wrapper.find('#test').prop('getCounterForRow');
-
- expect(getCounterForRow(15)).toEqual(16);
- });
-
- test('should skip over collapsed subtree', () => {
- const wrapper = shallow();
- wrapper.find('#test').prop('addEvents')(eventsList);
- wrapper.find('#test').prop('toggleNodeIsCollapsed')('abc-002');
- const getCounterForRow = wrapper.find('#test').prop('getCounterForRow');
- expect(getCounterForRow(4)).toEqual(8);
- });
-
- test('should estimate counter after skipping collapsed subtree', async () => {
- callbacks.fetchChildrenSummary.mockResolvedValue({
- data: {
- children_summary: {
- 1: { rowNumber: 0, numChildren: 85 },
- 2: { rowNumber: 1, numChildren: 66 },
- 69: { rowNumber: 68, numChildren: 17 },
- },
- meta_event_nested_uuid: {},
- },
- });
- const wrapper = mount();
- await act(async () => {
- wrapper.find('#test').prop('addEvents')([
- eventsList[0],
- eventsList[1],
- eventsList[2],
- eventsList[3],
- eventsList[4],
- {
- id: 169,
- counter: 69,
- rowNumber: 68,
- event_level: 2,
- uuid: 'abc-069',
- parent_uuid: 'abc-001',
- },
- ]);
- wrapper.find('#test').prop('toggleNodeIsCollapsed')('abc-002');
- });
- wrapper.update();
-
- const getCounterForRow = wrapper.find('#test').prop('getCounterForRow');
- expect(getCounterForRow(3)).toEqual(70);
- });
-
- test('should estimate counter in gap between loaded events', async () => {
- callbacks.fetchChildrenSummary.mockResolvedValue({
- data: {
- children_summary: {
- 1: { rowNumber: 0, numChildren: 30 },
- },
- meta_event_nested_uuid: {},
- },
- });
- const wrapper = mount();
- await act(async () => {
- wrapper.find('#test').prop('addEvents')([
- eventsList[0],
- {
- id: 102,
- counter: 2,
- rowNumber: 1,
- uuid: 'abc-002',
- event_level: 1,
- parent_uuid: 'abc-001',
- },
- {
- id: 103,
- counter: 3,
- rowNumber: 2,
- uuid: 'abc-003',
- event_level: 1,
- parent_uuid: 'abc-001',
- },
- {
- id: 120,
- counter: 20,
- rowNumber: 19,
- uuid: 'abc-020',
- event_level: 1,
- parent_uuid: 'abc-001',
- },
- {
- id: 121,
- counter: 21,
- rowNumber: 20,
- uuid: 'abc-021',
- event_level: 1,
- parent_uuid: 'abc-001',
- },
- {
- id: 122,
- counter: 22,
- rowNumber: 21,
- uuid: 'abc-022',
- event_level: 1,
- parent_uuid: 'abc-001',
- },
- ]);
- });
- wrapper.update();
-
- const getCounterForRow = wrapper.find('#test').prop('getCounterForRow');
- expect(getCounterForRow(10)).toEqual(11);
- });
-
- test('should estimate counter in gap before loaded sibling events', async () => {
- callbacks.fetchChildrenSummary.mockResolvedValue({
- data: {
- children_summary: {
- 1: { rowNumber: 0, numChildren: 30 },
- },
- meta_event_nested_uuid: {},
- },
- });
- const wrapper = mount();
- await act(async () => {
- wrapper.find('#test').prop('addEvents')([
- eventsList[0],
- {
- id: 120,
- counter: 20,
- rowNumber: 19,
- uuid: 'abc-020',
- event_level: 1,
- parent_uuid: 'abc-001',
- },
- {
- id: 121,
- counter: 21,
- rowNumber: 20,
- uuid: 'abc-021',
- event_level: 1,
- parent_uuid: 'abc-001',
- },
- {
- id: 122,
- counter: 22,
- rowNumber: 21,
- uuid: 'abc-022',
- event_level: 1,
- parent_uuid: 'abc-001',
- },
- ]);
- });
- wrapper.update();
-
- const getCounterForRow = wrapper.find('#test').prop('getCounterForRow');
- expect(getCounterForRow(10)).toEqual(11);
- });
-
- test('should get counter for node between unloaded siblings', async () => {
- callbacks.fetchChildrenSummary.mockResolvedValue({
- data: {
- children_summary: {
- 1: { rowNumber: 0, numChildren: 30 },
- },
- meta_event_nested_uuid: {},
- },
- });
- const wrapper = mount();
- await act(async () => {
- wrapper.find('#test').prop('addEvents')([
- eventsList[0],
- {
- id: 109,
- counter: 9,
- rowNumber: 8,
- uuid: 'abc-009',
- event_level: 1,
- parent_uuid: 'abc-001',
- },
- {
- id: 110,
- counter: 10,
- rowNumber: 9,
- uuid: 'abc-010',
- event_level: 1,
- parent_uuid: 'abc-001',
- },
- {
- id: 111,
- counter: 11,
- rowNumber: 10,
- uuid: 'abc-011',
- event_level: 1,
- parent_uuid: 'abc-001',
- },
- ]);
- });
- wrapper.update();
-
- const getCounterForRow = wrapper.find('#test').prop('getCounterForRow');
- expect(getCounterForRow(10)).toEqual(11);
- });
- });
-});
diff --git a/awx/ui/src/screens/Job/JobTypeRedirect.js b/awx/ui/src/screens/Job/JobTypeRedirect.js
deleted file mode 100644
index 69c906dc4ccb..000000000000
--- a/awx/ui/src/screens/Job/JobTypeRedirect.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { Redirect, Link } from 'react-router-dom';
-import { PageSection, Card } from '@patternfly/react-core';
-
-import { t } from '@lingui/macro';
-import useRequest from 'hooks/useRequest';
-import { UnifiedJobsAPI } from 'api';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import { JOB_TYPE_URL_SEGMENTS } from '../../constants';
-
-const NOT_FOUND = 'not found';
-
-function JobTypeRedirect({ id, path, view }) {
- const {
- isLoading,
- error,
- result: { job },
- request: loadJob,
- } = useRequest(
- useCallback(async () => {
- const {
- data: { results },
- } = await UnifiedJobsAPI.read({ id });
- const [item] = results;
- return { job: item };
- }, [id]),
- { job: {} }
- );
- useEffect(() => {
- loadJob();
- }, [loadJob]);
-
- if (error) {
- return (
-
-
- {error === NOT_FOUND ? (
-
- {t`View all Jobs`}
-
- ) : (
-
- )}
-
-
- );
- }
- if (isLoading || !job?.id) {
- return (
-
-
-
-
-
- );
- }
- const typeSegment = JOB_TYPE_URL_SEGMENTS[job.type];
- return ;
-}
-
-JobTypeRedirect.defaultProps = {
- view: 'output',
-};
-export default JobTypeRedirect;
diff --git a/awx/ui/src/screens/Job/Jobs.js b/awx/ui/src/screens/Job/Jobs.js
deleted file mode 100644
index aed47b5cd12d..000000000000
--- a/awx/ui/src/screens/Job/Jobs.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import React, { useState, useCallback } from 'react';
-import { Route, Switch, useParams, useRouteMatch } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { PageSection } from '@patternfly/react-core';
-import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
-import JobList from 'components/JobList';
-import PersistentFilters from 'components/PersistentFilters';
-import Job from './Job';
-import JobTypeRedirect from './JobTypeRedirect';
-import { JOB_TYPE_URL_SEGMENTS } from '../../constants';
-
-function TypeRedirect({ view }) {
- const { id } = useParams();
- const { path } = useRouteMatch();
- return ;
-}
-
-function Jobs() {
- const match = useRouteMatch();
- const [breadcrumbConfig, setBreadcrumbConfig] = useState({
- '/jobs': t`Jobs`,
- });
-
- const buildBreadcrumbConfig = useCallback((job) => {
- if (!job) {
- return;
- }
-
- const typeSegment = JOB_TYPE_URL_SEGMENTS[job.type];
- setBreadcrumbConfig({
- '/jobs': t`Jobs`,
- [`/jobs/${typeSegment}/${job.id}`]: `${job.id} - ${job.name}`,
- [`/jobs/${typeSegment}/${job.id}/output`]: t`Output`,
- [`/jobs/${typeSegment}/${job.id}/details`]: t`Details`,
- });
- }, []);
-
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-}
-
-export { Jobs as _Jobs };
-export default Jobs;
diff --git a/awx/ui/src/screens/Job/Jobs.test.js b/awx/ui/src/screens/Job/Jobs.test.js
deleted file mode 100644
index 55685907f84b..000000000000
--- a/awx/ui/src/screens/Job/Jobs.test.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import Jobs from './Jobs';
-
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useRouteMatch: () => ({
- path: '/',
- }),
-}));
-
-describe('', () => {
- test('initially renders successfully', async () => {
- const wrapper = shallow();
- expect(wrapper.find('JobList')).toHaveLength(1);
- });
-
- test('should display a breadcrumb heading', () => {
- const wrapper = shallow();
- const screenHeader = wrapper.find('ScreenHeader');
- expect(screenHeader).toHaveLength(1);
- expect(screenHeader.prop('breadcrumbConfig')).toEqual({
- '/jobs': 'Jobs',
- });
- });
-});
diff --git a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutput.js b/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutput.js
deleted file mode 100644
index 9b4cc98d2294..000000000000
--- a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutput.js
+++ /dev/null
@@ -1,122 +0,0 @@
-import React, { useEffect, useReducer } from 'react';
-
-import styled from 'styled-components';
-import { shape } from 'prop-types';
-import { CardBody as PFCardBody } from '@patternfly/react-core';
-import {
- WorkflowDispatchContext,
- WorkflowStateContext,
-} from 'contexts/Workflow';
-import { layoutGraph } from 'components/Workflow/WorkflowUtils';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import workflowReducer, {
- initReducer,
-} from 'components/Workflow/workflowReducer';
-import { WorkflowJobsAPI } from 'api';
-import WorkflowOutputGraph from './WorkflowOutputGraph';
-import WorkflowOutputToolbar from './WorkflowOutputToolbar';
-import useWsWorkflowOutput from './useWsWorkflowOutput';
-
-const CardBody = styled(PFCardBody)`
- display: flex;
- flex-direction: column;
- height: calc(100vh - 240px);
-`;
-
-const Wrapper = styled.div`
- display: flex;
- flex-flow: column;
- height: 100%;
- position: relative;
-`;
-
-const fetchWorkflowNodes = async (jobId, pageNo = 1, nodes = []) => {
- const { data } = await WorkflowJobsAPI.readNodes(jobId, {
- page_size: 200,
- page: pageNo,
- });
-
- if (data.next) {
- return fetchWorkflowNodes(jobId, pageNo + 1, nodes.concat(data.results));
- }
- return nodes.concat(data.results);
-};
-
-function WorkflowOutput({ job }) {
- const [state, dispatch] = useReducer(workflowReducer, {}, initReducer);
- const { contentError, isLoading, links, nodePositions, nodes } = state;
-
- useEffect(() => {
- async function fetchData() {
- try {
- const workflowNodes = await fetchWorkflowNodes(job.id);
- dispatch({
- type: 'GENERATE_NODES_AND_LINKS',
- nodes: workflowNodes,
- });
- } catch (error) {
- dispatch({ type: 'SET_CONTENT_ERROR', value: error });
- } finally {
- dispatch({ type: 'SET_IS_LOADING', value: false });
- }
- }
- dispatch({ type: 'RESET' });
- fetchData();
- }, [job.id]);
-
- // Update positions of nodes/links
- useEffect(() => {
- if (nodes) {
- const newNodePositions = {};
- const g = layoutGraph(nodes, links);
-
- g.nodes().forEach((node) => {
- newNodePositions[node] = g.node(node);
- });
-
- dispatch({ type: 'SET_NODE_POSITIONS', value: newNodePositions });
- }
- }, [job.id, links, nodes]);
-
- const updatedNodes = useWsWorkflowOutput(job.id, nodes);
-
- useEffect(() => {
- dispatch({ type: 'SET_NODES', value: updatedNodes });
- }, [updatedNodes]);
-
- if (isLoading) {
- return (
-
-
-
- );
- }
-
- if (contentError) {
- return (
-
-
-
- );
- }
-
- return (
-
-
-
-
-
- {nodePositions && }
-
-
-
-
- );
-}
-
-WorkflowOutput.propTypes = {
- job: shape().isRequired,
-};
-
-export default WorkflowOutput;
diff --git a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutput.test.js b/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutput.test.js
deleted file mode 100644
index 5088561c3574..000000000000
--- a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutput.test.js
+++ /dev/null
@@ -1,151 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { WorkflowJobsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import WorkflowOutput from './WorkflowOutput';
-
-jest.mock('../../../api');
-
-const job = {
- id: 1,
- name: 'Foo JT',
- status: 'successful',
-};
-
-const mockWorkflowJobNodes = [
- {
- id: 8,
- success_nodes: [10],
- failure_nodes: [],
- always_nodes: [9],
- summary_fields: {
- job: {
- elapsed: 10,
- id: 14,
- name: 'A Playbook',
- status: 'successful',
- type: 'job',
- },
- },
- },
- {
- id: 9,
- success_nodes: [],
- failure_nodes: [],
- always_nodes: [],
- summary_fields: {
- job: {
- elapsed: 10,
- id: 14,
- name: 'A Project Update',
- status: 'successful',
- type: 'project_update',
- },
- },
- },
- {
- id: 10,
- success_nodes: [],
- failure_nodes: [],
- always_nodes: [],
- summary_fields: {
- job: {
- elapsed: 10,
- id: 14,
- name: 'An Inventory Source Sync',
- status: 'successful',
- type: 'inventory_update',
- },
- },
- },
- {
- id: 11,
- success_nodes: [9],
- failure_nodes: [],
- always_nodes: [],
- summary_fields: {
- job: {
- elapsed: 10,
- id: 14,
- name: 'Pause',
- status: 'successful',
- type: 'workflow_approval',
- },
- },
- },
-];
-
-describe('WorkflowOutput', () => {
- let wrapper;
- beforeEach(() => {
- WorkflowJobsAPI.readNodes.mockResolvedValue({
- data: {
- count: mockWorkflowJobNodes.length,
- results: mockWorkflowJobNodes,
- },
- });
- window.SVGElement.prototype.height = {
- baseVal: {
- value: 100,
- },
- };
- window.SVGElement.prototype.width = {
- baseVal: {
- value: 100,
- },
- };
- window.SVGElement.prototype.getBBox = () => ({
- x: 0,
- y: 0,
- width: 500,
- height: 250,
- });
-
- window.SVGElement.prototype.getBoundingClientRect = () => ({
- x: 303,
- y: 252.359375,
- width: 1329,
- height: 259.640625,
- top: 252.359375,
- right: 1632,
- bottom: 512,
- left: 303,
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- delete window.SVGElement.prototype.getBBox;
- delete window.SVGElement.prototype.getBoundingClientRect;
- delete window.SVGElement.prototype.height;
- delete window.SVGElement.prototype.width;
- });
-
- test('renders successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
- expect(wrapper.find('ContentError')).toHaveLength(0);
- expect(wrapper.find('WorkflowStartNode')).toHaveLength(1);
- expect(wrapper.find('WorkflowOutputNode')).toHaveLength(4);
- expect(wrapper.find('WorkflowOutputLink')).toHaveLength(5);
- });
-
- test('error shown to user when error thrown fetching workflow job nodes', async () => {
- WorkflowJobsAPI.readNodes.mockRejectedValue(new Error());
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
- expect(wrapper.find('ContentError')).toHaveLength(1);
- });
-});
diff --git a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputGraph.js b/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputGraph.js
deleted file mode 100644
index a4c366ad9553..000000000000
--- a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputGraph.js
+++ /dev/null
@@ -1,189 +0,0 @@
-import 'styled-components/macro';
-import React, { useContext, useEffect, useRef, useState } from 'react';
-import * as d3 from 'd3';
-import { WorkflowStateContext } from 'contexts/Workflow';
-import {
- getScaleAndOffsetToFit,
- getTranslatePointsForZoom,
-} from 'components/Workflow/WorkflowUtils';
-import {
- WorkflowHelp,
- WorkflowLegend,
- WorkflowLinkHelp,
- WorkflowNodeHelp,
- WorkflowStartNode,
- WorkflowTools,
-} from 'components/Workflow';
-import WorkflowOutputLink from './WorkflowOutputLink';
-import WorkflowOutputNode from './WorkflowOutputNode';
-
-function WorkflowOutputGraph() {
- const [linkHelp, setLinkHelp] = useState();
- const [nodeHelp, setNodeHelp] = useState();
- const [zoomPercentage, setZoomPercentage] = useState(100);
- const svgRef = useRef(null);
- const gRef = useRef(null);
-
- const { links, nodePositions, nodes, showLegend, showTools } =
- useContext(WorkflowStateContext);
-
- // This is the zoom function called by using the mousewheel/click and drag
- const zoom = (event) => {
- const translation = [event.transform.x, event.transform.y];
- d3.select(gRef.current).attr(
- 'transform',
- `translate(${translation}) scale(${event.transform.k})`
- );
- setZoomPercentage(event.transform.k * 100);
- };
-
- const handlePan = (direction) => {
- const transform = d3.zoomTransform(d3.select(svgRef.current).node());
- let { x: xPos, y: yPos } = transform;
- const { k: currentScale } = transform;
- switch (direction) {
- case 'up':
- yPos -= 50;
- break;
- case 'down':
- yPos += 50;
- break;
- case 'left':
- xPos -= 50;
- break;
- case 'right':
- xPos += 50;
- break;
- default:
- // Throw an error?
- break;
- }
- d3.select(svgRef.current).call(
- zoomRef.transform,
- d3.zoomIdentity.translate(xPos, yPos).scale(currentScale)
- );
- };
- const handlePanToMiddle = () => {
- const svgBoundingClientRect = svgRef.current.getBoundingClientRect();
- d3.select(svgRef.current).call(
- zoomRef.transform,
- d3.zoomIdentity
- .translate(0, svgBoundingClientRect.height / 2 - 30)
- .scale(1)
- );
- setZoomPercentage(100);
- };
-
- const handleZoomChange = (newScale) => {
- const svgBoundingClientRect = svgRef.current.getBoundingClientRect();
- const currentScaleAndOffset = d3.zoomTransform(
- d3.select(svgRef.current).node()
- );
- const [translateX, translateY] = getTranslatePointsForZoom(
- svgBoundingClientRect,
- currentScaleAndOffset,
- newScale
- );
- d3.select(svgRef.current).call(
- zoomRef.transform,
- d3.zoomIdentity.translate(translateX, translateY).scale(newScale)
- );
- setZoomPercentage(newScale * 100);
- };
- const handleFitGraph = () => {
- const { k: currentScale } = d3.zoomTransform(
- d3.select(svgRef.current).node()
- );
- const gBoundingClientRect = d3
- .select(gRef.current)
- .node()
- .getBoundingClientRect();
-
- const gBBoxDimensions = d3.select(gRef.current).node().getBBox();
-
- const svgBoundingClientRect = svgRef.current.getBoundingClientRect();
- const [scaleToFit, yTranslate] = getScaleAndOffsetToFit(
- gBoundingClientRect,
- svgBoundingClientRect,
- gBBoxDimensions,
- currentScale
- );
- d3.select(svgRef.current).call(
- zoomRef.transform,
- d3.zoomIdentity.translate(0, yTranslate).scale(scaleToFit)
- );
- setZoomPercentage(scaleToFit * 100);
- };
-
- const zoomRef = d3.zoom().scaleExtent([0.1, 2]).on('zoom', zoom);
-
- // Initialize the zoom
- useEffect(() => {
- d3.select(svgRef.current).call(zoomRef);
- }, [zoomRef]);
- // Attempt to zoom the graph to fit the available screen space
- useEffect(() => {
- handleFitGraph();
- // We only want this to run once (when the component mounts)
- // Including handleFitGraph in the deps array will cause this to
- // run very frequently.
- // Discussion: https://github.com/facebook/create-react-app/issues/6880
- // and https://github.com/facebook/react/issues/15865 amongst others
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
- return (
- <>
- {(nodeHelp || linkHelp) && (
-
- {nodeHelp && }
- {linkHelp && }
-
- )}
-
-
- {showTools && (
-
- )}
- {showLegend && }
-
- >
- );
-}
-export default WorkflowOutputGraph;
diff --git a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputGraph.test.js b/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputGraph.test.js
deleted file mode 100644
index 1e8828a17625..000000000000
--- a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputGraph.test.js
+++ /dev/null
@@ -1,242 +0,0 @@
-import React from 'react';
-import { WorkflowStateContext } from 'contexts/Workflow';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import WorkflowOutputGraph from './WorkflowOutputGraph';
-
-const workflowContext = {
- links: [
- {
- source: {
- id: 2,
- },
- target: {
- id: 4,
- },
- linkType: 'success',
- },
- {
- source: {
- id: 2,
- },
- target: {
- id: 3,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 5,
- },
- target: {
- id: 3,
- },
- linkType: 'success',
- },
- {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
- linkType: 'always',
- },
- {
- source: {
- id: 1,
- },
- target: {
- id: 5,
- },
- linkType: 'success',
- },
- ],
- nodePositions: {
- 1: { label: '', width: 72, height: 40, x: 36, y: 85 },
- 2: { label: '', width: 180, height: 60, x: 282, y: 40 },
- 3: { label: '', width: 180, height: 60, x: 582, y: 130 },
- 4: { label: '', width: 180, height: 60, x: 582, y: 30 },
- 5: { label: '', width: 180, height: 60, x: 282, y: 140 },
- },
- nodes: [
- {
- id: 1,
- },
- {
- id: 2,
- originalNodeObject: {
- identifier: 'Node identifier',
- summary_fields: {
- job: {
- name: 'Foo JT',
- type: 'job',
- status: 'successful',
- elapsed: 60,
- },
- unified_job_template: {
- name: 'Foo JT',
- type: 'job_template',
- },
- },
- },
- },
- {
- id: 3,
- },
- {
- id: 4,
- },
- {
- id: 5,
- },
- ],
- showLegend: false,
- showTools: false,
-};
-
-describe('WorkflowOutputGraph', () => {
- beforeEach(() => {
- window.SVGElement.prototype.height = {
- baseVal: {
- value: 100,
- },
- };
- window.SVGElement.prototype.width = {
- baseVal: {
- value: 100,
- },
- };
- window.SVGElement.prototype.getBBox = () => ({
- x: 0,
- y: 0,
- width: 500,
- height: 250,
- });
-
- window.SVGElement.prototype.getBoundingClientRect = () => ({
- x: 303,
- y: 252.359375,
- width: 1329,
- height: 259.640625,
- top: 252.359375,
- right: 1632,
- bottom: 512,
- left: 303,
- });
- });
-
- afterEach(() => {
- delete window.SVGElement.prototype.getBBox;
- delete window.SVGElement.prototype.getBoundingClientRect;
- delete window.SVGElement.prototype.height;
- delete window.SVGElement.prototype.width;
- });
-
- test('mounts successfully', () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper).toHaveLength(1);
- });
-
- test('tools and legend are shown when flags are true', () => {
- const wrapper = mountWithContexts(
-
- );
-
- expect(wrapper.find('WorkflowLegend')).toHaveLength(1);
- expect(wrapper.find('WorkflowTools')).toHaveLength(1);
- });
-
- test('nodes and links are properly rendered', () => {
- const wrapper = mountWithContexts(
-
- );
-
- expect(wrapper.find('WorkflowStartNode')).toHaveLength(1);
- expect(wrapper.find('WorkflowOutputNode')).toHaveLength(4);
- expect(wrapper.find('WorkflowOutputLink')).toHaveLength(5);
- expect(wrapper.find('#link-2-4')).toHaveLength(1);
- expect(wrapper.find('#link-2-3')).toHaveLength(1);
- expect(wrapper.find('#link-5-3')).toHaveLength(1);
- expect(wrapper.find('#link-1-2')).toHaveLength(1);
- expect(wrapper.find('#link-1-5')).toHaveLength(1);
- });
-
- test('proper help text is shown when hovering over links and nodes', () => {
- const wrapper = mountWithContexts(
-
- );
-
- expect(wrapper.find('WorkflowNodeHelp')).toHaveLength(0);
- expect(wrapper.find('WorkflowLinkHelp')).toHaveLength(0);
- wrapper.find('g#node-2').simulate('mouseenter');
- expect(wrapper.find('WorkflowNodeHelp')).toHaveLength(1);
- expect(
- wrapper.find('WorkflowNodeHelp').contains(Node Alias)
- ).toEqual(true);
- expect(
- wrapper
- .find('WorkflowNodeHelp')
- .containsMatchingElement(Node identifier)
- ).toEqual(true);
- expect(
- wrapper.find('WorkflowNodeHelp').contains(Resource Name)
- ).toEqual(true);
- expect(
- wrapper.find('WorkflowNodeHelp').containsMatchingElement(Foo JT)
- ).toEqual(true);
- expect(wrapper.find('WorkflowNodeHelp').contains(Type)).toEqual(
- true
- );
- expect(
- wrapper
- .find('WorkflowNodeHelp')
- .containsMatchingElement(Job Template)
- ).toEqual(true);
- expect(
- wrapper.find('WorkflowNodeHelp').contains(Job Status)
- ).toEqual(true);
- expect(
- wrapper
- .find('WorkflowNodeHelp')
- .containsMatchingElement(Successful)
- ).toEqual(true);
- expect(wrapper.find('WorkflowNodeHelp').contains(Elapsed)).toEqual(
- true
- );
- expect(
- wrapper
- .find('WorkflowNodeHelp')
- .containsMatchingElement(00:01:00)
- ).toEqual(true);
- wrapper.find('g#node-2').simulate('mouseleave');
- expect(wrapper.find('WorkflowNodeHelp')).toHaveLength(0);
- wrapper.find('g#link-2-3').simulate('mouseenter');
- expect(wrapper.find('WorkflowLinkHelp')).toHaveLength(1);
- expect(wrapper.find('WorkflowLinkHelp').contains(Run)).toEqual(true);
- expect(
- wrapper.find('WorkflowLinkHelp').containsMatchingElement(Always)
- ).toEqual(true);
- wrapper.find('g#link-2-3').simulate('mouseleave');
- expect(wrapper.find('WorkflowLinkHelp')).toHaveLength(0);
- });
-});
diff --git a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputLink.js b/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputLink.js
deleted file mode 100644
index 2fd8103cc10e..000000000000
--- a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputLink.js
+++ /dev/null
@@ -1,76 +0,0 @@
-import React, { useContext, useEffect, useRef, useState } from 'react';
-import { func, shape } from 'prop-types';
-import { WorkflowStateContext } from 'contexts/Workflow';
-import {
- generateLine,
- getLinePoints,
- getLinkOverlayPoints,
-} from 'components/Workflow/WorkflowUtils';
-
-function WorkflowOutputLink({ link, mouseEnter, mouseLeave }) {
- const ref = useRef(null);
- const [hovering, setHovering] = useState(false);
- const [pathD, setPathD] = useState();
- const [pathStroke, setPathStroke] = useState('#CCCCCC');
- const { nodePositions } = useContext(WorkflowStateContext);
-
- const handleLinkMouseEnter = () => {
- ref.current.parentNode.appendChild(ref.current);
- setHovering(true);
- mouseEnter();
- };
-
- const handleLinkMouseLeave = () => {
- ref.current.parentNode.prepend(ref.current);
- setHovering(null);
- mouseLeave();
- };
-
- useEffect(() => {
- if (link.linkType === 'failure') {
- setPathStroke('#d9534f');
- }
- if (link.linkType === 'success') {
- setPathStroke('#5cb85c');
- }
- if (link.linkType === 'always') {
- setPathStroke('#337ab7');
- }
- }, [link.linkType]);
-
- useEffect(() => {
- const linePoints = getLinePoints(link, nodePositions);
- setPathD(generateLine(linePoints));
- }, [link, nodePositions]);
-
- return (
-
-
-
- mouseEnter()}
- onMouseLeave={() => mouseLeave()}
- opacity="0"
- points={getLinkOverlayPoints(link, nodePositions)}
- />
-
- );
-}
-
-WorkflowOutputLink.propTypes = {
- link: shape().isRequired,
- mouseEnter: func.isRequired,
- mouseLeave: func.isRequired,
-};
-
-export default WorkflowOutputLink;
diff --git a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputLink.test.js b/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputLink.test.js
deleted file mode 100644
index 97b91518e345..000000000000
--- a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputLink.test.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import React from 'react';
-import { mount } from 'enzyme';
-import { WorkflowStateContext } from 'contexts/Workflow';
-import WorkflowOutputLink from './WorkflowOutputLink';
-
-const link = {
- source: {
- id: 1,
- },
- target: {
- id: 2,
- },
-};
-
-const nodePositions = {
- 1: {
- width: 72,
- height: 40,
- x: 0,
- y: 0,
- },
- 2: {
- width: 180,
- height: 60,
- x: 282,
- y: 40,
- },
-};
-
-describe('WorkflowOutputLink', () => {
- test('mounts successfully', () => {
- const wrapper = mount(
-
- );
- expect(wrapper).toHaveLength(1);
- });
-});
diff --git a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputNode.js b/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputNode.js
deleted file mode 100644
index 153a14b6c1a8..000000000000
--- a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputNode.js
+++ /dev/null
@@ -1,186 +0,0 @@
-import React, { useContext } from 'react';
-import { useHistory } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import styled from 'styled-components';
-import { func, shape } from 'prop-types';
-import { WorkflowStateContext } from 'contexts/Workflow';
-import StatusIcon from 'components/StatusIcon';
-import { WorkflowNodeTypeLetter } from 'components/Workflow';
-import { secondsToHHMMSS } from 'util/dates';
-import { stringIsUUID } from 'util/strings';
-import { constants as wfConstants } from 'components/Workflow/WorkflowUtils';
-
-const NodeG = styled.g`
- cursor: ${(props) => (props.job ? 'pointer' : 'default')};
-`;
-
-const JobTopLine = styled.div`
- align-items: center;
- display: flex;
- margin-top: 5px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-
- p {
- margin-left: 10px;
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
- }
-`;
-
-const Elapsed = styled.div`
- margin-top: 5px;
- text-align: center;
-
- span {
- font-size: 12px;
- font-weight: bold;
- background-color: #ededed;
- padding: 3px 12px;
- border-radius: 14px;
- }
-`;
-
-const NodeContents = styled.div`
- font-size: 13px;
- padding: 0px 10px;
-`;
-
-const NodeDefaultLabel = styled.p`
- margin-top: 20px;
- overflow: hidden;
- text-align: center;
- text-overflow: ellipsis;
- white-space: nowrap;
-`;
-
-const ConvergenceLabel = styled.p`
- font-size: 12px;
- color: #ffffff;
-`;
-
-Elapsed.displayName = 'Elapsed';
-
-function WorkflowOutputNode({ mouseEnter, mouseLeave, node }) {
- const history = useHistory();
- const { nodePositions } = useContext(WorkflowStateContext);
- const job = node?.originalNodeObject?.summary_fields?.job;
-
- let borderColor = '#93969A';
-
- if (job) {
- if (
- job.status === 'failed' ||
- job.status === 'error' ||
- job.status === 'canceled'
- ) {
- borderColor = '#d9534f';
- }
- if (job.status === 'successful' || job.status === 'ok') {
- borderColor = '#5cb85c';
- }
- }
-
- const handleNodeClick = () => {
- if (job) {
- const basePath =
- job.type !== 'workflow_approval' ? 'jobs' : 'workflow_approvals';
- history.push(`/${basePath}/${job.id}/details`);
- }
- };
-
- let nodeName;
-
- if (
- node?.identifier ||
- (node?.originalNodeObject?.identifier &&
- !stringIsUUID(node.originalNodeObject.identifier))
- ) {
- nodeName = node?.identifier
- ? node?.identifier
- : node?.originalNodeObject?.identifier;
- } else {
- nodeName =
- node?.fullUnifiedJobTemplate?.name ||
- node?.originalNodeObject?.summary_fields?.unified_job_template?.name ||
- t`DELETED`;
- }
-
- return (
-
- {(node.all_parents_must_converge ||
- node?.originalNodeObject?.all_parents_must_converge) && (
- <>
-
-
- {t`ALL`}
-
- >
- )}
-
-
-
- {job ? (
- <>
-
- {job.status !== 'pending' && }
- {nodeName}
-
- {!!job?.elapsed && (
- {secondsToHHMMSS(job.elapsed)}
- )}
- >
- ) : (
- {nodeName}
- )}
-
-
- {(node.unifiedJobTemplate || job) && (
-
- )}
-
- );
-}
-
-WorkflowOutputNode.propTypes = {
- mouseEnter: func.isRequired,
- mouseLeave: func.isRequired,
- node: shape().isRequired,
-};
-
-export default WorkflowOutputNode;
diff --git a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputNode.test.js b/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputNode.test.js
deleted file mode 100644
index 415c7712c80a..000000000000
--- a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputNode.test.js
+++ /dev/null
@@ -1,121 +0,0 @@
-import React from 'react';
-import { WorkflowStateContext } from 'contexts/Workflow';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import WorkflowOutputNode from './WorkflowOutputNode';
-
-const nodeWithJT = {
- id: 2,
- originalNodeObject: {
- summary_fields: {
- job: {
- elapsed: 7,
- id: 9000,
- name: 'Automation JT',
- status: 'successful',
- type: 'job',
- },
- unified_job_template: {
- name: 'Automation JT',
- },
- },
- unifiedJobTemplate: {
- id: 77,
- name: 'Automation JT',
- unified_job_type: 'job',
- },
- },
-};
-
-const nodeWithoutJT = {
- id: 2,
- originalNodeObject: {
- summary_fields: {
- job: {
- elapsed: 7,
- id: 9000,
- name: 'Automation JT 2',
- status: 'successful',
- type: 'job',
- },
- unified_job_template: {
- name: 'Automation JT 2',
- },
- },
- },
-};
-
-const nodePositions = {
- 1: {
- width: 72,
- height: 40,
- x: 0,
- y: 0,
- },
- 2: {
- width: 180,
- height: 60,
- x: 282,
- y: 40,
- },
-};
-
-describe('WorkflowOutputNode', () => {
- test('mounts successfully', () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper).toHaveLength(1);
- });
- test('node contents displayed correctly when Job and Job Template exist', () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.text('p')).toContain('Automation JT');
- expect(wrapper.find('WorkflowOutputNode Elapsed').text()).toBe('00:00:07');
- });
- test('node contents displayed correctly when Job Template deleted', () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.contains(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/src/screens/Job/WorkflowOutput/WorkflowOutputToolbar.js b/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputToolbar.js
deleted file mode 100644
index 55b826292192..000000000000
--- a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputToolbar.js
+++ /dev/null
@@ -1,135 +0,0 @@
-import React, { useContext } from 'react';
-import { useHistory } from 'react-router-dom';
-import { t } from '@lingui/macro';
-import { shape } from 'prop-types';
-import { Badge as PFBadge, Button, Tooltip } from '@patternfly/react-core';
-
-import {
- CompassIcon,
- WrenchIcon,
- ProjectDiagramIcon,
-} from '@patternfly/react-icons';
-import styled from 'styled-components';
-import StatusLabel from 'components/StatusLabel';
-import JobCancelButton from 'components/JobCancelButton';
-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`
- display: inline-flex;
- align-items: center;
-
- h1 {
- margin-right: 10px;
- font-weight: var(--pf-global--FontWeight--bold);
- }
-`;
-
-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;
- }
-`;
-function WorkflowOutputToolbar({ job }) {
- const dispatch = useContext(WorkflowDispatchContext);
- const history = useHistory();
- const { nodes, showLegend, showTools } = useContext(WorkflowStateContext);
-
- const totalNodes = nodes.reduce((n, node) => n + !node.isDeleted, 0) - 1;
- const navToWorkflow = () => {
- history.push(
- `/templates/workflow_job_template/${job.unified_job_template}/visualizer`
- );
- };
- return (
-
-
- {job.name}
-
-
-
- {['new', 'pending', 'waiting', 'running'].includes(job?.status) &&
- job?.summary_fields?.user_capabilities?.start ? (
-
- ) : null}
-
-
-
-
- {t`Total Nodes`}
- {totalNodes}
-
- dispatch({ type: 'TOGGLE_LEGEND' })}
- variant="plain"
- >
-
-
-
-
- dispatch({ type: 'TOGGLE_TOOLS' })}
- variant="plain"
- >
-
-
-
-
-
- );
-}
-
-WorkflowOutputToolbar.propTypes = {
- job: shape().isRequired,
-};
-
-export default WorkflowOutputToolbar;
diff --git a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputToolbar.test.js b/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputToolbar.test.js
deleted file mode 100644
index a2bb9967ceb5..000000000000
--- a/awx/ui/src/screens/Job/WorkflowOutput/WorkflowOutputToolbar.test.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import React from 'react';
-import {
- WorkflowDispatchContext,
- WorkflowStateContext,
-} from 'contexts/Workflow';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import WorkflowOutputToolbar from './WorkflowOutputToolbar';
-
-let wrapper;
-const dispatch = jest.fn();
-const job = {
- id: 1,
- status: 'running',
- summary_fields: {
- user_capabilities: {
- start: true,
- },
- },
-};
-const workflowContext = {
- nodes: [],
- showLegend: false,
- showTools: false,
-};
-
-function shouldFind(element) {
- expect(wrapper.find(element)).toHaveLength(1);
-}
-describe('WorkflowOutputToolbar', () => {
- beforeAll(() => {
- const nodes = [
- {
- id: 1,
- },
- {
- id: 2,
- },
- {
- id: 3,
- isDeleted: true,
- },
- ];
- wrapper = mountWithContexts(
-
-
-
-
-
- );
- });
-
- test('should render correct toolbar item', () => {
- shouldFind(`Button[ouiaId="edit-workflow"]`);
- shouldFind('Button#workflow-output-toggle-legend');
- shouldFind('Badge');
- shouldFind('Button#workflow-output-toggle-tools');
- shouldFind('JobCancelButton');
- });
-
- test('Shows correct number of nodes', () => {
- // The start node (id=1) and deleted nodes (isDeleted=true) should be ignored
- expect(wrapper.find('Badge').text()).toBe('1');
- });
-
- test('Toggle Legend button dispatches as expected', () => {
- wrapper.find('CompassIcon').simulate('click');
- expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_LEGEND' });
- });
-
- test('Toggle Tools button dispatches as expected', () => {
- wrapper.find('WrenchIcon').simulate('click');
- expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_TOOLS' });
- });
-});
diff --git a/awx/ui/src/screens/Job/WorkflowOutput/index.js b/awx/ui/src/screens/Job/WorkflowOutput/index.js
deleted file mode 100644
index 879db4950260..000000000000
--- a/awx/ui/src/screens/Job/WorkflowOutput/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export { default as WorkflowOutput } from './WorkflowOutput';
-export { default as WorkflowOutputGraph } from './WorkflowOutputGraph';
-export { default as WorkflowOutputLink } from './WorkflowOutputLink';
-export { default as WorkflowOutputNode } from './WorkflowOutputNode';
-export { default as WorkflowOutputToolbar } from './WorkflowOutputToolbar';
diff --git a/awx/ui/src/screens/Job/WorkflowOutput/useWsWorkflowOutput.js b/awx/ui/src/screens/Job/WorkflowOutput/useWsWorkflowOutput.js
deleted file mode 100644
index ee8f794bb741..000000000000
--- a/awx/ui/src/screens/Job/WorkflowOutput/useWsWorkflowOutput.js
+++ /dev/null
@@ -1,111 +0,0 @@
-import { useState, useEffect } from 'react';
-import useWebsocket from 'hooks/useWebsocket';
-import { WorkflowJobsAPI } from 'api';
-
-const fetchWorkflowNodes = async (jobId, pageNo = 1, nodes = []) => {
- const { data } = await WorkflowJobsAPI.readNodes(jobId, {
- page_size: 200,
- page: pageNo,
- });
-
- if (data.next) {
- return fetchWorkflowNodes(jobId, pageNo + 1, nodes.concat(data.results));
- }
- return nodes.concat(data.results);
-};
-
-export default function useWsWorkflowOutput(workflowJobId, initialNodes) {
- const [nodes, setNodes] = useState(initialNodes);
- const lastMessage = useWebsocket({
- jobs: ['status_changed'],
- control: ['limit_reached_1'],
- });
-
- useEffect(() => {
- setNodes(initialNodes);
- }, [initialNodes]);
-
- useEffect(
- () => {
- async function refreshNodeObjects() {
- const refreshedNodes = [];
- const updatedNodeObjects = await fetchWorkflowNodes(workflowJobId);
- const updatedNodeObjectsMap = updatedNodeObjects.reduce((map, node) => {
- map[node.id] = node;
- return map;
- }, {});
- nodes.forEach((node) => {
- if (node.id === 1) {
- // This is our artificial start node
- refreshedNodes.push({
- ...node,
- });
- } else {
- refreshedNodes.push({
- ...node,
- originalNodeObject:
- updatedNodeObjectsMap[node.originalNodeObject.id],
- });
- }
- });
- setNodes(refreshedNodes);
- }
-
- if (
- lastMessage?.unified_job_id === workflowJobId &&
- ['successful', 'failed', 'error', 'cancelled'].includes(
- lastMessage.status
- )
- ) {
- refreshNodeObjects();
- } else {
- if (
- !nodes ||
- nodes.length === 0 ||
- lastMessage?.workflow_job_id !== workflowJobId
- ) {
- return;
- }
-
- const index = nodes.findIndex(
- (node) =>
- node?.originalNodeObject?.id === lastMessage.workflow_node_id
- );
-
- if (index > -1) {
- setNodes(updateNode(nodes, index, lastMessage));
- }
- }
- },
- [lastMessage] // eslint-disable-line react-hooks/exhaustive-deps
- );
-
- return nodes;
-}
-
-function updateNode(nodes, index, message) {
- const node = {
- ...nodes[index],
- originalNodeObject: {
- ...nodes[index]?.originalNodeObject,
- job: message.unified_job_id,
- summary_fields: {
- ...nodes[index]?.originalNodeObject?.summary_fields,
- job: {
- ...nodes[index]?.originalNodeObject?.summary_fields?.job,
- id: message.unified_job_id,
- status: message.status,
- type: message.type,
- },
- },
- },
- job: {
- ...nodes[index]?.job,
- id: message.unified_job_id,
- status: message.status,
- type: message.type,
- },
- };
-
- return [...nodes.slice(0, index), node, ...nodes.slice(index + 1)];
-}
diff --git a/awx/ui/src/screens/Job/index.js b/awx/ui/src/screens/Job/index.js
deleted file mode 100644
index 16710ff4fa21..000000000000
--- a/awx/ui/src/screens/Job/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as Job } from './Job';
-export { default as Jobs } from './Jobs';
diff --git a/awx/ui/src/screens/Job/shared/data.job.json b/awx/ui/src/screens/Job/shared/data.job.json
deleted file mode 100644
index f3c2f5064e74..000000000000
--- a/awx/ui/src/screens/Job/shared/data.job.json
+++ /dev/null
@@ -1,194 +0,0 @@
-{
- "id": 2,
- "type": "job",
- "url": "/api/v2/jobs/2/",
- "related": {
- "created_by": "/api/v2/users/1/",
- "labels": "/api/v2/jobs/2/labels/",
- "inventory": "/api/v2/inventories/1/",
- "project": "/api/v2/projects/6/",
- "credentials": "/api/v2/jobs/2/credentials/",
- "unified_job_template": "/api/v2/job_templates/7/",
- "stdout": "/api/v2/jobs/2/stdout/",
- "job_events": "/api/v2/jobs/2/job_events/",
- "job_host_summaries": "/api/v2/jobs/2/job_host_summaries/",
- "activity_stream": "/api/v2/jobs/2/activity_stream/",
- "notifications": "/api/v2/jobs/2/notifications/",
- "create_schedule": "/api/v2/jobs/2/create_schedule/",
- "job_template": "/api/v2/job_templates/7/",
- "cancel": "/api/v2/jobs/2/cancel/",
- "project_update": "/api/v2/project_updates/4/",
- "relaunch": "/api/v2/jobs/2/relaunch/"
- },
- "summary_fields": {
- "inventory": {
- "id": 1,
- "name": "Demo Inventory",
- "description": "",
- "has_active_failures": false,
- "total_hosts": 1,
- "hosts_with_active_failures": 0,
- "total_groups": 0,
- "groups_with_active_failures": 0,
- "has_inventory_sources": false,
- "total_inventory_sources": 0,
- "inventory_sources_with_failures": 0,
- "organization_id": 1,
- "kind": ""
- },
- "execution_environment": {
- "id": 1,
- "name": "Default EE",
- "description": "",
- "image": "quay.io/ansible/awx-ee"
- },
- "project": {
- "id": 6,
- "name": "Demo Project",
- "description": "",
- "status": "successful",
- "scm_type": "git"
- },
- "project_update": {
- "id": 4,
- "name": "Demo Project",
- "description": "",
- "status": "successful",
- "failed": false
- },
- "job_template": {
- "id": 7,
- "name": "Demo Job Template",
- "description": ""
- },
- "unified_job_template": {
- "id": 7,
- "name": "Demo Job Template",
- "description": "",
- "unified_job_type": "job"
- },
- "instance_group": {
- "id": 1,
- "name": "tower"
- },
- "created_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "user_capabilities": {
- "delete": true,
- "start": true
- },
- "labels": {
- "count": 0,
- "results": []
- },
- "credentials": [
- {
- "id": 1,
- "name": "Demo Credential",
- "description": "",
- "kind": "ssh",
- "cloud": false
- }
- ]
- },
- "created": "2019-08-08T19:24:05.344276Z",
- "modified": "2019-08-08T19:24:18.162949Z",
- "name": "Demo Job Template",
- "description": "",
- "job_type": "run",
- "inventory": 1,
- "project": 6,
- "playbook": "chatty_tasks.yml",
- "scm_branch": "main",
- "forks": 42,
- "limit": "",
- "verbosity": 0,
- "extra_vars": "{\"num_messages\": 94}",
- "job_tags": "a,b",
- "force_handlers": false,
- "skip_tags": "c,d",
- "start_at_task": "",
- "timeout": 0,
- "use_fact_cache": false,
- "unified_job_template": 7,
- "launch_type": "manual",
- "status": "successful",
- "failed": false,
- "started": "2019-08-08T19:24:18.329589Z",
- "finished": "2019-08-08T19:24:50.119995Z",
- "elapsed": 31.79,
- "job_args": "[\"bwrap\", \"--unshare-pid\", \"--dev-bind\", \"/\", \"/\", \"--proc\", \"/proc\", \"--bind\", \"/tmp/ansible_runner_pi_pzufy15c/ansible_runner_pi_r_aeukpy/tmpvsg8ly2y\", \"/etc/ssh\", \"--bind\", \"/tmp/ansible_runner_pi_pzufy15c/ansible_runner_pi_r_aeukpy/tmpq_grmdym\", \"/projects\", \"--bind\", \"/tmp/ansible_runner_pi_pzufy15c/ansible_runner_pi_r_aeukpy/tmpfq8ea2z6\", \"/tmp\", \"--bind\", \"/tmp/ansible_runner_pi_pzufy15c/ansible_runner_pi_r_aeukpy/tmpq6v4y_tt\", \"/var/lib/awx\", \"--bind\", \"/tmp/ansible_runner_pi_pzufy15c/ansible_runner_pi_r_aeukpy/tmpupj_jhhb\", \"/var/log\", \"--ro-bind\", \"/var/lib/awx/venv/ansible\", \"/var/lib/awx/venv/ansible\", \"--ro-bind\", \"/var/lib/awx/venv/awx\", \"/var/lib/awx/venv/awx\", \"--bind\", \"/projects/_6__demo_project\", \"/projects/_6__demo_project\", \"--bind\", \"/tmp/awx_2_a4b1afiw\", \"/tmp/awx_2_a4b1afiw\", \"--chdir\", \"/projects/_6__demo_project\", \"ansible-playbook\", \"-u\", \"admin\", \"-i\", \"/tmp/awx_2_a4b1afiw/tmppb57i4_e\", \"-e\", \"@/tmp/awx_2_a4b1afiw/env/extravars\", \"chatty_tasks.yml\"]",
- "job_cwd": "/projects/_6__demo_project",
- "job_env": {
- "HOSTNAME": "awx",
- "MAKEFLAGS": "w",
- "OS": "Operating System: Docker for Mac",
- "LC_ALL": "en_US.UTF-8",
- "SDB_HOST": "0.0.0.0",
- "MAKELEVEL": "2",
- "VIRTUAL_ENV": "/var/lib/awx/venv/ansible",
- "MFLAGS": "-w",
- "PATH": "/var/lib/awx/venv/ansible/bin:/var/lib/awx/venv/awx/bin:/var/lib/awx/venv/awx/bin:/usr/local/n/versions/node/10.15.0/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
- "SUPERVISOR_GROUP_NAME": "tower-processes",
- "PWD": "/awx_devel",
- "LANG": "\"en-us\"",
- "PS1": "(awx) ",
- "SUPERVISOR_ENABLED": "1",
- "SHLVL": "2",
- "HOME": "/var/lib/awx",
- "LANGUAGE": "en_US:en",
- "AWX_GROUP_QUEUES": "tower",
- "SUPERVISOR_SERVER_URL": "unix:///tmp/supervisor.sock",
- "SUPERVISOR_PROCESS_NAME": "awx-dispatcher",
- "CURRENT_UID": "501",
- "_": "/var/lib/awx/venv/awx/bin/python3",
- "DJANGO_SETTINGS_MODULE": "awx.settings.development",
- "DJANGO_LIVE_TEST_SERVER_ADDRESS": "localhost:9013-9199",
- "SDB_NOTIFY_HOST": "docker.for.mac.host.internal",
- "TZ": "UTC",
- "ANSIBLE_FORCE_COLOR": "True",
- "ANSIBLE_HOST_KEY_CHECKING": "False",
- "ANSIBLE_INVENTORY_UNPARSED_FAILED": "True",
- "ANSIBLE_PARAMIKO_RECORD_HOST_KEYS": "False",
- "ANSIBLE_VENV_PATH": "/var/lib/awx/venv/ansible",
- "AWX_PRIVATE_DATA_DIR": "/tmp/awx_2_a4b1afiw",
- "ANSIBLE_COLLECTIONS_PATHS": "/tmp/collections",
- "PYTHONPATH": "/var/lib/awx/venv/ansible/lib/python2.7/site-packages:/awx_devel/awx/lib:",
- "JOB_ID": "2",
- "INVENTORY_ID": "1",
- "PROJECT_REVISION": "23f070aad8e2da131d97ea98b42b553ccf0b0b82",
- "ANSIBLE_RETRY_FILES_ENABLED": "False",
- "MAX_EVENT_RES": "700000",
- "ANSIBLE_CALLBACK_PLUGINS": "/awx_devel/awx/plugins/callback",
- "AWX_HOST": "https://towerhost",
- "ANSIBLE_SSH_CONTROL_PATH_DIR": "/tmp/awx_2_a4b1afiw/cp",
- "ANSIBLE_STDOUT_CALLBACK": "awx_display"
- },
- "job_explanation": "Job explanation placeholder",
- "execution_node": "awx",
- "controller_node": "",
- "result_traceback": "",
- "event_processing_finished": true,
- "job_template": 7,
- "passwords_needed_to_start": [],
- "allow_simultaneous": false,
- "artifacts": {},
- "scm_revision": "23f070aad8e2da131d97ea98b42b553ccf0b0b82",
- "instance_group": 1,
- "diff_mode": false,
- "job_slice_number": 0,
- "job_slice_count": 1,
- "host_status_counts": {
- "ok": 1
- },
- "playbook_counts": {
- "play_count": 1,
- "task_count": 1
- },
- "custom_virtualenv": "/var/lib/awx/venv/ansible",
- "execution_environment": 1
-}
diff --git a/awx/ui/src/screens/Job/useWsJob.js b/awx/ui/src/screens/Job/useWsJob.js
deleted file mode 100644
index 5c08a995e989..000000000000
--- a/awx/ui/src/screens/Job/useWsJob.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import { useState, useEffect } from 'react';
-import useWebsocket from 'hooks/useWebsocket';
-import { getJobModel } from 'util/jobs';
-
-export default function useWsJob(initialJob) {
- const [job, setJob] = useState(initialJob);
- const [pendingMessages, setPendingMessages] = useState([]);
- const lastMessage = useWebsocket({
- jobs: ['status_changed'],
- control: ['limit_reached_1'],
- });
-
- useEffect(() => {
- setJob(initialJob);
- }, [initialJob]);
-
- const processMessage = (message) => {
- if (message.unified_job_id !== job.id) {
- return;
- }
-
- if (
- ['successful', 'failed', 'error', 'cancelled'].includes(message.status)
- ) {
- fetchJob();
- }
- setJob(updateJob(job, message));
- };
-
- async function fetchJob() {
- const { data } = await getJobModel(job.type).readDetail(job.id);
- setJob(data);
- }
-
- useEffect(
- () => {
- if (!lastMessage) {
- return;
- }
- if (job) {
- processMessage(lastMessage);
- } else if (lastMessage.unified_job_id) {
- setPendingMessages(pendingMessages.concat(lastMessage));
- }
- },
- [lastMessage] // eslint-disable-line react-hooks/exhaustive-deps
- );
-
- useEffect(() => {
- if (!job || !pendingMessages.length) {
- return;
- }
- pendingMessages.forEach((message) => {
- processMessage(message);
- });
- setPendingMessages([]);
- }, [job, pendingMessages]); // eslint-disable-line react-hooks/exhaustive-deps
-
- return job;
-}
-
-function updateJob(job, message) {
- return {
- ...job,
- finished: message.finished,
- status: message.status,
- };
-}
diff --git a/awx/ui/src/screens/Login/Login.js b/awx/ui/src/screens/Login/Login.js
deleted file mode 100644
index 7b70e150d8ae..000000000000
--- a/awx/ui/src/screens/Login/Login.js
+++ /dev/null
@@ -1,416 +0,0 @@
-/* eslint-disable react/jsx-no-useless-fragment */
-import React, { useCallback, useState, useEffect, useRef } from 'react';
-import { Redirect, withRouter } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { Formik } from 'formik';
-import styled from 'styled-components';
-import DOMPurify from 'dompurify';
-
-import {
- Alert,
- Brand,
- LoginMainFooterLinksItem,
- LoginForm,
- Login as PFLogin,
- LoginHeader,
- LoginFooter,
- LoginMainHeader,
- LoginMainBody,
- LoginMainFooter,
- Tooltip,
-} from '@patternfly/react-core';
-
-import {
- AzureIcon,
- GoogleIcon,
- GithubIcon,
- UserCircleIcon,
-} from '@patternfly/react-icons';
-import useRequest, { useDismissableError } from 'hooks/useRequest';
-import { AuthAPI, RootAPI, MeAPI } from 'api';
-import AlertModal from 'components/AlertModal';
-import ErrorDetail from 'components/ErrorDetail';
-import { useSession } from 'contexts/Session';
-import LoadingSpinner from 'components/LoadingSpinner';
-import { SESSION_REDIRECT_URL, SESSION_USER_ID } from '../../constants';
-
-const loginLogoSrc = 'static/media/logo-login.svg';
-
-const Login = styled(PFLogin)`
- & .pf-c-brand {
- max-height: 285px;
- }
-`;
-
-function AWXLogin({ alt, isAuthenticated }) {
- const [userId, setUserId] = useState(null);
- const { authRedirectTo, isSessionExpired } = useSession();
- const isNewUser = useRef(true);
- const hasVerifiedUser = useRef(false);
-
- const {
- isLoading: isCustomLoginInfoLoading,
- error: customLoginInfoError,
- request: fetchCustomLoginInfo,
- result: { brandName, logo, loginInfo, socialAuthOptions },
- } = useRequest(
- useCallback(async () => {
- const [
- {
- data: { custom_logo, custom_login_info },
- },
- {
- data: { BRAND_NAME },
- },
- { data: authData },
- ] = await Promise.all([
- RootAPI.read(),
- RootAPI.readAssetVariables(),
- AuthAPI.read(),
- ]);
- const logoSrc = custom_logo
- ? `data:image/jpeg;${custom_logo}`
- : loginLogoSrc;
-
- return {
- brandName: BRAND_NAME,
- logo: logoSrc,
- loginInfo: custom_login_info,
- socialAuthOptions: authData,
- };
- }, []),
- {
- brandName: null,
- logo: loginLogoSrc,
- loginInfo: null,
- socialAuthOptions: {},
- }
- );
-
- const { error: loginInfoError, dismissError: dismissLoginInfoError } =
- useDismissableError(customLoginInfoError);
-
- useEffect(() => {
- fetchCustomLoginInfo();
- }, [fetchCustomLoginInfo]);
-
- const {
- isLoading: isAuthenticating,
- error: authenticationError,
- request: authenticate,
- } = useRequest(
- useCallback(async ({ username, password }) => {
- await RootAPI.login(username, password);
- }, [])
- );
-
- const { error: authError, dismissError: dismissAuthError } =
- useDismissableError(authenticationError);
-
- const { isLoading: isUserIdLoading, request: fetchUserId } = useRequest(
- useCallback(async () => {
- if (isAuthenticated(document.cookie)) {
- const { data } = await MeAPI.read();
- setUserId(data.results[0].id);
- }
- }, [isAuthenticated])
- );
-
- const handleSubmit = async (values) => {
- dismissAuthError();
- await authenticate(values);
- await fetchUserId();
- };
-
- useEffect(() => {
- fetchUserId();
- }, [fetchUserId]);
-
- const setLocalStorageAndRedirect = useCallback(() => {
- if (userId && !hasVerifiedUser.current) {
- const verifyIsNewUser = () => {
- const previousUserId = JSON.parse(
- window.localStorage.getItem(SESSION_USER_ID)
- );
- if (previousUserId === null) {
- return true;
- }
- return userId.toString() !== previousUserId.toString();
- };
- isNewUser.current = verifyIsNewUser();
- hasVerifiedUser.current = true;
- window.localStorage.setItem(SESSION_USER_ID, JSON.stringify(userId));
- }
- }, [userId]);
-
- useEffect(() => {
- setLocalStorageAndRedirect();
- }, [userId, setLocalStorageAndRedirect]);
-
- let helperText;
- if (authError?.response?.status === 401) {
- helperText = t`Invalid username or password. Please try again.`;
- } else {
- helperText = t`There was a problem logging in. Please try again.`;
- }
-
- const HeaderBrand = (
-
- );
- const Header = ;
- const Footer = (
-
- );
-
- const setSessionRedirect = () => {
- window.sessionStorage.setItem(SESSION_REDIRECT_URL, authRedirectTo);
- };
-
- if (isCustomLoginInfoLoading) {
- return null;
- }
- if (isUserIdLoading) {
- return ;
- }
- if (userId && hasVerifiedUser.current) {
- const redirect = isNewUser.current ? '/home' : authRedirectTo;
-
- return ;
- }
- return (
-
-
-
- {isSessionExpired.current ? (
-
- ) : null}
-
- {(formik) => (
- {
- formik.setFieldValue('password', val);
- dismissAuthError();
- }}
- onChangeUsername={(val) => {
- formik.setFieldValue('username', val);
- dismissAuthError();
- }}
- onLoginButtonClick={formik.handleSubmit}
- passwordLabel={t`Password`}
- passwordValue={formik.values.password}
- showHelperText={authError}
- usernameLabel={t`Username`}
- usernameValue={formik.values.username}
- />
- )}
-
- {loginInfoError && (
-
- {t`Failed to fetch custom login configuration settings. System defaults will be shown instead.`}
-
-
- )}
-
-
- {socialAuthOptions &&
- Object.keys(socialAuthOptions).map((authKey) => {
- const loginUrl = socialAuthOptions[authKey].login_url;
- if (authKey === 'azuread-oauth2') {
- return (
-
-
-
-
-
- );
- }
- if (authKey === 'github') {
- return (
-
-
-
-
-
- );
- }
- if (authKey === 'github-org') {
- return (
-
-
-
-
-
- );
- }
- if (authKey === 'github-team') {
- return (
-
-
-
-
-
- );
- }
- if (authKey === 'github-enterprise') {
- return (
-
-
-
-
-
- );
- }
- if (authKey === 'github-enterprise-org') {
- return (
-
-
-
-
-
- );
- }
- if (authKey === 'github-enterprise-team') {
- return (
-
-
-
-
-
- );
- }
- if (authKey === 'google-oauth2') {
- return (
-
-
-
-
-
- );
- }
- if (authKey === 'oidc') {
- return (
-
-
-
-
-
- );
- }
- if (authKey.startsWith('saml')) {
- const samlIDP = authKey.split(':')[1] || null;
- return (
-
-
-
-
-
- );
- }
-
- return null;
- })}
- >
- }
- />
-
- );
-}
-
-export default withRouter(AWXLogin);
-export { AWXLogin as _AWXLogin };
diff --git a/awx/ui/src/screens/Login/Login.test.js b/awx/ui/src/screens/Login/Login.test.js
deleted file mode 100644
index 98ca0a5b3995..000000000000
--- a/awx/ui/src/screens/Login/Login.test.js
+++ /dev/null
@@ -1,476 +0,0 @@
-import React from 'react';
-import { createMemoryHistory } from 'history';
-import { act } from 'react-dom/test-utils';
-import { AuthAPI, RootAPI, MeAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../testUtils/enzymeHelpers';
-
-import AWXLogin from './Login';
-import { getCurrentUserId } from 'util/auth';
-
-import { SESSION_USER_ID } from '../../constants';
-
-jest.mock('../../api/models/Auth.js');
-jest.mock('../../api/models/Root.js');
-jest.mock('../../api/models/Me.js');
-
-jest.mock('util/auth', () => ({
- getCurrentUserId: jest.fn(),
-}));
-
-RootAPI.readAssetVariables.mockResolvedValue({
- data: {
- BRAND_NAME: 'AWX',
- },
-});
-
-AuthAPI.read.mockResolvedValue({
- data: {},
-});
-
-describe('', () => {
- async function findChildren(wrapper) {
- const [
- awxLogin,
- loginForm,
- usernameInput,
- passwordInput,
- submitButton,
- loginHeaderLogo,
- ] = await Promise.all([
- waitForElement(wrapper, 'AWXLogin', (el) => el.length === 1),
- waitForElement(wrapper, 'LoginForm', (el) => el.length === 1),
- waitForElement(
- wrapper,
- 'input#pf-login-username-id',
- (el) => el.length === 1
- ),
- waitForElement(
- wrapper,
- 'input#pf-login-password-id',
- (el) => el.length === 1
- ),
- waitForElement(wrapper, 'Button[type="submit"]', (el) => el.length === 1),
- waitForElement(wrapper, 'img', (el) => el.length === 1),
- ]);
- return {
- awxLogin,
- loginForm,
- usernameInput,
- passwordInput,
- submitButton,
- loginHeaderLogo,
- };
- }
-
- beforeEach(() => {
- RootAPI.readAssetVariables.mockResolvedValue({
- data: {
- BRAND_NAME: 'AWX',
- },
- });
-
- AuthAPI.read.mockResolvedValue({
- data: {},
- });
- RootAPI.read.mockResolvedValue({
- data: {
- custom_login_info:
- 'TEST
',
- custom_logo: 'images/foo.jpg',
- },
- });
- Object.defineProperty(window, 'localStorage', {
- value: {
- getItem: jest.fn(() => '42'),
- setItem: jest.fn(() => null),
- },
- writable: true,
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('initially renders without crashing', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts( false} />);
- });
- const { usernameInput, passwordInput, submitButton } = await findChildren(
- wrapper
- );
- expect(usernameInput.props().value).toBe('');
- expect(passwordInput.props().value).toBe('');
- expect(submitButton.props().isDisabled).toBe(false);
- expect(wrapper.find('AlertModal').length).toBe(0);
- expect(wrapper.find('LoginMainHeader').prop('subtitle')).toBe(
- 'Please log in'
- );
- expect(wrapper.find('LoginMainHeader').prop('title')).toBe(
- 'Welcome to AWX!'
- );
- });
-
- test('custom logo renders Brand component with correct src and alt', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
- false} />
- );
- });
- const { loginHeaderLogo } = await findChildren(wrapper);
- const { alt, src } = loginHeaderLogo.props();
- expect([alt, src]).toEqual([
- 'Foo Application',
- 'data:image/jpeg;images/foo.jpg',
- ]);
- });
-
- test('default logo renders Brand component with correct src and alt', async () => {
- RootAPI.read.mockResolvedValue({ data: {} });
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts( false} />);
- });
- const { loginHeaderLogo } = await findChildren(wrapper);
- const { alt, src } = loginHeaderLogo.props();
- expect([alt, src]).toEqual(['AWX', 'static/media/logo-login.svg']);
- });
-
- test('custom login info handled correctly', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts( false} />);
- });
- await findChildren(wrapper);
- expect(wrapper.find('footer').html()).toContain(
- ''
- );
- });
-
- test('data initialization error is properly handled', async () => {
- RootAPI.read.mockRejectedValueOnce(
- new Error({
- response: {
- config: {
- method: 'get',
- url: '/api/v2',
- },
- data: 'An error occurred',
- status: 500,
- },
- })
- );
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts( false} />);
- });
- const { loginHeaderLogo } = await findChildren(wrapper);
- const { alt, src } = loginHeaderLogo.props();
- expect([alt, src]).toEqual([null, 'static/media/logo-login.svg']);
- expect(wrapper.find('AlertModal').length).toBe(1);
- });
-
- test('state maps to un/pw input value props', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts( false} />);
- });
- await waitForElement(wrapper, 'LoginForm', (el) => el.length === 1);
- await act(async () => {
- wrapper.find('TextInputBase#pf-login-username-id').prop('onChange')('un');
- wrapper.find('TextInputBase#pf-login-password-id').prop('onChange')('pw');
- });
- wrapper.update();
- expect(
- wrapper.find('TextInputBase#pf-login-username-id').prop('value')
- ).toEqual('un');
- expect(
- wrapper.find('TextInputBase#pf-login-password-id').prop('value')
- ).toEqual('pw');
- });
-
- test('handles input validation errors and clears on input value change', async () => {
- RootAPI.login.mockRejectedValueOnce(
- new Error({
- response: {
- config: {
- method: 'post',
- url: '/api/login/',
- },
- data: 'An error occurred',
- status: 401,
- },
- })
- );
-
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts( false} />);
- });
- await waitForElement(wrapper, 'LoginForm', (el) => el.length === 1);
-
- expect(
- wrapper.find('TextInputBase#pf-login-username-id').prop('value')
- ).toEqual('');
- expect(
- wrapper.find('TextInputBase#pf-login-password-id').prop('value')
- ).toEqual('');
- expect(wrapper.find('FormHelperText').prop('isHidden')).toEqual(true);
-
- await act(async () => {
- wrapper.find('TextInputBase#pf-login-username-id').prop('onChange')('un');
- wrapper.find('TextInputBase#pf-login-password-id').prop('onChange')('pw');
- });
- wrapper.update();
-
- expect(
- wrapper.find('TextInputBase#pf-login-username-id').prop('value')
- ).toEqual('un');
- expect(
- wrapper.find('TextInputBase#pf-login-password-id').prop('value')
- ).toEqual('pw');
-
- await act(async () => {
- wrapper.find('Button[type="submit"]').invoke('onClick')();
- });
- wrapper.update();
-
- expect(wrapper.find('FormHelperText').prop('isHidden')).toEqual(false);
- expect(
- wrapper.find('TextInput#pf-login-username-id').prop('validated')
- ).toEqual('error');
- expect(
- wrapper.find('TextInput#pf-login-password-id').prop('validated')
- ).toEqual('error');
-
- await act(async () => {
- wrapper.find('TextInputBase#pf-login-username-id').prop('onChange')(
- 'foo'
- );
- wrapper.find('TextInputBase#pf-login-password-id').prop('onChange')(
- 'bar'
- );
- });
- wrapper.update();
-
- expect(
- wrapper.find('TextInputBase#pf-login-username-id').prop('value')
- ).toEqual('foo');
- expect(
- wrapper.find('TextInputBase#pf-login-password-id').prop('value')
- ).toEqual('bar');
- expect(wrapper.find('FormHelperText').prop('isHidden')).toEqual(true);
- expect(
- wrapper.find('TextInput#pf-login-username-id').prop('validated')
- ).toEqual('default');
- expect(
- wrapper.find('TextInput#pf-login-password-id').prop('validated')
- ).toEqual('default');
- });
-
- test('submit calls api.login successfully', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts( false} />);
- });
- await waitForElement(wrapper, 'LoginForm', (el) => el.length === 1);
-
- await act(async () => {
- wrapper.find('TextInputBase#pf-login-username-id').prop('onChange')('un');
- wrapper.find('TextInputBase#pf-login-password-id').prop('onChange')('pw');
- });
- wrapper.update();
-
- await act(async () => {
- wrapper.find('Button[type="submit"]').invoke('onClick')();
- });
- wrapper.update();
-
- expect(RootAPI.login).toHaveBeenCalledTimes(1);
- expect(RootAPI.login).toHaveBeenCalledWith('un', 'pw');
- });
-
- test('render Redirect to / when already authenticated as a new user', async () => {
- MeAPI.read.mockResolvedValue({ data: { results: [{ id: 1 }] } });
- const history = createMemoryHistory({
- initialEntries: ['/login'],
- });
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts( true} />, {
- context: {
- router: { history },
- session: {
- authRedirectTo: '/projects',
- handleSessionContinue: () => {},
- isSessionExpired: false,
- isUserBeingLoggedOut: false,
- loginRedirectOverride: null,
- logout: () => {},
- sessionCountdown: 60,
- setAuthRedirectTo: () => {},
- },
- },
- });
- });
- expect(MeAPI.read).toHaveBeenCalled();
- expect(window.localStorage.getItem).toHaveBeenCalledWith(SESSION_USER_ID);
- expect(window.localStorage.setItem).toHaveBeenCalledWith(
- SESSION_USER_ID,
- '1'
- );
- await waitForElement(wrapper, 'Redirect', (el) => el.length === 1);
- await waitForElement(
- wrapper,
- 'Redirect',
- (el) => el.props().to === '/home'
- );
- });
-
- test('render redirect to authRedirectTo when authenticated as a previous user', async () => {
- MeAPI.read.mockResolvedValue({ data: { results: [{ id: 42 }] } });
- const history = createMemoryHistory({
- initialEntries: ['/login'],
- });
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts( true} />, {
- context: {
- router: { history },
- session: {
- authRedirectTo: '/projects',
- handleSessionContinue: () => {},
- isSessionExpired: false,
- isUserBeingLoggedOut: false,
- loginRedirectOverride: null,
- logout: () => {},
- sessionCountdown: 60,
- setAuthRedirectTo: () => {},
- },
- },
- });
- });
-
- wrapper.update();
- expect(window.localStorage.getItem).toHaveBeenCalledWith(SESSION_USER_ID);
- expect(window.localStorage.setItem).toHaveBeenCalledWith(
- SESSION_USER_ID,
- '42'
- );
- wrapper.update();
- await waitForElement(wrapper, 'Redirect', (el) => el.length === 1);
- await waitForElement(
- wrapper,
- 'Redirect',
- (el) => el.props().to === '/projects'
- );
- });
-
- test('GitHub auth buttons shown', async () => {
- AuthAPI.read.mockResolvedValue({
- data: {
- github: {
- login_url: '/sso/login/github/',
- complete_url: 'https://localhost:8043/sso/complete/github/',
- },
- 'github-org': {
- login_url: '/sso/login/github-org/',
- complete_url: 'https://localhost:8043/sso/complete/github-org/',
- },
- 'github-team': {
- login_url: '/sso/login/github-team/',
- complete_url: 'https://localhost:8043/sso/complete/github-team/',
- },
- },
- });
-
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts( false} />);
- });
- wrapper.update();
- expect(wrapper.find('GithubIcon').length).toBe(3);
- expect(wrapper.find('AzureIcon').length).toBe(0);
- expect(wrapper.find('GoogleIcon').length).toBe(0);
- expect(wrapper.find('UserCircleIcon').length).toBe(0);
- });
-
- test('Google auth button shown', async () => {
- AuthAPI.read.mockResolvedValue({
- data: {
- 'google-oauth2': {
- login_url: '/sso/login/google-oauth2/',
- complete_url: 'https://localhost:8043/sso/complete/google-oauth2/',
- },
- },
- });
-
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts( false} />);
- });
- wrapper.update();
- expect(wrapper.find('GithubIcon').length).toBe(0);
- expect(wrapper.find('AzureIcon').length).toBe(0);
- expect(wrapper.find('GoogleIcon').length).toBe(1);
- expect(wrapper.find('UserCircleIcon').length).toBe(0);
- });
-
- test('Azure AD auth button shown', async () => {
- AuthAPI.read.mockResolvedValue({
- data: {
- 'azuread-oauth2': {
- login_url: '/sso/login/azuread-oauth2/',
- complete_url: 'https://localhost:8043/sso/complete/azuread-oauth2/',
- },
- },
- });
-
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts( false} />);
- });
- wrapper.update();
- expect(wrapper.find('GithubIcon').length).toBe(0);
- expect(wrapper.find('AzureIcon').length).toBe(1);
- expect(wrapper.find('GoogleIcon').length).toBe(0);
- expect(wrapper.find('UserCircleIcon').length).toBe(0);
- });
-
- test('SAML auth buttons shown', async () => {
- AuthAPI.read.mockResolvedValue({
- data: {
- saml: {
- login_url: '/sso/login/saml/',
- complete_url: 'https://localhost:8043/sso/complete/saml/',
- metadata_url: '/sso/metadata/saml/',
- },
- 'saml:onelogin': {
- login_url: '/sso/login/saml/?idp=onelogin',
- complete_url: 'https://localhost:8043/sso/complete/saml/',
- metadata_url: '/sso/metadata/saml/',
- },
- 'saml:someotheridp': {
- login_url: '/sso/login/saml/?idp=someotheridp',
- complete_url: 'https://localhost:8043/sso/complete/saml/',
- metadata_url: '/sso/metadata/saml/',
- },
- },
- });
-
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts( false} />);
- });
- wrapper.update();
- expect(wrapper.find('GithubIcon').length).toBe(0);
- expect(wrapper.find('AzureIcon').length).toBe(0);
- expect(wrapper.find('GoogleIcon').length).toBe(0);
- expect(wrapper.find('UserCircleIcon').length).toBe(3);
- });
-});
diff --git a/awx/ui/src/screens/Login/index.js b/awx/ui/src/screens/Login/index.js
deleted file mode 100644
index 2a741cdbd279..000000000000
--- a/awx/ui/src/screens/Login/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './Login';
diff --git a/awx/ui/src/screens/ManagementJob/ManagementJob.js b/awx/ui/src/screens/ManagementJob/ManagementJob.js
deleted file mode 100644
index 6678006b68b1..000000000000
--- a/awx/ui/src/screens/ManagementJob/ManagementJob.js
+++ /dev/null
@@ -1,192 +0,0 @@
-import React, { useState, useEffect, useCallback } from 'react';
-import {
- Link,
- Redirect,
- Route,
- Switch,
- useLocation,
- useParams,
- useRouteMatch,
-} from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { CaretLeftIcon } from '@patternfly/react-icons';
-import { Card, PageSection } from '@patternfly/react-core';
-
-import { SystemJobTemplatesAPI, OrganizationsAPI } from 'api';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import NotificationList from 'components/NotificationList';
-import RoutedTabs from 'components/RoutedTabs';
-import { Schedules } from 'components/Schedule';
-import { useConfig } from 'contexts/Config';
-import useRequest from 'hooks/useRequest';
-
-function ManagementJob({ setBreadcrumb }) {
- const basePath = '/management_jobs';
-
- const match = useRouteMatch();
- const { id } = useParams();
- const { pathname } = useLocation();
- const { me } = useConfig();
-
- const [isNotificationAdmin, setIsNotificationAdmin] = useState(false);
-
- const { isLoading, error, request, result } = useRequest(
- useCallback(
- () =>
- Promise.all([
- SystemJobTemplatesAPI.readDetail(id),
- OrganizationsAPI.read({
- page_size: 1,
- role_level: 'notification_admin_role',
- }),
- ]).then(([{ data: systemJobTemplate }, notificationRoles]) => ({
- systemJobTemplate,
- notificationRoles,
- })),
- [id]
- )
- );
-
- useEffect(() => {
- request();
- }, [request, pathname]);
-
- useEffect(() => {
- if (!result) return;
- setIsNotificationAdmin(
- Boolean(result?.notificationRoles?.data?.results?.length)
- );
- setBreadcrumb(result);
- }, [result, setBreadcrumb, setIsNotificationAdmin]);
-
- useEffect(() => {
- if (!result) return;
-
- setBreadcrumb(result);
- }, [result, setBreadcrumb]);
-
- const createSchedule = useCallback(
- (data) =>
- SystemJobTemplatesAPI.createSchedule(result?.systemJobTemplate.id, data),
- [result]
- );
- const loadSchedules = useCallback(
- (params) =>
- SystemJobTemplatesAPI.readSchedules(result?.systemJobTemplate.id, params),
- [result]
- );
- const loadScheduleOptions = useCallback(
- () =>
- SystemJobTemplatesAPI.readScheduleOptions(result?.systemJobTemplate.id),
- [result]
- );
-
- const shouldShowNotifications =
- result?.systemJobTemplate?.id &&
- (isNotificationAdmin || me?.is_system_auditor);
- const shouldShowSchedules = !!result?.systemJobTemplate?.id;
-
- const tabsArray = [
- {
- id: 99,
- link: basePath,
- name: (
- <>
-
- {t`Back to management jobs`}
- >
- ),
- isBackButton: true,
- },
- ];
-
- if (shouldShowSchedules) {
- tabsArray.push({
- id: 0,
- name: t`Schedules`,
- link: `${match.url}/schedules`,
- });
- }
-
- if (shouldShowNotifications) {
- tabsArray.push({
- id: 1,
- name: t`Notifications`,
- link: `${match.url}/notifications`,
- });
- }
-
- let Tabs = ;
- if (pathname.includes('edit') || pathname.includes('schedules/')) {
- Tabs = null;
- }
-
- if (error) {
- return (
-
-
-
- {error?.response?.status === 404 && (
-
- {t`Management job not found.`}
-
- {t`View all management jobs`}
-
- )}
-
-
-
- );
- }
-
- if (isLoading) {
- return (
-
-
- {Tabs}
-
-
-
- );
- }
-
- return (
-
-
- {Tabs}
-
-
- {shouldShowNotifications ? (
-
-
-
- ) : null}
- {shouldShowSchedules ? (
-
-
-
- ) : null}
-
-
-
- );
-}
-
-export default ManagementJob;
diff --git a/awx/ui/src/screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js b/awx/ui/src/screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js
deleted file mode 100644
index 73c73ff1f6bd..000000000000
--- a/awx/ui/src/screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js
+++ /dev/null
@@ -1,82 +0,0 @@
-import React, { useState } from 'react';
-
-import { t } from '@lingui/macro';
-import { Button, TextInput, Tooltip } from '@patternfly/react-core';
-import { RocketIcon } from '@patternfly/react-icons';
-
-import AlertModal from 'components/AlertModal';
-
-const MAX_RETENTION = 99999;
-
-const clamp = (val, min, max) => {
- if (val < min) {
- return min;
- }
- if (val > max) {
- return max;
- }
- return val;
-};
-
-function LaunchManagementPrompt({
- isOpen,
- isLoading,
- onClick,
- onClose,
- onConfirm,
- defaultDays,
-}) {
- const [dataRetention, setDataRetention] = useState(defaultDays);
- return (
- <>
-
-
-
- onConfirm(dataRetention)}
- >
- {t`Launch`}
- ,
- ,
- ]}
- >
- {t`Set how many days of data should be retained.`}
- setDataRetention(clamp(value, 0, MAX_RETENTION))}
- aria-label={t`Data retention period`}
- />
-
- >
- );
-}
-
-export default LaunchManagementPrompt;
diff --git a/awx/ui/src/screens/ManagementJob/ManagementJobList/ManagementJobList.js b/awx/ui/src/screens/ManagementJob/ManagementJobList/ManagementJobList.js
deleted file mode 100644
index ca3db0cc847b..000000000000
--- a/awx/ui/src/screens/ManagementJob/ManagementJobList/ManagementJobList.js
+++ /dev/null
@@ -1,135 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { t } from '@lingui/macro';
-
-import { useLocation } from 'react-router-dom';
-import { Card, PageSection } from '@patternfly/react-core';
-
-import { SystemJobTemplatesAPI } from 'api';
-import AlertModal from 'components/AlertModal';
-import DatalistToolbar from 'components/DataListToolbar';
-import ErrorDetail from 'components/ErrorDetail';
-import PaginatedTable, {
- HeaderRow,
- HeaderCell,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import { useConfig } from 'contexts/Config';
-import { parseQueryString, getQSConfig } from 'util/qs';
-import useRequest from 'hooks/useRequest';
-
-import ManagementJobListItem from './ManagementJobListItem';
-
-const QS_CONFIG = getQSConfig('system_job_templates', {
- page: 1,
- page_size: 20,
-});
-
-const buildSearchKeys = (options) => {
- const actions = options?.data?.actions?.GET || {};
- const searchableKeys = getSearchableKeys(actions);
-
- const relatedSearchableKeys = (
- options?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8));
-
- return { searchableKeys, relatedSearchableKeys };
-};
-
-const loadManagementJobs = async (search) => {
- const params = parseQueryString(QS_CONFIG, search);
- const [
- {
- data: { results: items, count },
- },
- options,
- ] = await Promise.all([
- SystemJobTemplatesAPI.read(params),
- SystemJobTemplatesAPI.readOptions(),
- ]);
-
- return { items, count, options };
-};
-
-function ManagementJobList() {
- const { search } = useLocation();
- const { me } = useConfig();
- const [launchError, setLaunchError] = useState(null);
-
- const {
- request,
- error = false,
- isLoading = true,
- result: { options = {}, items = [], count = 0 },
- } = useRequest(
- useCallback(async () => loadManagementJobs(search), [search]),
- {}
- );
-
- useEffect(() => {
- request();
- }, [request]);
-
- const { searchableKeys, relatedSearchableKeys } = buildSearchKeys(options);
-
- return (
- <>
-
-
- (
-
- )}
- headerRow={
-
- {t`Name`}
- {t`Description`}
- {t`Actions`}
-
- }
- renderRow={({ id, name, description, job_type }) => (
-
- )}
- />
-
-
- setLaunchError(null)}
- >
- {t`Failed to launch job.`}
-
-
- >
- );
-}
-
-export default ManagementJobList;
diff --git a/awx/ui/src/screens/ManagementJob/ManagementJobList/ManagementJobList.test.js b/awx/ui/src/screens/ManagementJob/ManagementJobList/ManagementJobList.test.js
deleted file mode 100644
index e6a66fcc9427..000000000000
--- a/awx/ui/src/screens/ManagementJob/ManagementJobList/ManagementJobList.test.js
+++ /dev/null
@@ -1,111 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { SystemJobTemplatesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import ManagementJobList from './ManagementJobList';
-
-jest.mock('../../../api/models/SystemJobTemplates');
-
-const managementJobs = {
- data: {
- results: [
- {
- id: 1,
- name: 'Cleanup Activity Stream',
- description: 'Remove activity stream history',
- job_type: 'cleanup_activitystream',
- url: '/api/v2/system_job_templates/1/',
- },
- {
- id: 2,
- name: 'Cleanup Expired OAuth 2 Tokens',
- description: 'Cleanup expired OAuth 2 access and refresh tokens',
- job_type: 'cleanup_tokens',
- url: '/api/v2/system_job_templates/2/',
- },
- {
- id: 3,
- name: 'Cleanup Expired Sessions',
- description: 'Cleans out expired browser sessions',
- job_type: 'cleanup_sessions',
- url: '/api/v2/system_job_templates/3/',
- },
- {
- id: 4,
- name: 'Cleanup Job Details',
- description: 'Remove job history older than X days',
- job_type: 'cleanup_tokens',
- url: '/api/v2/system_job_templates/4/',
- },
- ],
- count: 4,
- },
-};
-
-const options = { data: { actions: { POST: true } } };
-
-describe('', () => {
- beforeEach(() => {
- SystemJobTemplatesAPI.read.mockResolvedValue(managementJobs);
- SystemJobTemplatesAPI.readOptions.mockResolvedValue(options);
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
- let wrapper;
-
- test('should mount successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ManagementJobList', (el) => el.length > 0);
- });
-
- test('should have data fetched and render 4 rows', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ManagementJobList', (el) => el.length > 0);
-
- expect(wrapper.find('ManagementJobListItem').length).toBe(4);
- expect(SystemJobTemplatesAPI.read).toBeCalled();
- expect(SystemJobTemplatesAPI.readOptions).toBeCalled();
- });
-
- test('should throw content error', async () => {
- SystemJobTemplatesAPI.read.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'GET',
- url: '/api/v2/system_job_templates',
- },
- data: 'An error occurred',
- },
- })
- );
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ManagementJobList', (el) => el.length > 0);
- expect(wrapper.find('ContentError').length).toBe(1);
- });
-
- test('should not render add button', async () => {
- SystemJobTemplatesAPI.read.mockResolvedValue(managementJobs);
- SystemJobTemplatesAPI.readOptions.mockResolvedValue({
- data: { actions: { POST: false } },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- waitForElement(wrapper, 'ManagementJobList', (el) => el.length > 0);
- expect(wrapper.find('ToolbarAddButton').length).toBe(0);
- });
-});
diff --git a/awx/ui/src/screens/ManagementJob/ManagementJobList/ManagementJobListItem.js b/awx/ui/src/screens/ManagementJob/ManagementJobList/ManagementJobListItem.js
deleted file mode 100644
index 5d6c3564f270..000000000000
--- a/awx/ui/src/screens/ManagementJob/ManagementJobList/ManagementJobListItem.js
+++ /dev/null
@@ -1,119 +0,0 @@
-import React, { useState } from 'react';
-
-import { t } from '@lingui/macro';
-import { Link, useHistory } from 'react-router-dom';
-import { Button, Tooltip } from '@patternfly/react-core';
-import { Tr, Td } from '@patternfly/react-table';
-import { RocketIcon } from '@patternfly/react-icons';
-
-import { SystemJobTemplatesAPI } from 'api';
-import AlertModal from 'components/AlertModal';
-import ErrorDetail from 'components/ErrorDetail';
-import { ActionsTd, ActionItem } from 'components/PaginatedTable';
-import LaunchManagementPrompt from './LaunchManagementPrompt';
-
-function ManagementJobListItem({
- onLaunchError,
- isPrompted,
- isSuperUser,
- id,
- jobType,
- name,
- description,
-}) {
- const detailsUrl = `/management_jobs/${id}`;
-
- const history = useHistory();
- const [isLaunchLoading, setIsLaunchLoading] = useState(false);
-
- const [isManagementPromptOpen, setIsManagementPromptOpen] = useState(false);
- const [isManagementPromptLoading, setIsManagementPromptLoading] =
- useState(false);
- const [managementPromptError, setManagementPromptError] = useState(null);
- const handleManagementPromptClick = () => setIsManagementPromptOpen(true);
- const handleManagementPromptClose = () => setIsManagementPromptOpen(false);
-
- const handleManagementPromptConfirm = async (days) => {
- setIsManagementPromptLoading(true);
- try {
- const { data } = await SystemJobTemplatesAPI.launch(id, {
- extra_vars: { days },
- });
- history.push(`/jobs/management/${data.id}/output`);
- } catch (error) {
- setManagementPromptError(error);
- } finally {
- setIsManagementPromptLoading(false);
- }
- };
-
- const handleLaunch = async () => {
- setIsLaunchLoading(true);
- try {
- const { data } = await SystemJobTemplatesAPI.launch(id);
- history.push(`/jobs/management/${data.id}/output`);
- } catch (error) {
- onLaunchError(error);
- } finally {
- setIsLaunchLoading(false);
- }
- };
-
- const rowId = `mgmt-jobs-row-${jobType ? jobType.replace('_', '-') : ''}`;
- return (
- <>
-
- |
-
-
- {name}
-
- |
- {description} |
-
-
- {isSuperUser ? (
- <>
- {isPrompted ? (
-
- ) : (
-
-
-
- )}{' '}
- >
- ) : null}
-
-
-
- {managementPromptError && (
- setManagementPromptError(null)}
- title={t`Management job launch error`}
- label={t`Management job launch error`}
- >
-
-
- )}
- >
- );
-}
-
-export default ManagementJobListItem;
diff --git a/awx/ui/src/screens/ManagementJob/ManagementJobList/ManagementJobListItem.test.js b/awx/ui/src/screens/ManagementJob/ManagementJobList/ManagementJobListItem.test.js
deleted file mode 100644
index 48052b224d2e..000000000000
--- a/awx/ui/src/screens/ManagementJob/ManagementJobList/ManagementJobListItem.test.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import ManagementJobListItem from './ManagementJobListItem';
-
-describe('', () => {
- let wrapper;
-
- const managementJob = {
- id: 3,
- name: 'Cleanup Expired Sessions',
- description: 'Cleans out expired browser sessions',
- job_type: 'cleanup_sessions',
- url: '/api/v2/system_job_templates/3/',
- };
-
- test('should mount successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('ManagementJobListItem').length).toBe(1);
- });
-
- test('should render the proper data', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('Td').at(1).text()).toBe(managementJob.name);
- expect(wrapper.find('Td').at(2).text()).toBe(managementJob.description);
-
- expect(wrapper.find('RocketIcon').exists()).toBeTruthy();
- });
-});
diff --git a/awx/ui/src/screens/ManagementJob/ManagementJobList/index.js b/awx/ui/src/screens/ManagementJob/ManagementJobList/index.js
deleted file mode 100644
index e55f0f261f4f..000000000000
--- a/awx/ui/src/screens/ManagementJob/ManagementJobList/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ManagementJobList';
diff --git a/awx/ui/src/screens/ManagementJob/ManagementJobs.js b/awx/ui/src/screens/ManagementJob/ManagementJobs.js
deleted file mode 100644
index 6d0d13c3fb85..000000000000
--- a/awx/ui/src/screens/ManagementJob/ManagementJobs.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import React, { useState, useCallback } from 'react';
-import { t } from '@lingui/macro';
-import { Route, Switch } from 'react-router-dom';
-import ScreenHeader from 'components/ScreenHeader';
-import PersistentFilters from 'components/PersistentFilters';
-import ManagementJob from './ManagementJob';
-import ManagementJobList from './ManagementJobList';
-
-function ManagementJobs() {
- const basePath = '/management_jobs';
-
- const [breadcrumbConfig, setBreadcrumbConfig] = useState({
- [basePath]: t`Management jobs`,
- });
-
- const buildBreadcrumbConfig = useCallback(({ id, name }, nested) => {
- if (!id) return;
-
- setBreadcrumbConfig({
- [basePath]: t`Management job`,
- [`${basePath}/${id}`]: name,
- [`${basePath}/${id}/notifications`]: t`Notifications`,
- [`${basePath}/${id}/schedules`]: t`Schedules`,
- [`${basePath}/${id}/schedules/add`]: t`Create New Schedule`,
- [`${basePath}/${id}/schedules/${nested?.id}`]: `${nested?.name}`,
- [`${basePath}/${id}/schedules/${nested?.id}/details`]: t`Details`,
- [`${basePath}/${id}/schedules/${nested?.id}/edit`]: t`Edit Details`,
- });
- }, []);
-
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-}
-
-export default ManagementJobs;
diff --git a/awx/ui/src/screens/ManagementJob/ManagementJobs.test.js b/awx/ui/src/screens/ManagementJob/ManagementJobs.test.js
deleted file mode 100644
index 173d343633f2..000000000000
--- a/awx/ui/src/screens/ManagementJob/ManagementJobs.test.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import React from 'react';
-
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-
-import ManagementJobs from './ManagementJobs';
-
-describe('', () => {
- let pageWrapper;
- let pageSections;
-
- beforeEach(() => {
- pageWrapper = mountWithContexts();
- pageSections = pageWrapper.find('PageSection');
- });
-
- test('renders ok', () => {
- expect(pageWrapper.length).toBe(1);
- expect(pageWrapper.find('ScreenHeader').length).toBe(1);
- expect(pageSections.length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/ManagementJob/index.js b/awx/ui/src/screens/ManagementJob/index.js
deleted file mode 100644
index 0f2749824a3b..000000000000
--- a/awx/ui/src/screens/ManagementJob/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ManagementJobs';
diff --git a/awx/ui/src/screens/Metrics/LineChart.js b/awx/ui/src/screens/Metrics/LineChart.js
deleted file mode 100644
index 3168a6a1de07..000000000000
--- a/awx/ui/src/screens/Metrics/LineChart.js
+++ /dev/null
@@ -1,253 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-import { t } from '@lingui/macro';
-import * as d3 from 'd3';
-
-function LineChart({ data, helpText }) {
- const count = data[0]?.values.length;
- const draw = useCallback(() => {
- const margin = 80;
- const getWidth = () => {
- let width;
- // This is in an a try/catch due to an error from jest.
- // Even though the d3.select returns a valid selector with
- // style function, it says it is null in the test
- try {
- width =
- parseInt(d3.select(`#chart`).style('width'), 10) - margin || 700;
- } catch (error) {
- width = 700;
- }
-
- return width;
- };
- const width = getWidth();
- const height = 500;
- const duration = 250;
- const circleRadius = 6;
- const circleRadiusHover = 8;
-
- /* Scale */
- let smallestY;
- let largestY;
- data.map((line) =>
- line.values.forEach((value) => {
- if (smallestY === undefined) {
- smallestY = value.y;
- }
- if (value.y < smallestY) {
- smallestY = value.y;
- }
- if (largestY === undefined) {
- largestY = smallestY + 10;
- }
- if (value.y > largestY) {
- largestY = value.y;
- }
- })
- );
-
- const xScale = d3
- .scaleLinear()
- .domain(
- d3.max(data[0].values, (d) => d.x) > 49
- ? d3.extent(data[0].values, (d) => d.x)
- : [0, 50]
- )
- .range([0, width - margin]);
-
- const yScale = d3
- .scaleLinear()
- .domain([smallestY, largestY])
- .range([height - margin, 0]);
-
- const color = d3.scaleOrdinal(d3.schemeCategory10);
-
- /* Add SVG */
- d3.selectAll(`#chart > *`).remove();
-
- const renderTooltip = (d) => {
- d3.selectAll(`.tooltip > *`).remove();
-
- d3.select('#chart')
- .append('span')
- .attr('class', 'tooltip')
- .attr('stroke', 'black')
- .attr('fill', 'white')
- .style('padding-left', '50px');
- const tooltip = {};
- data.map((datum) => {
- datum.values.forEach((value) => {
- if (d.x === value.x) {
- tooltip[datum.name] = value.y;
- }
- });
- return tooltip;
- });
- Object.entries(tooltip).forEach(([key, value], i) => {
- d3.select('.tooltip')
- .append('span')
- .attr('class', 'tooltip-text-wrapper')
- .append('text')
- .attr('class', 'tooltip-text')
- .style('color', color(i))
- .style('padding-right', '20px')
- .text(`${key}: ${value}`);
- });
- };
- const removeTooltip = () => {
- d3.select('.tooltip')
- .style('cursor', 'none')
- .selectAll(`.tooltip > *`)
- .remove();
- };
-
- // Add legend
- d3.selectAll(`.legend > *`).remove();
- const legendContainer = d3
- .select('#chart')
- .append('div')
- .style('display', 'flex')
- .attr('class', 'legend')
- .attr('height', '400px')
- .attr('width', '500px')
- .style('padding-left', '50px');
-
- legendContainer
- .append('text')
- .attr('class', 'legend-title')
- .attr('x', '100')
- .attr('y', '50')
- .text(t`Legend`);
-
- legendContainer.data(data, (d, i) => {
- if (d?.name) {
- const legendItemContainer = legendContainer
- .append('div')
- .style('display', 'flex')
- .attr('id', 'legend-item-container')
- .style('padding-left', '20px');
-
- legendItemContainer
- .append('div')
- .style('background-color', color(i))
- .style('height', '8px')
- .style('width', '8px')
- .style('border-radius', '50%')
- .style('padding', '5px')
- .style('margin-top', '6px');
-
- legendItemContainer
- .append('text')
- .style('padding-left', '20px')
- .text(d.name);
- }
- });
-
- // Add help text to top of chart
-
- d3.select('#chart')
- .append('div')
- .attr('class', 'help-text')
- .style('padding-left', '50px')
- .style('padding-top', '20px')
- .text(helpText);
-
- const svg = d3
- .select('#chart')
- .append('svg')
- .attr('width', `${width + margin}px`)
- .attr('height', `${height + margin}px`)
- .append('g')
- .attr('transform', `translate(${margin}, ${margin})`);
-
- /* Add line into SVG */
- const line = d3
- .line()
- .curve(d3.curveMonotoneX)
- .x((d) => xScale(d.x))
- .y((d) => yScale(d.y));
-
- const lines = svg.append('g');
-
- lines
- .selectAll('.line-group')
- .data(data)
- .enter()
- .append('g')
- .attr('class', 'line-group')
- .append('path')
- .attr('class', 'line')
- .style('fill', 'none')
- .attr('d', (d) => line(d.values))
- .style('stroke', (d, i) => color(i))
- .style('stroke-width', '3px');
-
- /* Add circles in the line */
- lines
- .selectAll('circle-group')
- .data(data)
- .enter()
- .append('g')
- .style('fill', (d, i) => color(i))
- .selectAll('circle')
- .data((d) => d.values)
- .enter()
- .append('g')
- .attr('class', 'circle')
- .on('mouseover', (d, i) => {
- if (data.length) {
- renderTooltip(d, i);
- }
- })
- .on('mouseout', () => {
- removeTooltip();
- })
- .append('circle')
- .attr('cx', (d) => xScale(d.x))
- .attr('cy', (d) => yScale(d.y))
- .attr('r', circleRadius)
- .on('mouseover', () => {
- d3.select(this)
- .transition()
- .duration(duration)
- .attr('r', circleRadiusHover);
- })
- .on('mouseout', () => {
- d3.select(this).transition().duration(duration).attr('r', circleRadius);
- });
-
- /* Add Axis into SVG */
- const xAxis = d3
- .axisBottom(xScale)
- .ticks(data[0].values.length > 5 ? data[0].values.length : 5);
- const yAxis = d3.axisLeft(yScale).ticks(5);
-
- svg
- .append('g')
- .attr('class', 'x axis')
- .attr('transform', `translate(0, ${height - margin})`)
- .call(xAxis);
-
- svg.append('g').attr('class', 'y axis').call(yAxis);
- }, [data, helpText]);
-
- useEffect(() => {
- draw();
- }, [count, draw]);
-
- useEffect(() => {
- function handleResize() {
- draw();
- }
-
- window.addEventListener('resize', handleResize);
-
- handleResize();
-
- return () => window.removeEventListener('resize', handleResize);
- }, [draw]);
-
- return ;
-}
-
-export default LineChart;
diff --git a/awx/ui/src/screens/Metrics/LineChart.test.js b/awx/ui/src/screens/Metrics/LineChart.test.js
deleted file mode 100644
index a19e20072f88..000000000000
--- a/awx/ui/src/screens/Metrics/LineChart.test.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import LineChart from './LineChart';
-
-describe('', () => {
- test('should render properly', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('LineChart').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Metrics/Metrics.js b/awx/ui/src/screens/Metrics/Metrics.js
deleted file mode 100644
index bee67f00ed95..000000000000
--- a/awx/ui/src/screens/Metrics/Metrics.js
+++ /dev/null
@@ -1,252 +0,0 @@
-import React, { useEffect, useCallback, useState, useRef } from 'react';
-
-import { t } from '@lingui/macro';
-import {
- PageSection,
- Card,
- CardHeader,
- CardBody,
- Toolbar,
- ToolbarGroup,
- ToolbarContent,
- ToolbarItem,
- Select,
- SelectOption,
-} from '@patternfly/react-core';
-
-import { MetricsAPI, InstancesAPI } from 'api';
-import useRequest from 'hooks/useRequest';
-import ContentEmpty from 'components/ContentEmpty';
-import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
-import ContentError from 'components/ContentError';
-import LineChart from './LineChart';
-
-let count = [0];
-
-// hook thats calls api every 3 seconds to get data
-function useInterval(callback, delay, instance, metric) {
- const savedCallback = useRef();
- useEffect(() => {
- savedCallback.current = callback;
- }, [callback]);
- useEffect(() => {
- function tick() {
- count.push(count.length);
- if (instance && metric) {
- savedCallback.current();
- }
- }
-
- const id = setInterval(tick, delay);
- return () => {
- clearInterval(id);
- };
- }, [callback, delay, instance, metric]);
- return { count };
-}
-function Metrics() {
- const [instanceIsOpen, setInstanceIsOpen] = useState(false);
- const [instance, setInstance] = useState(null);
- const [metric, setMetric] = useState(null);
- const [metricIsOpen, setMetricIsOpen] = useState(false);
- const [renderedData, setRenderedData] = useState([]);
- const {
- result: { instances, metrics },
- error: fetchInitialError,
- request: fetchInstances,
- } = useRequest(
- useCallback(async () => {
- const [
- {
- data: { results },
- },
- { data: mets },
- ] = await Promise.all([
- InstancesAPI.read(),
- MetricsAPI.read({
- subsystemonly: 1,
- format: 'json',
- }),
- ]);
-
- const metricOptions = Object.keys(mets);
- const instanceNames = [];
- results.forEach((result) => {
- if (result.node_type !== 'execution') {
- instanceNames.push(result.hostname);
- }
- });
-
- return {
- instances:
- instanceNames.length > 1 ? [...instanceNames, t`All`] : instanceNames,
- metrics: metricOptions,
- };
- }, []),
- { instances: [], metrics: [] }
- );
-
- const {
- result: helpText,
- error: updateError,
- request: fetchData,
- } = useRequest(
- useCallback(async () => {
- const { data } = await MetricsAPI.read({
- subsystemonly: 1,
- format: 'json',
- node: instance === 'All' ? null : instance,
- metric,
- });
-
- const rendered = renderedData;
- const instanceData = Object.values(data);
- instanceData.forEach((value) => {
- value.samples.forEach((sample) => {
- instances.forEach((i) => {
- if (i === sample.labels.node) {
- const renderedIndex = renderedData.findIndex(
- (rd) => rd.name === i
- );
-
- if (renderedIndex === -1) {
- rendered.push({
- name: i,
- values: [
- {
- y: sample.value,
- x: count.length - 1,
- },
- ],
- });
- } else if (
- rendered[renderedIndex].values?.length === 0 ||
- !rendered[renderedIndex].values
- ) {
- rendered[renderedIndex].values = [
- { y: sample.value, x: count.length - 1 },
- ];
- } else {
- rendered[renderedIndex].values = [
- ...rendered[renderedIndex].values,
- { y: sample.value, x: count.length - 1 },
- ];
- }
- }
- });
- });
- });
- let countRestrictedData = rendered;
- if (count.length > 49) {
- countRestrictedData = rendered.map(({ values, name }) => ({
- name,
- values: values.slice(-50),
- }));
- }
-
- setRenderedData(countRestrictedData);
- return data[metric].help_text;
-
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [instance, metric, instances]),
- ''
- );
-
- useInterval(fetchData, 3000, instance, metric);
-
- useEffect(() => {
- if (instance && metric) {
- fetchData();
- }
- }, [fetchData, instance, metric]);
-
- useEffect(() => {
- fetchInstances();
- }, [fetchInstances]);
- if (fetchInitialError || updateError) {
- return (
-
-
-
- ;
-
-
-
- );
- }
- return (
- <>
-
-
-
-
-
-
-
-
- {t`Instance`}
-
-
-
- {t`Metric`}
-
-
-
-
-
-
-
-
- {instance && metric ? (
- Object.keys(renderedData).length > 0 && (
-
- )
- ) : (
-
- )}
-
-
-
- >
- );
-}
-
-export default Metrics;
diff --git a/awx/ui/src/screens/Metrics/Metrics.test.js b/awx/ui/src/screens/Metrics/Metrics.test.js
deleted file mode 100644
index ceff796c58cb..000000000000
--- a/awx/ui/src/screens/Metrics/Metrics.test.js
+++ /dev/null
@@ -1,88 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { MetricsAPI, InstancesAPI } from 'api';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import Metrics from './Metrics';
-
-jest.mock('../../api/models/Instances');
-jest.mock('../../api/models/Metrics');
-
-describe('', () => {
- let wrapper;
- beforeEach(async () => {
- InstancesAPI.read.mockResolvedValue({
- data: {
- results: [
- { hostname: 'instance 1', node_type: 'control' },
- { hostname: 'instance 2', node_type: 'hybrid' },
- { hostname: 'receptor', node_type: 'execution' },
- ],
- },
- });
- MetricsAPI.read.mockResolvedValue({
- data: {
- metric1: {
- helptext: 'metric 1 help text',
- samples: [{ labels: { node: 'metric 1' }, value: 20 }],
- },
- metric2: {
- helptext: 'metric 2 help text',
- samples: [{ labels: { node: 'metric 2' }, value: 10 }],
- },
- },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- });
- afterEach(() => {
- jest.clearAllMocks();
- });
- test('should mound properly', () => {
- expect(wrapper.find('Metrics').length).toBe(1);
- expect(wrapper.find('EmptyStateBody').length).toBe(1);
- expect(wrapper.find('ChartLine').length).toBe(0);
- });
- test('should render chart after selecting metric and instance', async () => {
- await act(async () => {
- wrapper.find('Select[ouiaId="Instance-select"]').prop('onToggle')(true);
- });
- wrapper.update();
- await act(async () => {
- wrapper
- .find('SelectOption[value="instance 1"]')
- .find('button')
- .prop('onClick')({}, 'instance 1');
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('Select[ouiaId="Metric-select"]').prop('onToggle')(true);
- });
- wrapper.update();
- await act(async () => {
- wrapper
- .find('SelectOption[value="metric1"]')
- .find('button')
- .prop('onClick')({}, 'metric1');
- });
- wrapper.update();
- expect(MetricsAPI.read).toBeCalledWith({
- subsystemonly: 1,
- format: 'json',
- metric: 'metric1',
- node: 'instance 1',
- });
- });
-
- test('should not include receptor instances', async () => {
- await act(async () => {
- wrapper.find('Select[ouiaId="Instance-select"]').prop('onToggle')(true);
- });
- wrapper.update();
- expect(wrapper.find('SelectOption[value="receptor"]')).toHaveLength(0);
- expect(
- wrapper.find('Select[ouiaId="Instance-select"]').find('SelectOption')
- ).toHaveLength(3);
- });
-});
diff --git a/awx/ui/src/screens/Metrics/index.js b/awx/ui/src/screens/Metrics/index.js
deleted file mode 100644
index 8e5880233fef..000000000000
--- a/awx/ui/src/screens/Metrics/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './Metrics';
diff --git a/awx/ui/src/screens/NotFound.js b/awx/ui/src/screens/NotFound.js
deleted file mode 100644
index 8976d5edb2ff..000000000000
--- a/awx/ui/src/screens/NotFound.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import React from 'react';
-import { PageSection, Card } from '@patternfly/react-core';
-import ContentError from 'components/ContentError';
-
-function NotFound() {
- return (
-
-
-
-
-
- );
-}
-
-export default NotFound;
diff --git a/awx/ui/src/screens/NotificationTemplate/NotificationTemplate.js b/awx/ui/src/screens/NotificationTemplate/NotificationTemplate.js
deleted file mode 100644
index 90f20f9b85b2..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/NotificationTemplate.js
+++ /dev/null
@@ -1,122 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-import { t } from '@lingui/macro';
-
-import { Card, PageSection } from '@patternfly/react-core';
-import { CaretLeftIcon } from '@patternfly/react-icons';
-import {
- Link,
- Switch,
- Route,
- Redirect,
- useParams,
- useRouteMatch,
- useLocation,
-} from 'react-router-dom';
-import useRequest from 'hooks/useRequest';
-import RoutedTabs from 'components/RoutedTabs';
-import ContentError from 'components/ContentError';
-import { NotificationTemplatesAPI } from 'api';
-import ContentLoading from 'components/ContentLoading';
-import NotificationTemplateDetail from './NotificationTemplateDetail';
-import NotificationTemplateEdit from './NotificationTemplateEdit';
-
-function NotificationTemplate({ setBreadcrumb }) {
- const { id: templateId } = useParams();
- const match = useRouteMatch();
- const location = useLocation();
- const {
- result: { template, defaultMessages },
- isLoading,
- error,
- request: fetchTemplate,
- } = useRequest(
- useCallback(async () => {
- const [detail, options] = await Promise.all([
- NotificationTemplatesAPI.readDetail(templateId),
- NotificationTemplatesAPI.readOptions(),
- ]);
- setBreadcrumb(detail.data);
- return {
- template: detail.data,
- defaultMessages: options.data.actions?.POST?.messages,
- };
- }, [templateId, setBreadcrumb]),
- { template: null, defaultMessages: null }
- );
-
- useEffect(() => {
- fetchTemplate();
- }, [fetchTemplate, location.pathname]);
-
- if (!isLoading && error) {
- return (
-
-
-
- {error.response?.status === 404 && (
-
- {t`Notification Template not found.`}{' '}
-
- {t`View all Notification Templates.`}
-
-
- )}
-
-
-
- );
- }
-
- const showCardHeader = !location.pathname.endsWith('edit');
- const tabs = [
- {
- name: (
- <>
-
- {t`Back to Notifications`}
- >
- ),
- link: `/notification_templates`,
- id: 99,
- isBackButton: true,
- },
- {
- name: t`Details`,
- link: `${match.url}/details`,
- id: 0,
- },
- ];
- return (
-
-
- {showCardHeader && }
-
-
- {isLoading && }
- {template && (
- <>
-
-
-
-
-
-
- >
- )}
-
-
-
- );
-}
-
-export default NotificationTemplate;
diff --git a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateAdd.js b/awx/ui/src/screens/NotificationTemplate/NotificationTemplateAdd.js
deleted file mode 100644
index ab2252829c68..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateAdd.js
+++ /dev/null
@@ -1,81 +0,0 @@
-import React, { useState, useEffect, useCallback } from 'react';
-import { useHistory, Link } from 'react-router-dom';
-import { t } from '@lingui/macro';
-
-import { Card, PageSection } from '@patternfly/react-core';
-import { CardBody } from 'components/Card';
-import { NotificationTemplatesAPI } from 'api';
-import useRequest from 'hooks/useRequest';
-import ContentError from 'components/ContentError';
-import NotificationTemplateForm from './shared/NotificationTemplateForm';
-
-function NotificationTemplateAdd() {
- const history = useHistory();
- const [formError, setFormError] = useState(null);
- const {
- result: defaultMessages,
- error,
- request: fetchDefaultMessages,
- } = useRequest(
- useCallback(async () => {
- const { data } = await NotificationTemplatesAPI.readOptions();
- return data.actions.POST.messages;
- }, [])
- );
-
- useEffect(() => {
- fetchDefaultMessages();
- }, [fetchDefaultMessages]);
-
- const handleSubmit = async (values) => {
- try {
- const { data } = await NotificationTemplatesAPI.create(values);
- history.push(`/notification_templates/${data.id}`);
- } catch (err) {
- setFormError(err);
- }
- };
-
- const handleCancel = () => {
- history.push('/notification_templates');
- };
-
- if (error) {
- return (
-
-
-
- {error.response.status === 404 && (
-
- {t`Notification Template not found.`}{' '}
-
- {t`View all Notification Templates.`}
-
-
- )}
-
-
-
- );
- }
-
- return (
-
-
-
- {defaultMessages && (
-
- )}
-
-
-
- );
-}
-
-export { NotificationTemplateAdd as _NotificationTemplateAdd };
-export default NotificationTemplateAdd;
diff --git a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js b/awx/ui/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js
deleted file mode 100644
index aaf12f2cd98b..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js
+++ /dev/null
@@ -1,617 +0,0 @@
-import React, { useState, useCallback } from 'react';
-import { Link, useHistory } from 'react-router-dom';
-import {
- Button,
- TextList,
- TextListItem,
- TextListItemVariants,
- TextListVariants,
-} from '@patternfly/react-core';
-import { t } from '@lingui/macro';
-import AlertModal from 'components/AlertModal';
-import { CardBody, CardActionsRow } from 'components/Card';
-import {
- Detail,
- ArrayDetail,
- DetailList,
- DeletedDetail,
- UserDateDetail,
-} from 'components/DetailList';
-import CodeDetail from 'components/DetailList/CodeDetail';
-import DeleteButton from 'components/DeleteButton';
-import ErrorDetail from 'components/ErrorDetail';
-import { NotificationTemplatesAPI, NotificationsAPI } from 'api';
-import useRequest, { useDismissableError } from 'hooks/useRequest';
-import StatusLabel from 'components/StatusLabel';
-import hasCustomMessages from '../shared/hasCustomMessages';
-import { NOTIFICATION_TYPES } from '../constants';
-import getHelpText from '../shared/Notifications.helptext';
-
-const NUM_RETRIES = 25;
-const RETRY_TIMEOUT = 5000;
-
-function NotificationTemplateDetail({ template, defaultMessages }) {
- const helpText = getHelpText();
- const history = useHistory();
- const [testStatus, setTestStatus] = useState(
- template.summary_fields?.recent_notifications[0]?.status ?? undefined
- );
- const {
- created,
- modified,
- notification_configuration: configuration,
- summary_fields,
- messages,
- } = template;
-
- const renderOptionsField = configuration.use_ssl || configuration.use_tls;
-
- const renderOptions = (
-
- {configuration.use_ssl && (
-
- {t`Use SSL`}
-
- )}
- {configuration.use_tls && (
-
- {t`Use TLS`}
-
- )}
-
- );
-
- const {
- request: deleteTemplate,
- isLoading,
- error: deleteError,
- } = useRequest(
- useCallback(async () => {
- await NotificationTemplatesAPI.destroy(template.id);
- history.push(`/notification_templates`);
- }, [template.id, history])
- );
-
- const { request: sendTestNotification, error: testError } = useRequest(
- useCallback(async () => {
- setTestStatus('running');
-
- let retries = NUM_RETRIES;
- const {
- data: { notification: notificationId },
- } = await NotificationTemplatesAPI.test(template.id);
-
- async function pollForStatusChange() {
- const { data: notification } = await NotificationsAPI.readDetail(
- notificationId
- );
- if (notification.status !== 'pending') {
- setTestStatus(notification.status);
- return;
- }
- retries--;
- if (retries > 0) {
- setTimeout(pollForStatusChange, RETRY_TIMEOUT);
- }
- }
-
- setTimeout(pollForStatusChange, RETRY_TIMEOUT);
- }, [template.id])
- );
-
- const { error, dismissError } = useDismissableError(deleteError || testError);
- const typeMessageDefaults = defaultMessages?.[template?.notification_type];
- return (
-
-
-
-
- {summary_fields.recent_notifications.length ? (
- }
- />
- ) : null}
- {summary_fields.organization ? (
-
- {summary_fields.organization.name}
-
- }
- />
- ) : (
-
- )}
-
- {template.notification_type === 'email' && (
- <>
-
-
-
-
-
-
- {renderOptionsField && (
-
- )}
- >
- )}
- {template.notification_type === 'grafana' && (
- <>
-
-
-
-
-
- >
- )}
- {template.notification_type === 'irc' && (
- <>
-
-
-
-
-
- >
- )}
- {template.notification_type === 'mattermost' && (
- <>
-
-
-
-
-
- >
- )}
- {template.notification_type === 'pagerduty' && (
- <>
-
-
-
- >
- )}
- {template.notification_type === 'rocketchat' && (
- <>
-
-
-
-
- >
- )}
- {template.notification_type === 'slack' && (
- <>
-
-
- >
- )}
- {template.notification_type === 'twilio' && (
- <>
-
-
-
- >
- )}
- {template.notification_type === 'webhook' && (
- <>
-
-
-
-
-
- >
- )}
-
-
- {typeMessageDefaults &&
- hasCustomMessages(messages, typeMessageDefaults) ? (
-
- ) : null}
-
-
- {summary_fields.user_capabilities?.edit && (
- <>
-
-
- >
- )}
- {summary_fields.user_capabilities?.delete && (
-
- {t`Delete`}
-
- )}
-
- {error && (
-
- {deleteError
- ? t`Failed to delete notification.`
- : t`Notification test failed.`}
-
-
- )}
-
- );
-}
-
-function CustomMessageDetails({ messages, defaults, type }) {
- const showMessages = type !== 'webhook';
- const showBodies = ['email', 'pagerduty', 'webhook'].includes(type);
-
- return (
- <>
- {showMessages && (
-
- )}
- {showBodies && (
-
- )}
- {showMessages && (
-
- )}
- {showBodies && (
-
- )}
- {showMessages && (
-
- )}
- {showBodies && (
-
- )}
- {showMessages && (
-
- )}
- {showBodies && (
-
- )}
- {showMessages && (
-
- )}
- {showBodies && (
-
- )}
- {showMessages && (
-
- )}
- {showBodies && (
-
- )}
- {showMessages && (
-
- )}
- {showBodies && (
-
- )}
- >
- );
-}
-
-export default NotificationTemplateDetail;
diff --git a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.test.js b/awx/ui/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.test.js
deleted file mode 100644
index 792aabc8ba51..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.test.js
+++ /dev/null
@@ -1,128 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import NotificationTemplateDetail from './NotificationTemplateDetail';
-import defaultMessages from '../shared/notification-template-default-messages.json';
-
-jest.mock('../../../api');
-
-const mockTemplate = {
- id: 1,
- type: 'notification_template',
- url: '/api/v2/notification_templates/1/',
- related: {
- named_url: '/api/v2/notification_templates/abc++Default/',
- created_by: '/api/v2/users/2/',
- modified_by: '/api/v2/users/2/',
- test: '/api/v2/notification_templates/1/test/',
- notifications: '/api/v2/notification_templates/1/notifications/',
- copy: '/api/v2/notification_templates/1/copy/',
- organization: '/api/v2/organizations/1/',
- },
- summary_fields: {
- organization: {
- id: 1,
- name: 'Default',
- description: '',
- },
- created_by: {
- id: 2,
- username: 'test',
- first_name: '',
- last_name: '',
- },
- modified_by: {
- id: 2,
- username: 'test',
- first_name: '',
- last_name: '',
- },
- user_capabilities: {
- edit: true,
- delete: true,
- copy: true,
- },
- recent_notifications: [{ status: 'success' }],
- },
- created: '2021-06-16T18:52:23.811374Z',
- modified: '2021-06-16T18:53:37.631371Z',
- name: 'abc',
- description: 'foo description',
- organization: 1,
- notification_type: 'email',
- notification_configuration: {
- username: '',
- password: '',
- host: 'https://localhost',
- recipients: ['foo@ansible.com'],
- sender: 'bar@ansible.com',
- port: 324,
- timeout: 11,
- use_ssl: true,
- use_tls: true,
- },
- messages: null,
-};
-
-describe('', () => {
- let wrapper;
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should render Details', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- function assertDetail(label, value) {
- expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
- expect(wrapper.find(`Detail[label="${label}"] dd`).text()).toBe(value);
- }
- assertDetail('Name', mockTemplate.name);
- assertDetail('Description', mockTemplate.description);
- expect(
- wrapper
- .find('Detail[label="Email Options"]')
- .containsAllMatchingElements([Use SSL, Use TLS])
- ).toEqual(true);
- expect(
- wrapper.find('Detail[label="Email Options"]').prop('helpText')
- ).toBeDefined();
- });
-
- test('should render Details when defaultMessages is missing', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- function assertDetail(label, value) {
- expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
- expect(wrapper.find(`Detail[label="${label}"] dd`).text()).toBe(value);
- }
- assertDetail('Name', mockTemplate.name);
- assertDetail('Description', mockTemplate.description);
- expect(
- wrapper
- .find('Detail[label="Email Options"]')
- .containsAllMatchingElements([Use SSL, Use TLS])
- ).toEqual(true);
- expect(
- wrapper.find('Detail[label="Email Options"]').prop('helpText')
- ).toBeDefined();
- });
-});
diff --git a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateDetail/index.js b/awx/ui/src/screens/NotificationTemplate/NotificationTemplateDetail/index.js
deleted file mode 100644
index 118818bb645c..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateDetail/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import NotificationTemplateDetail from './NotificationTemplateDetail';
-
-export default NotificationTemplateDetail;
diff --git a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateEdit/NotificationTemplateEdit.js b/awx/ui/src/screens/NotificationTemplate/NotificationTemplateEdit/NotificationTemplateEdit.js
deleted file mode 100644
index 69c675a86064..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateEdit/NotificationTemplateEdit.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import React, { useState } from 'react';
-import PropTypes from 'prop-types';
-import { useHistory } from 'react-router-dom';
-import { CardBody } from 'components/Card';
-import { NotificationTemplatesAPI } from 'api';
-import NotificationTemplateForm from '../shared/NotificationTemplateForm';
-
-function NotificationTemplateEdit({ template, defaultMessages }) {
- const detailsUrl = `/notification_templates/${template.id}/details`;
- const history = useHistory();
- const [formError, setFormError] = useState(null);
-
- const handleSubmit = async (values) => {
- try {
- await NotificationTemplatesAPI.update(template.id, values);
- history.push(detailsUrl);
- } catch (error) {
- setFormError(error);
- }
- };
-
- const handleCancel = () => {
- history.push(detailsUrl);
- };
-
- return (
-
-
-
- );
-}
-
-NotificationTemplateEdit.propTypes = {
- template: PropTypes.shape().isRequired,
-};
-
-export { NotificationTemplateEdit as _NotificationTemplateEdit };
-export default NotificationTemplateEdit;
diff --git a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateEdit/index.js b/awx/ui/src/screens/NotificationTemplate/NotificationTemplateEdit/index.js
deleted file mode 100644
index be9b40a69c44..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateEdit/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import NotificationTemplateEdit from './NotificationTemplateEdit';
-
-export default NotificationTemplateEdit;
diff --git a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js b/awx/ui/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js
deleted file mode 100644
index defa2ef9206a..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js
+++ /dev/null
@@ -1,244 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { useLocation, useRouteMatch } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { Card, PageSection } from '@patternfly/react-core';
-import { NotificationTemplatesAPI } from 'api';
-import PaginatedTable, {
- HeaderRow,
- HeaderCell,
- ToolbarAddButton,
- ToolbarDeleteButton,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import AlertModal from 'components/AlertModal';
-import ErrorDetail from 'components/ErrorDetail';
-import DataListToolbar from 'components/DataListToolbar';
-import useRequest, { useDeleteItems } from 'hooks/useRequest';
-import useSelected from 'hooks/useSelected';
-import useToast, { AlertVariant } from 'hooks/useToast';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import NotificationTemplateListItem from './NotificationTemplateListItem';
-
-const QS_CONFIG = getQSConfig('notification-templates', {
- page: 1,
- page_size: 20,
- order_by: 'name',
-});
-
-function NotificationTemplatesList() {
- const location = useLocation();
- const match = useRouteMatch();
- // const [testToasts, setTestToasts] = useState([]);
- const { addToast, Toast, toastProps } = useToast();
-
- const addUrl = `${match.url}/add`;
-
- const {
- result: {
- templates,
- count,
- actions,
- relatedSearchableKeys,
- searchableKeys,
- },
- error: contentError,
- isLoading: isTemplatesLoading,
- request: fetchTemplates,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
- const [response, actionsResponse] = await Promise.all([
- NotificationTemplatesAPI.read(params),
- NotificationTemplatesAPI.readOptions(),
- ]);
- return {
- templates: response.data.results,
- count: response.data.count,
- actions: actionsResponse.data.actions,
- relatedSearchableKeys: (
- actionsResponse.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
- };
- }, [location]),
- {
- templates: [],
- count: 0,
- actions: {},
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- useEffect(() => {
- fetchTemplates();
- }, [fetchTemplates]);
-
- const { selected, isAllSelected, handleSelect, clearSelected, selectAll } =
- useSelected(templates);
-
- const {
- isLoading: isDeleteLoading,
- deleteItems: deleteTemplates,
- deletionError,
- clearDeletionError,
- } = useDeleteItems(
- useCallback(
- () =>
- Promise.all(
- selected.map(({ id }) => NotificationTemplatesAPI.destroy(id))
- ),
- [selected]
- ),
- {
- qsConfig: QS_CONFIG,
- allItemsSelected: isAllSelected,
- fetchItems: fetchTemplates,
- }
- );
-
- const handleDelete = async () => {
- await deleteTemplates();
- clearSelected();
- };
-
- const canAdd = actions && actions.POST;
-
- return (
- <>
-
-
- (
- ]
- : []),
- ,
- ]}
- />
- )}
- headerRow={
-
- {t`Name`}
- {t`Status`}
- {t`Type`}
- {t`Actions`}
-
- }
- renderRow={(template, index) => (
- {
- if (notification.status === 'pending') {
- return;
- }
-
- let message;
- if (notification.status === 'successful') {
- message = t`Notification sent successfully`;
- }
- if (notification.status === 'failed') {
- if (notification?.error === 'timed out') {
- message = t`Notification timed out`;
- } else {
- message = notification.error;
- }
- }
-
- addToast({
- id: notification.id,
- title:
- notification.summary_fields.notification_template.name,
- variant:
- notification.status === 'failed'
- ? AlertVariant.danger
- : AlertVariant.success,
- hasTimeout: notification.status !== 'failed',
- message,
- });
- }}
- key={template.id}
- fetchTemplates={fetchTemplates}
- template={template}
- detailUrl={`${match.url}/${template.id}`}
- isSelected={selected.some((row) => row.id === template.id)}
- onSelect={() => handleSelect(template)}
- rowIndex={index}
- />
- )}
- emptyStateControls={
- canAdd ? : null
- }
- />
-
-
-
- {t`Failed to delete one or more notification template.`}
-
-
-
- >
- );
-}
-
-export default NotificationTemplatesList;
diff --git a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.test.js b/awx/ui/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.test.js
deleted file mode 100644
index aa7c73724cb8..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.test.js
+++ /dev/null
@@ -1,246 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import {
- NotificationsAPI,
- NotificationTemplatesAPI,
- OrganizationsAPI,
-} from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import NotificationTemplateList from './NotificationTemplateList';
-
-jest.mock('../../../api');
-
-const mockTemplates = {
- data: {
- count: 3,
- results: [
- {
- name: 'Boston',
- id: 1,
- url: '/notification_templates/1',
- type: 'slack',
- summary_fields: {
- recent_notifications: [
- {
- status: 'success',
- },
- ],
- user_capabilities: {
- delete: true,
- edit: true,
- },
- },
- },
- {
- name: 'Minneapolis',
- id: 2,
- url: '/notification_templates/2',
- summary_fields: {
- recent_notifications: [],
- user_capabilities: {
- delete: true,
- edit: true,
- },
- },
- },
- {
- name: 'Philidelphia',
- id: 3,
- url: '/notification_templates/3',
- summary_fields: {
- recent_notifications: [
- {
- status: 'failed',
- },
- {
- status: 'success',
- },
- ],
- user_capabilities: {
- delete: true,
- edit: true,
- },
- },
- },
- ],
- },
-};
-
-describe('', () => {
- let wrapper;
- beforeEach(() => {
- OrganizationsAPI.read.mockResolvedValue(mockTemplates);
- OrganizationsAPI.readOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- },
- });
- });
- afterEach(() => {
- jest.resetAllMocks();
- });
-
- test('should load notifications', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
- expect(OrganizationsAPI.read).toHaveBeenCalledTimes(1);
- expect(wrapper.find('NotificationTemplateListItem').length).toBe(3);
- });
-
- test('should select item', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').prop('checked')
- ).toEqual(false);
- await act(async () => {
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .props()
- .onChange();
- });
- wrapper.update();
- expect(
- wrapper.find('.pf-c-table__check').first().find('input').prop('checked')
- ).toEqual(true);
- });
-
- test('should delete notifications', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
- expect(OrganizationsAPI.read).toHaveBeenCalledTimes(1);
- await act(async () => {
- wrapper.find('Checkbox#select-all').props().onChange(true);
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('button[aria-label="Delete"]').simulate('click');
- wrapper.update();
- });
- const deleteButton = global.document.querySelector(
- 'body div[role="dialog"] button[aria-label="confirm delete"]'
- );
- expect(deleteButton).not.toEqual(null);
- await act(async () => {
- deleteButton.click();
- });
- expect(OrganizationsAPI.destroy).toHaveBeenCalledTimes(3);
- expect(OrganizationsAPI.read).toHaveBeenCalledTimes(2);
- });
-
- test('should show error dialog shown for failed deletion', async () => {
- OrganizationsAPI.destroy.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'delete',
- url: '/api/v2/organizations/1',
- },
- data: 'An error occurred',
- },
- })
- );
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
- await act(async () => {
- wrapper
- .find('.pf-c-table__check')
- .first()
- .find('input')
- .props()
- .onChange();
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('button[aria-label="Delete"]').simulate('click');
- wrapper.update();
- });
- const deleteButton = global.document.querySelector(
- 'body div[role="dialog"] button[aria-label="confirm delete"]'
- );
- expect(deleteButton).not.toEqual(null);
- await act(async () => {
- deleteButton.click();
- });
- wrapper.update();
-
- const modal = wrapper.find('Modal');
- expect(modal.prop('isOpen')).toEqual(true);
- expect(modal.prop('title')).toEqual('Error!');
- });
-
- test('should show add button', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
- expect(wrapper.find('ToolbarAddButton').length).toBe(1);
- });
-
- test('should show toast after test resolves', async () => {
- jest.useFakeTimers();
- NotificationTemplatesAPI.test.mockResolvedValue({
- data: {
- notification: 9182,
- },
- });
- NotificationsAPI.readDetail.mockResolvedValue({
- data: {
- id: 9182,
- status: 'failed',
- error: 'There was an error with the notification',
- summary_fields: {
- notification_template: {
- name: 'foobar',
- },
- },
- },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
- expect(wrapper.find('Alert').length).toBe(0);
- await act(async () => {
- wrapper
- .find('button[aria-label="Test Notification"]')
- .at(0)
- .simulate('click');
- });
- wrapper.update();
-
- await act(async () => {
- jest.runAllTimers();
- });
- wrapper.update();
- expect(wrapper.find('Alert').length).toBe(1);
- });
-
- test('should hide add button (rbac)', async () => {
- OrganizationsAPI.readOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {},
- },
- },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
- expect(wrapper.find('ToolbarAddButton').length).toBe(0);
- });
-});
diff --git a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js b/awx/ui/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js
deleted file mode 100644
index ef98cdbd0f0e..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.js
+++ /dev/null
@@ -1,180 +0,0 @@
-import 'styled-components/macro';
-import React, { useState, useEffect, useCallback } from 'react';
-import { t } from '@lingui/macro';
-import { Link } from 'react-router-dom';
-import { Button } from '@patternfly/react-core';
-import { Tr, Td } from '@patternfly/react-table';
-import { PencilAltIcon, BellIcon } from '@patternfly/react-icons';
-import { ActionsTd, ActionItem, TdBreakWord } from 'components/PaginatedTable';
-import { timeOfDay } from 'util/dates';
-import { NotificationTemplatesAPI, NotificationsAPI } from 'api';
-import StatusLabel from 'components/StatusLabel';
-import CopyButton from 'components/CopyButton';
-import AlertModal from 'components/AlertModal';
-import ErrorDetail from 'components/ErrorDetail';
-import useRequest, { useDismissableError } from 'hooks/useRequest';
-import { NOTIFICATION_TYPES } from '../constants';
-
-const NUM_RETRIES = 25;
-const RETRY_TIMEOUT = 5000;
-
-function NotificationTemplateListItem({
- onAddToast,
- template,
- detailUrl,
- fetchTemplates,
- isSelected,
- onSelect,
- rowIndex,
-}) {
- const recentNotifications = template.summary_fields?.recent_notifications;
- const latestStatus = recentNotifications
- ? recentNotifications[0]?.status
- : null;
- const [status, setStatus] = useState(latestStatus);
- const [isCopyDisabled, setIsCopyDisabled] = useState(false);
-
- const copyTemplate = useCallback(async () => {
- await NotificationTemplatesAPI.copy(template.id, {
- name: `${template.name} @ ${timeOfDay()}`,
- });
- await fetchTemplates();
- }, [template.id, template.name, fetchTemplates]);
-
- const handleCopyStart = useCallback(() => {
- setIsCopyDisabled(true);
- }, []);
-
- const handleCopyFinish = useCallback(() => {
- setIsCopyDisabled(false);
- }, []);
-
- useEffect(() => {
- setStatus(latestStatus);
- }, [latestStatus]);
-
- const {
- request: sendTestNotification,
- isLoading,
- error,
- } = useRequest(
- useCallback(async () => {
- const request = NotificationTemplatesAPI.test(template.id);
- setStatus('running');
- let retries = NUM_RETRIES;
- const {
- data: { notification: notificationId },
- } = await request;
-
- async function pollForStatusChange() {
- const { data: notification } = await NotificationsAPI.readDetail(
- notificationId
- );
- if (notification.status !== 'pending') {
- onAddToast(notification);
- setStatus(notification.status);
- return;
- }
- retries--;
- if (retries > 0) {
- setTimeout(pollForStatusChange, RETRY_TIMEOUT);
- }
- }
-
- setTimeout(pollForStatusChange, RETRY_TIMEOUT);
- }, [template.id, onAddToast])
- );
-
- const { error: sendTestError, dismissError } = useDismissableError(error);
-
- useEffect(() => {
- if (error) {
- setStatus('error');
- }
- }, [error]);
-
- const labelId = `template-name-${template.id}`;
-
- return (
- <>
-
- |
-
-
- {template.name}
-
-
-
- {status && }
- |
-
- {NOTIFICATION_TYPES[template.notification_type] ||
- template.notification_type}
- |
-
-
-
-
-
-
-
-
-
-
-
-
- {sendTestError && (
-
- {t`Failed to send test notification.`}
-
-
- )}
- >
- );
-}
-
-export default NotificationTemplateListItem;
diff --git a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.test.js b/awx/ui/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.test.js
deleted file mode 100644
index eba9049bf2b3..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.test.js
+++ /dev/null
@@ -1,150 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { NotificationTemplatesAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import NotificationTemplateListItem from './NotificationTemplateListItem';
-
-jest.mock('../../../api/models/NotificationTemplates');
-jest.mock('../../../api/models/Notifications');
-
-const template = {
- id: 3,
- notification_type: 'slack',
- name: 'Test Notification',
- summary_fields: {
- user_capabilities: {
- edit: true,
- copy: true,
- },
- recent_notifications: [
- {
- status: 'success',
- },
- ],
- },
-};
-
-describe('', () => {
- test('should render template row', () => {
- const wrapper = mountWithContexts(
-
- );
-
- const cells = wrapper.find('Td');
- expect(cells).toHaveLength(5);
- expect(cells.at(1).text()).toEqual('Test Notification');
- expect(cells.at(2).text()).toEqual('Success');
- expect(cells.at(3).text()).toEqual('Slack');
- });
-
- test('should send test notification', async () => {
- NotificationTemplatesAPI.test.mockResolvedValue({
- data: { notification: 1 },
- });
-
- const wrapper = mountWithContexts(
-
- );
- await act(async () => {
- wrapper.find('Button').at(0).invoke('onClick')();
- });
- expect(NotificationTemplatesAPI.test).toHaveBeenCalledTimes(1);
- expect(wrapper.find('Td').at(2).text()).toEqual('Running');
- });
-
- test('should call api to copy inventory', async () => {
- NotificationTemplatesAPI.copy.mockResolvedValue({ name: 'Foo' });
-
- const wrapper = mountWithContexts(
-
- );
-
- await act(async () =>
- wrapper.find('Button[aria-label="Copy"]').prop('onClick')()
- );
- expect(NotificationTemplatesAPI.copy).toHaveBeenCalled();
- jest.clearAllMocks();
- });
-
- test('should render proper alert modal on copy error', async () => {
- NotificationTemplatesAPI.copy.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'post',
- url: '/api/v2/notification_templates/3/copy',
- },
- data: 'An error ocurred',
- status: 403,
- },
- })
- );
-
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('Modal').length).toBe(0);
- await act(async () =>
- wrapper.find('Button[aria-label="Copy"]').prop('onClick')()
- );
- wrapper.update();
- expect(wrapper.find('Modal').length).toBe(1);
- expect(wrapper.find('Modal').prop('isOpen')).toBe(true);
- jest.clearAllMocks();
- });
-
- test('should not render copy button', async () => {
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('CopyButton').length).toBe(0);
- });
-});
diff --git a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateList/index.js b/awx/ui/src/screens/NotificationTemplate/NotificationTemplateList/index.js
deleted file mode 100644
index 335e76dd6c9c..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateList/index.js
+++ /dev/null
@@ -1,4 +0,0 @@
-import NotificationTemplateList from './NotificationTemplateList';
-
-export default NotificationTemplateList;
-export { default as NotificationTemplateListItem } from './NotificationTemplateListItem';
diff --git a/awx/ui/src/screens/NotificationTemplate/NotificationTemplates.js b/awx/ui/src/screens/NotificationTemplate/NotificationTemplates.js
deleted file mode 100644
index 37961f4bc269..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/NotificationTemplates.js
+++ /dev/null
@@ -1,52 +0,0 @@
-import React, { useState, useCallback } from 'react';
-import { Route, Switch, useRouteMatch } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
-import PersistentFilters from 'components/PersistentFilters';
-import NotificationTemplateList from './NotificationTemplateList';
-import NotificationTemplateAdd from './NotificationTemplateAdd';
-import NotificationTemplate from './NotificationTemplate';
-
-function NotificationTemplates() {
- const match = useRouteMatch();
- const [breadcrumbConfig, setBreadcrumbConfig] = useState({
- '/notification_templates': t`Notification Templates`,
- '/notification_templates/add': t`Create New Notification Template`,
- });
-
- const updateBreadcrumbConfig = useCallback((notification) => {
- const { id } = notification;
- setBreadcrumbConfig({
- '/notification_templates': t`Notification Templates`,
- '/notification_templates/add': t`Create New Notification Template`,
- [`/notification_templates/${id}`]: notification.name,
- [`/notification_templates/${id}/edit`]: t`Edit Details`,
- [`/notification_templates/${id}/details`]: t`Details`,
- });
- }, []);
-
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-}
-
-export default NotificationTemplates;
diff --git a/awx/ui/src/screens/NotificationTemplate/NotificationTemplates.test.js b/awx/ui/src/screens/NotificationTemplate/NotificationTemplates.test.js
deleted file mode 100644
index 63ac3898be5f..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/NotificationTemplates.test.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import NotificationTemplates from './NotificationTemplates';
-
-jest.mock('./NotificationTemplateList', () => {
- const NotificationTemplateList = () => ;
- return {
- __esModule: true,
- default: NotificationTemplateList,
- };
-});
-
-describe('', () => {
- test('initially renders without crashing', () => {
- const wrapper = mountWithContexts();
-
- const pageSections = wrapper.find('PageSection');
- expect(pageSections).toHaveLength(1);
- expect(pageSections.first().props().variant).toBe('light');
- expect(wrapper.find('NotificationTemplateList')).toHaveLength(1);
- });
-});
diff --git a/awx/ui/src/screens/NotificationTemplate/constants.js b/awx/ui/src/screens/NotificationTemplate/constants.js
deleted file mode 100644
index 5937e4874388..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/constants.js
+++ /dev/null
@@ -1,12 +0,0 @@
-/* eslint-disable-next-line import/prefer-default-export */
-export const NOTIFICATION_TYPES = {
- email: 'Email',
- grafana: 'Grafana',
- irc: 'IRC',
- mattermost: 'Mattermost',
- pagerduty: 'Pagerduty',
- rocketchat: 'Rocket.Chat',
- slack: 'Slack',
- twilio: 'Twilio',
- webhook: 'Webhook',
-};
diff --git a/awx/ui/src/screens/NotificationTemplate/index.js b/awx/ui/src/screens/NotificationTemplate/index.js
deleted file mode 100644
index e7f43328a803..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './NotificationTemplates';
diff --git a/awx/ui/src/screens/NotificationTemplate/shared/CustomMessagesSubForm.js b/awx/ui/src/screens/NotificationTemplate/shared/CustomMessagesSubForm.js
deleted file mode 100644
index efdbd1c6e7db..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/shared/CustomMessagesSubForm.js
+++ /dev/null
@@ -1,234 +0,0 @@
-import 'styled-components/macro';
-import React, { useEffect, useRef } from 'react';
-
-import { t } from '@lingui/macro';
-import { useField, useFormikContext } from 'formik';
-import { Switch, Text } from '@patternfly/react-core';
-import { FormFullWidthLayout, SubFormLayout } from 'components/FormLayout';
-import CodeEditorField from 'components/CodeEditor/CodeEditorField';
-import { useConfig } from 'contexts/Config';
-import getDocsBaseUrl from 'util/getDocsBaseUrl';
-
-function CustomMessagesSubForm({ defaultMessages, type }) {
- const [useCustomField, , useCustomHelpers] = useField('useCustomMessages');
- const showMessages = type !== 'webhook';
- const showBodies = ['email', 'pagerduty', 'webhook'].includes(type);
-
- const { setFieldValue } = useFormikContext();
- const config = useConfig();
- const mountedRef = useRef(null);
- useEffect(
- () => {
- if (!mountedRef.current) {
- mountedRef.current = true;
- return;
- }
- const defs = defaultMessages[type];
-
- const resetFields = (name, defaults) => {
- setFieldValue(`${name}.message`, defaults.message || '');
- setFieldValue(`${name}.body`, defaults.body || '');
- };
-
- resetFields('messages.started', defs.started);
- resetFields('messages.success', defs.success);
- resetFields('messages.error', defs.error);
- resetFields(
- 'messages.workflow_approval.approved',
- defs.workflow_approval.approved
- );
- resetFields(
- 'messages.workflow_approval.denied',
- defs.workflow_approval.denied
- );
- resetFields(
- 'messages.workflow_approval.running',
- defs.workflow_approval.running
- );
- resetFields(
- 'messages.workflow_approval.timed_out',
- defs.workflow_approval.timed_out
- );
- },
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [type, setFieldValue]
- );
-
- return (
- <>
- useCustomHelpers.setValue(!useCustomField.value)}
- />
- {useCustomField.value && (
-
-
-
- {t`Use custom messages to change the content of
- notifications sent when a job starts, succeeds, or fails. Use
- curly braces to access information about the job:`}{' '}
-
- {'{{'} job_friendly_name {'}}'}
-
- ,{' '}
-
- {'{{'} url {'}}'}
-
- ,{' '}
-
- {'{{'} job.status {'}}'}
-
- .{' '}
- {t`You may apply a number of possible variables in the
- message. For more information, refer to the`}{' '}
-
- {t`Ansible Controller Documentation.`}
-
-
-
-
- {showMessages && (
-
- )}
- {showBodies && (
-
- )}
- {showMessages && (
-
- )}
- {showBodies && (
-
- )}
- {showMessages && (
-
- )}
- {showBodies && (
-
- )}
- {showMessages && (
-
- )}
- {showBodies && (
-
- )}
- {showMessages && (
-
- )}
- {showBodies && (
-
- )}
- {showMessages && (
-
- )}
- {showBodies && (
-
- )}
- {showMessages && (
-
- )}
- {showBodies && (
-
- )}
-
-
- )}
- >
- );
-}
-
-export default CustomMessagesSubForm;
diff --git a/awx/ui/src/screens/NotificationTemplate/shared/NotificationTemplateForm.js b/awx/ui/src/screens/NotificationTemplate/shared/NotificationTemplateForm.js
deleted file mode 100644
index 6c8a3493bb8f..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/shared/NotificationTemplateForm.js
+++ /dev/null
@@ -1,286 +0,0 @@
-import React, { useCallback } from 'react';
-import { shape, func } from 'prop-types';
-import { Formik, useField, useFormikContext } from 'formik';
-
-import { t } from '@lingui/macro';
-import { Form, FormGroup } from '@patternfly/react-core';
-
-import AnsibleSelect from 'components/AnsibleSelect';
-import FormField, { FormSubmitError } from 'components/FormField';
-import FormActionGroup from 'components/FormActionGroup/FormActionGroup';
-import { OrganizationLookup } from 'components/Lookup';
-import { required } from 'util/validators';
-import { FormColumnLayout } from 'components/FormLayout';
-import TypeInputsSubForm from './TypeInputsSubForm';
-import CustomMessagesSubForm from './CustomMessagesSubForm';
-import hasCustomMessages from './hasCustomMessages';
-import typeFieldNames, { initialConfigValues } from './typeFieldNames';
-
-function NotificationTemplateFormFields({ defaultMessages, template }) {
- const { setFieldValue, setFieldTouched } = useFormikContext();
- const [orgField, orgMeta, orgHelpers] = useField('organization');
- const [typeField, typeMeta] = useField({
- name: 'notification_type',
- validate: required(t`Select a value for this field`),
- });
-
- const handleOrganizationUpdate = useCallback(
- (value) => {
- setFieldValue('organization', value);
- setFieldTouched('organization', true, false);
- },
- [setFieldValue, setFieldTouched]
- );
-
- return (
- <>
-
-
- orgHelpers.setTouched()}
- onChange={handleOrganizationUpdate}
- value={orgField.value}
- touched={orgMeta.touched}
- error={orgMeta.error}
- required
- autoPopulate={!template?.id}
- validate={required(t`Select a value for this field`)}
- />
-
-
-
- {typeField.value && }
-
- >
- );
-}
-
-function NotificationTemplateForm({
- template,
- defaultMessages,
- onSubmit,
- onCancel,
- submitError,
-}) {
- const handleSubmit = (values) => {
- onSubmit(
- normalizeFields(
- {
- ...values,
- organization: values.organization?.id,
- },
- defaultMessages
- )
- );
- };
-
- const messages = template.messages || { workflow_approval: {} };
- const defs = defaultMessages[template.notification_type || 'email'];
- const mergeDefaultMessages = (def, templ = {}) => ({
- message: templ?.message || def.message || '',
- body: templ?.body || def.body || '',
- });
-
- const { headers } = template?.notification_configuration || {};
-
- return (
-
- {(formik) => (
-
- )}
-
- );
-}
-
-NotificationTemplateForm.propTypes = {
- template: shape(),
- defaultMessages: shape().isRequired,
- onSubmit: func.isRequired,
- onCancel: func.isRequired,
- submitError: shape(),
-};
-
-NotificationTemplateForm.defaultProps = {
- template: {
- name: '',
- description: '',
- notification_type: '',
- },
- submitError: null,
-};
-
-export default NotificationTemplateForm;
-
-function normalizeFields(values, defaultMessages) {
- return normalizeTypeFields(normalizeMessageFields(values, defaultMessages));
-}
-
-/* If the user filled in some of the Type Details fields, then switched
- * to a different notification type, unecessary fields may be set in the
- * notification_configuration — this function strips them off */
-function normalizeTypeFields(values) {
- const stripped = {};
- const fields = typeFieldNames[values.notification_type];
-
- fields.forEach((fieldName) => {
- if (typeof values.notification_configuration[fieldName] !== 'undefined') {
- stripped[fieldName] = values.notification_configuration[fieldName];
- }
- });
- if (values.notification_type === 'webhook') {
- stripped.headers = stripped.headers ? JSON.parse(stripped.headers) : {};
- }
- const { emailOptions, ...rest } = values;
-
- return {
- ...rest,
- notification_configuration: stripped,
- };
-}
-
-function normalizeMessageFields(values, defaults) {
- const { useCustomMessages, ...rest } = values;
- if (!useCustomMessages) {
- return {
- ...rest,
- messages: null,
- };
- }
- const { messages } = values;
- const defs = defaults[values.notification_type];
-
- const nullIfDefault = (m, d) => ({
- message: m.message === d.message ? null : m.message,
- body: m.body === d.body ? null : m.body,
- });
-
- const nonDefaultMessages = {
- started: nullIfDefault(messages.started, defs.started),
- success: nullIfDefault(messages.success, defs.success),
- error: nullIfDefault(messages.error, defs.error),
- workflow_approval: {
- approved: nullIfDefault(
- messages.workflow_approval.approved,
- defs.workflow_approval.approved
- ),
- denied: nullIfDefault(
- messages.workflow_approval.denied,
- defs.workflow_approval.denied
- ),
- running: nullIfDefault(
- messages.workflow_approval.running,
- defs.workflow_approval.running
- ),
- timed_out: nullIfDefault(
- messages.workflow_approval.timed_out,
- defs.workflow_approval.timed_out
- ),
- },
- };
-
- return {
- ...rest,
- messages: nonDefaultMessages,
- };
-}
diff --git a/awx/ui/src/screens/NotificationTemplate/shared/NotificationTemplateForm.test.js b/awx/ui/src/screens/NotificationTemplate/shared/NotificationTemplateForm.test.js
deleted file mode 100644
index 14f360d8c404..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/shared/NotificationTemplateForm.test.js
+++ /dev/null
@@ -1,171 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import NotificationTemplateForm from './NotificationTemplateForm';
-
-jest.mock('../../../api/models/NotificationTemplates');
-jest.mock('../../../api/models/Organizations');
-
-const template = {
- id: 3,
- notification_type: 'slack',
- name: 'Test Notification',
- description: 'a sample notification',
- url: '/notification_templates/3',
- organization: 1,
- summary_fields: {
- user_capabilities: {
- edit: true,
- },
- recent_notifications: [
- {
- status: 'success',
- },
- ],
- organization: {
- id: 1,
- name: 'The Organization',
- },
- },
-};
-
-const messageDef = {
- message: 'default message',
- body: 'default body',
-};
-const defaults = {
- started: messageDef,
- success: messageDef,
- error: messageDef,
- workflow_approval: {
- approved: messageDef,
- denied: messageDef,
- running: messageDef,
- timed_out: messageDef,
- },
-};
-const defaultMessages = {
- email: defaults,
- slack: defaults,
- twilio: defaults,
-};
-
-describe('', () => {
- let wrapper;
- test('should render form fields', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
-
- expect(wrapper.find('input#notification-name').prop('value')).toEqual(
- 'Test Notification'
- );
- expect(
- wrapper.find('input#notification-description').prop('value')
- ).toEqual('a sample notification');
- expect(wrapper.find('OrganizationLookup').prop('value')).toEqual({
- id: 1,
- name: 'The Organization',
- });
- expect(wrapper.find('AnsibleSelect').prop('value')).toEqual('slack');
- expect(wrapper.find('TypeInputsSubForm').prop('type')).toEqual('slack');
- expect(wrapper.find('CustomMessagesSubForm').prop('type')).toEqual('slack');
- expect(
- wrapper.find('CustomMessagesSubForm').prop('defaultMessages')
- ).toEqual(defaultMessages);
-
- expect(wrapper.find('input#option-use-ssl').length).toBe(0);
- expect(wrapper.find('input#option-use-tls').length).toBe(0);
-
- await act(async () => {
- wrapper.find('AnsibleSelect#notification-type').invoke('onChange')(
- {
- target: {
- name: 'notification_type',
- value: 'email',
- },
- },
- 'email'
- );
- });
-
- wrapper.update();
-
- expect(wrapper.find('input#option-use-ssl').length).toBe(1);
- expect(wrapper.find('input#option-use-tls').length).toBe(1);
- expect(
- wrapper.find('FormGroup[label="Email Options"]').find('HelpIcon').length
- ).toBe(1);
- });
-
- test('should render custom messages fields', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
-
- expect(wrapper.find('CodeEditor').at(0).prop('value')).toEqual('Started');
- });
-
- test('should submit', async () => {
- const handleSubmit = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
-
- await act(async () => {
- wrapper.find('FormActionGroup').invoke('onSubmit')();
- });
- wrapper.update();
-
- expect(handleSubmit).toHaveBeenCalledWith({
- name: 'Test Notification',
- description: 'a sample notification',
- organization: 1,
- notification_type: 'slack',
- notification_configuration: {
- channels: ['#foo'],
- hex_color: '',
- token: 'abc123',
- },
- messages: null,
- });
- });
-});
diff --git a/awx/ui/src/screens/NotificationTemplate/shared/Notifications.helptext.js b/awx/ui/src/screens/NotificationTemplate/shared/Notifications.helptext.js
deleted file mode 100644
index bb06445af238..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/shared/Notifications.helptext.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import React from 'react';
-import { t } from '@lingui/macro';
-
-const helpText = () => ({
- emailRecepients: t`Use one email address per line to create a recipient list for this type of notification.`,
- emailTimeout: t`The amount of time (in seconds) before the email
- notification stops trying to reach the host and times out. Ranges
- from 1 to 120 seconds.`,
- grafanaUrl: t`The base URL of the Grafana server - the
- /api/annotations endpoint will be added automatically to the base
- Grafana URL.`,
- grafanaTags: t`Use one Annotation Tag per line, without commas.`,
- ircTargets: t`Use one IRC channel or username per line. The pound
- symbol (#) for channels, and the at (@) symbol for users, are not
- required.`,
- slackChannels: (
- <>
- {t`One Slack channel per line. The pound symbol (#)
- is required for channels. To respond to or start a thread to a specific message add the parent message Id to the channel where the parent message Id is 16 digits. A dot (.) must be manually inserted after the 10th digit. ie:#destination-channel, 1231257890.006423. See Slack`}{' '}
- {t`documentation`}{' '}
- {t`for more information.`}
- >
- ),
- slackColor: t`Specify a notification color. Acceptable colors are hex
- color code (example: #3af or #789abc).`,
- twilioSourcePhoneNumber: t`The number associated with the "Messaging
- Service" in Twilio with the format +18005550199.`,
- twilioDestinationNumbers: t`Use one phone number per line to specify where to
- route SMS messages. Phone numbers should be formatted +11231231234. For more information see Twilio documentation`,
- webhookHeaders: t`Specify HTTP Headers in JSON format. Refer to
- the Ansible Controller documentation for example syntax.`,
- emailOptions: (
- <>
- {t`See Django`}{' '}
- {t`documentation`}{' '}
- {t`for more information.`}
- >
- ),
-});
-
-export default helpText;
diff --git a/awx/ui/src/screens/NotificationTemplate/shared/TypeInputsSubForm.js b/awx/ui/src/screens/NotificationTemplate/shared/TypeInputsSubForm.js
deleted file mode 100644
index 270737169fc8..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/shared/TypeInputsSubForm.js
+++ /dev/null
@@ -1,500 +0,0 @@
-import React from 'react';
-
-import { t } from '@lingui/macro';
-import { useField } from 'formik';
-import { FormGroup, Title } from '@patternfly/react-core';
-import {
- FormCheckboxLayout,
- FormColumnLayout,
- FormFullWidthLayout,
- SubFormLayout,
-} from 'components/FormLayout';
-import FormField, {
- PasswordField,
- CheckboxField,
- ArrayTextField,
-} from 'components/FormField';
-import AnsibleSelect from 'components/AnsibleSelect';
-import { CodeEditorField } from 'components/CodeEditor';
-import {
- combine,
- required,
- requiredEmail,
- url,
- minMaxValue,
- twilioPhoneNumber,
-} from 'util/validators';
-import { NotificationType } from 'types';
-import Popover from '../../../components/Popover/Popover';
-import getHelpText from './Notifications.helptext';
-
-const TypeFields = {
- email: EmailFields,
- grafana: GrafanaFields,
- irc: IRCFields,
- mattermost: MattermostFields,
- pagerduty: PagerdutyFields,
- rocketchat: RocketChatFields,
- slack: SlackFields,
- twilio: TwilioFields,
- webhook: WebhookFields,
-};
-function TypeInputsSubForm({ type }) {
- const Fields = TypeFields[type];
- return (
-
-
- {t`Type Details`}
-
-
-
-
-
- );
-}
-TypeInputsSubForm.propTypes = {
- type: NotificationType.isRequired,
-};
-
-export default TypeInputsSubForm;
-
-function EmailFields() {
- const helpText = getHelpText();
- return (
- <>
-
-
-
-
-
-
-
- }
- >
-
-
-
-
-
- >
- );
-}
-
-function GrafanaFields() {
- const helpText = getHelpText();
- return (
- <>
-
-
-
-
-
-
- >
- );
-}
-
-function IRCFields() {
- const helpText = getHelpText();
-
- return (
- <>
-
-
-
-
-
-
- >
- );
-}
-
-function MattermostFields() {
- return (
- <>
-
-
-
-
-
- >
- );
-}
-
-function PagerdutyFields() {
- return (
- <>
-
-
-
-
- >
- );
-}
-
-function RocketChatFields() {
- return (
- <>
-
-
-
-
- >
- );
-}
-
-function SlackFields() {
- const helpText = getHelpText();
-
- return (
- <>
-
-
-
- >
- );
-}
-
-function TwilioFields() {
- const helpText = getHelpText();
-
- return (
- <>
-
-
-
-
- >
- );
-}
-
-function WebhookFields() {
- const helpText = getHelpText();
-
- const [methodField, methodMeta] = useField({
- name: 'notification_configuration.http_method',
- validate: required(t`Select a value for this field`),
- });
- return (
- <>
-
-
-
-
-
-
-
-
-
-
- >
- );
-}
diff --git a/awx/ui/src/screens/NotificationTemplate/shared/hasCustomMessages.js b/awx/ui/src/screens/NotificationTemplate/shared/hasCustomMessages.js
deleted file mode 100644
index 375a84ae8fb4..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/shared/hasCustomMessages.js
+++ /dev/null
@@ -1,40 +0,0 @@
-export default function hasCustomMessages(messages, defaults) {
- if (!messages) {
- return false;
- }
-
- return (
- isCustomized(messages.started, defaults.started) ||
- isCustomized(messages.success, defaults.success) ||
- isCustomized(messages.error, defaults.error) ||
- isCustomized(
- messages.workflow_approval?.approved,
- defaults.workflow_approval.approved
- ) ||
- isCustomized(
- messages.workflow_approval?.denied,
- defaults.workflow_approval.denied
- ) ||
- isCustomized(
- messages.workflow_approval?.running,
- defaults.workflow_approval.running
- ) ||
- isCustomized(
- messages.workflow_approval?.timed_out,
- defaults.workflow_approval.timed_out
- )
- );
-}
-
-function isCustomized(message, defaultMessage) {
- if (!message) {
- return false;
- }
- if (message?.message !== defaultMessage?.message) {
- return true;
- }
- if (message?.body !== defaultMessage?.body) {
- return true;
- }
- return false;
-}
diff --git a/awx/ui/src/screens/NotificationTemplate/shared/notification-template-default-messages.json b/awx/ui/src/screens/NotificationTemplate/shared/notification-template-default-messages.json
deleted file mode 100644
index e2bd0ccaedba..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/shared/notification-template-default-messages.json
+++ /dev/null
@@ -1,302 +0,0 @@
-{
- "type": "json",
- "required": false,
- "label": "Messages",
- "help_text": "Optional custom messages for notification template.",
- "filterable": true,
- "default": {
- "started": null,
- "success": null,
- "error": null,
- "workflow_approval": null
- },
- "email": {
- "started": {
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
- "body": "{{ job_friendly_name }} #{{ job.id }} had status {{ job.status }}, view details at {{ url }}\n\n{{ job_metadata }}"
- },
- "success": {
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
- "body": "{{ job_friendly_name }} #{{ job.id }} had status {{ job.status }}, view details at {{ url }}\n\n{{ job_metadata }}"
- },
- "error": {
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
- "body": "{{ job_friendly_name }} #{{ job.id }} had status {{ job.status }}, view details at {{ url }}\n\n{{ job_metadata }}"
- },
- "workflow_approval": {
- "running": {
- "message": "The approval node \"{{ approval_node_name }}\" needs review. This node can be viewed at: {{ workflow_url }}",
- "body": "The approval node \"{{ approval_node_name }}\" needs review. This approval node can be viewed at: {{ workflow_url }}\n\n{{ job_metadata }}"
- },
- "approved": {
- "message": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}",
- "body": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}\n\n{{ job_metadata }}"
- },
- "timed_out": {
- "message": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}",
- "body": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}\n\n{{ job_metadata }}"
- },
- "denied": {
- "message": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}",
- "body": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}\n\n{{ job_metadata }}"
- }
- }
- },
- "slack": {
- "started": {
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
- "body": null
- },
- "success": {
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
- "body": null
- },
- "error": {
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
- "body": null
- },
- "workflow_approval": {
- "running": {
- "message": "The approval node \"{{ approval_node_name }}\" needs review. This node can be viewed at: {{ workflow_url }}",
- "body": null
- },
- "approved": {
- "message": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}",
- "body": null
- },
- "timed_out": {
- "message": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}",
- "body": null
- },
- "denied": {
- "message": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}",
- "body": null
- }
- }
- },
- "twilio": {
- "started": {
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
- "body": null
- },
- "success": {
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
- "body": null
- },
- "error": {
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
- "body": null
- },
- "workflow_approval": {
- "running": {
- "message": "The approval node \"{{ approval_node_name }}\" needs review. This node can be viewed at: {{ workflow_url }}",
- "body": null
- },
- "approved": {
- "message": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}",
- "body": null
- },
- "timed_out": {
- "message": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}",
- "body": null
- },
- "denied": {
- "message": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}",
- "body": null
- }
- }
- },
- "pagerduty": {
- "started": {
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
- "body": "{{ job_metadata }}"
- },
- "success": {
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
- "body": "{{ job_metadata }}"
- },
- "error": {
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
- "body": "{{ job_metadata }}"
- },
- "workflow_approval": {
- "running": {
- "message": "The approval node \"{{ approval_node_name }}\" needs review. This node can be viewed at: {{ workflow_url }}",
- "body": "The approval node \"{{ approval_node_name }}\" needs review. This approval node can be viewed at: {{ workflow_url }}\n\n{{ job_metadata }}"
- },
- "approved": {
- "message": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}",
- "body": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}\n\n{{ job_metadata }}"
- },
- "timed_out": {
- "message": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}",
- "body": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}\n\n{{ job_metadata }}"
- },
- "denied": {
- "message": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}",
- "body": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}\n\n{{ job_metadata }}"
- }
- }
- },
- "grafana": {
- "started": {
- "body": "{{ job_metadata }}",
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}"
- },
- "success": {
- "body": "{{ job_metadata }}",
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}"
- },
- "error": {
- "body": "{{ job_metadata }}",
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}"
- },
- "workflow_approval": {
- "running": {
- "message": "The approval node \"{{ approval_node_name }}\" needs review. This node can be viewed at: {{ workflow_url }}",
- "body": "The approval node \"{{ approval_node_name }}\" needs review. This approval node can be viewed at: {{ workflow_url }}\n\n{{ job_metadata }}"
- },
- "approved": {
- "message": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}",
- "body": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}\n\n{{ job_metadata }}"
- },
- "timed_out": {
- "message": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}",
- "body": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}\n\n{{ job_metadata }}"
- },
- "denied": {
- "message": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}",
- "body": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}\n\n{{ job_metadata }}"
- }
- }
- },
- "webhook": {
- "started": {
- "body": "{{ job_metadata }}"
- },
- "success": {
- "body": "{{ job_metadata }}"
- },
- "error": {
- "body": "{{ job_metadata }}"
- },
- "workflow_approval": {
- "running": {
- "body": {
- "body": "The approval node \"{{ approval_node_name }}\" needs review. This node can be viewed at: {{ workflow_url }}"
- }
- },
- "approved": {
- "body": {
- "body": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}"
- }
- },
- "timed_out": {
- "body": {
- "body": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}"
- }
- },
- "denied": {
- "body": {
- "body": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}"
- }
- }
- }
- },
- "mattermost": {
- "started": {
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
- "body": null
- },
- "success": {
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
- "body": null
- },
- "error": {
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
- "body": null
- },
- "workflow_approval": {
- "running": {
- "message": "The approval node \"{{ approval_node_name }}\" needs review. This node can be viewed at: {{ workflow_url }}",
- "body": null
- },
- "approved": {
- "message": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}",
- "body": null
- },
- "timed_out": {
- "message": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}",
- "body": null
- },
- "denied": {
- "message": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}",
- "body": null
- }
- }
- },
- "rocketchat": {
- "started": {
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
- "body": null
- },
- "success": {
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
- "body": null
- },
- "error": {
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
- "body": null
- },
- "workflow_approval": {
- "running": {
- "message": "The approval node \"{{ approval_node_name }}\" needs review. This node can be viewed at: {{ workflow_url }}",
- "body": null
- },
- "approved": {
- "message": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}",
- "body": null
- },
- "timed_out": {
- "message": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}",
- "body": null
- },
- "denied": {
- "message": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}",
- "body": null
- }
- }
- },
- "irc": {
- "started": {
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
- "body": null
- },
- "success": {
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
- "body": null
- },
- "error": {
- "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}",
- "body": null
- },
- "workflow_approval": {
- "running": {
- "message": "The approval node \"{{ approval_node_name }}\" needs review. This node can be viewed at: {{ workflow_url }}",
- "body": null
- },
- "approved": {
- "message": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}",
- "body": null
- },
- "timed_out": {
- "message": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}",
- "body": null
- },
- "denied": {
- "message": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}",
- "body": null
- }
- }
- }
-}
diff --git a/awx/ui/src/screens/NotificationTemplate/shared/typeFieldNames.js b/awx/ui/src/screens/NotificationTemplate/shared/typeFieldNames.js
deleted file mode 100644
index acfbd88097f8..000000000000
--- a/awx/ui/src/screens/NotificationTemplate/shared/typeFieldNames.js
+++ /dev/null
@@ -1,58 +0,0 @@
-const typeFieldNames = {
- email: [
- 'username',
- 'password',
- 'host',
- 'recipients',
- 'sender',
- 'port',
- 'timeout',
- 'use_ssl',
- 'use_tls',
- ],
- grafana: [
- 'grafana_url',
- 'grafana_key',
- 'dashboardId',
- 'panelId',
- 'annotation_tags',
- 'grafana_no_verify_ssl',
- ],
- irc: ['password', 'port', 'server', 'nickname', 'targets', 'use_ssl'],
- mattermost: [
- 'mattermost_url',
- 'mattermost_username',
- 'mattermost_channel',
- 'mattermost_icon_url',
- 'mattermost_no_verify_ssl',
- ],
- pagerduty: ['token', 'subdomain', 'service_key', 'client_name'],
- rocketchat: [
- 'rocketchat_url',
- 'rocketchat_username',
- 'rocketchat_icon_url',
- 'rocketchat_no_verify_ssl',
- ],
- slack: ['channels', 'token', 'hex_color'],
- twilio: ['account_token', 'from_number', 'to_numbers', 'account_sid'],
- webhook: [
- 'username',
- 'password',
- 'url',
- 'disable_ssl_verification',
- 'headers',
- 'http_method',
- ],
-};
-
-export default typeFieldNames;
-
-const initialConfigValues = {};
-Object.keys(typeFieldNames).forEach((key) => {
- typeFieldNames[key].forEach((fieldName) => {
- const isBoolean = fieldName.includes('_ssl') || fieldName === 'use_tls';
- initialConfigValues[fieldName] = isBoolean ? false : '';
- });
-});
-
-export { initialConfigValues };
diff --git a/awx/ui/src/screens/Organization/Organization.js b/awx/ui/src/screens/Organization/Organization.js
deleted file mode 100644
index f47c32b2f552..000000000000
--- a/awx/ui/src/screens/Organization/Organization.js
+++ /dev/null
@@ -1,239 +0,0 @@
-import React, { useCallback, useEffect, useRef } from 'react';
-
-import { t } from '@lingui/macro';
-import {
- Switch,
- Route,
- Redirect,
- Link,
- useLocation,
- useParams,
- useRouteMatch,
-} from 'react-router-dom';
-import { CaretLeftIcon } from '@patternfly/react-icons';
-import { Card, PageSection } from '@patternfly/react-core';
-import useRequest from 'hooks/useRequest';
-import RoutedTabs from 'components/RoutedTabs';
-import ContentError from 'components/ContentError';
-import NotificationList from 'components/NotificationList/NotificationList';
-import { ResourceAccessList } from 'components/ResourceAccessList';
-import { OrganizationsAPI } from 'api';
-import OrganizationDetail from './OrganizationDetail';
-import OrganizationEdit from './OrganizationEdit';
-import OrganizationTeams from './OrganizationTeams';
-import OrganizationExecEnvList from './OrganizationExecEnvList';
-
-function Organization({ setBreadcrumb, me }) {
- const location = useLocation();
- const { id: organizationId } = useParams();
- const match = useRouteMatch();
- const initialUpdate = useRef(true);
-
- const {
- result: { organization },
- isLoading: organizationLoading,
- error: organizationError,
- request: loadOrganization,
- } = useRequest(
- useCallback(async () => {
- const [{ data }, credentialsRes] = await Promise.all([
- OrganizationsAPI.readDetail(organizationId),
- OrganizationsAPI.readGalaxyCredentials(organizationId),
- ]);
- data.galaxy_credentials = credentialsRes.data.results;
- setBreadcrumb(data);
-
- return {
- organization: data,
- };
- }, [setBreadcrumb, organizationId]),
- {
- organization: null,
- }
- );
-
- const {
- result: { isNotifAdmin, isAuditorOfThisOrg, isAdminOfThisOrg },
- isLoading: rolesLoading,
- error: rolesError,
- request: loadRoles,
- } = useRequest(
- useCallback(async () => {
- const [notifAdminRes, auditorRes, adminRes] = await Promise.all([
- OrganizationsAPI.read({
- page_size: 1,
- role_level: 'notification_admin_role',
- }),
- OrganizationsAPI.read({
- id: organizationId,
- role_level: 'auditor_role',
- }),
- OrganizationsAPI.read({
- id: organizationId,
- role_level: 'admin_role',
- }),
- ]);
-
- return {
- isNotifAdmin: notifAdminRes.data.results.length > 0,
- isAuditorOfThisOrg: auditorRes.data.results.length > 0,
- isAdminOfThisOrg: adminRes.data.results.length > 0,
- };
- }, [organizationId]),
- {
- isNotifAdmin: false,
- isAuditorOfThisOrg: false,
- isAdminOfThisOrg: false,
- }
- );
- useEffect(() => {
- loadOrganization();
- loadRoles();
- }, [loadOrganization, loadRoles]);
-
- useEffect(() => {
- if (initialUpdate.current) {
- initialUpdate.current = false;
- return;
- }
-
- if (location.pathname === `/organizations/${organizationId}/details`) {
- loadOrganization();
- }
- }, [loadOrganization, organizationId, location.pathname]);
-
- const canSeeNotificationsTab =
- me.is_system_auditor || isNotifAdmin || isAuditorOfThisOrg;
- const canToggleNotifications =
- isNotifAdmin &&
- (me.is_system_auditor || isAuditorOfThisOrg || isAdminOfThisOrg);
-
- const tabsArray = [
- {
- name: (
- <>
-
- {t`Back to Organizations`}
- >
- ),
- link: `/organizations`,
- id: 99,
- isBackButton: true,
- },
- { name: t`Details`, link: `${match.url}/details`, id: 0 },
- { name: t`Access`, link: `${match.url}/access`, id: 1 },
- { name: t`Teams`, link: `${match.url}/teams`, id: 2 },
- {
- name: t`Execution Environments`,
- link: `${match.url}/execution_environments`,
- id: 4,
- },
- ];
-
- if (canSeeNotificationsTab) {
- tabsArray.push({
- name: t`Notifications`,
- link: `${match.url}/notifications`,
- id: 3,
- });
- }
-
- let showCardHeader = true;
-
- if (location.pathname.endsWith('edit')) {
- showCardHeader = false;
- }
-
- if (!organizationLoading && organizationError) {
- return (
-
-
-
- {organizationError.response.status === 404 && (
-
- {t`Organization not found.`}{' '}
- {t`View all Organizations.`}
-
- )}
-
-
-
- );
- }
-
- if (!rolesLoading && rolesError) {
- return (
-
-
-
-
-
- );
- }
-
- return (
-
-
- {showCardHeader && }
-
-
- {organization && (
-
-
-
- )}
- {organization && (
-
-
-
- )}
- {organization && (
-
-
-
- )}
-
-
-
- {canSeeNotificationsTab && (
-
-
-
- )}
- {organization && (
-
-
-
- )}
-
- {!organizationLoading && !rolesLoading && (
-
- {match.params.id && (
-
- {t`View Organization Details`}
-
- )}
-
- )}
-
- ,
-
-
-
- );
-}
-
-export default Organization;
-export { Organization as _Organization };
diff --git a/awx/ui/src/screens/Organization/Organization.test.js b/awx/ui/src/screens/Organization/Organization.test.js
deleted file mode 100644
index 10f68db5d053..000000000000
--- a/awx/ui/src/screens/Organization/Organization.test.js
+++ /dev/null
@@ -1,124 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { OrganizationsAPI } from 'api';
-import mockOrganization from 'util/data.organization.json';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../testUtils/enzymeHelpers';
-import Organization from './Organization';
-
-jest.mock('../../api');
-
-const mockMe = {
- is_super_user: true,
- is_system_auditor: false,
-};
-
-async function getOrganizations(params) {
- let results = [];
- if (params && params.role_level) {
- if (params.role_level === 'admin_role') {
- results = [mockOrganization];
- }
- if (params.role_level === 'auditor_role') {
- results = [mockOrganization];
- }
- if (params.role_level === 'notification_admin_role') {
- results = [mockOrganization];
- }
- }
- return {
- count: results.length,
- next: null,
- previous: null,
- data: { results },
- };
-}
-
-describe('', () => {
- let wrapper;
-
- beforeEach(() => {
- OrganizationsAPI.readDetail.mockResolvedValue({ data: mockOrganization });
- OrganizationsAPI.readGalaxyCredentials.mockResolvedValue({
- data: {
- results: [],
- },
- });
-
- OrganizationsAPI.read = jest.fn();
- OrganizationsAPI.read.mockImplementation(getOrganizations);
- });
-
- test('initially renders successfully', async () => {
- await act(async () => {
- mountWithContexts( {}} me={mockMe} />);
- });
- });
-
- test('notifications tab shown for admins', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
- {}} me={mockMe} />
- );
- });
-
- const tabs = await waitForElement(
- wrapper,
- '.pf-c-tabs__item',
- (el) => el.length === 6
- );
- expect(tabs.last().text()).toEqual('Notifications');
- });
-
- test('notifications tab hidden with reduced permissions', async () => {
- OrganizationsAPI.read = async () => ({
- count: 0,
- next: null,
- previous: null,
- data: { results: [] },
- });
-
- await act(async () => {
- wrapper = mountWithContexts(
- {}} me={mockMe} />
- );
- });
-
- const tabs = await waitForElement(
- wrapper,
- '.pf-c-tabs__item',
- (el) => el.length === 5
- );
- tabs.forEach((tab) => expect(tab.text()).not.toEqual('Notifications'));
- });
-
- test('should show content error when user attempts to navigate to erroneous route', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/organizations/1/foobar'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
- {}} me={mockMe} />,
- {
- context: {
- router: {
- history,
- route: {
- location: history.location,
- match: {
- params: { id: 1 },
- url: '/organizations/1/foobar',
- path: '/organizations/1/foobar',
- },
- },
- },
- },
- }
- );
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-});
diff --git a/awx/ui/src/screens/Organization/OrganizationAdd/OrganizationAdd.js b/awx/ui/src/screens/Organization/OrganizationAdd/OrganizationAdd.js
deleted file mode 100644
index f305f77951e9..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationAdd/OrganizationAdd.js
+++ /dev/null
@@ -1,108 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { useHistory } from 'react-router-dom';
-import { PageSection, Card } from '@patternfly/react-core';
-import useRequest from 'hooks/useRequest';
-import { CredentialsAPI, OrganizationsAPI } from 'api';
-import { CardBody } from 'components/Card';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import OrganizationForm from '../shared/OrganizationForm';
-
-function OrganizationAdd() {
- const history = useHistory();
- const [formError, setFormError] = useState(null);
-
- const {
- isLoading,
- error: defaultGalaxyCredentialError,
- request: fetchDefaultGalaxyCredential,
- result: defaultGalaxyCredential,
- } = useRequest(
- useCallback(async () => {
- const {
- data: { results },
- } = await CredentialsAPI.read({
- credential_type__kind: 'galaxy',
- managed: true,
- });
-
- return results[0] || null;
- }, []),
- null
- );
-
- useEffect(() => {
- fetchDefaultGalaxyCredential();
- }, [fetchDefaultGalaxyCredential]);
-
- const handleSubmit = async (values, groupsToAssociate) => {
- try {
- const { data: response } = await OrganizationsAPI.create({
- ...values,
- default_environment: values.default_environment?.id,
- });
- /* eslint-disable no-await-in-loop, no-restricted-syntax */
- // Resolve Promises sequentially to maintain order and avoid race condition
- for (const group of groupsToAssociate) {
- await OrganizationsAPI.associateInstanceGroup(response.id, group.id);
- }
- for (const credential of values.galaxy_credentials) {
- await OrganizationsAPI.associateGalaxyCredential(
- response.id,
- credential.id
- );
- }
- /* eslint-enable no-await-in-loop, no-restricted-syntax */
-
- history.push(`/organizations/${response.id}`);
- } catch (error) {
- setFormError(error);
- }
- };
-
- const handleCancel = () => {
- history.push('/organizations');
- };
-
- if (defaultGalaxyCredentialError) {
- return (
-
-
-
-
-
-
-
- );
- }
-
- if (isLoading) {
- return (
-
-
-
-
-
-
-
- );
- }
-
- return (
-
-
-
-
-
-
-
- );
-}
-
-export { OrganizationAdd as _OrganizationAdd };
-export default OrganizationAdd;
diff --git a/awx/ui/src/screens/Organization/OrganizationAdd/OrganizationAdd.test.js b/awx/ui/src/screens/Organization/OrganizationAdd/OrganizationAdd.test.js
deleted file mode 100644
index 9756ac5aaab7..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationAdd/OrganizationAdd.test.js
+++ /dev/null
@@ -1,173 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { CredentialsAPI, OrganizationsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import OrganizationAdd from './OrganizationAdd';
-
-jest.mock('../../../api');
-
-describe('', () => {
- beforeEach(() => {
- CredentialsAPI.read.mockResolvedValue({
- data: {
- results: [
- {
- id: 2,
- type: 'credential',
- name: 'Ansible Galaxy',
- credential_type: 18,
- managed: true,
- kind: 'galaxy_api_token',
- },
- ],
- },
- });
- });
-
- test('onSubmit should post to api', async () => {
- const updatedOrgData = {
- name: 'new name',
- description: 'new description',
- galaxy_credentials: [],
- default_environment: { id: 1, name: 'Foo' },
- };
- OrganizationsAPI.create.mockResolvedValueOnce({ data: {} });
- await act(async () => {
- const wrapper = mountWithContexts();
- wrapper.find('OrganizationForm').prop('onSubmit')(updatedOrgData, []);
- });
- expect(OrganizationsAPI.create).toHaveBeenCalledWith({
- ...updatedOrgData,
- default_environment: 1,
- });
- expect(OrganizationsAPI.create).toHaveBeenCalledTimes(1);
- });
-
- test('should navigate to organizations list when cancel is clicked', async () => {
- const history = createMemoryHistory({});
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- await act(async () => {
- wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
- });
- expect(history.location.pathname).toEqual('/organizations');
- });
-
- test('successful form submission should trigger redirect', async () => {
- const history = createMemoryHistory({});
- const orgData = {
- name: 'new name',
- description: 'new description',
- galaxy_credentials: [],
- };
- OrganizationsAPI.create.mockResolvedValueOnce({
- data: {
- id: 5,
- related: {
- instance_groups: '/bar',
- },
- ...orgData,
- },
- });
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- await waitForElement(wrapper, 'button[aria-label="Save"]');
- await wrapper.find('OrganizationForm').prop('onSubmit')(orgData, [3]);
- });
- expect(history.location.pathname).toEqual('/organizations/5');
- });
-
- test('onSubmit should post instance groups', async () => {
- const orgData = {
- name: 'new name',
- description: 'new description',
- galaxy_credentials: [],
- };
- const mockInstanceGroups = [
- {
- name: 'mock ig',
- id: 3,
- },
- ];
- OrganizationsAPI.create.mockResolvedValueOnce({
- data: {
- id: 5,
- related: {
- instance_groups: '/api/v2/organizations/5/instance_groups',
- },
- ...orgData,
- },
- });
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'button[aria-label="Save"]');
- await wrapper.find('OrganizationForm').prop('onSubmit')(
- orgData,
- mockInstanceGroups
- );
- expect(OrganizationsAPI.associateInstanceGroup).toHaveBeenCalledWith(5, 3);
- });
-
- test('onSubmit should post galaxy credentials', async () => {
- const orgData = {
- name: 'new name',
- description: 'new description',
- galaxy_credentials: [
- {
- id: 9000,
- },
- ],
- };
- OrganizationsAPI.create.mockResolvedValueOnce({
- data: {
- id: 5,
- related: {
- instance_groups: '/api/v2/organizations/5/instance_groups',
- },
- ...orgData,
- },
- });
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'button[aria-label="Save"]');
- await wrapper.find('OrganizationForm').prop('onSubmit')(orgData, [3]);
- expect(OrganizationsAPI.associateGalaxyCredential).toHaveBeenCalledWith(
- 5,
- 9000
- );
- });
-
- test('AnsibleSelect component does not render if there are 0 virtual environments', async () => {
- const mockInstanceGroups = [
- { name: 'One', id: 1 },
- { name: 'Two', id: 2 },
- ];
- OrganizationsAPI.readInstanceGroups.mockReturnValue({
- data: {
- results: mockInstanceGroups,
- },
- });
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(, {});
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('AnsibleSelect FormSelect')).toHaveLength(0);
- });
-});
diff --git a/awx/ui/src/screens/Organization/OrganizationAdd/index.js b/awx/ui/src/screens/Organization/OrganizationAdd/index.js
deleted file mode 100644
index c9780003b106..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationAdd/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './OrganizationAdd';
diff --git a/awx/ui/src/screens/Organization/OrganizationDetail/OrganizationDetail.js b/awx/ui/src/screens/Organization/OrganizationDetail/OrganizationDetail.js
deleted file mode 100644
index ac2329658aa1..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationDetail/OrganizationDetail.js
+++ /dev/null
@@ -1,197 +0,0 @@
-import React, { useEffect, useState, useCallback } from 'react';
-import { Link, useHistory, useRouteMatch } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { Button } from '@patternfly/react-core';
-import { OrganizationsAPI } from 'api';
-import { DetailList, Detail, UserDateDetail } from 'components/DetailList';
-import { CardBody, CardActionsRow } from 'components/Card';
-import AlertModal from 'components/AlertModal';
-import ChipGroup from 'components/ChipGroup';
-import CredentialChip from 'components/CredentialChip';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import DeleteButton from 'components/DeleteButton';
-import ErrorDetail from 'components/ErrorDetail';
-import useRequest, { useDismissableError } from 'hooks/useRequest';
-import { useConfig } from 'contexts/Config';
-import ExecutionEnvironmentDetail from 'components/ExecutionEnvironmentDetail';
-import InstanceGroupLabels from 'components/InstanceGroupLabels';
-import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
-
-function OrganizationDetail({ organization }) {
- const {
- params: { id },
- } = useRouteMatch();
- const {
- name,
- description,
- custom_virtualenv,
- max_hosts,
- created,
- modified,
- summary_fields,
- galaxy_credentials = [],
- } = organization;
- const [contentError, setContentError] = useState(null);
- const [hasContentLoading, setHasContentLoading] = useState(true);
- const [instanceGroups, setInstanceGroups] = useState([]);
- const history = useHistory();
- const { license_info = {} } = useConfig();
-
- useEffect(() => {
- (async () => {
- setContentError(null);
- setHasContentLoading(true);
- try {
- const {
- data: { results = [] },
- } = await OrganizationsAPI.readInstanceGroups(id);
- setInstanceGroups(results);
- } catch (error) {
- setContentError(error);
- } finally {
- setHasContentLoading(false);
- }
- })();
- }, [id]);
-
- const {
- request: deleteOrganization,
- isLoading,
- error: deleteError,
- } = useRequest(
- useCallback(async () => {
- await OrganizationsAPI.destroy(id);
- history.push(`/organizations`);
- }, [id, history])
- );
-
- const { error, dismissError } = useDismissableError(deleteError);
-
- const deleteDetailsRequests =
- relatedResourceDeleteRequests.organization(organization);
-
- if (hasContentLoading) {
- return ;
- }
-
- if (contentError) {
- return ;
- }
-
- return (
-
-
-
-
- {license_info?.license_type !== 'open' && (
-
- )}
-
-
-
- {instanceGroups && (
- }
- isEmpty={instanceGroups.length === 0}
- />
- )}
-
- {galaxy_credentials?.map((credential) => (
-
-
-
- ))}
-
- }
- isEmpty={galaxy_credentials?.length === 0}
- />
-
-
- {summary_fields.user_capabilities.edit && (
-
- )}
- {summary_fields.user_capabilities &&
- summary_fields.user_capabilities.delete && (
-
- {t`Delete`}
-
- )}
-
- {/* Update delete modal to show dependencies https://github.com/ansible/awx/issues/5546 */}
- {error && (
-
- {t`Failed to delete organization.`}
-
-
- )}
-
- );
-}
-
-export default OrganizationDetail;
diff --git a/awx/ui/src/screens/Organization/OrganizationDetail/OrganizationDetail.test.js b/awx/ui/src/screens/Organization/OrganizationDetail/OrganizationDetail.test.js
deleted file mode 100644
index be70bf751901..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationDetail/OrganizationDetail.test.js
+++ /dev/null
@@ -1,259 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { OrganizationsAPI, CredentialsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import OrganizationDetail from './OrganizationDetail';
-
-jest.mock('../../../api');
-
-describe('', () => {
- const mockOrganization = {
- id: 12,
- name: 'Foo',
- description: 'Bar',
- custom_virtualenv: 'Fizz',
- max_hosts: '0',
- created: '2015-07-07T17:21:26.429745Z',
- modified: '2019-08-11T19:47:37.980466Z',
- summary_fields: {
- user_capabilities: {
- edit: true,
- delete: true,
- },
- default_environment: {
- id: 1,
- name: 'Default EE',
- description: '',
- image: 'quay.io/ansible/awx-ee',
- },
- },
- default_environment: 1,
- };
- const mockInstanceGroups = {
- data: {
- results: [
- { name: 'One', id: 1 },
- { name: 'Two', id: 2 },
- ],
- },
- };
-
- beforeEach(() => {
- CredentialsAPI.read.mockResolvedValue({ data: { count: 0 } });
-
- OrganizationsAPI.readInstanceGroups.mockResolvedValue(mockInstanceGroups);
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('initially renders successfully', async () => {
- await act(async () => {
- mountWithContexts();
- });
- });
-
- test('should request instance groups from api', async () => {
- await act(async () => {
- mountWithContexts();
- });
- expect(OrganizationsAPI.readInstanceGroups).toHaveBeenCalledTimes(1);
- });
-
- test('should have proper number of delete detail requests', async () => {
- let component;
- await act(async () => {
- component = mountWithContexts(
-
- );
- });
- await waitForElement(component, 'ContentLoading', (el) => el.length === 0);
-
- expect(
- component.find('DeleteButton').prop('deleteDetailsRequests')
- ).toHaveLength(7);
- });
-
- test('should render the expected instance group', async () => {
- let component;
- await act(async () => {
- component = mountWithContexts(
-
- );
- });
- await waitForElement(component, 'ContentLoading', (el) => el.length === 0);
- expect(
- component
- .find('Label')
- .findWhere((el) => el.text() === 'One')
- .exists()
- ).toBe(true);
- });
-
- test('should render Details', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- const testParams = [
- { label: 'Name', value: 'Foo' },
- { label: 'Description', value: 'Bar' },
- { label: 'Created', value: '7/7/2015, 5:21:26 PM' },
- { label: 'Last Modified', value: '8/11/2019, 7:47:37 PM' },
- { label: 'Max Hosts', value: '0' },
- { label: 'Default Execution Environment', value: 'Default EE' },
- ];
- for (let i = 0; i < testParams.length; i++) {
- const { label, value } = testParams[i];
- // eslint-disable-next-line no-await-in-loop
- const detail = await waitForElement(wrapper, `Detail[label="${label}"]`);
- expect(detail.find('dt').text()).toBe(label);
- expect(detail.find('dd').text()).toBe(value);
- }
- });
-
- test('should show edit button for users with edit permission', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- const editButton = await waitForElement(
- wrapper,
- 'OrganizationDetail Button[aria-label="Edit"]'
- );
- expect(editButton.text()).toEqual('Edit');
- expect(editButton.prop('to')).toBe('/organizations/undefined/edit');
- });
-
- test('should hide edit button for users without edit permission', async () => {
- const readOnlyOrg = { ...mockOrganization };
- readOnlyOrg.summary_fields.user_capabilities.edit = false;
-
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'OrganizationDetail');
- expect(
- wrapper.find('OrganizationDetail Button[aria-label="Edit"]').length
- ).toBe(0);
- });
-
- test('expected api calls are made for delete', async () => {
- OrganizationsAPI.readInstanceGroups.mockResolvedValue({ data: {} });
-
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(
- wrapper,
- 'OrganizationDetail Button[aria-label="Delete"]'
- );
- await act(async () => {
- wrapper.find('DeleteButton').invoke('onConfirm')();
- });
- expect(OrganizationsAPI.destroy).toHaveBeenCalledTimes(1);
- });
-
- test('should show content error for failed instance group fetch', async () => {
- OrganizationsAPI.readInstanceGroups.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
-
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-
- test('Error dialog shown for failed deletion', async () => {
- OrganizationsAPI.destroy.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
-
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(
- wrapper,
- 'OrganizationDetail Button[aria-label="Delete"]'
- );
- await act(async () => {
- wrapper.find('DeleteButton').invoke('onConfirm')();
- });
- await waitForElement(
- wrapper,
- 'Modal[title="Error!"]',
- (el) => el.length === 1
- );
- await act(async () => {
- wrapper.find('Modal[title="Error!"]').invoke('onClose')();
- });
- await waitForElement(
- wrapper,
- 'Modal[title="Error!"]',
- (el) => el.length === 0
- );
- });
-
- test('should not load instance groups', async () => {
- OrganizationsAPI.readInstanceGroups.mockResolvedValue({
- data: {
- results: [],
- },
- });
-
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
- const instance_groups_detail = wrapper
- .find(`Detail[label="Instance Groups"]`)
- .at(0);
- expect(instance_groups_detail.prop('isEmpty')).toEqual(true);
- });
-
- test('should not load galaxy credentials', async () => {
- OrganizationsAPI.readInstanceGroups.mockResolvedValue({ data: {} });
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
- const galaxy_credentials_detail = wrapper
- .find(`Detail[label="Galaxy Credentials"]`)
- .at(0);
- expect(galaxy_credentials_detail.prop('isEmpty')).toEqual(true);
- });
-});
diff --git a/awx/ui/src/screens/Organization/OrganizationDetail/index.js b/awx/ui/src/screens/Organization/OrganizationDetail/index.js
deleted file mode 100644
index 4316ac734701..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationDetail/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './OrganizationDetail';
diff --git a/awx/ui/src/screens/Organization/OrganizationEdit/OrganizationEdit.js b/awx/ui/src/screens/Organization/OrganizationEdit/OrganizationEdit.js
deleted file mode 100644
index 27349f4c1780..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationEdit/OrganizationEdit.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import React, { useState } from 'react';
-import PropTypes from 'prop-types';
-import { useHistory } from 'react-router-dom';
-import { CardBody } from 'components/Card';
-import { OrganizationsAPI } from 'api';
-import OrganizationForm from '../shared/OrganizationForm';
-
-const isEqual = (array1, array2) =>
- array1.length === array2.length &&
- array1.every((element, index) => element.id === array2[index].id);
-
-function OrganizationEdit({ organization }) {
- const detailsUrl = `/organizations/${organization.id}/details`;
- const history = useHistory();
- const [formError, setFormError] = useState(null);
-
- const handleSubmit = async (
- values,
- groupsToAssociate,
- groupsToDisassociate
- ) => {
- try {
- await OrganizationsAPI.update(organization.id, {
- ...values,
- default_environment: values.default_environment?.id || null,
- });
- await OrganizationsAPI.orderInstanceGroups(
- organization.id,
- groupsToAssociate,
- groupsToDisassociate
- );
-
- /* eslint-disable no-await-in-loop, no-restricted-syntax */
- // Resolve Promises sequentially to avoid race condition
- if (
- !isEqual(organization.galaxy_credentials, values.galaxy_credentials)
- ) {
- for (const credential of organization.galaxy_credentials) {
- await OrganizationsAPI.disassociateGalaxyCredential(
- organization.id,
- credential.id
- );
- }
- for (const credential of values.galaxy_credentials) {
- await OrganizationsAPI.associateGalaxyCredential(
- organization.id,
- credential.id
- );
- }
- }
- /* eslint-enable no-await-in-loop, no-restricted-syntax */
- history.push(detailsUrl);
- } catch (error) {
- setFormError(error);
- }
- };
-
- const handleCancel = () => {
- history.push(detailsUrl);
- };
-
- return (
-
-
-
- );
-}
-
-OrganizationEdit.propTypes = {
- organization: PropTypes.shape().isRequired,
-};
-
-export { OrganizationEdit as _OrganizationEdit };
-export default OrganizationEdit;
diff --git a/awx/ui/src/screens/Organization/OrganizationEdit/OrganizationEdit.test.js b/awx/ui/src/screens/Organization/OrganizationEdit/OrganizationEdit.test.js
deleted file mode 100644
index 0a0619324367..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationEdit/OrganizationEdit.test.js
+++ /dev/null
@@ -1,114 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { OrganizationsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import OrganizationEdit from './OrganizationEdit';
-
-jest.mock('../../../api');
-
-describe('', () => {
- const mockData = {
- name: 'Foo',
- description: 'Bar',
- id: 1,
- related: {
- instance_groups: '/api/v2/organizations/1/instance_groups',
- },
- default_environment: 1,
- summary_fields: {
- default_environment: {
- id: 1,
- name: 'Baz',
- image: 'quay.io/ansible/awx-ee',
- },
- },
- };
-
- test('onSubmit should call api update', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
-
- const updatedOrgData = {
- name: 'new name',
- description: 'new description',
- default_environment: null,
- };
- await act(async () => {
- wrapper.find('OrganizationForm').prop('onSubmit')(updatedOrgData, [], []);
- });
-
- expect(OrganizationsAPI.update).toHaveBeenCalledWith(1, updatedOrgData);
- });
-
- test('onSubmit associates and disassociates instance groups', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
-
- const updatedOrgData = {
- name: 'new name',
- description: 'new description',
- };
- const newInstanceGroups = [
- {
- name: 'mock three',
- id: 3,
- },
- {
- name: 'mock four',
- id: 4,
- },
- ];
- const oldInstanceGroups = [
- {
- name: 'mock two',
- id: 2,
- },
- ];
-
- await act(async () => {
- wrapper.find('OrganizationForm').invoke('onSubmit')(
- updatedOrgData,
- newInstanceGroups,
- oldInstanceGroups
- );
- });
- expect(OrganizationsAPI.orderInstanceGroups).toHaveBeenCalledWith(
- mockData.id,
- newInstanceGroups,
- oldInstanceGroups
- );
- });
-
- test('should navigate to organization detail when cancel is clicked', async () => {
- const mockInstanceGroups = [
- { name: 'One', id: 1 },
- { name: 'Two', id: 2 },
- ];
- OrganizationsAPI.readInstanceGroups.mockReturnValue({
- data: {
- results: mockInstanceGroups,
- },
- });
- const history = createMemoryHistory({});
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- { context: { router: { history } } }
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- await act(async () => {
- wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
- });
- expect(history.location.pathname).toEqual('/organizations/1/details');
- });
-});
diff --git a/awx/ui/src/screens/Organization/OrganizationEdit/index.js b/awx/ui/src/screens/Organization/OrganizationEdit/index.js
deleted file mode 100644
index ffaea2be4ad4..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationEdit/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './OrganizationEdit';
diff --git a/awx/ui/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js b/awx/ui/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js
deleted file mode 100644
index 700e304050fc..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.js
+++ /dev/null
@@ -1,124 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-import { useLocation } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { Card } from '@patternfly/react-core';
-
-import { OrganizationsAPI } from 'api';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import useRequest from 'hooks/useRequest';
-import PaginatedTable, {
- HeaderRow,
- HeaderCell,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import DatalistToolbar from 'components/DataListToolbar';
-
-import OrganizationExecEnvListItem from './OrganizationExecEnvListItem';
-
-const QS_CONFIG = getQSConfig('organizations', {
- page: 1,
- page_size: 20,
- order_by: 'name',
-});
-
-function OrganizationExecEnvList({ organization }) {
- const { id } = organization;
- const location = useLocation();
-
- const {
- error: contentError,
- isLoading,
- request: fetchExecutionEnvironments,
- result: {
- executionEnvironments,
- executionEnvironmentsCount,
- relatedSearchableKeys,
- searchableKeys,
- },
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
-
- const [response, responseActions] = await Promise.all([
- OrganizationsAPI.readExecutionEnvironments(id, params),
- OrganizationsAPI.readExecutionEnvironmentsOptions(id),
- ]);
-
- return {
- executionEnvironments: response.data.results,
- executionEnvironmentsCount: response.data.count,
- actions: responseActions.data.actions,
- relatedSearchableKeys: (
- responseActions?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(responseActions.data.actions?.GET),
- };
- }, [location, id]),
- {
- executionEnvironments: [],
- executionEnvironmentsCount: 0,
- actions: {},
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- useEffect(() => {
- fetchExecutionEnvironments();
- }, [fetchExecutionEnvironments]);
-
- return (
-
- (
-
- )}
- headerRow={
-
- {t`Name`}
- {t`Image`}
-
- }
- renderRow={(executionEnvironment, index) => (
-
- )}
- />
-
- );
-}
-
-export default OrganizationExecEnvList;
diff --git a/awx/ui/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.test.js b/awx/ui/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.test.js
deleted file mode 100644
index b1fee72f40a9..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvList.test.js
+++ /dev/null
@@ -1,116 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { OrganizationsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import OrganizationExecEnvList from './OrganizationExecEnvList';
-
-jest.mock('../../../api/');
-
-const executionEnvironments = {
- data: {
- count: 3,
- results: [
- {
- id: 1,
- type: 'execution_environment',
- url: '/api/v2/execution_environments/1/',
- related: {
- organization: '/api/v2/organizations/1/',
- },
- organization: 1,
- image: 'https://localhost.com/image/disk',
- managed: false,
- credential: null,
- },
- {
- id: 2,
- type: 'execution_environment',
- url: '/api/v2/execution_environments/2/',
- related: {
- organization: '/api/v2/organizations/1/',
- },
- organization: 1,
- image: 'test/image123',
- managed: false,
- credential: null,
- },
- {
- id: 3,
- type: 'execution_environment',
- url: '/api/v2/execution_environments/3/',
- related: {
- organization: '/api/v2/organizations/1/',
- },
- organization: 1,
- image: 'test/test',
- managed: false,
- credential: null,
- },
- ],
- },
-};
-
-const mockOrganization = {
- id: 1,
- type: 'organization',
- name: 'Default',
-};
-
-const options = { data: { actions: { POST: {}, GET: {} } } };
-
-describe('', () => {
- let wrapper;
-
- test('should mount successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(
- wrapper,
- 'OrganizationExecEnvList',
- (el) => el.length > 0
- );
- });
-
- test('should have data fetched and render 3 rows', async () => {
- OrganizationsAPI.readExecutionEnvironments.mockResolvedValue(
- executionEnvironments
- );
-
- OrganizationsAPI.readExecutionEnvironmentsOptions.mockResolvedValue(
- options
- );
-
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(
- wrapper,
- 'OrganizationExecEnvList',
- (el) => el.length > 0
- );
-
- expect(wrapper.find('OrganizationExecEnvListItem').length).toBe(3);
- expect(OrganizationsAPI.readExecutionEnvironments).toBeCalled();
- expect(OrganizationsAPI.readExecutionEnvironmentsOptions).toBeCalled();
- });
-
- test('should not render add button', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- waitForElement(wrapper, 'OrganizationExecEnvList', (el) => el.length > 0);
- expect(wrapper.find('ToolbarAddButton').length).toBe(0);
- });
-});
diff --git a/awx/ui/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.js b/awx/ui/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.js
deleted file mode 100644
index 0da7b0384220..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react';
-import { string } from 'prop-types';
-
-import { t } from '@lingui/macro';
-import { Link } from 'react-router-dom';
-import { Tr, Td } from '@patternfly/react-table';
-
-import { ExecutionEnvironment } from 'types';
-
-function OrganizationExecEnvListItem({ executionEnvironment, detailUrl }) {
- return (
-
- |
-
- {executionEnvironment.name}
-
- |
- {executionEnvironment.image} |
-
- );
-}
-
-OrganizationExecEnvListItem.prototype = {
- executionEnvironment: ExecutionEnvironment.isRequired,
- detailUrl: string.isRequired,
-};
-
-export default OrganizationExecEnvListItem;
diff --git a/awx/ui/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.test.js b/awx/ui/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.test.js
deleted file mode 100644
index 7fcaa44403e1..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationExecEnvList/OrganizationExecEnvListItem.test.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import OrganizationExecEnvListItem from './OrganizationExecEnvListItem';
-
-describe('', () => {
- let wrapper;
- const executionEnvironment = {
- id: 1,
- image: 'https://registry.com/r/image/manifest',
- name: 'foo',
- organization: 1,
- credential: null,
- pull: 'always',
- };
-
- test('should mount successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('OrganizationExecEnvListItem').length).toBe(1);
- });
-
- test('should render the proper data', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('Td').at(1).text()).toBe(executionEnvironment.image);
- });
-});
diff --git a/awx/ui/src/screens/Organization/OrganizationExecEnvList/index.js b/awx/ui/src/screens/Organization/OrganizationExecEnvList/index.js
deleted file mode 100644
index 668a3beb619f..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationExecEnvList/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './OrganizationExecEnvList';
diff --git a/awx/ui/src/screens/Organization/OrganizationList/OrganizationList.js b/awx/ui/src/screens/Organization/OrganizationList/OrganizationList.js
deleted file mode 100644
index b1753cbf6bef..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationList/OrganizationList.js
+++ /dev/null
@@ -1,206 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-import { useLocation, useRouteMatch } from 'react-router-dom';
-import { t, Plural } from '@lingui/macro';
-import { Card, PageSection } from '@patternfly/react-core';
-
-import { OrganizationsAPI } from 'api';
-import useRequest, { useDeleteItems } from 'hooks/useRequest';
-import AlertModal from 'components/AlertModal';
-import DataListToolbar from 'components/DataListToolbar';
-import ErrorDetail from 'components/ErrorDetail';
-import PaginatedTable, {
- HeaderRow,
- HeaderCell,
- ToolbarAddButton,
- ToolbarDeleteButton,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import useSelected from 'hooks/useSelected';
-import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
-import OrganizationListItem from './OrganizationListItem';
-
-const QS_CONFIG = getQSConfig('organization', {
- page: 1,
- page_size: 20,
- order_by: 'name',
-});
-
-function OrganizationsList() {
- const location = useLocation();
- const match = useRouteMatch();
-
- const addUrl = `${match.url}/add`;
-
- const {
- result: {
- organizations,
- organizationCount,
- actions,
- relatedSearchableKeys,
- searchableKeys,
- },
- error: contentError,
- isLoading: isOrgsLoading,
- request: fetchOrganizations,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
- const [orgs, orgActions] = await Promise.all([
- OrganizationsAPI.read(params),
- OrganizationsAPI.readOptions(),
- ]);
- return {
- organizations: orgs.data.results,
- organizationCount: orgs.data.count,
- actions: orgActions.data.actions,
- relatedSearchableKeys: (
- orgActions?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(orgActions.data.actions?.GET),
- };
- }, [location]),
- {
- organizations: [],
- organizationCount: 0,
- actions: {},
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- useEffect(() => {
- fetchOrganizations();
- }, [fetchOrganizations]);
-
- const { selected, isAllSelected, handleSelect, selectAll, clearSelected } =
- useSelected(organizations);
-
- const {
- isLoading: isDeleteLoading,
- deleteItems: deleteOrganizations,
- deletionError,
- clearDeletionError,
- } = useDeleteItems(
- useCallback(
- () => Promise.all(selected.map(({ id }) => OrganizationsAPI.destroy(id))),
- [selected]
- ),
- {
- qsConfig: QS_CONFIG,
- allItemsSelected: isAllSelected,
- fetchItems: fetchOrganizations,
- }
- );
-
- const handleOrgDelete = async () => {
- await deleteOrganizations();
- clearSelected();
- };
-
- const hasContentLoading = isDeleteLoading || isOrgsLoading;
- const canAdd = actions && actions.POST;
-
- const deleteDetailsRequests = relatedResourceDeleteRequests.organization(
- selected[0]
- );
-
- return (
- <>
-
-
-
- {t`Name`}
- {t`Members`}
- {t`Teams`}
- {t`Actions`}
-
- }
- renderToolbar={(props) => (
- ]
- : []),
-
- }
- />,
- ]}
- />
- )}
- renderRow={(o, index) => (
- row.id === o.id)}
- onSelect={() => handleSelect(o)}
- />
- )}
- emptyStateControls={
- canAdd ? : null
- }
- />
-
-
-
- {t`Failed to delete one or more organizations.`}
-
-
- >
- );
-}
-
-export { OrganizationsList as _OrganizationsList };
-export default OrganizationsList;
diff --git a/awx/ui/src/screens/Organization/OrganizationList/OrganizationList.test.js b/awx/ui/src/screens/Organization/OrganizationList/OrganizationList.test.js
deleted file mode 100644
index 0edd3c4f84cb..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationList/OrganizationList.test.js
+++ /dev/null
@@ -1,308 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { OrganizationsAPI, CredentialsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import OrganizationsList from './OrganizationList';
-
-jest.mock('../../../api');
-
-const mockOrganizations = {
- data: {
- count: 3,
- results: [
- {
- name: 'Organization 0',
- id: 1,
- url: '/organizations/1',
- summary_fields: {
- related_field_counts: {
- teams: 3,
- users: 4,
- },
- user_capabilities: {
- delete: true,
- edit: true,
- },
- },
- },
- {
- name: 'Organization 1',
- id: 2,
- url: '/organizations/2',
- summary_fields: {
- related_field_counts: {
- teams: 2,
- users: 5,
- },
- user_capabilities: {
- delete: true,
- edit: true,
- },
- },
- },
- {
- name: 'Organization 2',
- id: 3,
- url: '/organizations/3',
- summary_fields: {
- related_field_counts: {
- teams: 5,
- users: 6,
- },
- user_capabilities: {
- delete: true,
- edit: true,
- },
- },
- },
- ],
- },
- isModalOpen: false,
- warningTitle: 'title',
- warningMsg: 'message',
-};
-
-describe('', () => {
- let wrapper;
- beforeEach(() => {
- CredentialsAPI.read.mockResolvedValue({ data: { count: 0 } });
- OrganizationsAPI.read.mockResolvedValue(mockOrganizations);
- OrganizationsAPI.readOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- },
- });
- });
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('Initially renders successfully', async () => {
- await act(async () => {
- mountWithContexts();
- });
- });
-
- test('should have proper number of delete detail requests', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(
- wrapper,
- 'OrganizationsList',
- (el) => el.find('ContentLoading').length === 0
- );
- expect(
- wrapper.find('ToolbarDeleteButton').prop('deleteDetailsRequests')
- ).toHaveLength(7);
- });
-
- test('Items are rendered after loading', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(
- wrapper,
- 'OrganizationsList',
- (el) => el.find('ContentLoading').length === 0
- );
- expect(wrapper.find('OrganizationListItem').length).toBe(3);
- });
-
- test('Item appears selected after selecting it', async () => {
- const itemCheckboxInput = 'tr#org-row-1 input[type="checkbox"]';
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(
- wrapper,
- 'OrganizationsList',
- (el) => el.find('ContentLoading').length === 0
- );
- await act(async () => {
- wrapper.find(itemCheckboxInput).props().onChange();
- });
- await waitForElement(
- wrapper,
- 'OrganizationsList',
- (el) => el.find(itemCheckboxInput).props().checked === true
- );
- });
-
- test('All items appear selected after select-all and unselected after unselect-all', async () => {
- const itemCheckboxInputs = [
- 'tr#org-row-1 input[type="checkbox"]',
- 'tr#org-row-2 input[type="checkbox"]',
- 'tr#org-row-3 input[type="checkbox"]',
- ];
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(
- wrapper,
- 'OrganizationsList',
- (el) => el.find('ContentLoading').length === 0
- );
- // Check for initially unselected items
- await waitForElement(
- wrapper,
- 'input#select-all',
- (el) => el.props().checked === false
- );
- itemCheckboxInputs.forEach((inputSelector) => {
- const checkboxInput = wrapper
- .find('OrganizationsList')
- .find(inputSelector);
- expect(checkboxInput.props().checked === false);
- });
- // Check select-all behavior
- await act(async () => {
- wrapper.find('Checkbox#select-all').props().onChange(true);
- });
- await waitForElement(
- wrapper,
- 'input#select-all',
- (el) => el.props().checked === true
- );
- itemCheckboxInputs.forEach((inputSelector) => {
- const checkboxInput = wrapper
- .find('OrganizationsList')
- .find(inputSelector);
- expect(checkboxInput.props().checked === true);
- });
- // Check unselect-all behavior
- await act(async () => {
- wrapper.find('Checkbox#select-all').props().onChange(false);
- });
- await waitForElement(
- wrapper,
- 'input#select-all',
- (el) => el.props().checked === false
- );
- itemCheckboxInputs.forEach((inputSelector) => {
- const checkboxInput = wrapper
- .find('OrganizationsList')
- .find(inputSelector);
- expect(checkboxInput.props().checked === false);
- });
- });
-
- test('Expected api calls are made for multi-delete', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(
- wrapper,
- 'OrganizationsList',
- (el) => el.find('ContentLoading').length === 0
- );
- expect(OrganizationsAPI.read).toHaveBeenCalledTimes(1);
- await act(async () => {
- wrapper.find('Checkbox#select-all').props().onChange(true);
- });
- await waitForElement(
- wrapper,
- 'input#select-all',
- (el) => el.props().checked === true
- );
- await act(async () => {
- wrapper.find('button[aria-label="Delete"]').simulate('click');
- wrapper.update();
- });
- const deleteButton = global.document.querySelector(
- 'body div[role="dialog"] button[aria-label="confirm delete"]'
- );
- expect(deleteButton).not.toEqual(null);
- await act(async () => {
- deleteButton.click();
- });
- expect(OrganizationsAPI.destroy).toHaveBeenCalledTimes(3);
- expect(OrganizationsAPI.read).toHaveBeenCalledTimes(2);
- });
-
- test('Error dialog shown for failed deletion', async () => {
- const itemCheckboxInput = 'tr#org-row-1 input[type="checkbox"]';
- OrganizationsAPI.destroy.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'delete',
- url: '/api/v2/organizations/1',
- },
- data: 'An error occurred',
- },
- })
- );
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(
- wrapper,
- 'OrganizationsList',
- (el) => el.find('ContentLoading').length === 0
- );
- await act(async () => {
- wrapper.find(itemCheckboxInput).props().onChange();
- });
- await waitForElement(
- wrapper,
- 'OrganizationsList',
- (el) => el.find(itemCheckboxInput).props().checked === true
- );
- await act(async () => {
- wrapper.find('button[aria-label="Delete"]').simulate('click');
- wrapper.update();
- });
- const deleteButton = global.document.querySelector(
- 'body div[role="dialog"] button[aria-label="confirm delete"]'
- );
- expect(deleteButton).not.toEqual(null);
- await act(async () => {
- deleteButton.click();
- });
- await waitForElement(
- wrapper,
- 'Modal',
- (el) => el.props().isOpen === true && el.props().title === 'Error!'
- );
- });
-
- test('Add button shown for users with ability to POST', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(
- wrapper,
- 'OrganizationsList',
- (el) => el.find('ContentLoading').length === 0
- );
- expect(wrapper.find('ToolbarAddButton').length).toBe(1);
- });
-
- test('Add button hidden for users without ability to POST', async () => {
- OrganizationsAPI.readOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {},
- },
- },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(
- wrapper,
- 'OrganizationsList',
- (el) => el.find('ContentLoading').length === 0
- );
- expect(wrapper.find('ToolbarAddButton').length).toBe(0);
- });
-});
diff --git a/awx/ui/src/screens/Organization/OrganizationList/OrganizationListItem.js b/awx/ui/src/screens/Organization/OrganizationList/OrganizationListItem.js
deleted file mode 100644
index 6b8bc6ba24a6..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationList/OrganizationListItem.js
+++ /dev/null
@@ -1,96 +0,0 @@
-import React from 'react';
-import { string, bool, func } from 'prop-types';
-
-import { t } from '@lingui/macro';
-import { Button, Tooltip } from '@patternfly/react-core';
-import { Tr, Td } from '@patternfly/react-table';
-import { Link } from 'react-router-dom';
-import styled from 'styled-components';
-import {
- ExclamationTriangleIcon as PFExclamationTriangleIcon,
- PencilAltIcon,
-} from '@patternfly/react-icons';
-import { ActionsTd, ActionItem, TdBreakWord } from 'components/PaginatedTable';
-
-import { Organization } from 'types';
-
-const ExclamationTriangleIcon = styled(PFExclamationTriangleIcon)`
- color: var(--pf-global--warning-color--100);
- margin-left: 18px;
-`;
-
-function OrganizationListItem({
- organization,
- isSelected,
- onSelect,
- rowIndex,
- detailUrl,
-}) {
- const labelId = `check-action-${organization.id}`;
-
- const missingExecutionEnvironment =
- organization.custom_virtualenv && !organization.default_environment;
-
- return (
-
- |
-
-
-
- {organization.name}
-
-
- {missingExecutionEnvironment && (
-
-
-
-
-
- )}
-
-
- {organization.summary_fields.related_field_counts.users}
- |
-
- {organization.summary_fields.related_field_counts.teams}
- |
-
-
-
-
-
-
- );
-}
-
-OrganizationListItem.propTypes = {
- organization: Organization.isRequired,
- detailUrl: string.isRequired,
- isSelected: bool.isRequired,
- onSelect: func.isRequired,
-};
-
-export default OrganizationListItem;
diff --git a/awx/ui/src/screens/Organization/OrganizationList/OrganizationListItem.test.js b/awx/ui/src/screens/Organization/OrganizationList/OrganizationListItem.test.js
deleted file mode 100644
index c2c7de666a26..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationList/OrganizationListItem.test.js
+++ /dev/null
@@ -1,145 +0,0 @@
-import React from 'react';
-import { MemoryRouter } from 'react-router-dom';
-import { I18nProvider } from '@lingui/react';
-
-import { en } from 'make-plural/plurals';
-import { i18n } from '@lingui/core';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import english from '../../../locales/en/messages';
-import OrganizationListItem from './OrganizationListItem';
-
-i18n.loadLocaleData({ en: { plurals: en } });
-i18n.load({ en: english });
-i18n.activate('en');
-
-describe('', () => {
- test('initially renders successfully', () => {
- mountWithContexts(
-
-
-
-
-
- );
- });
-
- test('edit button shown to users with edit capabilities', () => {
- const wrapper = mountWithContexts(
-
-
-
-
-
- );
- expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
- });
-
- test('edit button hidden from users without edit capabilities', () => {
- const wrapper = mountWithContexts(
-
-
-
-
-
- );
- expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
- });
-
- test('should render warning about missing execution environment', () => {
- const wrapper = mountWithContexts(
-
- );
- expect(
- wrapper.find('.missing-execution-environment').prop('content')
- ).toEqual(
- 'Custom virtual environment /var/lib/awx/env must be replaced by an execution environment.'
- );
- });
-});
diff --git a/awx/ui/src/screens/Organization/OrganizationList/index.js b/awx/ui/src/screens/Organization/OrganizationList/index.js
deleted file mode 100644
index ef71f26138e5..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationList/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as OrganizationList } from './OrganizationList';
-export { default as OrganizationListItem } from './OrganizationListItem';
diff --git a/awx/ui/src/screens/Organization/OrganizationTeams/OrganizationTeamList.js b/awx/ui/src/screens/Organization/OrganizationTeams/OrganizationTeamList.js
deleted file mode 100644
index 92566f94b4fb..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationTeams/OrganizationTeamList.js
+++ /dev/null
@@ -1,106 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import PropTypes from 'prop-types';
-import { useLocation } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { OrganizationsAPI } from 'api';
-import PaginatedTable, {
- HeaderRow,
- HeaderCell,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import useRequest from 'hooks/useRequest';
-import OrganizationTeamListItem from './OrganizationTeamListItem';
-
-const QS_CONFIG = getQSConfig('team', {
- page: 1,
- page_size: 5,
- order_by: 'name',
-});
-
-function OrganizationTeamList({ id }) {
- const location = useLocation();
-
- const {
- result: { teams, count, relatedSearchableKeys, searchableKeys },
- error,
- isLoading,
- request: fetchTeams,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
- const [response, actionsResponse] = await Promise.all([
- OrganizationsAPI.readTeams(id, params),
- OrganizationsAPI.readTeamsOptions(id),
- ]);
- return {
- teams: response.data.results,
- count: response.data.count,
- relatedSearchableKeys: (
- actionsResponse?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
- };
- }, [id, location]),
- {
- teams: [],
- count: 0,
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- useEffect(() => {
- fetchTeams();
- }, [fetchTeams]);
-
- return (
-
- {t`Name`}
- {t`Actions`}
-
- }
- renderRow={(item) => (
-
- )}
- />
- );
-}
-
-OrganizationTeamList.propTypes = {
- id: PropTypes.number.isRequired,
-};
-
-export { OrganizationTeamList as _OrganizationTeamList };
-export default OrganizationTeamList;
diff --git a/awx/ui/src/screens/Organization/OrganizationTeams/OrganizationTeamList.test.js b/awx/ui/src/screens/Organization/OrganizationTeams/OrganizationTeamList.test.js
deleted file mode 100644
index 3952039003f1..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationTeams/OrganizationTeamList.test.js
+++ /dev/null
@@ -1,129 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { OrganizationsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import OrganizationTeamList from './OrganizationTeamList';
-
-jest.mock('../../../api');
-
-const listData = {
- data: {
- count: 7,
- results: [
- {
- id: 1,
- name: 'one',
- url: '/org/team/1',
- summary_fields: { user_capabilities: { edit: true, delete: true } },
- },
- {
- id: 2,
- name: 'two',
- url: '/org/team/2',
- summary_fields: { user_capabilities: { edit: true, delete: true } },
- },
- {
- id: 3,
- name: 'three',
- url: '/org/team/3',
- summary_fields: { user_capabilities: { edit: true, delete: true } },
- },
- {
- id: 4,
- name: 'four',
- url: '/org/team/4',
- summary_fields: { user_capabilities: { edit: true, delete: true } },
- },
- {
- id: 5,
- name: 'five',
- url: '/org/team/5',
- summary_fields: { user_capabilities: { edit: true, delete: true } },
- },
- ],
- },
-};
-
-describe('', () => {
- beforeEach(() => {
- OrganizationsAPI.readTeams.mockResolvedValue(listData);
- OrganizationsAPI.readTeamsOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- related_search_fields: [],
- },
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('renders successfully', async () => {
- await act(async () => {
- mountWithContexts(
-
- );
- });
- });
-
- test('should load teams on mount', async () => {
- await act(async () => {
- mountWithContexts().find(
- 'OrganizationTeamList'
- );
- });
- expect(OrganizationsAPI.readTeams).toHaveBeenCalledWith(1, {
- page: 1,
- page_size: 5,
- order_by: 'name',
- });
- });
-
- test('should pass fetched teams to PaginatedTable', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- wrapper.update();
-
- const list = wrapper.find('PaginatedTable');
- expect(list.prop('items')).toEqual(listData.data.results);
- expect(list.prop('itemCount')).toEqual(listData.data.count);
- expect(list.prop('qsConfig')).toEqual({
- namespace: 'team',
- dateFields: ['modified', 'created'],
- defaultParams: {
- page: 1,
- page_size: 5,
- order_by: 'name',
- },
- integerFields: ['page', 'page_size'],
- });
- });
-
- test('should show content error for failed instance group fetch', async () => {
- OrganizationsAPI.readTeams.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-});
diff --git a/awx/ui/src/screens/Organization/OrganizationTeams/OrganizationTeamListItem.js b/awx/ui/src/screens/Organization/OrganizationTeams/OrganizationTeamListItem.js
deleted file mode 100644
index fe472e5e18f4..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationTeams/OrganizationTeamListItem.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import React from 'react';
-import { Link } from 'react-router-dom';
-import PropTypes from 'prop-types';
-import { Button } from '@patternfly/react-core';
-import { Tr, Td } from '@patternfly/react-table';
-import { t } from '@lingui/macro';
-
-import { PencilAltIcon } from '@patternfly/react-icons';
-import { ActionsTd, ActionItem } from 'components/PaginatedTable';
-
-function OrganizationTeamListItem({ team, detailUrl }) {
- return (
-
- |
-
- {team.name}
-
- |
-
-
-
-
-
-
- );
-}
-
-OrganizationTeamListItem.propTypes = {
- team: PropTypes.shape({ id: PropTypes.number, name: PropTypes.string })
- .isRequired,
- detailUrl: PropTypes.string.isRequired,
-};
-
-export default OrganizationTeamListItem;
diff --git a/awx/ui/src/screens/Organization/OrganizationTeams/OrgnizationTeamListItem.test.js b/awx/ui/src/screens/Organization/OrganizationTeams/OrgnizationTeamListItem.test.js
deleted file mode 100644
index 51a638bb5835..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationTeams/OrgnizationTeamListItem.test.js
+++ /dev/null
@@ -1,57 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import OrganizationTeamListItem from './OrganizationTeamListItem';
-
-const team = {
- id: 1,
- name: 'one',
- url: '/org/team/1',
- summary_fields: { user_capabilities: { edit: true, delete: true } },
-};
-
-describe('', () => {
- let wrapper;
- test('should mount properly', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('OrganizationTeamListItem').length).toBe(1);
- });
-
- test('should render proper data', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find(`Td`).first().text()).toBe('one');
- expect(wrapper.find('PencilAltIcon').length).toBe(1);
- });
-
- test('should not render edit button', async () => {
- team.summary_fields.user_capabilities.edit = false;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- expect(wrapper.find('PencilAltIcon').length).toBe(0);
- });
-});
diff --git a/awx/ui/src/screens/Organization/OrganizationTeams/index.js b/awx/ui/src/screens/Organization/OrganizationTeams/index.js
deleted file mode 100644
index de8b47e40700..000000000000
--- a/awx/ui/src/screens/Organization/OrganizationTeams/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './OrganizationTeamList';
diff --git a/awx/ui/src/screens/Organization/Organizations.js b/awx/ui/src/screens/Organization/Organizations.js
deleted file mode 100644
index ce30bfac1cc7..000000000000
--- a/awx/ui/src/screens/Organization/Organizations.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import React, { useCallback, useState } from 'react';
-import { Route, withRouter, Switch, useRouteMatch } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-
-import { Config } from 'contexts/Config';
-import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
-import PersistentFilters from 'components/PersistentFilters';
-import OrganizationsList from './OrganizationList/OrganizationList';
-import OrganizationAdd from './OrganizationAdd/OrganizationAdd';
-import Organization from './Organization';
-
-function Organizations() {
- const match = useRouteMatch();
- const [breadcrumbConfig, setBreadcrumbConfig] = useState({
- '/organizations': t`Organizations`,
- '/organizations/add': t`Create New Organization`,
- });
-
- const setBreadcrumb = useCallback((organization) => {
- if (!organization) {
- return;
- }
-
- const breadcrumb = {
- '/organizations': t`Organizations`,
- '/organizations/add': t`Create New Organization`,
- [`/organizations/${organization.id}`]: `${organization.name}`,
- [`/organizations/${organization.id}/edit`]: t`Edit Details`,
- [`/organizations/${organization.id}/details`]: t`Details`,
- [`/organizations/${organization.id}/access`]: t`Access`,
- [`/organizations/${organization.id}/teams`]: t`Teams`,
- [`/organizations/${organization.id}/notifications`]: t`Notifications`,
- [`/organizations/${organization.id}/execution_environments`]: t`Execution Environments`,
- };
- setBreadcrumbConfig(breadcrumb);
- }, []);
-
- return (
- <>
-
-
-
-
-
-
-
- {({ me }) => (
-
- )}
-
-
-
-
-
-
-
-
- >
- );
-}
-
-export { Organizations as _Organizations };
-export default withRouter(Organizations);
diff --git a/awx/ui/src/screens/Organization/Organizations.test.js b/awx/ui/src/screens/Organization/Organizations.test.js
deleted file mode 100644
index 6f0405f59f0b..000000000000
--- a/awx/ui/src/screens/Organization/Organizations.test.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import Organizations from './Organizations';
-
-jest.mock('../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
-}));
-
-describe('', () => {
- test('initially renders successfully', async () => {
- await act(async () => {
- mountWithContexts(
-
- );
- });
- });
-});
diff --git a/awx/ui/src/screens/Organization/index.js b/awx/ui/src/screens/Organization/index.js
deleted file mode 100644
index 321b7782c447..000000000000
--- a/awx/ui/src/screens/Organization/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './Organizations';
diff --git a/awx/ui/src/screens/Organization/shared/OrganizationForm.js b/awx/ui/src/screens/Organization/shared/OrganizationForm.js
deleted file mode 100644
index b3e65feaffa8..000000000000
--- a/awx/ui/src/screens/Organization/shared/OrganizationForm.js
+++ /dev/null
@@ -1,241 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import PropTypes from 'prop-types';
-import { Formik, useField, useFormikContext } from 'formik';
-
-import { t, Trans } from '@lingui/macro';
-import { Form } from '@patternfly/react-core';
-
-import { OrganizationsAPI } from 'api';
-import { useConfig } from 'contexts/Config';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import FormField, { FormSubmitError } from 'components/FormField';
-import FormActionGroup from 'components/FormActionGroup/FormActionGroup';
-import {
- InstanceGroupsLookup,
- ExecutionEnvironmentLookup,
-} from 'components/Lookup';
-import { required, minMaxValue } from 'util/validators';
-import { FormColumnLayout } from 'components/FormLayout';
-import CredentialLookup from 'components/Lookup/CredentialLookup';
-
-function OrganizationFormFields({
- instanceGroups,
- setInstanceGroups,
- organizationId,
-}) {
- const { license_info = {}, me = {} } = useConfig();
-
- const { setFieldValue } = useFormikContext();
-
- const [
- galaxyCredentialsField,
- galaxyCredentialsMeta,
- galaxyCredentialsHelpers,
- ] = useField('galaxy_credentials');
-
- const [
- executionEnvironmentField,
- executionEnvironmentMeta,
- executionEnvironmentHelpers,
- ] = useField('default_environment');
-
- const handleCredentialUpdate = useCallback(
- (value) => {
- setFieldValue('galaxy_credentials', value);
- },
- [setFieldValue]
- );
-
- return (
- <>
-
-
- {license_info?.license_type !== 'open' && (
-
- )}
-
- executionEnvironmentHelpers.setTouched()}
- value={executionEnvironmentField.value}
- onChange={(value) => executionEnvironmentHelpers.setValue(value)}
- popoverContent={t`The execution environment that will be used for jobs inside of this organization. This will be used a fallback when an execution environment has not been explicitly assigned at the project, job template or workflow level.`}
- globallyAvailable
- organizationId={organizationId}
- isDefaultEnvironment
- fieldName="default_environment"
- />
- galaxyCredentialsHelpers.setTouched()}
- onChange={handleCredentialUpdate}
- value={galaxyCredentialsField.value}
- multiple
- isSelectedDraggable
- fieldName="galaxy_credentials"
- modalDescription={
- <>
-
- Selected
-
-
-
- Note: The order of these credentials sets precedence for the sync
- and lookup of the content. Select more than one to enable drag.
-
- >
- }
- />
- >
- );
-}
-
-function OrganizationForm({
- organization,
- onCancel,
- onSubmit,
- submitError,
- defaultGalaxyCredential,
- ...rest
-}) {
- const [contentError, setContentError] = useState(null);
- const [hasContentLoading, setHasContentLoading] = useState(true);
- const [initialInstanceGroups, setInitialInstanceGroups] = useState([]);
- const [instanceGroups, setInstanceGroups] = useState([]);
-
- const handleCancel = () => {
- onCancel();
- };
-
- const handleSubmit = (values) => {
- if (
- typeof values.max_hosts !== 'number' ||
- values.max_hosts === 'undefined'
- ) {
- values.max_hosts = 0;
- }
- onSubmit(values, instanceGroups, initialInstanceGroups);
- };
-
- useEffect(() => {
- (async () => {
- const { id } = organization;
- if (!id) {
- setHasContentLoading(false);
- return;
- }
- setContentError(null);
- setHasContentLoading(true);
- try {
- const {
- data: { results = [] },
- } = await OrganizationsAPI.readInstanceGroups(id);
- setInitialInstanceGroups(results);
- setInstanceGroups(results);
- } catch (error) {
- setContentError(error);
- } finally {
- setHasContentLoading(false);
- }
- })();
- }, [organization]);
-
- if (contentError) {
- return ;
- }
-
- if (hasContentLoading) {
- return ;
- }
-
- return (
-
- {(formik) => (
-
- )}
-
- );
-}
-
-OrganizationForm.propTypes = {
- defaultGalaxyCredential: PropTypes.shape(),
- organization: PropTypes.shape(),
- onSubmit: PropTypes.func.isRequired,
- onCancel: PropTypes.func.isRequired,
- submitError: PropTypes.shape(),
-};
-
-OrganizationForm.defaultProps = {
- defaultGalaxyCredential: null,
- organization: {
- id: '',
- name: '',
- description: '',
- max_hosts: '0',
- default_environment: '',
- },
- submitError: null,
-};
-
-export { OrganizationForm as _OrganizationForm };
-export default OrganizationForm;
diff --git a/awx/ui/src/screens/Organization/shared/OrganizationForm.test.js b/awx/ui/src/screens/Organization/shared/OrganizationForm.test.js
deleted file mode 100644
index be1007aaa3b1..000000000000
--- a/awx/ui/src/screens/Organization/shared/OrganizationForm.test.js
+++ /dev/null
@@ -1,365 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { OrganizationsAPI, ExecutionEnvironmentsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-
-import OrganizationForm from './OrganizationForm';
-
-jest.mock('../../../api');
-
-describe('', () => {
- const network = {};
- const meConfig = {
- me: {
- is_superuser: false,
- },
- };
- const mockData = {
- id: 1,
- name: 'Foo',
- description: 'Bar',
- max_hosts: 1,
- related: {
- instance_groups: '/api/v2/organizations/1/instance_groups',
- },
- };
- const mockInstanceGroups = [
- { name: 'One', id: 1 },
- { name: 'Two', id: 2 },
- ];
-
- const mockExecutionEnvironment = [
- { id: 1, name: 'EE', image: 'quay.io/ansible/awx-ee' },
- ];
-
- beforeEach(() => {
- OrganizationsAPI.readInstanceGroups.mockReturnValue({
- data: {
- results: mockInstanceGroups,
- },
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should render default galaxy credential when passed', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { network },
- }
- );
- });
- await waitForElement(wrapper, 'CredentialLookup', (el) => el.length === 1);
- expect(wrapper.find('CredentialLookup Chip span')).toHaveLength(1);
- });
-
- test('should request related instance groups from api', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { network },
- }
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(OrganizationsAPI.readInstanceGroups).toHaveBeenCalledTimes(1);
- });
-
- test('componentDidMount should set instanceGroups to state', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { network },
- }
- );
- });
-
- await waitForElement(
- wrapper,
- 'InstanceGroupsLookup',
- (el) => el.length === 1
- );
- expect(OrganizationsAPI.readInstanceGroups).toHaveBeenCalled();
- expect(wrapper.find('InstanceGroupsLookup Chip span')).toHaveLength(2);
- });
-
- test('Instance group is rendered when added', async () => {
- OrganizationsAPI.readInstanceGroups.mockReturnValue({
- data: { results: [] },
- });
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- const lookup = await waitForElement(
- wrapper,
- 'InstanceGroupsLookup',
- (el) => el.length === 1
- );
- expect(lookup.length).toBe(1);
- expect(lookup.find('Chip span')).toHaveLength(0);
- await act(async () => {
- lookup.prop('onChange')(
- [
- {
- id: 1,
- name: 'foo',
- },
- ],
- 'instanceGroups'
- );
- });
- const group = await waitForElement(
- wrapper,
- 'InstanceGroupsLookup Chip span',
- (el) => el.length === 1
- );
- expect(group.text()).toEqual('foo');
- });
-
- test('changing inputs and saving triggers expected callback', async () => {
- OrganizationsAPI.readInstanceGroups.mockReturnValue({
- data: {
- results: mockInstanceGroups,
- },
- });
- ExecutionEnvironmentsAPI.read.mockReturnValue({
- data: {
- results: mockExecutionEnvironment,
- },
- });
- let wrapper;
- const onSubmit = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- await act(async () => {
- wrapper.find('input#org-name').simulate('change', {
- target: { value: 'new foo', name: 'name' },
- });
- wrapper.find('input#org-description').simulate('change', {
- target: { value: 'new bar', name: 'description' },
- });
- wrapper.find('input#org-max_hosts').simulate('change', {
- target: { value: 134, name: 'max_hosts' },
- });
- wrapper.find('ExecutionEnvironmentLookup').invoke('onChange')({
- id: 1,
- name: 'Test EE',
- });
- });
- await act(async () => {
- wrapper.find('button[aria-label="Save"]').simulate('click');
- });
- wrapper.update();
- expect(onSubmit).toHaveBeenCalledTimes(1);
- expect(onSubmit.mock.calls[0][0]).toEqual({
- name: 'new foo',
- description: 'new bar',
- galaxy_credentials: [],
- max_hosts: 134,
- default_environment: { id: 1, name: 'Test EE' },
- });
- });
-
- test('onSubmit associates and disassociates instance groups', async () => {
- OrganizationsAPI.readInstanceGroups.mockReturnValue({
- data: {
- results: mockInstanceGroups,
- },
- });
- ExecutionEnvironmentsAPI.read.mockReturnValue({
- data: { results: mockExecutionEnvironment },
- });
- const mockDataForm = {
- name: 'Foo',
- description: 'Bar',
- galaxy_credentials: [],
- max_hosts: 1,
- default_environment: null,
- };
- const onSubmit = jest.fn();
- OrganizationsAPI.update.mockResolvedValue(1, mockDataForm);
- OrganizationsAPI.associateInstanceGroup.mockResolvedValue('done');
- OrganizationsAPI.disassociateInstanceGroup.mockResolvedValue('done');
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { network },
- }
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- await act(async () => {
- wrapper.find('InstanceGroupsLookup').prop('onChange')(
- [
- { name: 'One', id: 1 },
- { name: 'Three', id: 3 },
- ],
- 'instanceGroups'
- );
- });
- await act(async () => {
- wrapper.find('button[aria-label="Save"]').simulate('click');
- });
- expect(onSubmit).toHaveBeenCalledWith(
- mockDataForm,
- [
- { name: 'One', id: 1 },
- { name: 'Three', id: 3 },
- ],
- mockInstanceGroups
- );
- });
-
- test('onSubmit does not get called if max_hosts value is out of range', async () => {
- const onSubmit = jest.fn();
- // mount with negative value
- let wrapper1;
- const mockDataNegative = JSON.parse(JSON.stringify(mockData));
- mockDataNegative.max_hosts = -5;
- await act(async () => {
- wrapper1 = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper1, 'ContentLoading', (el) => el.length === 0);
- await act(async () => {
- wrapper1.find('button[aria-label="Save"]').simulate('click');
- });
- expect(onSubmit).not.toHaveBeenCalled();
-
- // mount with out of range value
- let wrapper2;
- const mockDataOutOfRange = JSON.parse(JSON.stringify(mockData));
- mockDataOutOfRange.max_hosts = 999999999999999999999;
- await act(async () => {
- wrapper2 = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper2, 'ContentLoading', (el) => el.length === 0);
- await act(async () => {
- wrapper2.find('button[aria-label="Save"]').simulate('click');
- });
- expect(onSubmit).not.toHaveBeenCalled();
- });
-
- test('onSubmit is called and max_hosts value defaults to 0 if input is not a number', async () => {
- const onSubmit = jest.fn();
- // mount with String value (default to zero)
- const mockDataString = JSON.parse(JSON.stringify(mockData));
- mockDataString.max_hosts = 'Bee';
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- await act(async () => {
- wrapper.find('button[aria-label="Save"]').simulate('click');
- });
- expect(onSubmit).toHaveBeenCalledWith(
- {
- name: 'Foo',
- description: 'Bar',
- galaxy_credentials: [],
- max_hosts: 0,
- default_environment: null,
- },
- mockInstanceGroups,
- mockInstanceGroups
- );
- });
-
- test('calls "onCancel" when Cancel button is clicked', async () => {
- const onCancel = jest.fn();
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(onCancel).not.toHaveBeenCalled();
- wrapper.find('button[aria-label="Cancel"]').prop('onClick')();
- expect(onCancel).toBeCalled();
- });
-});
diff --git a/awx/ui/src/screens/Organization/shared/index.js b/awx/ui/src/screens/Organization/shared/index.js
deleted file mode 100644
index 2ddcf675b7fb..000000000000
--- a/awx/ui/src/screens/Organization/shared/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-/* eslint-disable-next-line import/prefer-default-export */
-export { default as OrganizationForm } from './OrganizationForm';
diff --git a/awx/ui/src/screens/Project/Project.js b/awx/ui/src/screens/Project/Project.js
deleted file mode 100644
index 6ec4bd9558da..000000000000
--- a/awx/ui/src/screens/Project/Project.js
+++ /dev/null
@@ -1,213 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-
-import { t } from '@lingui/macro';
-import {
- Switch,
- Route,
- Redirect,
- Link,
- useParams,
- useLocation,
-} from 'react-router-dom';
-import { CaretLeftIcon } from '@patternfly/react-icons';
-import { Card, PageSection } from '@patternfly/react-core';
-import { useConfig } from 'contexts/Config';
-import useRequest from 'hooks/useRequest';
-import RoutedTabs from 'components/RoutedTabs';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import NotificationList from 'components/NotificationList';
-import { ResourceAccessList } from 'components/ResourceAccessList';
-import { Schedules } from 'components/Schedule';
-import RelatedTemplateList from 'components/RelatedTemplateList';
-import { OrganizationsAPI, ProjectsAPI } from 'api';
-import ProjectDetail from './ProjectDetail';
-import ProjectEdit from './ProjectEdit';
-
-function Project({ setBreadcrumb }) {
- const { me = {} } = useConfig();
- const { id } = useParams();
- const location = useLocation();
-
- const {
- request: fetchProjectAndRoles,
- result: { project, isNotifAdmin },
- isLoading: hasContentLoading,
- error: contentError,
- } = useRequest(
- useCallback(async () => {
- const [{ data }, notifAdminRes] = await Promise.all([
- ProjectsAPI.readDetail(id),
- OrganizationsAPI.read({
- page_size: 1,
- role_level: 'notification_admin_role',
- }),
- ]);
-
- if (data.summary_fields.credentials) {
- const params = {
- page: 1,
- page_size: 200,
- order_by: 'name',
- };
- const {
- data: { results },
- } = await ProjectsAPI.readCredentials(data.id, params);
-
- data.summary_fields.credentials = results;
- }
- return {
- project: data,
- isNotifAdmin: notifAdminRes.data.results.length > 0,
- };
- }, [id]),
- {
- project: null,
- notifAdminRes: null,
- }
- );
-
- useEffect(() => {
- fetchProjectAndRoles();
- }, [fetchProjectAndRoles, location.pathname]);
-
- useEffect(() => {
- if (project) {
- setBreadcrumb(project);
- }
- }, [project, setBreadcrumb]);
-
- const loadScheduleOptions = useCallback(
- () => ProjectsAPI.readScheduleOptions(project.id),
- [project]
- );
-
- const loadSchedules = useCallback(
- (params) => ProjectsAPI.readSchedules(project.id, params),
- [project]
- );
-
- const canSeeNotificationsTab = me.is_system_auditor || isNotifAdmin;
- const canToggleNotifications = isNotifAdmin;
- const tabsArray = [
- {
- name: (
- <>
-
- {t`Back to Projects`}
- >
- ),
- link: `/projects`,
- id: 99,
- isBackButton: true,
- },
- { name: t`Details`, link: `/projects/${id}/details` },
- { name: t`Access`, link: `/projects/${id}/access` },
- {
- name: t`Job Templates`,
- link: `/projects/${id}/job_templates`,
- },
- ];
-
- if (canSeeNotificationsTab) {
- tabsArray.push({
- name: t`Notifications`,
- link: `/projects/${id}/notifications`,
- });
- }
- if (project?.scm_type) {
- tabsArray.push({
- name: t`Schedules`,
- link: `/projects/${id}/schedules`,
- });
- }
-
- tabsArray.forEach((tab, n) => {
- tab.id = n;
- });
-
- if (contentError) {
- return (
-
-
-
- {contentError.response.status === 404 && (
-
- {t`Project not found.`}{' '}
- {t`View all Projects.`}
-
- )}
-
-
-
- );
- }
-
- const showCardHeader = !(
- location.pathname.endsWith('edit') ||
- location.pathname.includes('schedules/')
- );
-
- return (
-
-
- {showCardHeader && }
- {hasContentLoading && }
- {!hasContentLoading && project && (
-
-
-
-
-
-
-
-
-
-
-
- {canSeeNotificationsTab && (
-
-
-
- )}
-
-
-
- {project?.scm_type && project.scm_type !== '' && (
-
-
-
- )}
-
-
- {id && (
-
- {t`View Project Details`}
-
- )}
-
-
-
- )}
-
-
- );
-}
-
-export default Project;
-export { Project as _Project };
diff --git a/awx/ui/src/screens/Project/Project.test.js b/awx/ui/src/screens/Project/Project.test.js
deleted file mode 100644
index 48621a31ac80..000000000000
--- a/awx/ui/src/screens/Project/Project.test.js
+++ /dev/null
@@ -1,159 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { OrganizationsAPI, ProjectsAPI } from 'api';
-import mockOrganization from 'util/data.organization.json';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../testUtils/enzymeHelpers';
-import mockDetails from './data.project.json';
-import Project from './Project';
-
-jest.mock('../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useRouteMatch: () => ({
- pathname: '/projects/1/details',
- url: '/projects/1',
- }),
- useParams: () => ({ id: 1 }),
-}));
-
-const mockMe = {
- is_super_user: true,
- is_system_auditor: false,
-};
-
-async function getOrganizations() {
- return {
- count: 1,
- next: null,
- previous: null,
- data: {
- results: [mockOrganization],
- },
- };
-}
-
-describe('', () => {
- let wrapper;
-
- beforeEach(() => {
- OrganizationsAPI.read = jest.fn();
- ProjectsAPI.readDetail = jest.fn();
- ProjectsAPI.readDetail.mockResolvedValue({ data: mockDetails });
- OrganizationsAPI.read.mockImplementation(getOrganizations);
- });
-
- test('initially renders successfully', async () => {
- await act(async () => {
- mountWithContexts( {}} me={mockMe} />);
- });
- });
-
- test('notifications tab shown for admins', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
- {}} me={mockMe} />
- );
- });
- const tabs = await waitForElement(
- wrapper,
- '.pf-c-tabs__item-text',
- (el) => el.length === 6
- );
- expect(tabs.at(4).text()).toEqual('Notifications');
- });
-
- test('notifications tab hidden with reduced permissions', async () => {
- OrganizationsAPI.read = async () => ({
- count: 0,
- next: null,
- previous: null,
- data: { results: [] },
- });
- await act(async () => {
- wrapper = mountWithContexts(
- {}} me={mockMe} />
- );
- });
- const tabs = await waitForElement(
- wrapper,
- '.pf-c-tabs__item-text',
- (el) => el.length === 5
- );
- tabs.forEach((tab) => expect(tab.text()).not.toEqual('Notifications'));
- });
-
- test('schedules tab shown for scm based projects.', async () => {
- OrganizationsAPI.read = async () => ({
- count: 0,
- next: null,
- previous: null,
- data: { results: [] },
- });
-
- await act(async () => {
- wrapper = mountWithContexts(
- {}} me={mockMe} />
- );
- });
- const tabs = await waitForElement(
- wrapper,
- '.pf-c-tabs__item',
- (el) => el.length === 5
- );
- expect(tabs.at(4).text()).toEqual('Schedules');
- });
-
- test('schedules tab hidden for manual projects.', async () => {
- const manualDetails = Object.assign(mockDetails, { scm_type: '' });
- ProjectsAPI.readDetail = async () => ({ data: manualDetails });
- OrganizationsAPI.read = async () => ({
- count: 0,
- next: null,
- previous: null,
- data: { results: [] },
- });
-
- await act(async () => {
- wrapper = mountWithContexts(
- {}} me={mockMe} />
- );
- });
- const tabs = await waitForElement(
- wrapper,
- '.pf-c-tabs__item',
- (el) => el.length === 4
- );
- tabs.forEach((tab) => expect(tab.text()).not.toEqual('Schedules'));
- });
-
- test('should show content error when user attempts to navigate to erroneous route', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/projects/1/foobar'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
- {}} me={mockMe} />,
- {
- context: {
- router: {
- history,
- route: {
- location: history.location,
- match: {
- params: { id: 1 },
- url: '/projects/1/foobar',
- path: '/project/1/foobar',
- },
- },
- },
- },
- }
- );
- });
- await waitForElement(wrapper, 'ContentError', (el) => el.length === 1);
- });
-});
diff --git a/awx/ui/src/screens/Project/ProjectAdd/ProjectAdd.js b/awx/ui/src/screens/Project/ProjectAdd/ProjectAdd.js
deleted file mode 100644
index baf052ef09c9..000000000000
--- a/awx/ui/src/screens/Project/ProjectAdd/ProjectAdd.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import React, { useState } from 'react';
-import { useHistory } from 'react-router-dom';
-import { Card, PageSection } from '@patternfly/react-core';
-import { CardBody } from 'components/Card';
-import { ProjectsAPI } from 'api';
-import ProjectForm from '../shared/ProjectForm';
-
-function ProjectAdd() {
- const [formSubmitError, setFormSubmitError] = useState(null);
- const history = useHistory();
-
- const handleSubmit = async (values) => {
- if (values.scm_type === 'manual') {
- values.scm_type = '';
- }
- if (!values.credential) {
- // Depending on the permissions of the user submitting the form,
- // the API might throw an unexpected error if our creation request
- // has a zero-length string as its credential field. As a work-around,
- // normalize falsey credential fields by deleting them.
- values.credential = null;
- } else if (typeof values.credential.id === 'number') {
- values.credential = values.credential.id;
- }
- if (!values.signature_validation_credential) {
- values.signature_validation_credential = null;
- } else if (typeof values.signature_validation_credential.id === 'number') {
- values.signature_validation_credential =
- values.signature_validation_credential.id;
- }
- setFormSubmitError(null);
- try {
- const {
- data: { id },
- } = await ProjectsAPI.create({
- ...values,
- organization: values.organization.id,
- default_environment: values.default_environment?.id,
- });
- history.push(`/projects/${id}/details`);
- } catch (error) {
- setFormSubmitError(error);
- }
- };
-
- const handleCancel = () => {
- history.push(`/projects`);
- };
-
- return (
-
-
-
-
-
-
-
- );
-}
-
-export default ProjectAdd;
diff --git a/awx/ui/src/screens/Project/ProjectAdd/ProjectAdd.test.js b/awx/ui/src/screens/Project/ProjectAdd/ProjectAdd.test.js
deleted file mode 100644
index 044f0232e933..000000000000
--- a/awx/ui/src/screens/Project/ProjectAdd/ProjectAdd.test.js
+++ /dev/null
@@ -1,179 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { ProjectsAPI, CredentialTypesAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import ProjectAdd from './ProjectAdd';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
- const projectData = {
- name: 'foo',
- description: 'bar',
- scm_type: 'git',
- scm_url: 'https://foo.bar',
- scm_clean: true,
- scm_track_submodules: false,
- credential: 100,
- signature_validation_credential: 200,
- local_path: '',
- organization: { id: 2, name: 'Bar' },
- scm_update_on_launch: true,
- scm_update_cache_timeout: 3,
- allow_override: false,
- custom_virtualenv: '/var/lib/awx/venv/custom-env',
- default_environment: { id: 1, name: 'Foo' },
- };
-
- const projectOptionsResolve = {
- data: {
- actions: {
- GET: {
- scm_type: {
- choices: [
- ['', 'Manual'],
- ['git', 'Git'],
- ['svn', 'Subversion'],
- ['archive', 'Remote Archive'],
- ['insights', 'Red Hat Insights'],
- ],
- },
- },
- },
- },
- };
-
- const scmCredentialResolve = {
- data: {
- results: [
- {
- id: 4,
- name: 'Source Control',
- kind: 'scm',
- },
- ],
- count: 1,
- },
- };
-
- const insightsCredentialResolve = {
- data: {
- results: [
- {
- id: 5,
- name: 'Insights',
- kind: 'insights',
- },
- ],
- count: 1,
- },
- };
-
- const cryptographyCredentialResolve = {
- data: {
- results: [
- {
- id: 6,
- name: 'GPG Public Key',
- kind: 'cryptography',
- },
- ],
- count: 1,
- },
- };
-
- beforeEach(async () => {
- await ProjectsAPI.readOptions.mockImplementation(
- () => projectOptionsResolve
- );
- await CredentialTypesAPI.read.mockImplementation(
- () => scmCredentialResolve
- );
- await CredentialTypesAPI.read.mockImplementation(
- () => insightsCredentialResolve
- );
- await CredentialTypesAPI.read.mockImplementation(
- () => cryptographyCredentialResolve
- );
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('initially renders successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- expect(wrapper.length).toBe(1);
- });
-
- test('handleSubmit should post to the api', async () => {
- ProjectsAPI.create.mockResolvedValueOnce({
- data: { ...projectData },
- });
- await act(async () => {
- wrapper = mountWithContexts();
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- wrapper.find('ProjectForm').invoke('handleSubmit')(projectData);
- expect(ProjectsAPI.create).toHaveBeenCalledTimes(1);
- expect(ProjectsAPI.create).toHaveBeenCalledWith({
- ...projectData,
- organization: 2,
- default_environment: 1,
- signature_validation_credential: 200,
- });
- });
-
- test('handleSubmit should throw an error', async () => {
- const config = {
- project_local_paths: ['foobar', 'qux'],
- project_base_dir: 'dir/foo/bar',
- };
- const error = {
- response: {
- config: {
- method: 'create',
- url: '/api/v2/projects/',
- },
- data: { detail: 'An error occurred' },
- },
- };
- ProjectsAPI.create.mockRejectedValue(error);
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { config },
- });
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- await act(async () => {
- wrapper.find('ProjectForm').prop('handleSubmit')(
- { ...projectData },
- { scm_type: 'manual' }
- );
- });
- wrapper.update();
- expect(ProjectsAPI.create).toHaveBeenCalledTimes(1);
- expect(wrapper.find('ProjectForm').prop('submitError')).toEqual(error);
- });
-
- test('CardBody cancel button should navigate to projects list', async () => {
- const history = createMemoryHistory();
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
- await waitForElement(wrapper, 'EmptyStateBody', (el) => el.length === 0);
- await act(async () => {
- wrapper.find('ProjectAdd button[aria-label="Cancel"]').simulate('click');
- });
- expect(history.location.pathname).toEqual('/projects');
- });
-});
diff --git a/awx/ui/src/screens/Project/ProjectAdd/index.js b/awx/ui/src/screens/Project/ProjectAdd/index.js
deleted file mode 100644
index b182f153ac5d..000000000000
--- a/awx/ui/src/screens/Project/ProjectAdd/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ProjectAdd';
diff --git a/awx/ui/src/screens/Project/ProjectDetail/ProjectDetail.js b/awx/ui/src/screens/Project/ProjectDetail/ProjectDetail.js
deleted file mode 100644
index 9a96da1d6c84..000000000000
--- a/awx/ui/src/screens/Project/ProjectDetail/ProjectDetail.js
+++ /dev/null
@@ -1,363 +0,0 @@
-import React, { useCallback } from 'react';
-import { Link, useHistory } from 'react-router-dom';
-import { t } from '@lingui/macro';
-import styled from 'styled-components';
-import {
- Button,
- ClipboardCopy,
- TextList,
- TextListItem,
- TextListVariants,
- TextListItemVariants,
- Tooltip,
-} from '@patternfly/react-core';
-import { Project } from 'types';
-import { Config, useConfig } from 'contexts/Config';
-import AlertModal from 'components/AlertModal';
-import { CardBody, CardActionsRow } from 'components/Card';
-import DeleteButton from 'components/DeleteButton';
-import { DetailList, Detail, UserDateDetail } from 'components/DetailList';
-import ErrorDetail from 'components/ErrorDetail';
-import JobCancelButton from 'components/JobCancelButton';
-import ExecutionEnvironmentDetail from 'components/ExecutionEnvironmentDetail';
-import CredentialChip from 'components/CredentialChip';
-import { ProjectsAPI } from 'api';
-import { toTitleCase } from 'util/strings';
-import useRequest, { useDismissableError } from 'hooks/useRequest';
-import useBrandName from 'hooks/useBrandName';
-import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
-import StatusLabel from 'components/StatusLabel';
-import { formatDateString } from 'util/dates';
-import Popover from 'components/Popover';
-import getDocsBaseUrl from 'util/getDocsBaseUrl';
-import ProjectSyncButton from '../shared/ProjectSyncButton';
-import getProjectHelpText from '../shared/Project.helptext';
-import useWsProject from './useWsProject';
-
-const Label = styled.span`
- color: var(--pf-global--disabled-color--100);
-`;
-
-function ProjectDetail({ project }) {
- const projectHelpText = getProjectHelpText();
- const {
- allow_override,
- created,
- custom_virtualenv,
- description,
- id,
- local_path,
- modified,
- name,
- scm_branch,
- scm_clean,
- scm_delete_on_update,
- scm_track_submodules,
- scm_refspec,
- scm_revision,
- scm_type,
- scm_update_on_launch,
- scm_update_cache_timeout,
- scm_url,
- summary_fields,
- } = useWsProject(project);
- const docsURL = `${getDocsBaseUrl(
- useConfig()
- )}/html/userguide/projects.html#manage-playbooks-using-source-control`;
- const history = useHistory();
- const {
- request: deleteProject,
- isLoading,
- error: deleteError,
- } = useRequest(
- useCallback(async () => {
- await ProjectsAPI.destroy(id);
- history.push(`/projects`);
- }, [id, history])
- );
- const brandName = useBrandName();
-
- const { error, dismissError } = useDismissableError(deleteError);
- const deleteDetailsRequests = relatedResourceDeleteRequests.project(project);
- let optionsList = '';
- if (
- scm_clean ||
- scm_delete_on_update ||
- scm_track_submodules ||
- scm_update_on_launch ||
- allow_override
- ) {
- optionsList = (
-
- {scm_clean && (
-
- {t`Discard local changes before syncing`}
-
-
- )}
- {scm_delete_on_update && (
-
- {t`Delete the project before syncing`}{' '}
-
-
- )}
- {scm_track_submodules && (
-
- {t`Track submodules latest commit on branch`}{' '}
-
-
- )}
- {scm_update_on_launch && (
-
- {t`Update revision on job launch`}{' '}
-
-
- )}
- {allow_override && (
-
- {t`Allow branch override`}{' '}
-
-
- )}
-
- );
- }
- const generateLastJobTooltip = (job) => (
- <>
- {t`MOST RECENT SYNC`}
-
- {t`JOB ID:`} {job.id}
-
-
- {t`STATUS:`} {job.status.toUpperCase()}
-
- {job.finished && (
-
- {t`FINISHED:`} {formatDateString(job.finished)}
-
- )}
- >
- );
-
- let job = null;
-
- if (summary_fields?.current_job) {
- job = summary_fields.current_job;
- } else if (summary_fields?.last_job) {
- job = summary_fields.last_job;
- }
-
- const getSourceControlUrlHelpText = () =>
- scm_type === 'git'
- ? projectHelpText.githubSourceControlUrl
- : projectHelpText.svnSourceControlUrl;
- return (
-
-
-
-
-
-
-
- )
- }
- />
-
-
- {summary_fields.organization && (
-
- {summary_fields.organization.name}
-
- }
- />
- )}
-
-
- navigator.clipboard.writeText(scm_revision.toString())
- }
- >
- {scm_revision.substring(0, 7)}
-
- ) : (
-
- )
- }
- alwaysVisible
- />
-
-
-
- {summary_fields.signature_validation_credential && (
-
- }
- isEmpty={
- summary_fields.signature_validation_credential.length === 0
- }
- />
- )}
- {summary_fields.credential && (
-
- }
- isEmpty={summary_fields.credential.length === 0}
- />
- )}
-
-
-
- {({ project_base_dir }) => (
-
- )}
-
-
-
-
- {optionsList && (
-
- )}
-
-
- {summary_fields.user_capabilities?.edit && (
-
- )}
- {summary_fields.user_capabilities?.start &&
- (['running', 'pending', 'waiting'].includes(job?.status) ? (
-
- ) : (
-
- ))}
- {summary_fields.user_capabilities?.delete && (
-
- {t`Delete`}
-
- )}
-
- {error && (
-
- {t`Failed to delete project.`}
-
-
- )}
-
- );
-}
-
-ProjectDetail.propTypes = {
- project: Project.isRequired,
-};
-
-export default ProjectDetail;
diff --git a/awx/ui/src/screens/Project/ProjectDetail/ProjectDetail.test.js b/awx/ui/src/screens/Project/ProjectDetail/ProjectDetail.test.js
deleted file mode 100644
index 36875a8713ea..000000000000
--- a/awx/ui/src/screens/Project/ProjectDetail/ProjectDetail.test.js
+++ /dev/null
@@ -1,301 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import {
- ProjectsAPI,
- JobTemplatesAPI,
- WorkflowJobTemplatesAPI,
- InventorySourcesAPI,
-} from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import ProjectDetail from './ProjectDetail';
-
-jest.mock('../../../api');
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useRouteMatch: () => ({
- url: '/projects/1/details',
- }),
-}));
-jest.mock('hooks/useBrandName', () => ({
- __esModule: true,
- default: () => ({
- current: 'AWX',
- }),
-}));
-describe('', () => {
- const mockProject = {
- id: 1,
- type: 'project',
- url: '/api/v2/projects/1',
- summary_fields: {
- organization: {
- id: 10,
- name: 'Foo',
- },
- default_environment: {
- id: 12,
- name: 'Bar',
- image: 'quay.io/ansible/awx-ee',
- },
- credential: {
- id: 1000,
- name: 'qux',
- kind: 'scm',
- },
- signature_validation_credential: {
- id: 2000,
- name: 'svc',
- kind: 'cryptography',
- },
- last_job: {
- id: 9000,
- status: 'successful',
- },
- created_by: {
- id: 1,
- username: 'admin',
- },
- modified_by: {
- id: 1,
- username: 'admin',
- },
- user_capabilities: {
- edit: true,
- delete: true,
- start: true,
- schedule: true,
- copy: true,
- },
- },
- created: '2019-10-10T01:15:06.780472Z',
- modified: '2019-10-10T01:15:06.780490Z',
- name: 'Project 1',
- description: 'lorem ipsum',
- scm_type: 'git',
- scm_url: 'https://mock.com/bar',
- scm_branch: 'baz',
- scm_refspec: 'refs/remotes/*',
- scm_clean: true,
- scm_delete_on_update: true,
- scm_track_submodules: true,
- credential: 100,
- signature_validation_credential: 200,
- status: 'successful',
- organization: 10,
- scm_update_on_launch: true,
- scm_update_cache_timeout: 5,
- allow_override: true,
- custom_virtualenv: '/custom-venv',
- default_environment: 1,
- };
-
- test('initially renders successfully', () => {
- mountWithContexts();
- });
-
- test('should render Details', () => {
- const wrapper = mountWithContexts();
- function assertDetail(label, value) {
- expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
- expect(wrapper.find(`Detail[label="${label}"] dd`).text()).toBe(value);
- }
- assertDetail('Name', mockProject.name);
- assertDetail('Description', mockProject.description);
- assertDetail('Organization', mockProject.summary_fields.organization.name);
- assertDetail('Source Control Type', 'Git');
- assertDetail('Source Control URL', mockProject.scm_url);
- assertDetail('Source Control Branch', mockProject.scm_branch);
- assertDetail('Source Control Refspec', mockProject.scm_refspec);
- assertDetail(
- 'Source Control Credential',
- `Scm: ${mockProject.summary_fields.credential.name}`
- );
- assertDetail(
- 'Content Signature Validation Credential',
- `Cryptography: ${mockProject.summary_fields.signature_validation_credential.name}`
- );
- assertDetail(
- 'Cache Timeout',
- `${mockProject.scm_update_cache_timeout} Seconds`
- );
- const executionEnvironment = wrapper.find('ExecutionEnvironmentDetail');
- expect(executionEnvironment).toHaveLength(1);
- expect(executionEnvironment.find('dt').text()).toEqual(
- 'Default Execution Environment'
- );
- expect(executionEnvironment.find('dd').text()).toEqual(
- mockProject.summary_fields.default_environment.name
- );
-
- const dateDetails = wrapper.find('UserDateDetail');
- expect(dateDetails).toHaveLength(2);
- expect(dateDetails.at(0).prop('label')).toEqual('Created');
- expect(dateDetails.at(0).prop('date')).toEqual(
- '2019-10-10T01:15:06.780472Z'
- );
- expect(dateDetails.at(1).prop('label')).toEqual('Last Modified');
- expect(dateDetails.at(1).prop('date')).toEqual(
- '2019-10-10T01:15:06.780490Z'
- );
- expect(
- wrapper.find('Detail[label="Enabled Options"]').find('li')
- ).toHaveLength(5);
- const options = [
- 'Discard local changes before syncing',
- 'Delete the project before syncing',
- 'Track submodules latest commit on branch',
- 'Update revision on job launch',
- 'Allow branch override',
- ];
- wrapper.find('li').map((item, index) => {
- expect(item.text().includes(options[index]));
- });
- });
-
- test('should hide options label when all project options return false', () => {
- const mockOptions = {
- scm_type: '',
- scm_clean: false,
- scm_delete_on_update: false,
- scm_track_submodules: false,
- scm_update_on_launch: false,
- allow_override: false,
- created: '',
- modified: '',
- };
- const wrapper = mountWithContexts(
-
- );
- expect(wrapper.find('Detail[label="Enabled Options"]').length).toBe(0);
- });
-
- test('should have proper number of delete detail requests', () => {
- JobTemplatesAPI.read.mockResolvedValue({ data: { count: 0 } });
- WorkflowJobTemplatesAPI.read.mockResolvedValue({ data: { count: 0 } });
- InventorySourcesAPI.read.mockResolvedValue({ data: { count: 0 } });
- const mockOptions = {
- scm_type: '',
- scm_clean: false,
- scm_delete_on_update: false,
- scm_update_on_launch: false,
- allow_override: false,
- created: '',
- modified: '',
- };
- const wrapper = mountWithContexts(
-
- );
- expect(
- wrapper.find('DeleteButton').prop('deleteDetailsRequests')
- ).toHaveLength(3);
- });
-
- test('should render with missing summary fields', async () => {
- const wrapper = mountWithContexts(
-
- );
- await waitForElement(
- wrapper,
- 'Detail[label="Name"]',
- (el) => el.length === 1
- );
- });
-
- test('should show edit and sync button for users with edit permission', async () => {
- const wrapper = mountWithContexts();
- const editButton = await waitForElement(
- wrapper,
- 'ProjectDetail Button[aria-label="edit"]'
- );
-
- const syncButton = await waitForElement(
- wrapper,
- 'ProjectDetail Button[aria-label="Sync Project"]'
- );
- expect(editButton.text()).toEqual('Edit');
- expect(syncButton.text()).toEqual('Sync');
- expect(editButton.prop('to')).toBe(`/projects/${mockProject.id}/edit`);
- });
-
- test('should hide edit button for users without edit permission', async () => {
- const wrapper = mountWithContexts(
-
- );
- await waitForElement(wrapper, 'ProjectDetail');
- expect(wrapper.find('ProjectDetail Button[aria-label="edit"]').length).toBe(
- 0
- );
- expect(wrapper.find('ProjectDetail Button[aria-label="sync"]').length).toBe(
- 0
- );
- });
-
- test('edit button should navigate to project edit', () => {
- const history = createMemoryHistory();
- const wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- expect(wrapper.find('Button[aria-label="edit"]').length).toBe(1);
- wrapper
- .find('Button[aria-label="edit"] Link')
- .simulate('click', { button: 0 });
- expect(history.location.pathname).toEqual('/projects/1/edit');
- });
-
- test('sync button should call api to sync project', async () => {
- ProjectsAPI.readSync.mockResolvedValue({ data: { can_update: true } });
- const wrapper = mountWithContexts();
- await act(() =>
- wrapper
- .find('ProjectDetail Button[aria-label="Sync Project"]')
- .prop('onClick')(1)
- );
- expect(ProjectsAPI.sync).toHaveBeenCalledTimes(1);
- });
-
- test('expected api calls are made for delete', async () => {
- const wrapper = mountWithContexts();
- await waitForElement(wrapper, 'ProjectDetail Button[aria-label="Delete"]');
- await act(async () => {
- wrapper.find('DeleteButton').invoke('onConfirm')();
- });
- expect(ProjectsAPI.destroy).toHaveBeenCalledTimes(1);
- });
-
- test('Error dialog shown for failed deletion', async () => {
- ProjectsAPI.destroy.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
- const wrapper = mountWithContexts();
- await waitForElement(wrapper, 'ProjectDetail Button[aria-label="Delete"]');
- await act(async () => {
- wrapper.find('DeleteButton').invoke('onConfirm')();
- });
- await waitForElement(
- wrapper,
- 'Modal[title="Error!"]',
- (el) => el.length === 1
- );
- await act(async () => {
- wrapper.find('Modal[title="Error!"]').invoke('onClose')();
- });
- await waitForElement(
- wrapper,
- 'Modal[title="Error!"]',
- (el) => el.length === 0
- );
- });
-});
diff --git a/awx/ui/src/screens/Project/ProjectDetail/index.js b/awx/ui/src/screens/Project/ProjectDetail/index.js
deleted file mode 100644
index e0b5e229b1e3..000000000000
--- a/awx/ui/src/screens/Project/ProjectDetail/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ProjectDetail';
diff --git a/awx/ui/src/screens/Project/ProjectDetail/useWsProject.js b/awx/ui/src/screens/Project/ProjectDetail/useWsProject.js
deleted file mode 100644
index 58ad4cf9d722..000000000000
--- a/awx/ui/src/screens/Project/ProjectDetail/useWsProject.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import { useState, useEffect } from 'react';
-import useWebsocket from 'hooks/useWebsocket';
-import { ProjectsAPI } from 'api';
-
-export default function useWsProjects(initialProject) {
- const [project, setProject] = useState(initialProject);
- const lastMessage = useWebsocket({
- jobs: ['status_changed'],
- control: ['limit_reached_1'],
- });
-
- const refreshProject = async () => {
- const { data } = await ProjectsAPI.readDetail(project.id);
- setProject(data);
- };
-
- useEffect(() => {
- setProject(initialProject);
- }, [initialProject]);
-
- useEffect(
- () => {
- if (
- !project ||
- !lastMessage?.unified_job_id ||
- lastMessage.type !== 'project_update'
- ) {
- return;
- }
-
- if (lastMessage.finished) {
- refreshProject();
- return;
- }
-
- const updatedProject = {
- ...project,
- summary_fields: {
- ...project.summary_fields,
- current_job: {
- id: lastMessage.unified_job_id,
- status: lastMessage.status,
- finished: lastMessage.finished,
- },
- },
- };
- setProject(updatedProject);
- },
- [lastMessage] // eslint-disable-line react-hooks/exhaustive-deps
- );
-
- return project;
-}
diff --git a/awx/ui/src/screens/Project/ProjectDetail/useWsProject.test.js b/awx/ui/src/screens/Project/ProjectDetail/useWsProject.test.js
deleted file mode 100644
index 1f9fc1d2ec7f..000000000000
--- a/awx/ui/src/screens/Project/ProjectDetail/useWsProject.test.js
+++ /dev/null
@@ -1,167 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import WS from 'jest-websocket-mock';
-import { ProjectsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import useWsProject from './useWsProject';
-
-jest.mock('../../../api/models/Projects');
-
-function TestInner() {
- return ;
-}
-function Test({ project }) {
- const synced = useWsProject(project);
- return ;
-}
-
-describe('useWsProject', () => {
- let debug;
- let wrapper;
-
- beforeEach(() => {
- debug = global.console.debug; // eslint-disable-line prefer-destructuring
- global.console.debug = () => {};
- ProjectsAPI.readDetail.mockResolvedValue({
- data: {
- id: 1,
- summary_fields: {
- last_job: {
- id: 19,
- name: 'Test Project',
- description: '',
- finished: '2021-06-01T18:43:53.332201Z',
- status: 'successful',
- failed: false,
- },
- },
- },
- });
- });
-
- afterEach(() => {
- global.console.debug = debug;
- jest.clearAllMocks();
- });
-
- test('should return project detail', async () => {
- const project = { id: 1 };
- await act(async () => {
- wrapper = await mountWithContexts();
- });
-
- expect(wrapper.find('TestInner').prop('project')).toEqual(project);
- WS.clean();
- });
-
- test('should establish websocket connection', async () => {
- global.document.cookie = 'csrftoken=abc123';
- const mockServer = new WS('ws://localhost/websocket/');
-
- const project = { id: 1 };
- await act(async () => {
- wrapper = await mountWithContexts();
- });
-
- await mockServer.connected;
- await expect(mockServer).toReceiveMessage(
- JSON.stringify({
- xrftoken: 'abc123',
- groups: {
- jobs: ['status_changed'],
- control: ['limit_reached_1'],
- },
- })
- );
- WS.clean();
- });
-
- test('should update project status', async () => {
- global.document.cookie = 'csrftoken=abc123';
- const mockServer = new WS('ws://localhost/websocket/');
-
- const project = {
- id: 1,
- summary_fields: {
- last_job: {
- id: 1,
- status: 'successful',
- finished: '2020-07-02T16:25:31.839071Z',
- },
- },
- };
-
- await act(async () => {
- wrapper = await mountWithContexts();
- });
-
- await mockServer.connected;
- await expect(mockServer).toReceiveMessage(
- JSON.stringify({
- xrftoken: 'abc123',
- groups: {
- jobs: ['status_changed'],
- control: ['limit_reached_1'],
- },
- })
- );
- expect(
- wrapper.find('TestInner').prop('project').summary_fields.current_job
- ).toBeUndefined();
- expect(
- wrapper.find('TestInner').prop('project').summary_fields.last_job.status
- ).toEqual('successful');
-
- await act(async () => {
- mockServer.send(
- JSON.stringify({
- group_name: 'jobs',
- project_id: 1,
- status: 'running',
- type: 'project_update',
- unified_job_id: 2,
- unified_job_template_id: 1,
- })
- );
- });
- wrapper.update();
-
- expect(
- wrapper.find('TestInner').prop('project').summary_fields.current_job
- ).toEqual({
- id: 2,
- status: 'running',
- finished: undefined,
- });
-
- await act(async () => {
- mockServer.send(
- JSON.stringify({
- group_name: 'jobs',
- project_id: 1,
- status: 'successful',
- type: 'project_update',
- unified_job_id: 2,
- unified_job_template_id: 1,
- finished: '2020-07-02T16:28:31.839071Z',
- })
- );
- });
-
- wrapper.update();
-
- expect(ProjectsAPI.readDetail).toHaveBeenCalledTimes(1);
-
- expect(
- wrapper.find('TestInner').prop('project').summary_fields.last_job
- ).toEqual({
- id: 19,
- name: 'Test Project',
- description: '',
- finished: '2021-06-01T18:43:53.332201Z',
- status: 'successful',
- failed: false,
- });
- WS.clean();
- });
-});
diff --git a/awx/ui/src/screens/Project/ProjectEdit/ProjectEdit.js b/awx/ui/src/screens/Project/ProjectEdit/ProjectEdit.js
deleted file mode 100644
index f2ed08cbe8d1..000000000000
--- a/awx/ui/src/screens/Project/ProjectEdit/ProjectEdit.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import React, { useState } from 'react';
-import { useHistory } from 'react-router-dom';
-import { Card } from '@patternfly/react-core';
-import { CardBody } from 'components/Card';
-import { ProjectsAPI } from 'api';
-import ProjectForm from '../shared/ProjectForm';
-
-function ProjectEdit({ project }) {
- const [formSubmitError, setFormSubmitError] = useState(null);
- const history = useHistory();
-
- const handleSubmit = async (values) => {
- if (values.scm_type === 'manual') {
- values.scm_type = '';
- }
- if (!values.credential) {
- // Depending on the permissions of the user submitting the form,
- // the API might throw an unexpected error if our creation request
- // has a zero-length string as its credential field. As a work-around,
- // normalize falsey credential fields by deleting them.
- values.credential = null;
- } else if (typeof values.credential.id === 'number') {
- values.credential = values.credential.id;
- }
- if (!values.signature_validation_credential) {
- values.signature_validation_credential = null;
- } else if (typeof values.signature_validation_credential.id === 'number') {
- values.signature_validation_credential =
- values.signature_validation_credential.id;
- }
-
- try {
- const {
- data: { id },
- } = await ProjectsAPI.update(project.id, {
- ...values,
- organization: values.organization.id,
- default_environment: values.default_environment?.id || null,
- });
- history.push(`/projects/${id}/details`);
- } catch (error) {
- setFormSubmitError(error);
- }
- };
-
- const handleCancel = () => {
- history.push(`/projects/${project.id}/details`);
- };
-
- return (
-
-
-
-
-
- );
-}
-
-export default ProjectEdit;
diff --git a/awx/ui/src/screens/Project/ProjectEdit/ProjectEdit.test.js b/awx/ui/src/screens/Project/ProjectEdit/ProjectEdit.test.js
deleted file mode 100644
index 4b7193dc1256..000000000000
--- a/awx/ui/src/screens/Project/ProjectEdit/ProjectEdit.test.js
+++ /dev/null
@@ -1,196 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { ProjectsAPI, CredentialTypesAPI, RootAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import ProjectEdit from './ProjectEdit';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
- const projectData = {
- id: 123,
- name: 'foo',
- description: 'bar',
- scm_type: 'git',
- scm_url: 'https://foo.bar',
- scm_clean: true,
- scm_track_submodules: false,
- credential: 100,
- signature_validation_credential: 200,
- local_path: 'bar',
- organization: 2,
- scm_update_on_launch: true,
- scm_update_cache_timeout: 3,
- allow_override: false,
- custom_virtualenv: '/var/lib/awx/venv/custom-env',
- summary_fields: {
- credential: {
- id: 100,
- credential_type_id: 5,
- kind: 'insights',
- },
- signature_validation_credential: {
- id: 200,
- credential_type_id: 6,
- kind: 'cryptography',
- name: 'foo',
- },
- organization: {
- id: 2,
- name: 'Default',
- },
- },
- };
-
- const projectOptionsResolve = {
- data: {
- actions: {
- GET: {
- scm_type: {
- choices: [
- ['', 'Manual'],
- ['git', 'Git'],
- ['svn', 'Subversion'],
- ['archive', 'Remote Archive'],
- ['insights', 'Red Hat Insights'],
- ],
- },
- },
- },
- },
- };
-
- const scmCredentialResolve = {
- data: {
- count: 1,
- results: [
- {
- id: 4,
- name: 'Source Control',
- kind: 'scm',
- },
- ],
- },
- };
-
- const insightsCredentialResolve = {
- data: {
- count: 1,
- results: [
- {
- id: 5,
- name: 'Insights',
- kind: 'insights',
- },
- ],
- },
- };
-
- const cryptographyCredentialResolve = {
- data: {
- count: 1,
- results: [
- {
- id: 6,
- name: 'GPG Public Key',
- kind: 'cryptography',
- },
- ],
- },
- };
-
- beforeEach(async () => {
- RootAPI.readAssetVariables.mockResolvedValue({
- data: {
- BRAND_NAME: 'AWX',
- },
- });
- await ProjectsAPI.readOptions.mockImplementation(
- () => projectOptionsResolve
- );
- await CredentialTypesAPI.read.mockImplementation(
- () => scmCredentialResolve
- );
- await CredentialTypesAPI.read.mockImplementation(
- () => insightsCredentialResolve
- );
- await CredentialTypesAPI.read.mockImplementation(
- () => cryptographyCredentialResolve
- );
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('initially renders successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts();
- });
- expect(wrapper.length).toBe(1);
- });
-
- test('handleSubmit should post to the api', async () => {
- const history = createMemoryHistory();
- ProjectsAPI.update.mockResolvedValueOnce({
- data: { ...projectData },
- });
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- await act(async () => {
- wrapper.find('form').simulate('submit');
- });
- wrapper.update();
- expect(ProjectsAPI.update).toHaveBeenCalledTimes(1);
- });
-
- test('handleSubmit should throw an error', async () => {
- const config = {
- project_local_paths: [],
- project_base_dir: 'foo/bar',
- };
- const error = new Error('oops');
- const realConsoleError = global.console.error;
- global.console.error = jest.fn();
- ProjectsAPI.update.mockImplementation(() => Promise.reject(error));
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { config },
- }
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- await act(async () => {
- wrapper.find('form').simulate('submit');
- });
- wrapper.update();
- expect(ProjectsAPI.update).toHaveBeenCalledTimes(1);
- expect(wrapper.find('ProjectForm').prop('submitError')).toEqual(error);
- global.console.error = realConsoleError;
- });
-
- test('CardBody cancel button should navigate to project details', async () => {
- const history = createMemoryHistory();
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
- await waitForElement(wrapper, 'EmptyStateBody', (el) => el.length === 0);
- await act(async () => {
- wrapper.find('ProjectEdit button[aria-label="Cancel"]').simulate('click');
- });
- expect(history.location.pathname).toEqual('/projects/123/details');
- });
-});
diff --git a/awx/ui/src/screens/Project/ProjectEdit/index.js b/awx/ui/src/screens/Project/ProjectEdit/index.js
deleted file mode 100644
index 559b86bfc2b0..000000000000
--- a/awx/ui/src/screens/Project/ProjectEdit/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ProjectEdit';
diff --git a/awx/ui/src/screens/Project/ProjectList/ProjectList.js b/awx/ui/src/screens/Project/ProjectList/ProjectList.js
deleted file mode 100644
index 6c3e8290488a..000000000000
--- a/awx/ui/src/screens/Project/ProjectList/ProjectList.js
+++ /dev/null
@@ -1,314 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-import { useLocation, useRouteMatch } from 'react-router-dom';
-import { t, Plural } from '@lingui/macro';
-import { Card, PageSection } from '@patternfly/react-core';
-import { ProjectsAPI } from 'api';
-import useRequest, {
- useDeleteItems,
- useDismissableError,
-} from 'hooks/useRequest';
-import AlertModal from 'components/AlertModal';
-import DataListToolbar from 'components/DataListToolbar';
-import ErrorDetail from 'components/ErrorDetail';
-import PaginatedTable, {
- HeaderRow,
- HeaderCell,
- ToolbarAddButton,
- ToolbarDeleteButton,
- getSearchableKeys,
-} from 'components/PaginatedTable';
-import useSelected from 'hooks/useSelected';
-import useExpanded from 'hooks/useExpanded';
-import useToast, { AlertVariant } from 'hooks/useToast';
-import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
-import { getQSConfig, parseQueryString } from 'util/qs';
-import useWsProjects from './useWsProjects';
-
-import ProjectListItem from './ProjectListItem';
-
-const QS_CONFIG = getQSConfig('project', {
- page: 1,
- page_size: 20,
- order_by: 'name',
-});
-
-function ProjectList() {
- const location = useLocation();
- const match = useRouteMatch();
- const { addToast, Toast, toastProps } = useToast();
-
- const {
- request: fetchUpdatedProject,
- error: fetchUpdatedProjectError,
- result: updatedProject,
- } = useRequest(
- useCallback(async (projectId) => {
- if (!projectId) {
- return {};
- }
- const { data } = await ProjectsAPI.readDetail(projectId);
- return data;
- }, []),
- null
- );
-
- const {
- result: {
- results,
- itemCount,
- actions,
- relatedSearchableKeys,
- searchableKeys,
- },
- error: contentError,
- isLoading,
- request: fetchProjects,
- setValue: setProjects,
- } = useRequest(
- useCallback(async () => {
- const params = parseQueryString(QS_CONFIG, location.search);
- const [response, actionsResponse] = await Promise.all([
- ProjectsAPI.read(params),
- ProjectsAPI.readOptions(),
- ]);
- return {
- results: response.data.results,
- itemCount: response.data.count,
- actions: actionsResponse.data.actions,
- relatedSearchableKeys: (
- actionsResponse?.data?.related_search_fields || []
- ).map((val) => val.slice(0, -8)),
- searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
- };
- }, [location]),
- {
- results: [],
- itemCount: 0,
- actions: {},
- relatedSearchableKeys: [],
- searchableKeys: [],
- }
- );
-
- useEffect(() => {
- fetchProjects();
- }, [fetchProjects]);
-
- const projects = useWsProjects(results);
-
- const {
- selected,
- isAllSelected,
- handleSelect,
- setSelected,
- selectAll,
- clearSelected,
- } = useSelected(projects);
-
- const { expanded, isAllExpanded, handleExpand, expandAll } =
- useExpanded(projects);
-
- const {
- isLoading: isDeleteLoading,
- deleteItems: deleteProjects,
- deletionError,
- clearDeletionError,
- } = useDeleteItems(
- useCallback(
- () => Promise.all(selected.map(({ id }) => ProjectsAPI.destroy(id))),
- [selected]
- ),
- {
- qsConfig: QS_CONFIG,
- allItemsSelected: isAllSelected,
- fetchItems: fetchProjects,
- }
- );
-
- const handleCopy = useCallback(
- (newId) => {
- addToast({
- id: newId,
- title: t`Project copied successfully`,
- variant: AlertVariant.success,
- hasTimeout: true,
- });
- },
- [addToast]
- );
-
- const handleProjectDelete = async () => {
- await deleteProjects();
- setSelected([]);
- };
-
- const hasContentLoading = isDeleteLoading || isLoading;
- const canAdd = actions && actions.POST;
-
- const deleteDetailsRequests = relatedResourceDeleteRequests.project(
- selected[0]
- );
-
- useEffect(() => {
- if (updatedProject) {
- const updatedProjects = projects.map((project) =>
- project.id === updatedProject.id ? updatedProject : project
- );
- setProjects({
- results: updatedProjects,
- itemCount,
- actions,
- relatedSearchableKeys,
- searchableKeys,
- });
- }
- /* eslint-disable-next-line react-hooks/exhaustive-deps */
- }, [updatedProject]);
-
- const { error: projectError, dismissError: dismissProjectError } =
- useDismissableError(fetchUpdatedProjectError);
-
- return (
- <>
-
-
-
- {t`Name`}
- {t`Status`}
- {t`Type`}
- {t`Revision`}
- {t`Actions`}
-
- }
- renderToolbar={(props) => (
- ,
- ]
- : []),
-
- }
- />,
- ]}
- />
- )}
- renderRow={(project, index) => (
- row.id === project.id)}
- onExpand={() => handleExpand(project)}
- fetchProjects={fetchProjects}
- key={project.id}
- project={project}
- detailUrl={`${match.url}/${project.id}`}
- isSelected={selected.some((row) => row.id === project.id)}
- onSelect={() => handleSelect(project)}
- onCopy={handleCopy}
- rowIndex={index}
- onRefreshRow={(projectId) => fetchUpdatedProject(projectId)}
- />
- )}
- emptyStateControls={
- canAdd ? (
-
- ) : null
- }
- />
-
-
-
- {deletionError && (
-
- {t`Failed to delete one or more projects.`}
-
-
- )}
- {projectError && (
-
- {t`Failed to fetch the updated project data.`}
-
-
- )}
- >
- );
-}
-
-export default ProjectList;
diff --git a/awx/ui/src/screens/Project/ProjectList/ProjectList.test.js b/awx/ui/src/screens/Project/ProjectList/ProjectList.test.js
deleted file mode 100644
index c492a9c208a6..000000000000
--- a/awx/ui/src/screens/Project/ProjectList/ProjectList.test.js
+++ /dev/null
@@ -1,280 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import {
- ProjectsAPI,
- JobTemplatesAPI,
- WorkflowJobTemplatesAPI,
- InventorySourcesAPI,
-} from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import ProjectList from './ProjectList';
-
-jest.mock('../../../api');
-
-const mockProjects = [
- {
- id: 1,
- name: 'Project 1',
- url: '/api/v2/projects/1',
- type: 'project',
- scm_type: 'git',
- scm_revision: 'hfadsh89sa9gsaisdf0jogos0fgd9sgdf89adsf98',
- summary_fields: {
- last_job: {
- id: 9000,
- status: 'successful',
- },
- user_capabilities: {
- delete: true,
- update: true,
- },
- },
- },
- {
- id: 2,
- name: 'Project 2',
- url: '/api/v2/projects/2',
- type: 'project',
- scm_type: 'svn',
- scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf',
- summary_fields: {
- last_job: {
- id: 9002,
- status: 'successful',
- },
- user_capabilities: {
- delete: true,
- update: true,
- },
- },
- },
- {
- id: 3,
- name: 'Project 3',
- url: '/api/v2/projects/3',
- type: 'project',
- scm_type: 'insights',
- scm_revision: '4893adfi749493afjksjoaiosdgjoaisdjadfisjaso',
- summary_fields: {
- last_job: {
- id: 9003,
- status: 'successful',
- },
- user_capabilities: {
- delete: false,
- update: false,
- },
- },
- },
- {
- id: 4,
- name: 'Project 4',
- url: '/api/v2/projects/4',
- type: 'project',
- scm_type: 'archive',
- scm_revision: 'odsd9ajf8aagjisooajfij34ikdj3fs994s4daiaos7',
- summary_fields: {
- last_job: {
- id: 9004,
- status: 'successful',
- },
- user_capabilities: {
- delete: false,
- update: false,
- },
- },
- },
-];
-
-describe('', () => {
- beforeEach(() => {
- JobTemplatesAPI.read.mockResolvedValue({ data: { count: 0 } });
- WorkflowJobTemplatesAPI.read.mockResolvedValue({ data: { count: 0 } });
- InventorySourcesAPI.read.mockResolvedValue({ data: { count: 0 } });
- ProjectsAPI.read.mockResolvedValue({
- data: {
- count: mockProjects.length,
- results: mockProjects,
- },
- });
-
- ProjectsAPI.readOptions.mockResolvedValue({
- data: {
- actions: {
- GET: {},
- POST: {},
- },
- related_search_fields: [],
- },
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should load and render projects', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
-
- expect(wrapper.find('ProjectListItem')).toHaveLength(4);
- });
-
- test('should select project when checked', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
-
- await act(async () => {
- wrapper.find('ProjectListItem').first().invoke('onSelect')();
- });
- wrapper.update();
-
- expect(wrapper.find('ProjectListItem').first().prop('isSelected')).toEqual(
- true
- );
- });
-
- test('should have proper number of delete detail requests', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
- expect(
- wrapper.find('ToolbarDeleteButton').prop('deleteDetailsRequests')
- ).toHaveLength(3);
- });
-
- test('should select all', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
-
- await act(async () => {
- wrapper.find('DataListToolbar').invoke('onSelectAll')(true);
- });
- wrapper.update();
-
- const items = wrapper.find('ProjectListItem');
- expect(items).toHaveLength(4);
- items.forEach((item) => {
- expect(item.prop('isSelected')).toEqual(true);
- });
-
- expect(wrapper.find('ProjectListItem').first().prop('isSelected')).toEqual(
- true
- );
- });
-
- test('should disable delete button', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
-
- await act(async () => {
- wrapper.find('ProjectListItem').at(2).invoke('onSelect')();
- });
-
- waitForElement(
- wrapper,
- 'ToolbarDeleteButton button',
- (el) => el.prop('disabled') === true
- );
- });
-
- test('should call delete api', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
-
- await act(async () => {
- wrapper.find('ProjectListItem').at(0).invoke('onSelect')();
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('ProjectListItem').at(1).invoke('onSelect')();
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('ToolbarDeleteButton').invoke('onDelete')();
- });
-
- expect(ProjectsAPI.destroy).toHaveBeenCalledTimes(2);
- });
-
- test('should show deletion error', async () => {
- ProjectsAPI.destroy.mockRejectedValue(
- new Error({
- response: {
- config: {
- method: 'delete',
- url: '/api/v2/projects/1',
- },
- data: 'An error occurred',
- },
- })
- );
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
- expect(ProjectsAPI.read).toHaveBeenCalledTimes(1);
- await act(async () => {
- wrapper.find('ProjectListItem').at(0).invoke('onSelect')();
- });
- wrapper.update();
-
- await act(async () => {
- wrapper.find('ToolbarDeleteButton').invoke('onDelete')();
- });
- wrapper.update();
-
- const modal = wrapper.find('Modal');
- expect(modal).toHaveLength(1);
- expect(modal.prop('title')).toEqual('Error!');
- });
-
- test('Add button shown for users without ability to POST', async () => {
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
-
- expect(wrapper.find('ToolbarAddButton').length).toBe(1);
- });
-
- test('Add button hidden for users without ability to POST', async () => {
- ProjectsAPI.readOptions = () =>
- Promise.resolve({
- data: {
- actions: {
- GET: {},
- },
- },
- });
- let wrapper;
- await act(async () => {
- wrapper = mountWithContexts();
- });
- wrapper.update();
-
- expect(wrapper.find('ToolbarAddButton').length).toBe(0);
- });
-});
diff --git a/awx/ui/src/screens/Project/ProjectList/ProjectListItem.js b/awx/ui/src/screens/Project/ProjectList/ProjectListItem.js
deleted file mode 100644
index 5f92e182a7bb..000000000000
--- a/awx/ui/src/screens/Project/ProjectList/ProjectListItem.js
+++ /dev/null
@@ -1,324 +0,0 @@
-import 'styled-components/macro';
-import React, { useState, useCallback } from 'react';
-import { string, bool, func } from 'prop-types';
-import { Button, ClipboardCopy, Tooltip } from '@patternfly/react-core';
-import { Tr, Td, ExpandableRowContent } from '@patternfly/react-table';
-import { t } from '@lingui/macro';
-import { Link } from 'react-router-dom';
-import {
- PencilAltIcon,
- ExclamationTriangleIcon as PFExclamationTriangleIcon,
- UndoIcon,
-} from '@patternfly/react-icons';
-import styled from 'styled-components';
-import { ActionsTd, ActionItem, TdBreakWord } from 'components/PaginatedTable';
-import { formatDateString, timeOfDay } from 'util/dates';
-import { ProjectsAPI } from 'api';
-import { DetailList, Detail, DeletedDetail } from 'components/DetailList';
-import ExecutionEnvironmentDetail from 'components/ExecutionEnvironmentDetail';
-import StatusLabel from 'components/StatusLabel';
-import { toTitleCase } from 'util/strings';
-import { isJobRunning } from 'util/jobs';
-import CopyButton from 'components/CopyButton';
-import { Project } from 'types';
-import JobCancelButton from 'components/JobCancelButton';
-import ProjectSyncButton from '../shared/ProjectSyncButton';
-
-const Label = styled.span`
- color: var(--pf-global--disabled-color--100);
-`;
-
-const ExclamationTriangleIcon = styled(PFExclamationTriangleIcon)`
- color: var(--pf-global--warning-color--100);
- margin-left: 18px;
-`;
-
-function ProjectListItem({
- isExpanded,
- onExpand,
- project,
- isSelected,
- onSelect,
- onCopy,
- detailUrl,
- fetchProjects,
- rowIndex,
- onRefreshRow,
-}) {
- const [isDisabled, setIsDisabled] = useState(false);
- ProjectListItem.propTypes = {
- project: Project.isRequired,
- detailUrl: string.isRequired,
- isSelected: bool.isRequired,
- onSelect: func.isRequired,
- };
-
- const copyProject = useCallback(async () => {
- const response = await ProjectsAPI.copy(project.id, {
- name: `${project.name} @ ${timeOfDay()}`,
- });
- if (response.status === 201) {
- onCopy(response.data.id);
- }
- await fetchProjects();
- }, [project.id, project.name, fetchProjects, onCopy]);
-
- const generateLastJobTooltip = (job) => (
- <>
- {t`MOST RECENT SYNC`}
-
- {t`JOB ID:`} {job.id}
-
-
- {t`STATUS:`} {job.status.toUpperCase()}
-
- {job.finished && (
-
- {t`FINISHED:`} {formatDateString(job.finished)}
-
- )}
- >
- );
-
- const handleCopyStart = useCallback(() => {
- setIsDisabled(true);
- }, []);
-
- const handleCopyFinish = useCallback(() => {
- setIsDisabled(false);
- }, []);
-
- const renderRevision = () => {
- if (!project.summary_fields?.current_job || project.scm_revision) {
- return project.scm_revision ? (
-
- navigator.clipboard.writeText(project.scm_revision.toString())
- }
- >
- {project.scm_revision.substring(0, 7)}
-
- ) : (
-
- );
- }
-
- if (
- isJobRunning(project.summary_fields.current_job.status) &&
- !project.scm_revision
- ) {
- return (
-
- );
- }
-
- return (
- <>
-
-
-
-
- >
- );
- };
-
- const labelId = `check-action-${project.id}`;
-
- const missingExecutionEnvironment =
- project.custom_virtualenv && !project.default_environment;
-
- let job = null;
-
- if (project.summary_fields?.current_job) {
- job = project.summary_fields.current_job;
- } else if (project.summary_fields?.last_job) {
- job = project.summary_fields.last_job;
- }
-
- return (
- <>
-
- |
- |
-
-
-
- {project.name}
-
-
- {missingExecutionEnvironment && (
-
-
-
-
-
- )}
-
-
- {job ? (
-
-
-
-
-
- ) : (
-
-
-
- )}
- |
-
- {project.scm_type === '' ? t`Manual` : toTitleCase(project.scm_type)}
- |
- {renderRevision()} |
-
- {['running', 'pending', 'waiting'].includes(job?.status) ? (
-
-
-
- ) : (
-
-
-
- )}
-
-
-
-
-
-
-
-
-
- |
-
-
-
-
- {project.summary_fields.organization ? (
-
- {project.summary_fields.organization.name}
-
- }
- dataCy={`project-${project.id}-organization`}
- />
- ) : (
-
- )}
-
-
-
-
-
- |
-
- >
- );
-}
-export default ProjectListItem;
diff --git a/awx/ui/src/screens/Project/ProjectList/ProjectListItem.test.js b/awx/ui/src/screens/Project/ProjectList/ProjectListItem.test.js
deleted file mode 100644
index 01be85969082..000000000000
--- a/awx/ui/src/screens/Project/ProjectList/ProjectListItem.test.js
+++ /dev/null
@@ -1,453 +0,0 @@
-import React from 'react';
-
-import { act } from 'react-dom/test-utils';
-import { ProjectsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import ProjectsListItem from './ProjectListItem';
-
-jest.mock('../../../api/models/Projects');
-jest.mock('hooks/useBrandName', () => ({
- __esModule: true,
- default: () => ({
- current: 'AWX',
- }),
-}));
-describe('', () => {
- test('launch button shown to users with start capabilities', () => {
- const wrapper = mountWithContexts(
-
-
- {}}
- project={{
- id: 1,
- name: 'Project 1',
- url: '/api/v2/projects/1',
- type: 'project',
- scm_type: 'git',
- scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf',
- summary_fields: {
- last_job: {
- id: 9000,
- status: 'successful',
- },
- user_capabilities: {
- start: true,
- },
- },
- }}
- />
-
-
- );
- expect(wrapper.find('ProjectSyncButton').exists()).toBeTruthy();
- });
-
- test('should render warning about missing execution environment', () => {
- const wrapper = mountWithContexts(
-
-
- {}}
- project={{
- id: 1,
- name: 'Project 1',
- url: '/api/v2/projects/1',
- type: 'project',
- scm_type: 'git',
- scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf',
- summary_fields: {
- last_job: {
- id: 9000,
- status: 'successful',
- },
- user_capabilities: {
- start: true,
- },
- },
- custom_virtualenv: '/var/lib/awx/env',
- default_environment: null,
- }}
- />
-
-
- );
-
- expect(wrapper.find('ExclamationTrianglePopover').length).toBe(1);
- });
-
- test('launch button hidden from users without start capabilities', () => {
- const wrapper = mountWithContexts(
-
-
- {}}
- project={{
- id: 1,
- name: 'Project 1',
- url: '/api/v2/projects/1',
- type: 'project',
- scm_type: 'git',
- scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf',
- summary_fields: {
- last_job: {
- id: 9000,
- status: 'successful',
- },
- user_capabilities: {
- start: false,
- },
- },
- }}
- />
-
-
- );
- expect(wrapper.find('ProjectSyncButton').exists()).toBeFalsy();
- });
-
- test('edit button shown to users with edit capabilities', () => {
- const wrapper = mountWithContexts(
-
-
- {}}
- project={{
- id: 1,
- name: 'Project 1',
- url: '/api/v2/projects/1',
- type: 'project',
- scm_type: 'git',
- scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf',
- summary_fields: {
- last_job: {
- id: 9000,
- status: 'successful',
- },
- user_capabilities: {
- edit: true,
- },
- },
- }}
- />
-
-
- );
- expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
- });
-
- test('edit button hidden from users without edit capabilities', () => {
- const wrapper = mountWithContexts(
-
-
- {}}
- project={{
- id: 1,
- name: 'Project 1',
- url: '/api/v2/projects/1',
- type: 'project',
- scm_type: 'git',
- scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf',
- summary_fields: {
- last_job: {
- id: 9000,
- status: 'successful',
- },
- user_capabilities: {
- edit: false,
- },
- },
- }}
- />
-
-
- );
- expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
- });
-
- test('should call api to copy project', async () => {
- ProjectsAPI.copy.mockResolvedValue();
- const wrapper = mountWithContexts(
-
-
- {}}
- project={{
- id: 1,
- name: 'Project 1',
- url: '/api/v2/projects/1',
- type: 'project',
- scm_type: 'git',
- scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf',
- summary_fields: {
- last_job: {
- id: 9000,
- status: 'successful',
- },
- user_capabilities: {
- edit: false,
- copy: true,
- },
- },
- }}
- />
-
-
- );
-
- await act(async () =>
- wrapper.find('Button[aria-label="Copy"]').prop('onClick')()
- );
- expect(ProjectsAPI.copy).toHaveBeenCalled();
- jest.clearAllMocks();
- });
-
- test('should render proper alert modal on copy error', async () => {
- ProjectsAPI.copy.mockRejectedValue(new Error('This is an error'));
-
- const wrapper = mountWithContexts(
-
-
- {}}
- project={{
- id: 1,
- name: 'Project 1',
- url: '/api/v2/projects/1',
- type: 'project',
- scm_type: 'git',
- scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf',
- summary_fields: {
- last_job: {
- id: 9000,
- status: 'successful',
- },
- user_capabilities: {
- edit: false,
- copy: true,
- },
- },
- }}
- />
-
-
- );
- await act(async () =>
- wrapper.find('Button[aria-label="Copy"]').prop('onClick')()
- );
- wrapper.update();
- expect(wrapper.find('Modal').prop('isOpen')).toBe(true);
- jest.clearAllMocks();
- });
- test('should not render copy button', async () => {
- const wrapper = mountWithContexts(
-
-
- {}}
- project={{
- id: 1,
- name: 'Project 1',
- url: '/api/v2/projects/1',
- type: 'project',
- scm_type: 'git',
- scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf',
- summary_fields: {
- last_job: {
- id: 9000,
- status: 'successful',
- },
- user_capabilities: {
- edit: false,
- copy: false,
- },
- },
- }}
- />
-
-
- );
- expect(wrapper.find('CopyButton').length).toBe(0);
- });
- test('should render proper revision text when project has not been synced', () => {
- const wrapper = mountWithContexts(
-
-
- {}}
- project={{
- id: 1,
- name: 'Project 1',
- url: '/api/v2/projects/1',
- type: 'project',
- scm_type: 'git',
- scm_revision: '',
- summary_fields: {
- last_job: {
- id: 9000,
- status: 'successful',
- },
- user_capabilities: {
- edit: true,
- },
- },
- }}
- />
-
-
- );
- expect(wrapper.find('ClipboardCopy').length).toBe(0);
- expect(wrapper.find('td[data-label="Revision"]').text()).toBe(
- 'Sync for revision'
- );
- });
- test('should render the clipboard copy with the right text when scm revision available', () => {
- const wrapper = mountWithContexts(
-
-
- {}}
- project={{
- id: 1,
- name: 'Project 1',
- url: '/api/v2/projects/1',
- type: 'project',
- scm_type: 'git',
- scm_revision: 'osofej904r09a9sf0udfsajogsdfbh4e23489adf',
- summary_fields: {
- last_job: {
- id: 9000,
- status: 'successful',
- },
- user_capabilities: {
- edit: true,
- },
- },
- }}
- />
-
-
- );
- expect(wrapper.find('ClipboardCopy').length).toBe(1);
- expect(wrapper.find('ClipboardCopy').text()).toBe('osofej9');
- });
- test('should indicate that the revision needs to be refreshed when project sync is done', () => {
- const wrapper = mountWithContexts(
-
-
- {}}
- project={{
- id: 1,
- name: 'Project 1',
- url: '/api/v2/projects/1',
- type: 'project',
- scm_type: 'git',
- scm_revision: null,
- summary_fields: {
- current_job: {
- id: 9001,
- status: 'successful',
- finished: '2021-06-01T18:43:53.332201Z',
- },
- last_job: {
- id: 9000,
- status: 'successful',
- },
- user_capabilities: {
- edit: true,
- },
- },
- }}
- />
-
-
- );
- expect(wrapper.find('ClipboardCopy').length).toBe(0);
- expect(wrapper.find('td[data-label="Revision"]').text()).toBe(
- 'Refresh for revision'
- );
- expect(wrapper.find('UndoIcon').length).toBe(1);
- });
- test('should render expected details in expanded section', async () => {
- const wrapper = mountWithContexts(
-
-
- {}}
- project={{
- id: 1,
- name: 'Project 1',
- description: 'Project 1 description',
- url: '/api/v2/projects/1',
- type: 'project',
- scm_type: 'git',
- scm_revision: '123456789',
- summary_fields: {
- organization: {
- id: 999,
- description: '',
- name: 'Mock org',
- },
- last_job: {
- id: 9000,
- status: 'successful',
- },
- user_capabilities: {
- start: true,
- },
- default_environment: {
- id: 123,
- name: 'Mock EE',
- image: 'mock.image',
- },
- },
- custom_virtualenv: '/var/lib/awx/env',
- default_environment: 123,
- organization: 999,
- }}
- />
-
-
- );
-
- expect(wrapper.find('Tr').last().prop('isExpanded')).toBe(true);
-
- function assertDetail(label, value) {
- expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
- expect(wrapper.find(`Detail[label="${label}"] dd`).text()).toBe(value);
- }
- assertDetail('Description', 'Project 1 description');
- assertDetail('Organization', 'Mock org');
- assertDetail('Default Execution Environment', 'Mock EE');
- expect(wrapper.find('Detail[label="Last modified"]').length).toBe(1);
- expect(wrapper.find('Detail[label="Last used"]').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Project/ProjectList/index.js b/awx/ui/src/screens/Project/ProjectList/index.js
deleted file mode 100644
index 8c7a942a72e6..000000000000
--- a/awx/ui/src/screens/Project/ProjectList/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as ProjectList } from './ProjectList';
-export { default as ProjectListItem } from './ProjectListItem';
diff --git a/awx/ui/src/screens/Project/ProjectList/useWsProjects.js b/awx/ui/src/screens/Project/ProjectList/useWsProjects.js
deleted file mode 100644
index d8dd2d032720..000000000000
--- a/awx/ui/src/screens/Project/ProjectList/useWsProjects.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import { useState, useEffect } from 'react';
-import useWebsocket from 'hooks/useWebsocket';
-
-export default function useWsProjects(initialProjects) {
- const [projects, setProjects] = useState(initialProjects);
- const lastMessage = useWebsocket({
- jobs: ['status_changed'],
- control: ['limit_reached_1'],
- });
-
- useEffect(() => {
- setProjects(initialProjects);
- }, [initialProjects]);
-
- useEffect(() => {
- if (!lastMessage?.unified_job_id || lastMessage.type !== 'project_update') {
- return;
- }
- const index = projects.findIndex((p) => p.id === lastMessage.project_id);
- if (index === -1) {
- return;
- }
-
- const project = projects[index];
- const updatedProject = {
- ...project,
- summary_fields: {
- ...project.summary_fields,
- current_job: {
- id: lastMessage.unified_job_id,
- status: lastMessage.status,
- finished: lastMessage.finished,
- },
- },
- };
-
- if (lastMessage.finished) {
- updatedProject.scm_revision = null;
- }
-
- setProjects([
- ...projects.slice(0, index),
- updatedProject,
- ...projects.slice(index + 1),
- ]);
- }, [lastMessage]); // eslint-disable-line react-hooks/exhaustive-deps
-
- return projects;
-}
diff --git a/awx/ui/src/screens/Project/ProjectList/useWsProjects.test.js b/awx/ui/src/screens/Project/ProjectList/useWsProjects.test.js
deleted file mode 100644
index 426de6842908..000000000000
--- a/awx/ui/src/screens/Project/ProjectList/useWsProjects.test.js
+++ /dev/null
@@ -1,115 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import WS from 'jest-websocket-mock';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import useWsProjects from './useWsProjects';
-
-function TestInner() {
- return ;
-}
-function Test({ projects }) {
- const synced = useWsProjects(projects);
- return ;
-}
-
-describe('useWsProjects', () => {
- let debug;
- let wrapper;
- beforeEach(() => {
- debug = global.console.debug; // eslint-disable-line prefer-destructuring
- global.console.debug = () => {};
- });
-
- afterEach(() => {
- global.console.debug = debug;
- });
-
- test('should return projects list', async () => {
- const projects = [{ id: 1 }];
- await act(async () => {
- wrapper = await mountWithContexts();
- });
-
- expect(wrapper.find('TestInner').prop('projects')).toEqual(projects);
- WS.clean();
- });
-
- test('should establish websocket connection', async () => {
- global.document.cookie = 'csrftoken=abc123';
- const mockServer = new WS('ws://localhost/websocket/');
-
- const projects = [{ id: 1 }];
- await act(async () => {
- wrapper = await mountWithContexts();
- });
-
- await mockServer.connected;
- await expect(mockServer).toReceiveMessage(
- JSON.stringify({
- xrftoken: 'abc123',
- groups: {
- jobs: ['status_changed'],
- control: ['limit_reached_1'],
- },
- })
- );
- WS.clean();
- });
-
- test('should update project status', async () => {
- global.document.cookie = 'csrftoken=abc123';
- const mockServer = new WS('ws://localhost/websocket/');
-
- const projects = [
- {
- id: 1,
- summary_fields: {
- current_job: {
- id: 1,
- status: 'running',
- finished: null,
- },
- },
- },
- ];
- await act(async () => {
- wrapper = await mountWithContexts();
- });
-
- await mockServer.connected;
- await expect(mockServer).toReceiveMessage(
- JSON.stringify({
- xrftoken: 'abc123',
- groups: {
- jobs: ['status_changed'],
- control: ['limit_reached_1'],
- },
- })
- );
- expect(
- wrapper.find('TestInner').prop('projects')[0].summary_fields.current_job
- .status
- ).toEqual('running');
- await act(async () => {
- mockServer.send(
- JSON.stringify({
- project_id: 1,
- unified_job_id: 12,
- type: 'project_update',
- status: 'successful',
- finished: '2020-07-02T16:28:31.839071Z',
- })
- );
- });
- wrapper.update();
-
- expect(
- wrapper.find('TestInner').prop('projects')[0].summary_fields.current_job
- ).toEqual({
- id: 12,
- status: 'successful',
- finished: '2020-07-02T16:28:31.839071Z',
- });
- WS.clean();
- });
-});
diff --git a/awx/ui/src/screens/Project/Projects.js b/awx/ui/src/screens/Project/Projects.js
deleted file mode 100644
index 919da3d9f5d1..000000000000
--- a/awx/ui/src/screens/Project/Projects.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import React, { useState, useCallback } from 'react';
-import { Route, withRouter, Switch } from 'react-router-dom';
-import { t } from '@lingui/macro';
-import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
-import PersistentFilters from 'components/PersistentFilters';
-import ProjectsList from './ProjectList/ProjectList';
-import ProjectAdd from './ProjectAdd/ProjectAdd';
-import Project from './Project';
-
-function Projects() {
- const [breadcrumbConfig, setBreadcrumbConfig] = useState({
- '/projects': t`Projects`,
- '/projects/add': t`Create New Project`,
- });
-
- const buildBreadcrumbConfig = useCallback((project, nested) => {
- if (!project) {
- return;
- }
- const projectSchedulesPath = `/projects/${project.id}/schedules`;
- setBreadcrumbConfig({
- '/projects': t`Projects`,
- '/projects/add': t`Create New Project`,
- [`/projects/${project.id}`]: `${project.name}`,
- [`/projects/${project.id}/edit`]: t`Edit Details`,
- [`/projects/${project.id}/details`]: t`Details`,
- [`/projects/${project.id}/access`]: t`Access`,
- [`/projects/${project.id}/notifications`]: t`Notifications`,
- [`/projects/${project.id}/job_templates`]: t`Job Templates`,
-
- [`${projectSchedulesPath}`]: t`Schedules`,
- [`${projectSchedulesPath}/add`]: t`Create New Schedule`,
- [`${projectSchedulesPath}/${nested?.id}`]: `${nested?.name}`,
- [`${projectSchedulesPath}/${nested?.id}/details`]: t`Schedule Details`,
- [`${projectSchedulesPath}/${nested?.id}/edit`]: t`Edit Details`,
- });
- }, []);
-
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-}
-
-export { Projects as _Projects };
-export default withRouter(Projects);
diff --git a/awx/ui/src/screens/Project/Projects.test.js b/awx/ui/src/screens/Project/Projects.test.js
deleted file mode 100644
index b7e734700acf..000000000000
--- a/awx/ui/src/screens/Project/Projects.test.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import { _Projects as Projects } from './Projects';
-
-describe('', () => {
- test('should display a breadcrumb heading', () => {
- const wrapper = shallow();
-
- const header = wrapper.find('ScreenHeader');
- expect(header.prop('streamType')).toBe('project');
- expect(header.prop('breadcrumbConfig')).toEqual({
- '/projects': 'Projects',
- '/projects/add': 'Create New Project',
- });
- });
-});
diff --git a/awx/ui/src/screens/Project/data.project.json b/awx/ui/src/screens/Project/data.project.json
deleted file mode 100644
index 9f58e5243b39..000000000000
--- a/awx/ui/src/screens/Project/data.project.json
+++ /dev/null
@@ -1,123 +0,0 @@
-{
- "id": 6,
- "type": "project",
- "url": "/api/v2/projects/6/",
- "related": {
- "named_url": "/api/v2/projects/Mike's Project++Default/",
- "created_by": "/api/v2/users/1/",
- "modified_by": "/api/v2/users/1/",
- "last_job": "/api/v2/project_updates/8/",
- "teams": "/api/v2/projects/6/teams/",
- "playbooks": "/api/v2/projects/6/playbooks/",
- "inventory_files": "/api/v2/projects/6/inventories/",
- "update": "/api/v2/projects/6/update/",
- "project_updates": "/api/v2/projects/6/project_updates/",
- "scm_inventory_sources": "/api/v2/projects/6/scm_inventory_sources/",
- "schedules": "/api/v2/projects/6/schedules/",
- "activity_stream": "/api/v2/projects/6/activity_stream/",
- "notification_templates_started": "/api/v2/projects/6/notification_templates_started/",
- "notification_templates_success": "/api/v2/projects/6/notification_templates_success/",
- "notification_templates_error": "/api/v2/projects/6/notification_templates_error/",
- "access_list": "/api/v2/projects/6/access_list/",
- "object_roles": "/api/v2/projects/6/object_roles/",
- "copy": "/api/v2/projects/6/copy/",
- "organization": "/api/v2/organizations/1/",
- "last_update": "/api/v2/project_updates/8/"
- },
- "summary_fields": {
- "organization": {
- "id": 1,
- "name": "Default",
- "description": ""
- },
- "execution_environment": {
- "id": 1,
- "name": "Default EE",
- "description": "",
- "image": "quay.io/ansible/awx-ee"
- },
- "last_job": {
- "id": 8,
- "name": "Mike's Project",
- "description": "",
- "finished": "2019-09-30T18:06:34.713654Z",
- "status": "successful",
- "failed": false
- },
- "last_update": {
- "id": 8,
- "name": "Mike's Project",
- "description": "",
- "status": "successful",
- "failed": false
- },
- "created_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "modified_by": {
- "id": 1,
- "username": "admin",
- "first_name": "",
- "last_name": ""
- },
- "object_roles": {
- "admin_role": {
- "description": "Can manage all aspects of the project",
- "name": "Admin",
- "id": 20
- },
- "use_role": {
- "description": "Can use the project in a job template",
- "name": "Use",
- "id": 21
- },
- "update_role": {
- "description": "May update the project",
- "name": "Update",
- "id": 22
- },
- "read_role": {
- "description": "May view settings for the project",
- "name": "Read",
- "id": 23
- }
- },
- "user_capabilities": {
- "edit": true,
- "delete": true,
- "start": true,
- "schedule": true,
- "copy": true
- }
- },
- "created": "2019-09-30T16:17:37.956673Z",
- "modified": "2019-09-30T16:17:37.956705Z",
- "name": "Mike's Project",
- "description": "",
- "local_path": "_6__mikes_project",
- "scm_type": "git",
- "scm_url": "https://github.com/ansible/test-playbooks",
- "scm_branch": "",
- "scm_refspec": "",
- "scm_clean": false,
- "scm_delete_on_update": false,
- "scm_track_submodules": false,
- "credential": null,
- "timeout": 0,
- "scm_revision": "f5de82382e756b87143f3511c7c6c006d941830d",
- "last_job_run": "2019-09-30T18:06:34.713654Z",
- "last_job_failed": false,
- "next_job_run": null,
- "status": "successful",
- "organization": 1,
- "scm_update_on_launch": false,
- "scm_update_cache_timeout": 0,
- "allow_override": false,
- "custom_virtualenv": null,
- "last_update_failed": false,
- "last_updated": "2019-09-30T18:06:34.713654Z",
- "execution_environment": 1
-}
diff --git a/awx/ui/src/screens/Project/index.js b/awx/ui/src/screens/Project/index.js
deleted file mode 100644
index 3b68dc8cf086..000000000000
--- a/awx/ui/src/screens/Project/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './Projects';
diff --git a/awx/ui/src/screens/Project/shared/Project.helptext.js b/awx/ui/src/screens/Project/shared/Project.helptext.js
deleted file mode 100644
index 436d13f14fb0..000000000000
--- a/awx/ui/src/screens/Project/shared/Project.helptext.js
+++ /dev/null
@@ -1,137 +0,0 @@
-import React from 'react';
-import { t } from '@lingui/macro';
-
-const projectHelpTextStrings = () => ({
- executionEnvironment: t`The execution environment that will be used for jobs that use this project. This will be used as fallback when an execution environment has not been explicitly assigned at the job template or workflow level.`,
- projectBasePath: (brandName = '') => (
-
- {t`Base path used for locating playbooks. Directories
- found inside this path will be listed in the playbook directory drop-down.
- Together the base path and selected playbook directory provide the full
- path used to locate playbooks.`}
-
-
- {t`Change PROJECTS_ROOT when deploying
- ${brandName} to change this location.`}
-
- ),
- projectLocalPath: t`Select from the list of directories found in
- the Project Base Path. Together the base path and the playbook
- directory provide the full path used to locate playbooks.`,
- githubSourceControlUrl: (
-
- {t`Example URLs for GIT Source Control include:`}
-
- -
-
https://github.com/ansible/ansible.git
-
- -
-
git@github.com:ansible/ansible.git
-
- -
-
git://servername.example.com/ansible.git
-
-
- {t`Note: When using SSH protocol for GitHub or
- Bitbucket, enter an SSH key only, do not enter a username
- (other than git). Additionally, GitHub and Bitbucket do
- not support password authentication when using SSH. GIT
- read only protocol (git://) does not use username or
- password information.`}
-
- ),
- svnSourceControlUrl: (
-
- {t`Example URLs for Subversion Source Control include:`}
-
- -
-
https://github.com/ansible/ansible
-
- -
-
svn://servername.example.com/path
-
- -
-
svn+ssh://servername.example.com/path
-
-
-
- ),
- syncButtonDisabled: t`This project is currently on sync and cannot be clicked until sync process completed`,
- archiveUrl: (
-
- {t`Example URLs for Remote Archive Source Control include:`}
-
- -
-
https://github.com/username/project/archive/v0.0.1.tar.gz
-
- -
-
https://github.com/username/project/archive/v0.0.2.zip
-
-
-
- ),
-
- sourceControlRefspec: (url = '') => (
-
- {t`A refspec to fetch (passed to the Ansible git
- module). This parameter allows access to references via
- the branch field not otherwise available.`}
-
-
- {t`Note: This field assumes the remote name is "origin".`}
-
-
- {t`Examples include:`}
-
- -
-
refs/*:refs/remotes/origin/*
-
- -
-
refs/pull/62/head:refs/remotes/origin/pull/62/head
-
-
- {t`The first fetches all references. The second
- fetches the Github pull request number 62, in this example
- the branch needs to be "pull/62/head".`}
-
-
- {t`For more information, refer to the`}{' '}
-
- {t`Documentation.`}
-
-
- ),
- branchFormField: t`Branch to checkout. In addition to branches,
- you can input tags, commit hashes, and arbitrary refs. Some
- commit hashes and refs may not be available unless you also
- provide a custom refspec.`,
- signatureValidation: t`Enable content signing to verify that the content
- has remained secure when a project is synced.
- If the content has been tampered with, the
- job will not run.`,
- options: {
- clean: t`Remove any local modifications prior to performing an update.`,
- delete: t`Delete the local repository in its entirety prior to
- performing an update. Depending on the size of the
- repository this may significantly increase the amount
- of time required to complete an update.`,
- trackSubModules: t`Submodules will track the latest commit on
- their master branch (or other branch specified in
- .gitmodules). If no, submodules will be kept at
- the revision specified by the main project.
- This is equivalent to specifying the --remote
- flag to git submodule update.`,
- updateOnLaunch: t`Each time a job runs using this project, update the
- revision of the project prior to starting the job.`,
- allowBranchOverride: t`Allow changing the Source Control branch or revision in a job
- template that uses this project.`,
- cacheTimeout: t`Time in seconds to consider a project
- to be current. During job runs and callbacks the task
- system will evaluate the timestamp of the latest project
- update. If it is older than Cache Timeout, it is not
- considered current, and a new project update will be
- performed.`,
- },
-});
-
-export default projectHelpTextStrings;
diff --git a/awx/ui/src/screens/Project/shared/ProjectForm.js b/awx/ui/src/screens/Project/shared/ProjectForm.js
deleted file mode 100644
index 88d8732eabf0..000000000000
--- a/awx/ui/src/screens/Project/shared/ProjectForm.js
+++ /dev/null
@@ -1,497 +0,0 @@
-/* eslint no-nested-ternary: 0 */
-import React, { useCallback, useState, useEffect } from 'react';
-import PropTypes from 'prop-types';
-
-import { t } from '@lingui/macro';
-import { Formik, useField, useFormikContext } from 'formik';
-import { Form, FormGroup, Title } from '@patternfly/react-core';
-import { useConfig } from 'contexts/Config';
-import AnsibleSelect from 'components/AnsibleSelect';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import CredentialLookup from 'components/Lookup/CredentialLookup';
-import FormActionGroup from 'components/FormActionGroup/FormActionGroup';
-import FormField, { FormSubmitError } from 'components/FormField';
-import OrganizationLookup from 'components/Lookup/OrganizationLookup';
-import ExecutionEnvironmentLookup from 'components/Lookup/ExecutionEnvironmentLookup';
-import { CredentialTypesAPI, ProjectsAPI } from 'api';
-import { required } from 'util/validators';
-import { FormColumnLayout, SubFormLayout } from 'components/FormLayout';
-import getProjectHelpText from './Project.helptext';
-import {
- GitSubForm,
- SvnSubForm,
- ArchiveSubForm,
- InsightsSubForm,
- ManualSubForm,
-} from './ProjectSubForms';
-
-const fetchCredentials = async (credential) => {
- const [
- {
- data: {
- results: [scmCredentialType],
- },
- },
- {
- data: {
- results: [insightsCredentialType],
- },
- },
- {
- data: {
- results: [cryptographyCredentialType],
- },
- },
- ] = await Promise.all([
- CredentialTypesAPI.read({ kind: 'scm' }),
- CredentialTypesAPI.read({ name: 'Insights' }),
- CredentialTypesAPI.read({ kind: 'cryptography' }),
- ]);
-
- if (!credential) {
- return {
- scm: { typeId: scmCredentialType.id },
- insights: { typeId: insightsCredentialType.id },
- cryptography: { typeId: cryptographyCredentialType.id },
- };
- }
-
- const { credential_type_id } = credential;
- return {
- scm: {
- typeId: scmCredentialType.id,
- value: credential_type_id === scmCredentialType.id ? credential : null,
- },
- insights: {
- typeId: insightsCredentialType.id,
- value:
- credential_type_id === insightsCredentialType.id ? credential : null,
- },
- cryptography: {
- typeId: cryptographyCredentialType.id,
- value:
- credential_type_id === cryptographyCredentialType.id
- ? credential
- : null,
- },
- };
-};
-
-function ProjectFormFields({
- project,
- project_base_dir,
- project_local_paths,
- formik,
- setCredentials,
- setSignatureValidationCredentials,
- credentials,
- signatureValidationCredentials,
- scmTypeOptions,
- setScmSubFormState,
- scmSubFormState,
-}) {
- const projectHelpText = getProjectHelpText();
- const scmFormFields = {
- scm_url: '',
- scm_branch: '',
- scm_refspec: '',
- credential: '',
- signature_validation_credential: '',
- scm_clean: false,
- scm_delete_on_update: false,
- scm_track_submodules: false,
- scm_update_on_launch: false,
- allow_override: false,
- scm_update_cache_timeout: 0,
- };
- const { setFieldValue, setFieldTouched } = useFormikContext();
-
- const [scmTypeField, scmTypeMeta, scmTypeHelpers] = useField({
- name: 'scm_type',
- validate: required(t`Set a value for this field`),
- });
- const [organizationField, organizationMeta, organizationHelpers] =
- useField('organization');
-
- const [
- executionEnvironmentField,
- executionEnvironmentMeta,
- executionEnvironmentHelpers,
- ] = useField('default_environment');
-
- /* Save current scm subform field values to state */
- const saveSubFormState = (form) => {
- const currentScmFormFields = { ...scmFormFields };
-
- Object.keys(currentScmFormFields).forEach((label) => {
- currentScmFormFields[label] = form.values[label];
- });
-
- setScmSubFormState(currentScmFormFields);
- };
-
- /**
- * If scm type is !== the initial scm type value,
- * reset scm subform field values to defaults.
- * If scm type is === the initial scm type value,
- * reset scm subform field values to scmSubFormState.
- */
- const resetScmTypeFields = (value, form) => {
- if (form.values.scm_type === form.initialValues.scm_type) {
- saveSubFormState(formik);
- }
-
- Object.keys(scmFormFields).forEach((label) => {
- if (value === form.initialValues.scm_type) {
- form.setFieldValue(label, scmSubFormState[label]);
- } else {
- form.setFieldValue(label, scmFormFields[label]);
- }
- form.setFieldTouched(label, false);
- });
- };
-
- const handleCredentialSelection = useCallback(
- (type, value) => {
- setCredentials({
- ...credentials,
- [type]: {
- ...credentials[type],
- value,
- },
- });
- },
- [credentials, setCredentials]
- );
-
- const handleSignatureValidationCredentialSelection = useCallback(
- (type, value) => {
- setSignatureValidationCredentials({
- ...signatureValidationCredentials,
- [type]: {
- ...signatureValidationCredentials[type],
- value,
- },
- });
- },
- [signatureValidationCredentials, setSignatureValidationCredentials]
- );
-
- const handleSignatureValidationCredentialChange = useCallback(
- (value) => {
- handleSignatureValidationCredentialSelection('cryptography', value);
- setFieldValue('signature_validation_credential', value);
- setFieldTouched('signature_validation_credential', true, false);
- },
- [
- handleSignatureValidationCredentialSelection,
- setFieldValue,
- setFieldTouched,
- ]
- );
-
- const handleOrganizationUpdate = useCallback(
- (value) => {
- setFieldValue('organization', value);
- setFieldTouched('organization', true, false);
- },
- [setFieldValue, setFieldTouched]
- );
-
- const handleExecutionEnvironmentUpdate = useCallback(
- (value) => {
- setFieldValue('default_environment', value);
- setFieldTouched('default_environment', true, false);
- },
- [setFieldValue, setFieldTouched]
- );
-
- return (
- <>
-
-
- organizationHelpers.setTouched()}
- onChange={handleOrganizationUpdate}
- value={organizationField.value}
- required
- autoPopulate={!project?.id}
- validate={required(t`Select a value for this field`)}
- />
- executionEnvironmentHelpers.setTouched()}
- value={executionEnvironmentField.value}
- popoverContent={projectHelpText.executionEnvironment}
- onChange={handleExecutionEnvironmentUpdate}
- tooltip={t`Select an organization before editing the default execution environment.`}
- globallyAvailable
- isDisabled={!organizationField.value}
- organizationId={organizationField.value?.id}
- isDefaultEnvironment
- fieldName="default_environment"
- />
-
- {
- if (value === '') {
- value = 'manual';
- }
- return {
- label,
- value,
- key: value,
- };
- }),
- ]}
- onChange={(event, value) => {
- scmTypeHelpers.setValue(value);
- resetScmTypeFields(value, formik);
- }}
- />
-
-
- {formik.values.scm_type !== '' && (
-
-
- {t`Type Details`}
-
-
- {
- {
- manual: (
-
- ),
- git: (
-
- ),
- svn: (
-
- ),
- archive: (
-
- ),
- insights: (
-
- ),
- }[formik.values.scm_type]
- }
-
-
- )}
- >
- );
-}
-function ProjectForm({ project, submitError, ...props }) {
- const { handleCancel, handleSubmit } = props;
- const { summary_fields = {} } = project;
- const { project_base_dir, project_local_paths } = useConfig();
- const [contentError, setContentError] = useState(null);
- const [isLoading, setIsLoading] = useState(true);
- const [scmSubFormState, setScmSubFormState] = useState({
- scm_url: '',
- scm_branch: '',
- scm_refspec: '',
- credential: '',
- signature_validation_credential: '',
- scm_clean: false,
- scm_delete_on_update: false,
- scm_track_submodules: false,
- scm_update_on_launch: false,
- allow_override: false,
- scm_update_cache_timeout: 0,
- });
- const [scmTypeOptions, setScmTypeOptions] = useState(null);
- const [credentials, setCredentials] = useState({
- scm: { typeId: null, value: null },
- insights: { typeId: null, value: null },
- cryptography: { typeId: null, value: null },
- });
- const [signatureValidationCredentials, setSignatureValidationCredentials] =
- useState({
- scm: { typeId: null, value: null },
- insights: { typeId: null, value: null },
- cryptography: { typeId: null, value: null },
- });
-
- useEffect(() => {
- async function fetchData() {
- try {
- const credentialResponse = fetchCredentials(summary_fields.credential);
- const signatureValidationCredentialResponse = fetchCredentials(
- summary_fields.signature_validation_credential
- );
- const {
- data: {
- actions: {
- GET: {
- scm_type: { choices },
- },
- },
- },
- } = await ProjectsAPI.readOptions();
-
- setCredentials(await credentialResponse);
- setSignatureValidationCredentials(
- await signatureValidationCredentialResponse
- );
- setScmTypeOptions(choices);
- } catch (error) {
- setContentError(error);
- } finally {
- setIsLoading(false);
- }
- }
-
- fetchData();
- }, [
- summary_fields.credential,
- summary_fields.signature_validation_credential,
- ]);
-
- if (isLoading) {
- return ;
- }
-
- if (contentError) {
- return ;
- }
-
- return (
-
- {(formik) => (
-
- )}
-
- );
-}
-
-ProjectForm.propTypes = {
- handleCancel: PropTypes.func.isRequired,
- handleSubmit: PropTypes.func.isRequired,
- project: PropTypes.shape({}),
- submitError: PropTypes.shape({}),
-};
-
-ProjectForm.defaultProps = {
- project: {},
- submitError: null,
-};
-
-export default ProjectForm;
diff --git a/awx/ui/src/screens/Project/shared/ProjectForm.test.js b/awx/ui/src/screens/Project/shared/ProjectForm.test.js
deleted file mode 100644
index d75047be4f3d..000000000000
--- a/awx/ui/src/screens/Project/shared/ProjectForm.test.js
+++ /dev/null
@@ -1,444 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { CredentialTypesAPI, ProjectsAPI, RootAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import ProjectForm from './ProjectForm';
-
-jest.mock('../../../api');
-
-describe('', () => {
- let wrapper;
- const mockData = {
- name: 'foo',
- description: 'bar',
- scm_type: 'git',
- scm_url: 'https://foo.bar',
- scm_clean: true,
- scm_track_submodules: false,
- credential: 100,
- signature_validation_credential: 200,
- organization: 2,
- scm_update_on_launch: true,
- scm_update_cache_timeout: 3,
- allow_override: false,
- custom_virtualenv: '/var/lib/awx/venv/custom-env',
- summary_fields: {
- credential: {
- id: 100,
- credential_type_id: 4,
- kind: 'scm',
- name: 'Foo',
- },
- organization: {
- id: 2,
- name: 'Default',
- },
- signature_validation_credential: {
- id: 200,
- credential_type_id: 6,
- kind: 'cryptography',
- name: 'Svc',
- },
- },
- };
-
- const projectOptionsResolve = {
- data: {
- actions: {
- GET: {
- scm_type: {
- choices: [
- ['', 'Manual'],
- ['git', 'Git'],
- ['svn', 'Subversion'],
- ['archive', 'Remote Archive'],
- ['insights', 'Red Hat Insights'],
- ],
- },
- },
- },
- },
- };
-
- const scmCredentialResolve = {
- data: {
- count: 1,
- results: [
- {
- id: 4,
- name: 'Source Control',
- kind: 'scm',
- },
- ],
- },
- };
-
- const insightsCredentialResolve = {
- data: {
- count: 1,
- results: [
- {
- id: 5,
- name: 'Insights',
- kind: 'insights',
- },
- ],
- },
- };
-
- const cryptographyCredentialResolve = {
- data: {
- count: 1,
- results: [
- {
- id: 6,
- name: 'GPG Public Key',
- kind: 'cryptography',
- },
- ],
- },
- };
-
- beforeEach(async () => {
- RootAPI.readAssetVariables.mockResolvedValue({
- data: {
- BRAND_NAME: 'AWX',
- },
- });
- await ProjectsAPI.readOptions.mockImplementation(
- () => projectOptionsResolve
- );
- await CredentialTypesAPI.read.mockImplementation(
- () => scmCredentialResolve
- );
- await CredentialTypesAPI.read.mockImplementation(
- () => insightsCredentialResolve
- );
- await CredentialTypesAPI.read.mockImplementation(
- () => cryptographyCredentialResolve
- );
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('initially renders successfully', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
-
- expect(wrapper.find('ProjectForm').length).toBe(1);
- });
-
- test('new form displays primary form fields', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('FormGroup[label="Name"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Description"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Organization"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Source Control Type"]').length).toBe(
- 1
- );
- expect(wrapper.find('FormGroup[label="Ansible Environment"]').length).toBe(
- 0
- );
- expect(wrapper.find('FormGroup[label="Options"]').length).toBe(0);
- });
-
- test('should display scm subform when scm type select has a value', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- await act(async () => {
- await wrapper.find('AnsibleSelect[id="scm_type"]').invoke('onChange')(
- null,
- 'git'
- );
- });
- wrapper.update();
- expect(wrapper.find('FormGroup[label="Source Control URL"]').length).toBe(
- 1
- );
- expect(
- wrapper.find('FormGroup[label="Source Control Branch/Tag/Commit"]').length
- ).toBe(1);
- expect(
- wrapper.find('FormGroup[label="Source Control Refspec"]').length
- ).toBe(1);
- expect(
- wrapper.find('FormGroup[label="Content Signature Validation Credential"]')
- .length
- ).toBe(1);
- expect(
- wrapper.find('FormGroup[label="Source Control Credential"]').length
- ).toBe(1);
- expect(
- wrapper.find('FormGroup[label="Content Signature Validation Credential"]')
- .length
- ).toBe(1);
- expect(wrapper.find('FormGroup[label="Options"]').length).toBe(1);
- });
-
- test('inputs should update form value on change', async () => {
- const project = { ...mockData };
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- await act(async () => {
- wrapper.find('OrganizationLookup').invoke('onBlur')();
- wrapper.find('OrganizationLookup').invoke('onChange')({
- id: 1,
- name: 'organization',
- });
- wrapper
- .find('CredentialLookup[label="Source Control Credential"]')
- .invoke('onBlur')();
- wrapper
- .find('CredentialLookup[label="Source Control Credential"]')
- .invoke('onChange')({
- id: 10,
- name: 'credential',
- });
- wrapper
- .find(
- 'CredentialLookup[label="Content Signature Validation Credential"]'
- )
- .invoke('onBlur')();
- wrapper
- .find(
- 'CredentialLookup[label="Content Signature Validation Credential"]'
- )
- .invoke('onChange')({
- id: 20,
- name: 'signature_validation_credential',
- });
- });
- wrapper.update();
- expect(wrapper.find('OrganizationLookup').prop('value')).toEqual({
- id: 1,
- name: 'organization',
- });
- expect(
- wrapper
- .find('CredentialLookup[label="Source Control Credential"]')
- .prop('value')
- ).toEqual({
- id: 10,
- name: 'credential',
- });
- expect(
- wrapper
- .find(
- 'CredentialLookup[label="Content Signature Validation Credential"]'
- )
- .prop('value')
- ).toEqual({
- id: 20,
- name: 'signature_validation_credential',
- });
- });
-
- test('should display insights credential lookup when source control type is "insights"', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- await act(async () => {
- await wrapper.find('AnsibleSelect[id="scm_type"]').invoke('onChange')(
- null,
- 'insights'
- );
- });
- wrapper.update();
- expect(wrapper.find('FormGroup[label="Insights Credential"]').length).toBe(
- 1
- );
- await act(async () => {
- wrapper
- .find('CredentialLookup[label="Insights Credential"]')
- .invoke('onBlur')();
- wrapper
- .find('CredentialLookup[label="Insights Credential"]')
- .invoke('onChange')({
- id: 123,
- name: 'credential',
- });
- });
- wrapper.update();
- expect(
- wrapper
- .find('CredentialLookup[label="Insights Credential"]')
- .prop('value')
- ).toEqual({
- id: 123,
- name: 'credential',
- });
- });
-
- test('manual subform should display expected fields', async () => {
- const config = {
- project_local_paths: ['foobar', 'qux'],
- project_base_dir: 'dir/foo/bar',
- };
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { config },
- }
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- const playbookDirectorySelect = wrapper.find(
- 'FormGroup[label="Playbook Directory"] FormSelect'
- );
- await act(async () => {
- playbookDirectorySelect
- .props()
- .onChange('foobar', { target: { name: 'foobar' } });
- });
- expect(wrapper.find('FormGroup[label="Project Base Path"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Playbook Directory"]').length).toBe(
- 1
- );
- });
-
- test('manual subform should display warning message when playbook directory is empty', async () => {
- const config = {
- project_local_paths: [],
- project_base_dir: 'dir/foo/bar',
- };
- await act(async () => {
- wrapper = mountWithContexts(
- ,
- {
- context: { config },
- }
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('ManualSubForm Alert').length).toBe(1);
- });
-
- test('should reset source control subform values when source control type changes', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
-
- const scmTypeSelect = wrapper.find(
- 'FormGroup[label="Source Control Type"] FormSelect'
- );
- await act(async () => {
- scmTypeSelect.invoke('onChange')('svn', {
- target: { name: 'Subversion' },
- });
- });
- wrapper.update();
- await act(async () => {
- wrapper
- .find('FormGroup[label="Source Control URL"] input')
- .simulate('change', {
- target: { value: 'baz', name: 'scm_url' },
- });
- });
- wrapper.update();
- expect(wrapper.find('input#project-scm-url').prop('value')).toEqual('baz');
- await act(async () => {
- scmTypeSelect
- .props()
- .onChange('insights', { target: { name: 'insights' } });
- });
- wrapper.update();
- await act(async () => {
- scmTypeSelect.props().onChange('svn', { target: { name: 'Subversion' } });
- });
- wrapper.update();
- expect(wrapper.find('input#project-scm-url').prop('value')).toEqual('');
- });
-
- test('should call handleSubmit when Submit button is clicked', async () => {
- const handleSubmit = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(handleSubmit).not.toHaveBeenCalled();
- await act(async () => {
- wrapper.find('button[aria-label="Save"]').simulate('click');
- });
- expect(handleSubmit).toBeCalled();
- });
-
- test('should call handleCancel when Cancel button is clicked', async () => {
- const handleCancel = jest.fn();
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(handleCancel).not.toHaveBeenCalled();
- wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
- expect(handleCancel).toBeCalled();
- });
-
- test('should display ContentError on throw', async () => {
- CredentialTypesAPI.read.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('ContentError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Project/shared/ProjectSubForms/ArchiveSubForm.js b/awx/ui/src/screens/Project/shared/ProjectSubForms/ArchiveSubForm.js
deleted file mode 100644
index 4bc35817d254..000000000000
--- a/awx/ui/src/screens/Project/shared/ProjectSubForms/ArchiveSubForm.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import 'styled-components/macro';
-import React from 'react';
-import getProjectHelpText from '../Project.helptext';
-
-import {
- UrlFormField,
- ScmCredentialFormField,
- ScmTypeOptions,
-} from './SharedFields';
-
-const ArchiveSubForm = ({
- credential,
- onCredentialSelection,
- scmUpdateOnLaunch,
-}) => {
- const projectHelpText = getProjectHelpText();
- return (
- <>
-
-
-
- >
- );
-};
-
-export default ArchiveSubForm;
diff --git a/awx/ui/src/screens/Project/shared/ProjectSubForms/GitSubForm.js b/awx/ui/src/screens/Project/shared/ProjectSubForms/GitSubForm.js
deleted file mode 100644
index 7a41b080901a..000000000000
--- a/awx/ui/src/screens/Project/shared/ProjectSubForms/GitSubForm.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import 'styled-components/macro';
-import React from 'react';
-import { t } from '@lingui/macro';
-import FormField from 'components/FormField';
-import getDocsBaseUrl from 'util/getDocsBaseUrl';
-import { useConfig } from 'contexts/Config';
-
-import {
- UrlFormField,
- BranchFormField,
- ScmCredentialFormField,
- ScmTypeOptions,
-} from './SharedFields';
-import getProjectHelpStrings from '../Project.helptext';
-
-const GitSubForm = ({
- credential,
- onCredentialSelection,
- scmUpdateOnLaunch,
-}) => {
- const docsURL = `${getDocsBaseUrl(
- useConfig()
- )}/html/userguide/projects.html#manage-playbooks-using-source-control`;
- const projectHelpStrings = getProjectHelpStrings();
-
- return (
- <>
-
-
-
-
-
- >
- );
-};
-
-export default GitSubForm;
diff --git a/awx/ui/src/screens/Project/shared/ProjectSubForms/InsightsSubForm.js b/awx/ui/src/screens/Project/shared/ProjectSubForms/InsightsSubForm.js
deleted file mode 100644
index aab479d33152..000000000000
--- a/awx/ui/src/screens/Project/shared/ProjectSubForms/InsightsSubForm.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import React, { useCallback } from 'react';
-
-import { t } from '@lingui/macro';
-import { useField, useFormikContext } from 'formik';
-import CredentialLookup from 'components/Lookup/CredentialLookup';
-import { required } from 'util/validators';
-import { ScmTypeOptions } from './SharedFields';
-
-const InsightsSubForm = ({
- credential,
- onCredentialSelection,
- scmUpdateOnLaunch,
- autoPopulateCredential,
-}) => {
- const { setFieldValue, setFieldTouched } = useFormikContext();
- const [, credMeta, credHelpers] = useField('credential');
-
- const onCredentialChange = useCallback(
- (value) => {
- onCredentialSelection('insights', value);
- setFieldValue('credential', value);
- setFieldTouched('credential', true, false);
- },
- [onCredentialSelection, setFieldValue, setFieldTouched]
- );
-
- return (
- <>
- credHelpers.setTouched()}
- onChange={onCredentialChange}
- value={credential.value}
- required
- autoPopulate={autoPopulateCredential}
- validate={required(t`Select a value for this field`)}
- />
-
- >
- );
-};
-
-export default InsightsSubForm;
diff --git a/awx/ui/src/screens/Project/shared/ProjectSubForms/ManualSubForm.js b/awx/ui/src/screens/Project/shared/ProjectSubForms/ManualSubForm.js
deleted file mode 100644
index e1400664b77b..000000000000
--- a/awx/ui/src/screens/Project/shared/ProjectSubForms/ManualSubForm.js
+++ /dev/null
@@ -1,88 +0,0 @@
-import 'styled-components/macro';
-import React from 'react';
-import { t } from '@lingui/macro';
-import { useField } from 'formik';
-import { FormGroup, Alert } from '@patternfly/react-core';
-import { required } from 'util/validators';
-import AnsibleSelect from 'components/AnsibleSelect';
-import FormField from 'components/FormField';
-import Popover from 'components/Popover';
-import useBrandName from 'hooks/useBrandName';
-import getProjectHelpStrings from '../Project.helptext';
-
-const ManualSubForm = ({
- localPath,
- project_base_dir,
- project_local_paths,
-}) => {
- const projectHelpStrings = getProjectHelpStrings();
- const brandName = useBrandName();
- const localPaths = [...new Set([...project_local_paths, localPath])];
- const options = [
- {
- value: '',
- key: '',
- label: t`Choose a Playbook Directory`,
- },
- ...localPaths
- .filter((path) => path)
- .map((path) => ({
- value: path,
- key: path,
- label: path,
- })),
- ];
- const [pathField, pathMeta, pathHelpers] = useField({
- name: 'local_path',
- validate: required(t`Select a value for this field`),
- });
-
- return (
- <>
- {options.length === 1 && (
-
- {t`
- There are no available playbook directories in ${project_base_dir}.
- Either that directory is empty, or all of the contents are already
- assigned to other projects. Create a new directory there and make
- sure the playbook files can be read by the "awx" system user,
- or have ${brandName} directly retrieve your playbooks from
- source control using the Source Control Type option above.`}
-
- )}
-
- }
- >
- {
- pathHelpers.setValue(value);
- }}
- />
-
- >
- );
-};
-
-export default ManualSubForm;
diff --git a/awx/ui/src/screens/Project/shared/ProjectSubForms/SharedFields.js b/awx/ui/src/screens/Project/shared/ProjectSubForms/SharedFields.js
deleted file mode 100644
index 1db0242bb8af..000000000000
--- a/awx/ui/src/screens/Project/shared/ProjectSubForms/SharedFields.js
+++ /dev/null
@@ -1,125 +0,0 @@
-import React, { useCallback } from 'react';
-
-import { t } from '@lingui/macro';
-import { useFormikContext } from 'formik';
-import { FormGroup, Title } from '@patternfly/react-core';
-import CredentialLookup from 'components/Lookup/CredentialLookup';
-import FormField, { CheckboxField } from 'components/FormField';
-import { required } from 'util/validators';
-import { FormCheckboxLayout, FormFullWidthLayout } from 'components/FormLayout';
-import getProjectHelpStrings from '../Project.helptext';
-
-export const UrlFormField = ({ tooltip }) => (
-
-);
-
-export const BranchFormField = ({ label }) => {
- const projectHelpStrings = getProjectHelpStrings();
- return (
-
- );
-};
-
-export const ScmCredentialFormField = ({
- credential,
- onCredentialSelection,
-}) => {
- const { setFieldValue, setFieldTouched } = useFormikContext();
-
- const onCredentialChange = useCallback(
- (value) => {
- onCredentialSelection('scm', value);
- setFieldValue('credential', value);
- setFieldTouched('credential', true, false);
- },
- [onCredentialSelection, setFieldValue, setFieldTouched]
- );
-
- return (
-
- );
-};
-
-export const ScmTypeOptions = ({ scmUpdateOnLaunch, hideAllowOverride }) => {
- const { values } = useFormikContext();
- const projectHelpStrings = getProjectHelpStrings();
-
- return (
-
-
-
-
-
- {values.scm_type === 'git' ? (
-
- ) : null}
-
- {!hideAllowOverride && (
-
- )}
-
-
-
- {scmUpdateOnLaunch && (
- <>
-
- {t`Option Details`}
-
-
- >
- )}
-
- );
-};
diff --git a/awx/ui/src/screens/Project/shared/ProjectSubForms/SvnSubForm.js b/awx/ui/src/screens/Project/shared/ProjectSubForms/SvnSubForm.js
deleted file mode 100644
index 9de7098a9d3c..000000000000
--- a/awx/ui/src/screens/Project/shared/ProjectSubForms/SvnSubForm.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import 'styled-components/macro';
-import React from 'react';
-import { t } from '@lingui/macro';
-import getProjectHelpStrings from '../Project.helptext';
-
-import {
- UrlFormField,
- BranchFormField,
- ScmCredentialFormField,
- ScmTypeOptions,
-} from './SharedFields';
-
-const SvnSubForm = ({
- credential,
- onCredentialSelection,
- scmUpdateOnLaunch,
-}) => {
- const projectHelpStrings = getProjectHelpStrings();
- return (
- <>
-
-
-
-
- >
- );
-};
-
-export default SvnSubForm;
diff --git a/awx/ui/src/screens/Project/shared/ProjectSubForms/index.js b/awx/ui/src/screens/Project/shared/ProjectSubForms/index.js
deleted file mode 100644
index 1e8b3f60447b..000000000000
--- a/awx/ui/src/screens/Project/shared/ProjectSubForms/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export { default as GitSubForm } from './GitSubForm';
-export { default as InsightsSubForm } from './InsightsSubForm';
-export { default as ManualSubForm } from './ManualSubForm';
-export { default as SvnSubForm } from './SvnSubForm';
-export { default as ArchiveSubForm } from './ArchiveSubForm';
diff --git a/awx/ui/src/screens/Project/shared/ProjectSyncButton.js b/awx/ui/src/screens/Project/shared/ProjectSyncButton.js
deleted file mode 100644
index 4daab8b1c365..000000000000
--- a/awx/ui/src/screens/Project/shared/ProjectSyncButton.js
+++ /dev/null
@@ -1,75 +0,0 @@
-import React, { useCallback } from 'react';
-import { useRouteMatch } from 'react-router-dom';
-import { Button, Tooltip } from '@patternfly/react-core';
-import { SyncIcon } from '@patternfly/react-icons';
-
-import { number } from 'prop-types';
-
-import { t } from '@lingui/macro';
-import useRequest, { useDismissableError } from 'hooks/useRequest';
-
-import AlertModal from 'components/AlertModal';
-import ErrorDetail from 'components/ErrorDetail';
-import { ProjectsAPI } from 'api';
-import getProjectHelpStrings from './Project.helptext';
-
-function ProjectSyncButton({ projectId, lastJobStatus = null }) {
- const projectHelpStrings = getProjectHelpStrings();
- const match = useRouteMatch();
-
- const { request: handleSync, error: syncError } = useRequest(
- useCallback(async () => {
- await ProjectsAPI.sync(projectId);
- }, [projectId]),
- null
- );
- const { error, dismissError } = useDismissableError(syncError);
- const isDetailsView = match.url.endsWith('/details');
- const isDisabled = ['pending', 'waiting', 'running'].includes(lastJobStatus);
-
- return (
- <>
- {isDisabled ? (
-
-
-
-
-
- ) : (
-
- )}
- {error && (
-
- {t`Failed to sync project.`}
-
-
- )}
- >
- );
-}
-
-ProjectSyncButton.propTypes = {
- projectId: number.isRequired,
-};
-
-export default ProjectSyncButton;
diff --git a/awx/ui/src/screens/Project/shared/ProjectSyncButton.test.js b/awx/ui/src/screens/Project/shared/ProjectSyncButton.test.js
deleted file mode 100644
index 5e2b312d36c8..000000000000
--- a/awx/ui/src/screens/Project/shared/ProjectSyncButton.test.js
+++ /dev/null
@@ -1,104 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { ProjectsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-
-import ProjectSyncButton from './ProjectSyncButton';
-
-jest.mock('../../../api');
-jest.mock('hooks/useBrandName', () => ({
- __esModule: true,
- default: () => ({
- current: 'AWX',
- }),
-}));
-describe('ProjectSyncButton', () => {
- let wrapper;
-
- const children = (handleSync) => (
- ,
-
- Cancel
- ,
- ]}
- >
- {isLoading && }
- {!isLoading && error && (
-
-
-
- No subscriptions found
-
-
-
- We were unable to locate licenses associated with this account.
- {' '}
-
- Return to subscription management.
-
-
-
-
- )}
- {!isLoading && !error && subscriptions?.length === 0 && (
-
- )}
- {!isLoading && !error && subscriptions?.length > 0 && (
-
-
-
- |
- {t`Name`} |
- {t`Managed nodes`} |
- {t`Expires`} |
-
-
-
- {subscriptions.map((subscription) => (
-
- | setSelected([subscription]),
- isSelected: selected.some(
- (row) => row.id === subscription.id
- ),
- variant: 'radio',
- rowIndex: `row-${subscription.id}`,
- }}
- />
- | {subscription.subscription_name} |
-
- {subscription.instance_count}
- |
-
- {formatDateString(
- new Date(subscription.license_date * 1000).toISOString(),
- 'UTC'
- )}
- |
-
- ))}
-
-
- )}
-
- );
-}
-
-export default SubscriptionModal;
diff --git a/awx/ui/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.test.js b/awx/ui/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.test.js
deleted file mode 100644
index 4c8fa843f225..000000000000
--- a/awx/ui/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.test.js
+++ /dev/null
@@ -1,155 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { ConfigAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../../testUtils/enzymeHelpers';
-import SubscriptionModal from './SubscriptionModal';
-
-jest.mock('../../../../api');
-
-describe('', () => {
- let wrapper;
- const onConfirm = jest.fn();
- const onClose = jest.fn();
-
- beforeAll(async () => {
- ConfigAPI.readSubscriptions = async () => ({
- data: [
- {
- subscription_name: 'mock A',
- instance_count: 100,
- license_date: 1714000271,
- pool_id: 7,
- },
- {
- subscription_name: 'mock B',
- instance_count: 200,
- license_date: 1714000271,
- pool_id: 8,
- },
- {
- subscription_name: 'mock C',
- instance_count: 30,
- license_date: 1714000271,
- pool_id: 9,
- },
- ],
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('initially renders without crashing', async () => {
- expect(wrapper.find('SubscriptionModal').length).toBe(1);
- });
-
- test('should render header', async () => {
- wrapper.update();
- const header = wrapper.find('tr').first().find('th');
- expect(header.at(0).text()).toEqual('');
- expect(header.at(1).text()).toEqual('Name');
- expect(header.at(2).text()).toEqual('Managed nodes');
- expect(header.at(3).text()).toEqual('Expires');
- });
-
- test('should render subscription rows', async () => {
- const rows = wrapper.find('tbody tr');
- expect(rows).toHaveLength(3);
- const firstRow = rows.at(0).find('td');
- expect(firstRow.at(0).find('input[type="radio"]')).toHaveLength(1);
- expect(firstRow.at(1).text()).toEqual('mock A');
- expect(firstRow.at(2).text()).toEqual('100');
- expect(firstRow.at(3).text()).toEqual('4/24/2024, 11:11:11 PM');
- });
-
- test('submit button should call onConfirm', async () => {
- expect(
- wrapper.find('Button[aria-label="Confirm selection"]').prop('isDisabled')
- ).toBe(true);
- await act(async () => {
- wrapper
- .find('SubscriptionModal SelectColumn')
- .first()
- .invoke('onSelect')();
- });
- wrapper.update();
- expect(
- wrapper.find('Button[aria-label="Confirm selection"]').prop('isDisabled')
- ).toBe(false);
- expect(onConfirm).toHaveBeenCalledTimes(0);
- expect(onClose).toHaveBeenCalledTimes(0);
- await act(async () =>
- wrapper.find('Button[aria-label="Confirm selection"]').prop('onClick')()
- );
- expect(onConfirm).toHaveBeenCalledTimes(1);
- expect(onClose).toHaveBeenCalledTimes(1);
- });
-
- test('should show empty content', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- await waitForElement(wrapper, 'ContentEmpty', (el) => el.length === 1);
- });
- });
-
- test('should auto-select current selected subscription', async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- await waitForElement(wrapper, 'table');
- expect(wrapper.find('tr[id="row-1"] input').prop('checked')).toBe(false);
- expect(wrapper.find('tr[id="row-2"] input').prop('checked')).toBe(true);
- expect(wrapper.find('tr[id="row-3"] input').prop('checked')).toBe(false);
- });
- });
-
- test('should display error detail message', async () => {
- ConfigAPI.readSubscriptions = jest.fn();
- ConfigAPI.readSubscriptions.mockRejectedValueOnce(new Error());
- await act(async () => {
- wrapper = mountWithContexts(
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- await waitForElement(wrapper, 'ErrorDetail', (el) => el.length === 1);
- });
-});
diff --git a/awx/ui/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js b/awx/ui/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js
deleted file mode 100644
index 333a7939fdac..000000000000
--- a/awx/ui/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.js
+++ /dev/null
@@ -1,266 +0,0 @@
-import React, { useState } from 'react';
-
-import { Trans, t } from '@lingui/macro';
-import { useField, useFormikContext } from 'formik';
-import styled from 'styled-components';
-import { TimesIcon } from '@patternfly/react-icons';
-import {
- Button,
- Divider,
- FileUpload,
- Flex,
- FlexItem,
- FormGroup,
- ToggleGroup,
- ToggleGroupItem,
- Tooltip,
-} from '@patternfly/react-core';
-import { useConfig } from 'contexts/Config';
-import getDocsBaseUrl from 'util/getDocsBaseUrl';
-import useModal from 'hooks/useModal';
-import FormField, { PasswordField } from 'components/FormField';
-import Popover from 'components/Popover';
-import SubscriptionModal from './SubscriptionModal';
-
-const LICENSELINK = 'https://www.ansible.com/license';
-const FileUploadField = styled(FormGroup)`
- && {
- max-width: 500px;
- width: 100%;
- }
-`;
-
-function SubscriptionStep() {
- const config = useConfig();
- const hasValidKey = Boolean(config?.license_info?.valid_key);
-
- const { values } = useFormikContext();
-
- const [isSelected, setIsSelected] = useState(
- values.subscription ? 'selectSubscription' : 'uploadManifest'
- );
- const { isModalOpen, toggleModal, closeModal } = useModal();
- const [manifest, manifestMeta, manifestHelpers] = useField('manifest_file');
- const [manifestFilename, , manifestFilenameHelpers] =
- useField('manifest_filename');
- const [subscription, , subscriptionHelpers] = useField('subscription');
- const [username, usernameMeta, usernameHelpers] = useField('username');
- const [password, passwordMeta, passwordHelpers] = useField('password');
-
- return (
-
- {!hasValidKey && (
- <>
-
- {t`Welcome to Red Hat Ansible Automation Platform!
- Please complete the steps below to activate your subscription.`}
-
-
- {t`If you do not have a subscription, you can visit
- Red Hat to obtain a trial subscription.`}
-
-
- {t`Request subscription`}
-
-
- >
- )}
- {t`Select your Ansible Automation Platform subscription to use.`}
-
- setIsSelected('uploadManifest')}
- id="subscription-manifest"
- />
- setIsSelected('selectSubscription')}
- id="username-password"
- />
-
- {isSelected === 'uploadManifest' ? (
- <>
-
-
- Upload a Red Hat Subscription Manifest containing your
- subscription. To generate your subscription manifest, go to{' '}
-
- subscription allocations
- {' '}
- on the Red Hat Customer Portal.
-
-
-
- A subscription manifest is an export of a Red Hat
- Subscription. To generate a subscription manifest, go to{' '}
-
- access.redhat.com
-
- . For more information, see the{' '}
-
- User Guide
-
- .
-
- }
- />
- }
- >
- manifestHelpers.setError(true),
- }}
- onChange={(value, filename) => {
- if (!value) {
- manifestHelpers.setValue(null);
- manifestFilenameHelpers.setValue('');
- usernameHelpers.setValue(usernameMeta.initialValue);
- passwordHelpers.setValue(passwordMeta.initialValue);
- return;
- }
-
- try {
- const raw = new FileReader();
- raw.readAsBinaryString(value);
- raw.onload = () => {
- const rawValue = btoa(raw.result);
- manifestHelpers.setValue(rawValue);
- manifestFilenameHelpers.setValue(filename);
- };
- } catch (err) {
- manifestHelpers.setError(err);
- }
- }}
- />
-
- >
- ) : (
- <>
-
- {t`Provide your Red Hat or Red Hat Satellite credentials
- below and you can choose from a list of your available subscriptions.
- The credentials you use will be stored for future use in
- retrieving renewal or expanded subscriptions.`}
-
-
-
-
-
- {t`Get subscription`}
-
- {isModalOpen && (
- subscriptionHelpers.setValue(value)}
- />
- )}
-
- {subscription.value && (
-
- {t`Selected`}
-
- {subscription?.value?.subscription_name}
-
- subscriptionHelpers.setValue(null)}
- variant="plain"
- aria-label={t`Clear subscription selection`}
- ouiaId="clear-subscription-selection"
- >
-
-
-
-
-
- )}
- >
- )}
-
- );
-}
-export default SubscriptionStep;
diff --git a/awx/ui/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.test.js b/awx/ui/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.test.js
deleted file mode 100644
index e539a06e08f5..000000000000
--- a/awx/ui/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionStep.test.js
+++ /dev/null
@@ -1,120 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { Formik } from 'formik';
-import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
-import SubscriptionStep from './SubscriptionStep';
-
-describe('', () => {
- let wrapper;
-
- beforeAll(async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('initially renders without crashing', async () => {
- expect(wrapper.find('SubscriptionStep').length).toBe(1);
- });
-
- test('should update filename when a manifest zip file is uploaded', async () => {
- expect(wrapper.find('FileUploadField')).toHaveLength(1);
- expect(wrapper.find('label').text()).toEqual(
- 'Red Hat subscription manifest'
- );
- expect(wrapper.find('FileUploadField').prop('value')).toEqual(null);
- expect(wrapper.find('FileUploadField').prop('filename')).toEqual('');
- const mockFile = new Blob(['123'], { type: 'application/zip' });
- mockFile.name = 'new file name';
- mockFile.date = new Date();
- await act(async () => {
- wrapper.find('FileUpload').invoke('onChange')(mockFile, 'new file name');
- });
- await act(async () => {
- wrapper.update();
- });
- await act(async () => {
- wrapper.update();
- });
- expect(wrapper.find('FileUploadField').prop('value')).toEqual(
- expect.stringMatching(/^[\x00-\x7F]+$/) // eslint-disable-line no-control-regex
- );
- expect(wrapper.find('FileUploadField').prop('filename')).toEqual(
- 'new file name'
- );
- });
-
- test('clear button should clear manifest value and filename', async () => {
- await act(async () => {
- wrapper
- .find('FileUpload .pf-c-input-group button')
- .last()
- .simulate('click');
- });
- wrapper.update();
- expect(wrapper.find('FileUploadField').prop('value')).toEqual(null);
- expect(wrapper.find('FileUploadField').prop('filename')).toEqual('');
- });
-
- test('FileUpload should throw an error', async () => {
- expect(
- wrapper.find('div#subscription-manifest-helper.pf-m-error')
- ).toHaveLength(0);
- await act(async () => {
- wrapper.find('FileUpload').invoke('onChange')('✓', 'new file name');
- });
- wrapper.update();
- expect(
- wrapper.find('div#subscription-manifest-helper.pf-m-error')
- ).toHaveLength(1);
- expect(wrapper.find('div#subscription-manifest-helper').text()).toContain(
- 'Invalid file format. Please upload a valid Red Hat Subscription Manifest.'
- );
- });
-
- test('Username/password toggle button should show username credential fields', async () => {
- expect(wrapper.find('ToggleGroupItem').last().props().isSelected).toBe(
- false
- );
- wrapper
- .find('ToggleGroupItem[text="Username / password"] button')
- .simulate('click');
- wrapper.update();
- expect(wrapper.find('ToggleGroupItem').last().props().isSelected).toBe(
- true
- );
- await act(async () => {
- wrapper.find('input#username-field').simulate('change', {
- target: { value: 'username-cred', name: 'username' },
- });
- wrapper.find('input#password-field').simulate('change', {
- target: { value: 'password-cred', name: 'password' },
- });
- });
- wrapper.update();
- expect(wrapper.find('input#username-field').prop('value')).toEqual(
- 'username-cred'
- );
- expect(wrapper.find('input#password-field').prop('value')).toEqual(
- 'password-cred'
- );
- });
-});
diff --git a/awx/ui/src/screens/Setting/Subscription/SubscriptionEdit/index.js b/awx/ui/src/screens/Setting/Subscription/SubscriptionEdit/index.js
deleted file mode 100644
index 1b9aeadaec11..000000000000
--- a/awx/ui/src/screens/Setting/Subscription/SubscriptionEdit/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './SubscriptionEdit';
diff --git a/awx/ui/src/screens/Setting/Subscription/index.js b/awx/ui/src/screens/Setting/Subscription/index.js
deleted file mode 100644
index 41a92af34fa3..000000000000
--- a/awx/ui/src/screens/Setting/Subscription/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './Subscription';
diff --git a/awx/ui/src/screens/Setting/TACACS/TACACS.js b/awx/ui/src/screens/Setting/TACACS/TACACS.js
deleted file mode 100644
index 720ac4d1ca7b..000000000000
--- a/awx/ui/src/screens/Setting/TACACS/TACACS.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import React from 'react';
-import { Link, Redirect, Route, Switch } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { PageSection, Card } from '@patternfly/react-core';
-import ContentError from 'components/ContentError';
-import TACACSDetail from './TACACSDetail';
-import TACACSEdit from './TACACSEdit';
-
-function TACACS() {
- const baseURL = '/settings/tacacs';
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- {t`View TACACS+ settings`}
-
-
-
-
-
- );
-}
-
-export default TACACS;
diff --git a/awx/ui/src/screens/Setting/TACACS/TACACS.test.js b/awx/ui/src/screens/Setting/TACACS/TACACS.test.js
deleted file mode 100644
index 4a44ac73a794..000000000000
--- a/awx/ui/src/screens/Setting/TACACS/TACACS.test.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { SettingsProvider } from 'contexts/Settings';
-import { SettingsAPI } from 'api';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import mockAllOptions from '../shared/data.allSettingOptions.json';
-import TACACS from './TACACS';
-
-jest.mock('../../../api/models/Settings');
-SettingsAPI.readCategory.mockResolvedValue({
- data: {
- TACACSPLUS_HOST: 'mockhost',
- TACACSPLUS_PORT: 49,
- TACACSPLUS_SECRET: '$encrypted$',
- TACACSPLUS_SESSION_TIMEOUT: 5,
- TACACSPLUS_AUTH_PROTOCOL: 'ascii',
- },
-});
-
-describe('', () => {
- let wrapper;
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should render TACACS+ details', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/settings/tacacs/details'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- ,
- {
- context: { router: { history } },
- }
- );
- });
- expect(wrapper.find('TACACSDetail').length).toBe(1);
- });
-
- test('should render TACACS+ edit', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/settings/tacacs/edit'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- ,
- {
- context: { router: { history } },
- }
- );
- });
- expect(wrapper.find('TACACSEdit').length).toBe(1);
- });
-
- test('should show content error when user navigates to erroneous route', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/settings/tacacs/foo'],
- });
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
- expect(wrapper.find('ContentError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Setting/TACACS/TACACSDetail/TACACSDetail.js b/awx/ui/src/screens/Setting/TACACS/TACACSDetail/TACACSDetail.js
deleted file mode 100644
index 2311759d5a11..000000000000
--- a/awx/ui/src/screens/Setting/TACACS/TACACSDetail/TACACSDetail.js
+++ /dev/null
@@ -1,117 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-import { Link } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { Button, Alert as PFAlert } from '@patternfly/react-core';
-import { CaretLeftIcon } from '@patternfly/react-icons';
-import styled from 'styled-components';
-import { CardBody, CardActionsRow } from 'components/Card';
-import ContentLoading from 'components/ContentLoading';
-import ContentError from 'components/ContentError';
-import RoutedTabs from 'components/RoutedTabs';
-import { SettingsAPI } from 'api';
-import useRequest from 'hooks/useRequest';
-import { DetailList } from 'components/DetailList';
-import { useConfig } from 'contexts/Config';
-import { useSettings } from 'contexts/Settings';
-import { SettingDetail } from '../../shared';
-
-const Alert = styled(PFAlert)`
- margin-bottom: 20px;
-`;
-
-function TACACSDetail() {
- const { me } = useConfig();
- const { GET: options } = useSettings();
-
- const {
- isLoading,
- error,
- request,
- result: tacacs,
- } = useRequest(
- useCallback(async () => {
- const { data } = await SettingsAPI.readCategory('tacacsplus');
- return data;
- }, []),
- null
- );
-
- useEffect(() => {
- request();
- }, [request]);
-
- const tabsArray = [
- {
- name: (
- <>
-
- {t`Back to Settings`}
- >
- ),
- link: `/settings`,
- id: 99,
- },
- {
- name: t`Details`,
- link: `/settings/tacacs/details`,
- id: 0,
- },
- ];
- if (isLoading) {
- return ;
- }
- if (!isLoading && error) {
- return ;
- }
- return (
- <>
-
-
- {isLoading && }
- {!isLoading && error && }
- {!isLoading && tacacs && (
- <>
-
-
- {Object.keys(tacacs).map((key) => {
- const record = options?.[key];
- return (
-
- );
- })}
-
- >
- )}
- {me?.is_superuser && (
-
-
- {t`Edit`}
-
-
- )}
-
- >
- );
-}
-
-export default TACACSDetail;
diff --git a/awx/ui/src/screens/Setting/TACACS/TACACSDetail/TACACSDetail.test.js b/awx/ui/src/screens/Setting/TACACS/TACACSDetail/TACACSDetail.test.js
deleted file mode 100644
index 3244c1110d26..000000000000
--- a/awx/ui/src/screens/Setting/TACACS/TACACSDetail/TACACSDetail.test.js
+++ /dev/null
@@ -1,99 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { SettingsProvider } from 'contexts/Settings';
-import { SettingsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../../testUtils/enzymeHelpers';
-import { assertDetail } from '../../shared/settingTestUtils';
-import mockAllOptions from '../../shared/data.allSettingOptions.json';
-import TACACSDetail from './TACACSDetail';
-
-jest.mock('../../../../api');
-
-describe('', () => {
- let wrapper;
-
- beforeEach(() => {
- SettingsAPI.readCategory.mockResolvedValue({
- data: {
- TACACSPLUS_HOST: 'mockhost',
- TACACSPLUS_PORT: 49,
- TACACSPLUS_SECRET: '$encrypted$',
- TACACSPLUS_SESSION_TIMEOUT: 5,
- TACACSPLUS_AUTH_PROTOCOL: 'ascii',
- },
- });
- });
-
- beforeEach(async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('initially renders without crashing', () => {
- expect(wrapper.find('TACACSDetail').length).toBe(1);
- });
-
- test('should render expected tabs', () => {
- const expectedTabs = ['Back to Settings', 'Details'];
- wrapper.find('RoutedTabs li').forEach((tab, index) => {
- expect(tab.text()).toEqual(expectedTabs[index]);
- });
- });
-
- test('should render expected details', () => {
- expect(wrapper.find('Alert').prop('title')).toBe(
- 'This feature is deprecated and will be removed in a future release.'
- );
- assertDetail(wrapper, 'TACACS+ Server', 'mockhost');
- assertDetail(wrapper, 'TACACS+ Port', '49');
- assertDetail(wrapper, 'TACACS+ Secret', 'Encrypted');
- assertDetail(wrapper, 'TACACS+ Auth Session Timeout', '5 seconds');
- assertDetail(wrapper, 'TACACS+ Authentication Protocol', 'ascii');
- });
-
- test('should hide edit button from non-superusers', async () => {
- const config = {
- me: {
- is_superuser: false,
- },
- };
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- ,
- {
- context: { config },
- }
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('Button[aria-label="Edit"]').exists()).toBeFalsy();
- });
-
- test('should display content error when api throws error on initial render', async () => {
- SettingsAPI.readCategory.mockRejectedValue(new Error());
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('ContentError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Setting/TACACS/TACACSDetail/index.js b/awx/ui/src/screens/Setting/TACACS/TACACSDetail/index.js
deleted file mode 100644
index 1720e8b92180..000000000000
--- a/awx/ui/src/screens/Setting/TACACS/TACACSDetail/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './TACACSDetail';
diff --git a/awx/ui/src/screens/Setting/TACACS/TACACSEdit/TACACSEdit.js b/awx/ui/src/screens/Setting/TACACS/TACACSEdit/TACACSEdit.js
deleted file mode 100644
index d1894b53c807..000000000000
--- a/awx/ui/src/screens/Setting/TACACS/TACACSEdit/TACACSEdit.js
+++ /dev/null
@@ -1,141 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { useHistory } from 'react-router-dom';
-import { Formik } from 'formik';
-import { Form } from '@patternfly/react-core';
-import { CardBody } from 'components/Card';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import { FormSubmitError } from 'components/FormField';
-import { FormColumnLayout } from 'components/FormLayout';
-import { useSettings } from 'contexts/Settings';
-import useModal from 'hooks/useModal';
-import useRequest from 'hooks/useRequest';
-import { SettingsAPI } from 'api';
-import {
- ChoiceField,
- EncryptedField,
- InputField,
-} from '../../shared/SharedFields';
-import { RevertAllAlert, RevertFormActionGroup } from '../../shared';
-
-function TACACSEdit() {
- const history = useHistory();
- const { isModalOpen, toggleModal, closeModal } = useModal();
- const { PUT: options } = useSettings();
-
- const {
- isLoading,
- error,
- request: fetchTACACS,
- result: tacacs,
- } = useRequest(
- useCallback(async () => {
- const { data } = await SettingsAPI.readCategory('tacacsplus');
- const mergedData = {};
- Object.keys(data).forEach((key) => {
- mergedData[key] = options[key];
- mergedData[key].value = data[key];
- });
- return mergedData;
- }, [options]),
- null
- );
-
- useEffect(() => {
- fetchTACACS();
- }, [fetchTACACS]);
-
- const { error: submitError, request: submitForm } = useRequest(
- useCallback(
- async (values) => {
- await SettingsAPI.updateAll(values);
- history.push('/settings/tacacs/details');
- },
- [history]
- ),
- null
- );
-
- const { error: revertError, request: revertAll } = useRequest(
- useCallback(async () => {
- await SettingsAPI.revertCategory('tacacsplus');
- }, []),
- null
- );
-
- const handleSubmit = async (form) => {
- await submitForm(form);
- };
-
- const handleRevertAll = async () => {
- await revertAll();
-
- closeModal();
-
- history.push('/settings/tacacs/details');
- };
-
- const handleCancel = () => {
- history.push('/settings/tacacs/details');
- };
-
- const initialValues = (fields) =>
- Object.keys(fields).reduce((acc, key) => {
- acc[key] = fields[key].value ?? '';
- return acc;
- }, {});
-
- return (
-
- {isLoading && }
- {!isLoading && error && }
- {!isLoading && tacacs && (
-
- {(formik) => (
-
- )}
-
- )}
-
- );
-}
-
-export default TACACSEdit;
diff --git a/awx/ui/src/screens/Setting/TACACS/TACACSEdit/TACACSEdit.test.js b/awx/ui/src/screens/Setting/TACACS/TACACSEdit/TACACSEdit.test.js
deleted file mode 100644
index 4e41ddd58b5f..000000000000
--- a/awx/ui/src/screens/Setting/TACACS/TACACSEdit/TACACSEdit.test.js
+++ /dev/null
@@ -1,163 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { SettingsProvider } from 'contexts/Settings';
-import { SettingsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../../testUtils/enzymeHelpers';
-import mockAllOptions from '../../shared/data.allSettingOptions.json';
-import TACACSEdit from './TACACSEdit';
-
-jest.mock('../../../../api/');
-
-describe('', () => {
- let wrapper;
- let history;
-
- beforeEach(() => {
- SettingsAPI.revertCategory.mockResolvedValue({});
- SettingsAPI.updateAll.mockResolvedValue({});
- SettingsAPI.readCategory.mockResolvedValue({
- data: {
- TACACSPLUS_HOST: 'mockhost',
- TACACSPLUS_PORT: 49,
- TACACSPLUS_SECRET: '$encrypted$',
- TACACSPLUS_SESSION_TIMEOUT: 123,
- TACACSPLUS_AUTH_PROTOCOL: 'ascii',
- },
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- beforeEach(async () => {
- history = createMemoryHistory({
- initialEntries: ['/settings/tacacs/edit'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- ,
- {
- context: { router: { history } },
- }
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- test('initially renders without crashing', () => {
- expect(wrapper.find('TACACSEdit').length).toBe(1);
- });
-
- test('should display expected form fields', async () => {
- expect(wrapper.find('FormGroup[label="TACACS+ Server"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="TACACS+ Port"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="TACACS+ Secret"]').length).toBe(1);
- expect(
- wrapper.find('FormGroup[label="TACACS+ Auth Session Timeout"]').length
- ).toBe(1);
- expect(
- wrapper.find('FormGroup[label="TACACS+ Authentication Protocol"]').length
- ).toBe(1);
- });
-
- test('should successfully send default values to api on form revert all', async () => {
- expect(SettingsAPI.revertCategory).toHaveBeenCalledTimes(0);
- expect(wrapper.find('RevertAllAlert')).toHaveLength(0);
- await act(async () => {
- wrapper
- .find('button[aria-label="Revert all to default"]')
- .invoke('onClick')();
- });
- wrapper.update();
- expect(wrapper.find('RevertAllAlert')).toHaveLength(1);
- await act(async () => {
- wrapper
- .find('RevertAllAlert button[aria-label="Confirm revert all"]')
- .invoke('onClick')();
- });
- wrapper.update();
- expect(SettingsAPI.revertCategory).toHaveBeenCalledTimes(1);
- expect(SettingsAPI.revertCategory).toHaveBeenCalledWith('tacacsplus');
- });
-
- test('should successfully send request to api on form submission', async () => {
- act(() => {
- wrapper.find('input#TACACSPLUS_HOST').simulate('change', {
- target: { value: 'new_host', name: 'TACACSPLUS_HOST' },
- });
- wrapper.find('input#TACACSPLUS_PORT').simulate('change', {
- target: { value: 999, name: 'TACACSPLUS_PORT' },
- });
- wrapper
- .find(
- 'FormGroup[fieldId="TACACSPLUS_SECRET"] button[aria-label="Revert"]'
- )
- .invoke('onClick')();
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('Form').invoke('onSubmit')();
- });
- expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(1);
- expect(SettingsAPI.updateAll).toHaveBeenCalledWith({
- TACACSPLUS_HOST: 'new_host',
- TACACSPLUS_PORT: 999,
- TACACSPLUS_SECRET: '',
- TACACSPLUS_SESSION_TIMEOUT: 123,
- TACACSPLUS_AUTH_PROTOCOL: 'ascii',
- });
- });
-
- test('should navigate to tacacs detail on successful submission', async () => {
- await act(async () => {
- wrapper.find('Form').invoke('onSubmit')();
- });
- expect(history.location.pathname).toEqual('/settings/tacacs/details');
- });
-
- test('should navigate to tacacs detail when cancel is clicked', async () => {
- await act(async () => {
- wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
- });
- expect(history.location.pathname).toEqual('/settings/tacacs/details');
- });
-
- test('should display error message on unsuccessful submission', async () => {
- const error = {
- response: {
- data: { detail: 'An error occurred' },
- },
- };
- SettingsAPI.updateAll.mockImplementation(() => Promise.reject(error));
- expect(wrapper.find('FormSubmitError').length).toBe(0);
- expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(0);
- await act(async () => {
- wrapper.find('Form').invoke('onSubmit')();
- });
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(1);
- expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(1);
- });
-
- test('should display ContentError on throw', async () => {
- SettingsAPI.readCategory.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('ContentError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Setting/TACACS/TACACSEdit/index.js b/awx/ui/src/screens/Setting/TACACS/TACACSEdit/index.js
deleted file mode 100644
index 2b95f71aa8b6..000000000000
--- a/awx/ui/src/screens/Setting/TACACS/TACACSEdit/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './TACACSEdit';
diff --git a/awx/ui/src/screens/Setting/TACACS/index.js b/awx/ui/src/screens/Setting/TACACS/index.js
deleted file mode 100644
index d1cb31279ef7..000000000000
--- a/awx/ui/src/screens/Setting/TACACS/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './TACACS';
diff --git a/awx/ui/src/screens/Setting/UI/UI.js b/awx/ui/src/screens/Setting/UI/UI.js
deleted file mode 100644
index a2e683e15d22..000000000000
--- a/awx/ui/src/screens/Setting/UI/UI.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import React from 'react';
-import { Link, Redirect, Route, Switch } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { PageSection, Card } from '@patternfly/react-core';
-import ContentError from 'components/ContentError';
-import UIDetail from './UIDetail';
-import UIEdit from './UIEdit';
-
-function UI() {
- const baseURL = '/settings/ui';
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
- {t`View User Interface settings`}
-
-
-
-
-
-
- );
-}
-
-export default UI;
diff --git a/awx/ui/src/screens/Setting/UI/UI.test.js b/awx/ui/src/screens/Setting/UI/UI.test.js
deleted file mode 100644
index 84e1d63a54ef..000000000000
--- a/awx/ui/src/screens/Setting/UI/UI.test.js
+++ /dev/null
@@ -1,76 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { SettingsAPI } from 'api';
-import { SettingsProvider } from 'contexts/Settings';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../testUtils/enzymeHelpers';
-import mockAllOptions from '../shared/data.allSettingOptions.json';
-import UI from './UI';
-
-jest.mock('../../../api/models/Settings');
-SettingsAPI.readCategory.mockResolvedValue({
- data: {
- CUSTOM_LOGIN_INFO: '',
- CUSTOM_LOGO: '',
- PENDO_TRACKING_STATE: 'off',
- },
-});
-
-describe('', () => {
- let wrapper;
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- test('should render user interface details', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/settings/ui/details'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- ,
- {
- context: { router: { history } },
- }
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('UIDetail').length).toBe(1);
- });
-
- test('should render user interface edit', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/settings/ui/edit'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- ,
- {
- context: { router: { history } },
- }
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('UIEdit').length).toBe(1);
- });
-
- test('should show content error when user navigates to erroneous route', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/settings/ui/foo'],
- });
- await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
- });
- expect(wrapper.find('ContentError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Setting/UI/UIDetail/UIDetail.js b/awx/ui/src/screens/Setting/UI/UIDetail/UIDetail.js
deleted file mode 100644
index f02f78d4f743..000000000000
--- a/awx/ui/src/screens/Setting/UI/UIDetail/UIDetail.js
+++ /dev/null
@@ -1,119 +0,0 @@
-import React, { useEffect, useCallback } from 'react';
-import { Link, useHistory, useLocation } from 'react-router-dom';
-
-import { t } from '@lingui/macro';
-import { Button } from '@patternfly/react-core';
-import { CaretLeftIcon } from '@patternfly/react-icons';
-import { CardBody, CardActionsRow } from 'components/Card';
-import ContentLoading from 'components/ContentLoading';
-import ContentError from 'components/ContentError';
-import RoutedTabs from 'components/RoutedTabs';
-import { SettingsAPI } from 'api';
-import useRequest from 'hooks/useRequest';
-import { DetailList } from 'components/DetailList';
-import { useConfig } from 'contexts/Config';
-import { useSettings } from 'contexts/Settings';
-import { pluck } from '../../shared/settingUtils';
-import { SettingDetail } from '../../shared';
-
-function UIDetail() {
- const { me } = useConfig();
- const { GET: options } = useSettings();
- const history = useHistory();
- const { hardReload } = useLocation();
-
- if (hardReload) {
- history.go();
- }
-
- const {
- isLoading,
- error,
- request,
- result: ui,
- } = useRequest(
- useCallback(async () => {
- const { data } = await SettingsAPI.readCategory('ui');
-
- const uiData = pluck(
- data,
- 'PENDO_TRACKING_STATE',
- 'CUSTOM_LOGO',
- 'CUSTOM_LOGIN_INFO'
- );
-
- return uiData;
- }, []),
- null
- );
-
- useEffect(() => {
- request();
- }, [request]);
-
- const tabsArray = [
- {
- name: (
- <>
-
- {t`Back to Settings`}
- >
- ),
- link: `/settings`,
- id: 99,
- },
- {
- name: t`Details`,
- link: `/settings/ui/details`,
- id: 0,
- },
- ];
-
- // Change CUSTOM_LOGO type from string to image
- // to help SettingDetail render it as an
- if (options?.CUSTOM_LOGO) {
- options.CUSTOM_LOGO.type = 'image';
- }
-
- return (
- <>
-
-
- {isLoading && }
- {!isLoading && error && }
- {!isLoading && ui && (
-
- {Object.keys(ui).map((key) => {
- const record = options?.[key];
- return (
-
- );
- })}
-
- )}
- {me?.is_superuser && (
-
-
- {t`Edit`}
-
-
- )}
-
- >
- );
-}
-
-export default UIDetail;
diff --git a/awx/ui/src/screens/Setting/UI/UIDetail/UIDetail.test.js b/awx/ui/src/screens/Setting/UI/UIDetail/UIDetail.test.js
deleted file mode 100644
index 39249cfb051d..000000000000
--- a/awx/ui/src/screens/Setting/UI/UIDetail/UIDetail.test.js
+++ /dev/null
@@ -1,95 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { SettingsProvider } from 'contexts/Settings';
-import { SettingsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../../testUtils/enzymeHelpers';
-import { assertDetail } from '../../shared/settingTestUtils';
-import mockAllOptions from '../../shared/data.allSettingOptions.json';
-import UIDetail from './UIDetail';
-
-jest.mock('../../../../api');
-
-describe('', () => {
- let wrapper;
-
- beforeEach(() => {
- SettingsAPI.readCategory.mockResolvedValue({
- data: {
- CUSTOM_LOGIN_INFO: 'mock info',
- CUSTOM_LOGO: 'data:image/png',
- PENDO_TRACKING_STATE: 'off',
- },
- });
- });
-
- beforeEach(async () => {
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- afterAll(() => {
- jest.clearAllMocks();
- });
-
- test('initially renders without crashing', () => {
- expect(wrapper.find('UIDetail').length).toBe(1);
- });
-
- test('should render expected tabs', () => {
- const expectedTabs = ['Back to Settings', 'Details'];
- wrapper.find('RoutedTabs li').forEach((tab, index) => {
- expect(tab.text()).toEqual(expectedTabs[index]);
- });
- });
-
- test('should render expected details', () => {
- assertDetail(wrapper, 'User Analytics Tracking State', 'off');
- assertDetail(wrapper, 'Custom Login Info', 'mock info');
- expect(wrapper.find('Detail[label="Custom Logo"] dt').text()).toBe(
- 'Custom Logo'
- );
- expect(wrapper.find('Detail[label="Custom Logo"] dd img').length).toBe(1);
- });
-
- test('should hide edit button from non-superusers', async () => {
- const config = {
- me: {
- is_superuser: false,
- },
- };
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- ,
- {
- context: { config },
- }
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('Button[aria-label="Edit"]').exists()).toBeFalsy();
- });
-
- test('should display content error when api throws error on initial render', async () => {
- SettingsAPI.readCategory.mockRejectedValue(new Error());
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('ContentError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Setting/UI/UIDetail/index.js b/awx/ui/src/screens/Setting/UI/UIDetail/index.js
deleted file mode 100644
index 791d1d88734d..000000000000
--- a/awx/ui/src/screens/Setting/UI/UIDetail/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './UIDetail';
diff --git a/awx/ui/src/screens/Setting/UI/UIEdit/UIEdit.js b/awx/ui/src/screens/Setting/UI/UIEdit/UIEdit.js
deleted file mode 100644
index a86d94829312..000000000000
--- a/awx/ui/src/screens/Setting/UI/UIEdit/UIEdit.js
+++ /dev/null
@@ -1,152 +0,0 @@
-import React, { useCallback, useEffect } from 'react';
-import { useHistory } from 'react-router-dom';
-import { Formik } from 'formik';
-import { Form } from '@patternfly/react-core';
-import { CardBody } from 'components/Card';
-import ContentError from 'components/ContentError';
-import ContentLoading from 'components/ContentLoading';
-import { FormSubmitError } from 'components/FormField';
-import { FormColumnLayout } from 'components/FormLayout';
-import { useSettings } from 'contexts/Settings';
-import { useConfig } from 'contexts/Config';
-import useModal from 'hooks/useModal';
-import useRequest from 'hooks/useRequest';
-import { SettingsAPI } from 'api';
-import {
- ChoiceField,
- FileUploadField,
- TextAreaField,
-} from '../../shared/SharedFields';
-import { RevertAllAlert, RevertFormActionGroup } from '../../shared';
-
-function UIEdit() {
- const history = useHistory();
- const { isModalOpen, toggleModal, closeModal } = useModal();
- const { PUT: options } = useSettings();
- const { license_info } = useConfig();
-
- const {
- isLoading,
- error,
- request: fetchUI,
- result: uiData,
- } = useRequest(
- useCallback(async () => {
- const { data } = await SettingsAPI.readCategory('ui');
- const mergedData = {};
- Object.keys(data).forEach((key) => {
- if (!options[key]) {
- return;
- }
- mergedData[key] = options[key];
- mergedData[key].value = data[key];
- });
- return mergedData;
- }, [options]),
- null
- );
-
- useEffect(() => {
- fetchUI();
- }, [fetchUI]);
-
- const { error: submitError, request: submitForm } = useRequest(
- useCallback(
- async (values) => {
- await SettingsAPI.updateAll(values);
- if (
- values?.PENDO_TRACKING_STATE !== uiData?.PENDO_TRACKING_STATE?.value
- ) {
- history.push({
- pathname: '/settings/ui/details',
- hardReload: true,
- });
- } else {
- history.push('/settings/ui/details');
- }
- },
- [history, uiData]
- ),
- null
- );
-
- const { error: revertError, request: revertAll } = useRequest(
- useCallback(async () => {
- await SettingsAPI.revertCategory('ui');
- }, []),
- null
- );
-
- const handleSubmit = async (form) => {
- await submitForm(form);
- };
-
- const handleRevertAll = async () => {
- await revertAll();
-
- closeModal();
-
- history.push({
- pathname: '/settings/ui/details',
- hardReload: true,
- });
- };
-
- const handleCancel = () => {
- history.push('/settings/ui/details');
- };
-
- return (
-
- {isLoading && }
- {!isLoading && error && }
- {!isLoading && uiData && (
-
- {(formik) => (
-
- )}
-
- )}
-
- );
-}
-
-export default UIEdit;
diff --git a/awx/ui/src/screens/Setting/UI/UIEdit/UIEdit.test.js b/awx/ui/src/screens/Setting/UI/UIEdit/UIEdit.test.js
deleted file mode 100644
index fa3f0f57adf3..000000000000
--- a/awx/ui/src/screens/Setting/UI/UIEdit/UIEdit.test.js
+++ /dev/null
@@ -1,165 +0,0 @@
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { createMemoryHistory } from 'history';
-import { SettingsProvider } from 'contexts/Settings';
-import { SettingsAPI } from 'api';
-import {
- mountWithContexts,
- waitForElement,
-} from '../../../../../testUtils/enzymeHelpers';
-import mockAllOptions from '../../shared/data.allSettingOptions.json';
-import UIEdit from './UIEdit';
-
-jest.mock('../../../../api');
-
-describe('', () => {
- let wrapper;
- let history;
-
- beforeEach(() => {
- SettingsAPI.revertCategory.mockResolvedValue({});
- SettingsAPI.updateAll.mockResolvedValue({});
- SettingsAPI.readCategory.mockResolvedValue({
- data: {
- CUSTOM_LOGIN_INFO: 'mock info',
- CUSTOM_LOGO: 'data:mock/jpeg;',
- PENDO_TRACKING_STATE: 'detailed',
- },
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- beforeEach(async () => {
- history = createMemoryHistory({
- initialEntries: ['/settings/ui/edit'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
-
-
- ,
- {
- context: { router: { history } },
- }
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- });
-
- test('initially renders without crashing', () => {
- expect(wrapper.find('UIEdit').length).toBe(1);
- });
-
- test('should display expected form fields', async () => {
- expect(wrapper.find('FormGroup[label="Custom Login Info"]').length).toBe(1);
- expect(wrapper.find('FormGroup[label="Custom Logo"]').length).toBe(1);
- expect(
- wrapper.find('FormGroup[label="User Analytics Tracking State"]').length
- ).toBe(1);
- });
-
- test('should successfully send default values to api on form revert all', async () => {
- expect(SettingsAPI.revertCategory).toHaveBeenCalledTimes(0);
- expect(wrapper.find('RevertAllAlert')).toHaveLength(0);
- await act(async () => {
- wrapper
- .find('button[aria-label="Revert all to default"]')
- .invoke('onClick')();
- });
- wrapper.update();
- expect(wrapper.find('RevertAllAlert')).toHaveLength(1);
- await act(async () => {
- wrapper
- .find('RevertAllAlert button[aria-label="Confirm revert all"]')
- .invoke('onClick')();
- });
- wrapper.update();
- expect(SettingsAPI.revertCategory).toHaveBeenCalledTimes(1);
- expect(SettingsAPI.revertCategory).toHaveBeenCalledWith('ui');
- });
-
- test('should successfully send request to api on form submission', async () => {
- act(() => {
- wrapper.find('textarea#CUSTOM_LOGIN_INFO').simulate('change', {
- target: { value: 'new login info', name: 'CUSTOM_LOGIN_INFO' },
- });
- wrapper
- .find('FormGroup[fieldId="CUSTOM_LOGO"] button[aria-label="Revert"]')
- .invoke('onClick')();
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('Form').invoke('onSubmit')();
- });
- expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(1);
- expect(SettingsAPI.updateAll).toHaveBeenCalledWith({
- CUSTOM_LOGIN_INFO: 'new login info',
- CUSTOM_LOGO: '',
- PENDO_TRACKING_STATE: 'detailed',
- });
- });
-
- test('should navigate to ui detail on successful submission', async () => {
- await act(async () => {
- wrapper.find('Form').invoke('onSubmit')();
- });
- expect(history.location.pathname).toEqual('/settings/ui/details');
- expect(history.location.hardReload).toEqual(undefined);
- });
-
- test('should navigate to ui detail with reload param on successful submission where PENDO_TRACKING_STATE changes', async () => {
- act(() => {
- wrapper.find('select#PENDO_TRACKING_STATE').simulate('change', {
- target: { value: 'off', name: 'CUSTOM_LOGIN_INFO' },
- });
- });
- wrapper.update();
- await act(async () => {
- wrapper.find('Form').invoke('onSubmit')();
- });
- expect(history.location.pathname).toEqual('/settings/ui/details');
- expect(history.location.hardReload).toEqual(true);
- });
-
- test('should navigate to ui detail when cancel is clicked', async () => {
- await act(async () => {
- wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
- });
- expect(history.location.pathname).toEqual('/settings/ui/details');
- });
-
- test('should display error message on unsuccessful submission', async () => {
- const error = {
- response: {
- data: { detail: 'An error occurred' },
- },
- };
- SettingsAPI.updateAll.mockImplementation(() => Promise.reject(error));
- expect(wrapper.find('FormSubmitError').length).toBe(0);
- expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(0);
- await act(async () => {
- wrapper.find('Form').invoke('onSubmit')();
- });
- wrapper.update();
- expect(wrapper.find('FormSubmitError').length).toBe(1);
- expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(1);
- });
-
- test('should display ContentError on throw', async () => {
- SettingsAPI.readCategory.mockImplementationOnce(() =>
- Promise.reject(new Error())
- );
- await act(async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- });
- await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
- expect(wrapper.find('ContentError').length).toBe(1);
- });
-});
diff --git a/awx/ui/src/screens/Setting/UI/UIEdit/index.js b/awx/ui/src/screens/Setting/UI/UIEdit/index.js
deleted file mode 100644
index affad29bf8da..000000000000
--- a/awx/ui/src/screens/Setting/UI/UIEdit/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './UIEdit';
diff --git a/awx/ui/src/screens/Setting/UI/index.js b/awx/ui/src/screens/Setting/UI/index.js
deleted file mode 100644
index a33b447adfa2..000000000000
--- a/awx/ui/src/screens/Setting/UI/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './UI';
diff --git a/awx/ui/src/screens/Setting/index.js b/awx/ui/src/screens/Setting/index.js
deleted file mode 100644
index 63a5e968e443..000000000000
--- a/awx/ui/src/screens/Setting/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './Settings';
diff --git a/awx/ui/src/screens/Setting/shared/RevertAllAlert.js b/awx/ui/src/screens/Setting/shared/RevertAllAlert.js
deleted file mode 100644
index 496f71731827..000000000000
--- a/awx/ui/src/screens/Setting/shared/RevertAllAlert.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from 'react';
-
-import { t } from '@lingui/macro';
-import { Button } from '@patternfly/react-core';
-import AlertModal from 'components/AlertModal';
-
-function RevertAllAlert({ onClose, onRevertAll }) {
- return (
-
- {t`Revert all`}
- ,
-
- {t`Cancel`}
- ,
- ]}
- >
- {t`This will revert all configuration values on this page to
- their factory defaults. Are you sure you want to proceed?`}
-
- );
-}
-
-export default RevertAllAlert;
diff --git a/awx/ui/src/screens/Setting/shared/RevertAllAlert.test.js b/awx/ui/src/screens/Setting/shared/RevertAllAlert.test.js
deleted file mode 100644
index dea34403ccb3..000000000000
--- a/awx/ui/src/screens/Setting/shared/RevertAllAlert.test.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import RevertAllAlert from './RevertAllAlert';
-
-describe('RevertAllAlert', () => {
- test('renders the expected content', async () => {
- const wrapper = mountWithContexts(
- {}} onRevertAll={() => {}} />
- );
- expect(wrapper).toHaveLength(1);
- });
-});
diff --git a/awx/ui/src/screens/Setting/shared/RevertButton.js b/awx/ui/src/screens/Setting/shared/RevertButton.js
deleted file mode 100644
index 9a0eb40f7117..000000000000
--- a/awx/ui/src/screens/Setting/shared/RevertButton.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import { t } from '@lingui/macro';
-import { useField } from 'formik';
-import { Button, Tooltip } from '@patternfly/react-core';
-import styled from 'styled-components';
-
-const ButtonWrapper = styled.div`
- margin-left: auto;
- &&& {
- --pf-c-button--FontSize: var(--pf-c-button--m-small--FontSize);
- }
-`;
-
-function RevertButton({
- id,
- defaultValue,
- isDisabled = false,
- onRevertCallback = () => null,
-}) {
- const [field, meta, helpers] = useField(id);
- const initialValue = meta.initialValue ?? '';
- const currentValue = field.value;
- let isRevertable = true;
- let isMatch = false;
-
- if (currentValue === defaultValue && currentValue !== initialValue) {
- isRevertable = false;
- }
-
- if (currentValue === defaultValue && currentValue === initialValue) {
- isMatch = true;
- }
-
- const handleConfirm = () => {
- helpers.setValue(isRevertable ? defaultValue : initialValue);
- onRevertCallback();
- };
-
- const revertTooltipContent = isRevertable
- ? t`Revert to factory default.`
- : t`Restore initial value.`;
- const tooltipContent =
- isDisabled || isMatch
- ? t`Setting matches factory default.`
- : revertTooltipContent;
-
- return (
-
-
-
- {isRevertable ? t`Revert` : t`Undo`}
-
-
-
- );
-}
-
-RevertButton.propTypes = {
- id: PropTypes.string.isRequired,
-};
-
-export default RevertButton;
diff --git a/awx/ui/src/screens/Setting/shared/RevertButton.test.js b/awx/ui/src/screens/Setting/shared/RevertButton.test.js
deleted file mode 100644
index e2801843fe7f..000000000000
--- a/awx/ui/src/screens/Setting/shared/RevertButton.test.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import React from 'react';
-import { Formik } from 'formik';
-import { act } from 'react-dom/test-utils';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import RevertButton from './RevertButton';
-
-describe('RevertButton', () => {
- let wrapper;
-
- test('button text should display "Revert"', async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- expect(wrapper.find('button').text()).toEqual('Revert');
- });
-
- test('button text should display "Undo"', async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- expect(wrapper.find('button').text()).toEqual('Revert');
- });
-
- test('should revert value to default on button click', async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- expect(wrapper.find('button').text()).toEqual('Revert');
- await act(async () => {
- wrapper.find('button[aria-label="Revert"]').invoke('onClick')();
- });
- wrapper.update();
- expect(wrapper.find('button').text()).toEqual('Undo');
- });
-
- test('should be disabled when current value equals the initial and default values', async () => {
- wrapper = mountWithContexts(
-
-
-
- );
- expect(wrapper.find('button').text()).toEqual('Revert');
- expect(wrapper.find('button').props().disabled).toBe(true);
- });
-});
diff --git a/awx/ui/src/screens/Setting/shared/RevertFormActionGroup.js b/awx/ui/src/screens/Setting/shared/RevertFormActionGroup.js
deleted file mode 100644
index 7d5d213bbf4c..000000000000
--- a/awx/ui/src/screens/Setting/shared/RevertFormActionGroup.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import { t } from '@lingui/macro';
-import { ActionGroup, Button } from '@patternfly/react-core';
-import { FormFullWidthLayout } from 'components/FormLayout';
-
-const RevertFormActionGroup = ({ children, onCancel, onRevert, onSubmit }) => (
-
-
-
- {t`Save`}
-
-
- {t`Revert all to default`}
-
- {children}
-
- {t`Cancel`}
-
-
-
-);
-
-RevertFormActionGroup.propTypes = {
- onCancel: PropTypes.func.isRequired,
- onRevert: PropTypes.func.isRequired,
- onSubmit: PropTypes.func.isRequired,
-};
-
-export default RevertFormActionGroup;
diff --git a/awx/ui/src/screens/Setting/shared/RevertFormActionGroup.test.js b/awx/ui/src/screens/Setting/shared/RevertFormActionGroup.test.js
deleted file mode 100644
index 658491c5b361..000000000000
--- a/awx/ui/src/screens/Setting/shared/RevertFormActionGroup.test.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import React from 'react';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import RevertFormActionGroup from './RevertFormActionGroup';
-
-describe('RevertFormActionGroup', () => {
- test('should render the expected content', () => {
- const wrapper = mountWithContexts(
- {}}
- onCancel={() => {}}
- onRevert={() => {}}
- />
- );
- expect(wrapper).toHaveLength(1);
- });
-});
diff --git a/awx/ui/src/screens/Setting/shared/SettingDetail.js b/awx/ui/src/screens/Setting/shared/SettingDetail.js
deleted file mode 100644
index c133bfbe065c..000000000000
--- a/awx/ui/src/screens/Setting/shared/SettingDetail.js
+++ /dev/null
@@ -1,131 +0,0 @@
-import React from 'react';
-
-import { t } from '@lingui/macro';
-import { Detail } from 'components/DetailList';
-import CodeDetail from 'components/DetailList/CodeDetail';
-
-function sortObj(obj) {
- if (typeof obj !== 'object' || Array.isArray(obj) || obj === null) {
- return obj;
- }
- const sorted = {};
- Object.keys(obj)
- .sort()
- .forEach((key) => {
- sorted[key] = sortObj(obj[key]);
- });
- return sorted;
-}
-
-export default ({ helpText, id, label, type, unit = '', value }) => {
- const dataType = value === '$encrypted$' ? 'encrypted' : type;
- let detail = null;
-
- switch (dataType) {
- case 'nested object':
- detail = (
-
- );
- break;
- case 'list':
- detail = (
-
- );
- break;
- case 'certificate':
- detail = (
-
- );
- break;
- case 'image':
- detail = (
-
- )
- }
- />
- );
- break;
- case 'encrypted':
- detail = (
-
- );
- break;
- case 'boolean':
- detail = (
-
- );
- break;
- case 'choice':
- case 'field':
- case 'string':
- detail = (
-
- );
- break;
- case 'integer':
- detail = (
-
- );
- break;
- default:
- detail = null;
- }
- return detail;
-};
diff --git a/awx/ui/src/screens/Setting/shared/SharedFields.js b/awx/ui/src/screens/Setting/shared/SharedFields.js
deleted file mode 100644
index 06851e3b9e95..000000000000
--- a/awx/ui/src/screens/Setting/shared/SharedFields.js
+++ /dev/null
@@ -1,562 +0,0 @@
-import React, { useState } from 'react';
-import { shape, string } from 'prop-types';
-import { t } from '@lingui/macro';
-import { useField } from 'formik';
-import {
- Button,
- FileUpload,
- FormGroup as PFFormGroup,
- InputGroup,
- Switch,
- TextArea,
- TextInput,
- Tooltip,
- ButtonVariant,
-} from '@patternfly/react-core';
-import FileUploadIcon from '@patternfly/react-icons/dist/js/icons/file-upload-icon';
-import { ExclamationCircleIcon as PFExclamationCircleIcon } from '@patternfly/react-icons';
-import styled from 'styled-components';
-import AnsibleSelect from 'components/AnsibleSelect';
-import { ExecutionEnvironmentLookup } from 'components/Lookup';
-import CodeEditor from 'components/CodeEditor';
-import { PasswordInput } from 'components/FormField';
-import { FormFullWidthLayout } from 'components/FormLayout';
-import Popover from 'components/Popover';
-import { combine, minMaxValue, required, url, number } from 'util/validators';
-import AlertModal from 'components/AlertModal';
-import RevertButton from './RevertButton';
-
-const ExclamationCircleIcon = styled(PFExclamationCircleIcon)`
- && {
- color: var(--pf-global--danger-color--100);
- }
-`;
-
-const FormGroup = styled(PFFormGroup)`
- .pf-c-form__group-label {
- display: inline-flex;
- align-items: center;
- width: 100%;
- }
-`;
-
-const Selected = styled.div`
- display: flex;
- justify-content: space-between;
- background-color: white;
- border-bottom-color: var(--pf-global--BorderColor--200);
-`;
-
-const SettingGroup = ({
- children,
- defaultValue,
- fieldId,
- helperTextInvalid,
- isDisabled,
- isRequired,
- label,
- onRevertCallback,
- popoverContent,
- validated,
-}) => (
-
-
-
- >
- }
- >
- {children}
-
-);
-const BooleanField = ({
- ariaLabel = '',
- name,
- config,
- disabled = false,
- needsConfirmationModal,
- modalTitle,
-}) => {
- const [field, meta, helpers] = useField(name);
- const [isModalOpen, setIsModalOpen] = useState(false);
-
- return config ? (
-
- {isModalOpen && (
- {
- setIsModalOpen(false);
- }}
- actions={[
- {
- helpers.setValue(true);
- setIsModalOpen(false);
- }}
- >
- {t`Confirm`}
- ,
- {
- helpers.setValue(false);
- setIsModalOpen(false);
- }}
- >
- {t`Cancel`}
- ,
- ]}
- >{t`Are you sure you want to disable local authentication? Doing so could impact users' ability to log in and the system administrator's ability to reverse this change.`}
- )}
- {
- if (needsConfirmationModal && isOn) {
- setIsModalOpen(true);
- }
- helpers.setValue(!field.value);
- }}
- aria-label={ariaLabel || config.label}
- />
-
- ) : null;
-};
-BooleanField.propTypes = {
- name: string.isRequired,
- config: shape({}).isRequired,
-};
-
-const ChoiceField = ({ name, config, isRequired = false }) => {
- const validate = isRequired ? required(null) : null;
- const [field, meta] = useField({ name, validate });
- const isValid = !meta.error || !meta.touched;
-
- return config ? (
-
- ({
- label,
- value: value ?? '',
- key: value ?? index,
- })),
- ]}
- />
-
- ) : null;
-};
-ChoiceField.propTypes = {
- name: string.isRequired,
- config: shape({}).isRequired,
-};
-
-const EncryptedField = ({ name, config, isRequired = false }) => {
- const validate = isRequired ? required(null) : null;
- const [, meta] = useField({ name, validate });
- const isValid = !(meta.touched && meta.error);
-
- return config ? (
-
-
-
-
-
- ) : null;
-};
-EncryptedField.propTypes = {
- name: string.isRequired,
- config: shape({}).isRequired,
-};
-
-const ExecutionEnvField = ({ name, config, isRequired = false }) => {
- const [field, meta, helpers] = useField({ name });
- return config ? (
- helpers.setValue(config.default)}
- >
- {
- helpers.setValue(value, false);
- }}
- overrideLabel
- fieldName={name}
- />
-
- ) : null;
-};
-ExecutionEnvField.propTypes = {
- name: string.isRequired,
- config: shape({}).isRequired,
-};
-
-const InputAlertField = ({ name, config }) => {
- const [field, meta] = useField({ name });
- const isValid = !(meta.touched && meta.error);
- const [isModalOpen, setIsModalOpen] = useState(false);
- const [isDisable, setIsDisable] = useState(true);
-
- const handleSetIsOpen = () => {
- setIsModalOpen(true);
- };
-
- const handleEnableTextInput = () => {
- setIsDisable(false);
- };
-
- return config ? (
- <>
-
-
- {isDisable && (
-
- {
- handleSetIsOpen();
- }}
- ouiaId="confirm-edit-login-redirect"
- variant={ButtonVariant.control}
- >
-
-
-
- )}
- {
- field.onChange(event);
- }}
- isDisabled={isDisable}
- />
-
-
- {isModalOpen && isDisable && (
- {
- setIsModalOpen(false);
- }}
- actions={[
- {
- handleEnableTextInput();
- setIsModalOpen(false);
- }}
- >
- {t`Confirm`}
- ,
- {
- setIsModalOpen(false);
- }}
- >
- {t`Cancel`}
- ,
- ]}
- >
- {t`Are you sure you want to edit login redirect override URL? Doing so could impact users' ability to log in to the system once local authentication is also disabled.`}
-
- )}
- >
- ) : null;
-};
-
-InputAlertField.propTypes = {
- name: string.isRequired,
- config: shape({}).isRequired,
-};
-
-const InputField = ({ name, config, type = 'text', isRequired = false }) => {
- const min_value = config?.min_value ?? Number.MIN_SAFE_INTEGER;
- const max_value = config?.max_value ?? Number.MAX_SAFE_INTEGER;
- const validators = [
- ...(isRequired ? [required(null)] : []),
- ...(type === 'url' ? [url()] : []),
- ...(type === 'number' ? [number(), minMaxValue(min_value, max_value)] : []),
- ];
- const [field, meta] = useField({ name, validate: combine(validators) });
- const isValid = !(meta.touched && meta.error);
-
- return config ? (
-
- {
- field.onChange(event);
- }}
- />
-
- ) : null;
-};
-InputField.propTypes = {
- name: string.isRequired,
- config: shape({}),
-};
-InputField.defaultProps = {
- config: null,
-};
-
-const TextAreaField = ({ name, config, isRequired = false }) => {
- const validate = isRequired ? required(null) : null;
- const [field, meta] = useField({ name, validate });
- const isValid = !(meta.touched && meta.error);
-
- return config ? (
-
-
- ) : null;
-};
-TextAreaField.propTypes = {
- name: string.isRequired,
- config: shape({}).isRequired,
-};
-
-const ObjectField = ({ name, config, revertValue, isRequired = false }) => {
- const validate = isRequired ? required(null) : null;
- const [field, meta, helpers] = useField({ name, validate });
- const isValid = !(meta.touched && meta.error);
-
- const defaultRevertValue = config?.default
- ? JSON.stringify(config.default, null, 2)
- : null;
-
- return config ? (
-
-
- {
- helpers.setValue(value);
- }}
- placeholder={JSON.stringify(config?.placeholder, null, 2)}
- />
-
-
- ) : null;
-};
-ObjectField.propTypes = {
- name: string.isRequired,
- config: shape({}).isRequired,
-};
-
-const FileUploadIconWrapper = styled.div`
- margin: var(--pf-global--spacer--md);
-`;
-const FileUploadField = ({
- name,
- config,
- type = 'text',
- isRequired = false,
-}) => {
- const validate = isRequired ? required(null) : null;
- const [filename, setFilename] = useState('');
- const [fileIsUploading, setFileIsUploading] = useState(false);
- const [field, meta, helpers] = useField({ name, validate });
- const isValid = !(meta.touched && meta.error);
-
- return config ? (
-
- setFilename('')}
- >
- {
- helpers.setValue(value);
- setFilename(title);
- }}
- onReadStarted={() => setFileIsUploading(true)}
- onReadFinished={() => setFileIsUploading(false)}
- isLoading={fileIsUploading}
- allowEditingUploadedText
- validated={isValid ? 'default' : 'error'}
- hideDefaultPreview={type === 'dataURL'}
- >
- {type === 'dataURL' && (
-
- {field.value ? (
-
- ) : (
-
- )}
-
- )}
-
-
-
- ) : null;
-};
-FileUploadField.propTypes = {
- name: string.isRequired,
- config: shape({}).isRequired,
-};
-
-export {
- BooleanField,
- ChoiceField,
- EncryptedField,
- ExecutionEnvField,
- FileUploadField,
- InputField,
- ObjectField,
- TextAreaField,
- InputAlertField,
-};
diff --git a/awx/ui/src/screens/Setting/shared/SharedFields.test.js b/awx/ui/src/screens/Setting/shared/SharedFields.test.js
deleted file mode 100644
index b87a78200921..000000000000
--- a/awx/ui/src/screens/Setting/shared/SharedFields.test.js
+++ /dev/null
@@ -1,426 +0,0 @@
-import React from 'react';
-import { Formik } from 'formik';
-import { I18nProvider } from '@lingui/react';
-import { act } from 'react-dom/test-utils';
-import { i18n } from '@lingui/core';
-import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
-import {
- BooleanField,
- ChoiceField,
- EncryptedField,
- FileUploadField,
- InputAlertField,
- InputField,
- ObjectField,
- TextAreaField,
-} from './SharedFields';
-import en from '../../../locales/en/messages';
-
-describe('Setting form fields', () => {
- test('BooleanField renders the expected content', async () => {
- i18n.loadLocaleData({ en: { plurals: en } });
- i18n.load({ en });
- i18n.activate('en');
- const wrapper = mountWithContexts(
-
-
- {() => (
-
- )}
-
-
- );
- expect(wrapper.find('Switch')).toHaveLength(1);
- expect(wrapper.find('Switch').prop('isChecked')).toBe(true);
- expect(wrapper.find('Switch').prop('isDisabled')).toBe(false);
- await act(async () => {
- wrapper.find('Switch').invoke('onChange')();
- });
- wrapper.update();
- expect(wrapper.find('Switch').prop('isChecked')).toBe(false);
- });
-
- test('ChoiceField renders unrequired form field', async () => {
- const wrapper = mountWithContexts(
-
- {() => (
-
- )}
-
- );
- expect(wrapper.find('FormSelect')).toHaveLength(1);
- expect(wrapper.find('.pf-c-form__label-required')).toHaveLength(0);
- });
-
- test('EncryptedField renders the expected content', async () => {
- const wrapper = mountWithContexts(
-
- {() => (
-
- )}
-
- );
- expect(wrapper.find('PasswordInput')).toHaveLength(1);
- });
-
- test('InputField renders the expected content', async () => {
- const wrapper = mountWithContexts(
-
- {() => (
-
- )}
-
- );
- expect(wrapper.find('TextInputBase')).toHaveLength(1);
- expect(wrapper.find('TextInputBase').prop('value')).toEqual('');
- await act(async () => {
- wrapper.find('TextInputBase').invoke('onChange')(null, {
- target: {
- name: 'text',
- value: 'foo',
- },
- });
- });
- wrapper.update();
- expect(wrapper.find('TextInputBase').prop('value')).toEqual('foo');
- });
-
- test('InputField should revert to expected default value', async () => {
- const wrapper = mountWithContexts(
-
- {() => (
-
- )}
-
- );
- expect(wrapper.find('TextInputBase')).toHaveLength(1);
- expect(wrapper.find('TextInputBase').prop('value')).toEqual(5);
- await act(async () => {
- wrapper.find('button[aria-label="Revert"]').invoke('onClick')();
- });
- wrapper.update();
- expect(wrapper.find('TextInputBase').prop('value')).toEqual(0);
- });
-
- test('InputAlertField initially renders disable TextInput', async () => {
- const wrapper = mountWithContexts(
-
- {() => (
-
- )}
-
- );
- expect(wrapper.find('TextInput')).toHaveLength(1);
- expect(wrapper.find('TextInput').prop('value')).toEqual('');
- expect(wrapper.find('TextInput').prop('isDisabled')).toBe(true);
- });
-
- test('TextAreaField renders the expected content', async () => {
- const wrapper = mountWithContexts(
-
- {() => (
-
- )}
-
- );
- expect(wrapper.find('textarea')).toHaveLength(1);
- expect(wrapper.find('textarea#mock_textarea').prop('value')).toEqual('');
- await act(async () => {
- wrapper.find('textarea#mock_textarea').simulate('change', {
- target: { value: 'new textarea value', name: 'mock_textarea' },
- });
- });
- wrapper.update();
- expect(wrapper.find('textarea').prop('value')).toEqual(
- 'new textarea value'
- );
- });
-
- test('ObjectField renders the expected content', async () => {
- const wrapper = mountWithContexts(
-
- {() => (
-
- )}
-
- );
- expect(wrapper.find('CodeEditor')).toHaveLength(1);
- expect(wrapper.find('CodeEditor').prop('value')).toBe(
- '["one", "two", "three"]'
- );
- await act(async () => {
- wrapper.find('CodeEditor').invoke('onChange')('[]');
- });
- wrapper.update();
- expect(wrapper.find('CodeEditor').prop('value')).toBe('[]');
- });
-
- test('FileUploadField renders the expected content', async () => {
- const wrapper = mountWithContexts(
-
- {() => (
-
- )}
-
- );
-
- expect(
- wrapper.find('FileUploadField[value="mock file value"]')
- ).toHaveLength(1);
- expect(wrapper.find('label').text()).toEqual('mock file label');
- expect(wrapper.find('input#mock_file-filename').prop('value')).toEqual('');
- await act(async () => {
- wrapper.find('FileUpload').invoke('onChange')(
- {
- text: () =>
- '-----BEGIN PRIVATE KEY-----\\nAAAAAAAAAAAAAA\\n-----END PRIVATE KEY-----\\n',
- },
- 'new file name'
- );
- });
- wrapper.update();
- expect(wrapper.find('input#mock_file-filename').prop('value')).toEqual(
- 'new file name'
- );
- await act(async () => {
- wrapper.find('button[aria-label="Revert"]').invoke('onClick')();
- });
- wrapper.update();
- expect(wrapper.find('input#mock_file-filename').prop('value')).toEqual('');
- });
- test('should render confirmation modal when toggle on for disable local auth', async () => {
- const wrapper = mountWithContexts(
-
- {() => (
-
- )}
-
- );
- expect(wrapper.find('Switch')).toHaveLength(1);
- expect(wrapper.find('Switch').prop('isChecked')).toBe(false);
- expect(wrapper.find('Switch').prop('isDisabled')).toBe(false);
- await act(async () => {
- wrapper.find('Switch').invoke('onChange')(true);
- });
- wrapper.update();
-
- expect(wrapper.find('AlertModal')).toHaveLength(1);
- expect(
- wrapper.find('BooleanField[name="DISABLE_LOCAL_AUTH"]')
- ).toHaveLength(1);
- await act(async () =>
- wrapper
- .find('Button[ouiaId="confirm-misc-settings-modal"]')
- .prop('onClick')()
- );
- wrapper.update();
- expect(wrapper.find('AlertModal')).toHaveLength(0);
- expect(wrapper.find('Switch').prop('isChecked')).toBe(true);
- });
-
- test('should not render confirmation modal when toggling off', async () => {
- const wrapper = mountWithContexts(
-
- {() => (
-
- )}
-
- );
- expect(wrapper.find('Switch')).toHaveLength(1);
- expect(wrapper.find('Switch').prop('isChecked')).toBe(true);
- expect(wrapper.find('Switch').prop('isDisabled')).toBe(false);
- await act(async () => {
- wrapper.find('Switch').invoke('onChange')(false);
- });
- wrapper.update();
- expect(wrapper.find('AlertModal')).toHaveLength(0);
- expect(wrapper.find('Switch').prop('isChecked')).toBe(false);
- });
-
- test('should not toggle disable local auth', async () => {
- const wrapper = mountWithContexts(
-
- {() => (
-
- )}
-
- );
- expect(wrapper.find('Switch')).toHaveLength(1);
- expect(wrapper.find('Switch').prop('isChecked')).toBe(false);
- expect(wrapper.find('Switch').prop('isDisabled')).toBe(false);
- await act(async () => {
- wrapper.find('Switch').invoke('onChange')(true);
- });
- wrapper.update();
-
- expect(wrapper.find('AlertModal')).toHaveLength(1);
- await act(async () =>
- wrapper
- .find('Button[ouiaId="cancel-misc-settings-modal"]')
- .prop('onClick')()
- );
- wrapper.update();
-
- expect(wrapper.find('AlertModal')).toHaveLength(0);
- expect(wrapper.find('Switch').prop('isChecked')).toBe(false);
- });
-});
diff --git a/awx/ui/src/screens/Setting/shared/data.allSettingOptions.json b/awx/ui/src/screens/Setting/shared/data.allSettingOptions.json
deleted file mode 100644
index 586ac3d7ef73..000000000000
--- a/awx/ui/src/screens/Setting/shared/data.allSettingOptions.json
+++ /dev/null
@@ -1,6505 +0,0 @@
-{
- "name": "Setting Detail",
- "actions": {
- "PUT": {
- "ACTIVITY_STREAM_ENABLED": {
- "type": "boolean",
- "required": true,
- "label": "Enable Activity Stream",
- "help_text": "Enable capturing activity for the activity stream.",
- "category": "System",
- "category_slug": "system",
- "default": true
- },
- "ACTIVITY_STREAM_ENABLED_FOR_INVENTORY_SYNC": {
- "type": "boolean",
- "required": true,
- "label": "Enable Activity Stream for Inventory Sync",
- "help_text": "Enable capturing activity for the activity stream when running inventory sync.",
- "category": "System",
- "category_slug": "system",
- "default": false
- },
- "ORG_ADMINS_CAN_SEE_ALL_USERS": {
- "type": "boolean",
- "required": true,
- "label": "All Users Visible to Organization Admins",
- "help_text": "Controls whether any Organization Admin can view all users and teams, even those not associated with their Organization.",
- "category": "System",
- "category_slug": "system",
- "default": true
- },
- "MANAGE_ORGANIZATION_AUTH": {
- "type": "boolean",
- "required": true,
- "label": "Organization Admins Can Manage Users and Teams",
- "help_text": "Controls whether any Organization Admin has the privileges to create and manage users and teams. You may want to disable this ability if you are using an LDAP or SAML integration.",
- "category": "System",
- "category_slug": "system",
- "default": true
- },
- "TOWER_URL_BASE": {
- "type": "string",
- "required": true,
- "label": "Base URL of the service",
- "help_text": "This setting is used by services like notifications to render a valid url to the service.",
- "category": "System",
- "category_slug": "system",
- "default": "https://localhost:8043"
- },
- "REMOTE_HOST_HEADERS": {
- "type": "list",
- "required": true,
- "label": "Remote Host Headers",
- "help_text": "HTTP headers and meta keys to search to determine remote host name or IP. Add additional items to this list, such as \"HTTP_X_FORWARDED_FOR\", if behind a reverse proxy. See the \"Proxy Support\" section of the AAP Installation guide for more details.",
- "category": "System",
- "category_slug": "system",
- "default": ["REMOTE_ADDR", "REMOTE_HOST"],
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- },
- "PROXY_IP_ALLOWED_LIST": {
- "type": "list",
- "required": true,
- "label": "Proxy IP Allowed List",
- "help_text": "If the service is behind a reverse proxy/load balancer, use this setting to configure the proxy IP addresses from which the service should trust custom REMOTE_HOST_HEADERS header values. If this setting is an empty list (the default), the headers specified by REMOTE_HOST_HEADERS will be trusted unconditionally')",
- "category": "System",
- "category_slug": "system",
- "default": [],
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- },
- "REDHAT_USERNAME": {
- "type": "string",
- "required": false,
- "label": "Red Hat customer username",
- "help_text": "This username is used to send data to Automation Analytics",
- "category": "System",
- "category_slug": "system",
- "default": ""
- },
- "REDHAT_PASSWORD": {
- "type": "string",
- "required": false,
- "label": "Red Hat customer password",
- "help_text": "This password is used to send data to Automation Analytics",
- "category": "System",
- "category_slug": "system",
- "default": ""
- },
- "SUBSCRIPTIONS_USERNAME": {
- "type": "string",
- "required": false,
- "label": "Red Hat or Satellite username",
- "help_text": "This username is used to retrieve subscription and content information",
- "category": "System",
- "category_slug": "system",
- "default": ""
- },
- "SUBSCRIPTIONS_PASSWORD": {
- "type": "string",
- "required": false,
- "label": "Red Hat or Satellite password",
- "help_text": "This password is used to retrieve subscription and content information",
- "category": "System",
- "category_slug": "system",
- "default": ""
- },
- "AUTOMATION_ANALYTICS_URL": {
- "type": "string",
- "required": false,
- "label": "Automation Analytics upload URL",
- "help_text": "This setting is used to to configure the upload URL for data collection for Automation Analytics.",
- "category": "System",
- "category_slug": "system",
- "default": "https://example.com"
- },
- "DEFAULT_EXECUTION_ENVIRONMENT": {
- "type": "field",
- "required": false,
- "label": "Global default execution environment",
- "help_text": "The Execution Environment to be used when one has not been configured for a job template.",
- "category": "System",
- "category_slug": "system",
- "default": null
- },
- "CUSTOM_VENV_PATHS": {
- "type": "list",
- "required": false,
- "label": "Custom virtual environment paths",
- "help_text": "Paths where Tower will look for custom virtual environments (in addition to /var/lib/awx/venv/). Enter one path per line.",
- "category": "System",
- "category_slug": "system",
- "default": [],
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- },
- "AD_HOC_COMMANDS": {
- "type": "list",
- "required": false,
- "label": "Ansible Modules Allowed for Ad Hoc Jobs",
- "help_text": "List of modules allowed to be used by ad-hoc jobs.",
- "category": "Jobs",
- "category_slug": "jobs",
- "default": [
- "command",
- "shell",
- "yum",
- "apt",
- "apt_key",
- "apt_repository",
- "apt_rpm",
- "service",
- "group",
- "user",
- "mount",
- "ping",
- "selinux",
- "setup",
- "win_ping",
- "win_service",
- "win_updates",
- "win_group",
- "win_user"
- ],
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- },
- "ALLOW_JINJA_IN_EXTRA_VARS": {
- "type": "choice",
- "required": true,
- "label": "When can extra variables contain Jinja templates?",
- "help_text": "Ansible allows variable substitution via the Jinja2 templating language for --extra-vars. This poses a potential security risk where users with the ability to specify extra vars at job launch time can use Jinja2 templates to run arbitrary Python. It is recommended that this value be set to \"template\" or \"never\".",
- "category": "Jobs",
- "category_slug": "jobs",
- "default": "template",
- "choices": [
- ["always", "Always"],
- ["never", "Never"],
- ["template", "Only On Job Template Definitions"]
- ]
- },
- "AWX_ISOLATION_BASE_PATH": {
- "type": "string",
- "required": true,
- "label": "Job execution path",
- "help_text": "The directory in which the service will create new temporary directories for job execution and isolation (such as credential files).",
- "category": "Jobs",
- "category_slug": "jobs",
- "default": "/tmp"
- },
- "AWX_ISOLATION_SHOW_PATHS": {
- "type": "list",
- "required": false,
- "label": "Paths to expose to isolated jobs",
- "help_text": "List of paths that would otherwise be hidden to expose to isolated jobs. Enter one path per line.",
- "category": "Jobs",
- "category_slug": "jobs",
- "default": [],
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- },
- "AWX_TASK_ENV": {
- "type": "nested object",
- "required": false,
- "label": "Extra Environment Variables",
- "help_text": "Additional environment variables set for playbook runs, inventory updates, project updates, and notification sending.",
- "category": "Jobs",
- "category_slug": "jobs",
- "placeholder": {
- "HTTP_PROXY": "myproxy.local:8080"
- },
- "default": {},
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- },
- "GALAXY_TASK_ENV": {
- "type": "nested object",
- "required": true,
- "label": "Environment Variables for Galaxy Commands",
- "help_text": "Additional environment variables set for invocations of ansible-galaxy within project updates. Useful if you must use a proxy server for ansible-galaxy but not git.",
- "category": "Jobs",
- "category_slug": "jobs",
- "placeholder": {
- "HTTP_PROXY": "myproxy.local:8080"
- },
- "default": {
- "ANSIBLE_FORCE_COLOR": "false",
- "GIT_SSH_COMMAND": "ssh -o StrictHostKeyChecking=no"
- },
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- },
- "INSIGHTS_TRACKING_STATE": {
- "type": "boolean",
- "required": false,
- "label": "Gather data for Automation Analytics",
- "help_text": "Enables the service to gather data on automation and send it to Automation Analytics.",
- "category": "System",
- "category_slug": "system",
- "default": false
- },
- "PROJECT_UPDATE_VVV": {
- "type": "boolean",
- "required": true,
- "label": "Run Project Updates With Higher Verbosity",
- "help_text": "Adds the CLI -vvv flag to ansible-playbook runs of project_update.yml used for project updates.",
- "category": "Jobs",
- "category_slug": "jobs",
- "default": false
- },
- "AWX_ROLES_ENABLED": {
- "type": "boolean",
- "required": false,
- "label": "Enable Role Download",
- "help_text": "Allows roles to be dynamically downloaded from a requirements.yml file for SCM projects.",
- "category": "Jobs",
- "category_slug": "jobs",
- "default": true
- },
- "AWX_COLLECTIONS_ENABLED": {
- "type": "boolean",
- "required": false,
- "label": "Enable Collection(s) Download",
- "help_text": "Allows collections to be dynamically downloaded from a requirements.yml file for SCM projects.",
- "category": "Jobs",
- "category_slug": "jobs",
- "default": true
- },
- "AWX_SHOW_PLAYBOOK_LINKS": {
- "type": "boolean",
- "required": false,
- "label": "Follow symlinks",
- "help_text": "Follow symbolic links when scanning for playbooks. Be aware that setting this to True can lead to infinite recursion if a link points to a parent directory of itself.",
- "category": "Jobs",
- "category_slug": "jobs",
- "default": false
- },
- "AWX_MOUNT_ISOLATED_PATHS_ON_K8S": {
- "type": "boolean",
- "required": false,
- "label": "Expose host paths for Container Groups",
- "help_text": "Expose paths via hostPath for the Pods created by a Container Group. HostPath volumes present many security risks, and it is a best practice to avoid the use of HostPaths when possible. ",
- "category": "Jobs",
- "category_slug": "jobs",
- "default": false
- },
- "GALAXY_IGNORE_CERTS": {
- "type": "boolean",
- "required": false,
- "label": "Ignore Ansible Galaxy SSL Certificate Verification",
- "help_text": "If set to true, certificate validation will not be done when installing content from any Galaxy server.",
- "category": "Jobs",
- "category_slug": "jobs",
- "default": false
- },
- "STDOUT_MAX_BYTES_DISPLAY": {
- "type": "integer",
- "required": true,
- "label": "Standard Output Maximum Display Size",
- "help_text": "Maximum Size of Standard Output in bytes to display before requiring the output be downloaded.",
- "min_value": 0,
- "category": "Jobs",
- "category_slug": "jobs",
- "default": 1048576
- },
- "EVENT_STDOUT_MAX_BYTES_DISPLAY": {
- "type": "integer",
- "required": true,
- "label": "Job Event Standard Output Maximum Display Size",
- "help_text": "Maximum Size of Standard Output in bytes to display for a single job or ad hoc command event. `stdout` will end with `…` when truncated.",
- "min_value": 0,
- "category": "Jobs",
- "category_slug": "jobs",
- "default": 1024
- },
- "SCHEDULE_MAX_JOBS": {
- "type": "integer",
- "required": true,
- "label": "Maximum Scheduled Jobs",
- "help_text": "Maximum number of the same job template that can be waiting to run when launching from a schedule before no more are created.",
- "min_value": 1,
- "category": "Jobs",
- "category_slug": "jobs",
- "default": 10
- },
- "AWX_ANSIBLE_CALLBACK_PLUGINS": {
- "type": "list",
- "required": false,
- "label": "Ansible Callback Plugins",
- "help_text": "List of paths to search for extra callback plugins to be used when running jobs. Enter one path per line.",
- "category": "Jobs",
- "category_slug": "jobs",
- "default": [],
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- },
- "DEFAULT_JOB_TIMEOUT": {
- "type": "integer",
- "required": false,
- "label": "Default Job Timeout",
- "help_text": "Maximum time in seconds to allow jobs to run. Use value of 0 to indicate that no timeout should be imposed. A timeout set on an individual job template will override this.",
- "min_value": 0,
- "category": "Jobs",
- "category_slug": "jobs",
- "unit": "seconds",
- "default": 0
- },
- "DEFAULT_JOB_IDLE_TIMEOUT": {
- "type": "integer",
- "required": false,
- "label": "Default Job Idle Timeout",
- "help_text": "If no output is detected from ansible in this number of seconds the execution will be terminated. Use value of 0 to used default idle_timeout is 600s.",
- "min_value": 0,
- "category": "Jobs",
- "category_slug": "jobs",
- "unit": "seconds",
- "default": 0
- },
- "DEFAULT_INVENTORY_UPDATE_TIMEOUT": {
- "type": "integer",
- "required": false,
- "label": "Default Inventory Update Timeout",
- "help_text": "Maximum time in seconds to allow inventory updates to run. Use value of 0 to indicate that no timeout should be imposed. A timeout set on an individual inventory source will override this.",
- "min_value": 0,
- "category": "Jobs",
- "category_slug": "jobs",
- "unit": "seconds",
- "default": 0
- },
- "DEFAULT_PROJECT_UPDATE_TIMEOUT": {
- "type": "integer",
- "required": false,
- "label": "Default Project Update Timeout",
- "help_text": "Maximum time in seconds to allow project updates to run. Use value of 0 to indicate that no timeout should be imposed. A timeout set on an individual project will override this.",
- "min_value": 0,
- "category": "Jobs",
- "category_slug": "jobs",
- "unit": "seconds",
- "default": 0
- },
- "ANSIBLE_FACT_CACHE_TIMEOUT": {
- "type": "integer",
- "required": false,
- "label": "Per-Host Ansible Fact Cache Timeout",
- "help_text": "Maximum time, in seconds, that stored Ansible facts are considered valid since the last time they were modified. Only valid, non-stale, facts will be accessible by a playbook. Note, this does not influence the deletion of ansible_facts from the database. Use a value of 0 to indicate that no timeout should be imposed.",
- "min_value": 0,
- "category": "Jobs",
- "category_slug": "jobs",
- "unit": "seconds",
- "default": 0
- },
- "MAX_FORKS": {
- "type": "integer",
- "required": false,
- "label": "Maximum number of forks per job",
- "help_text": "Saving a Job Template with more than this number of forks will result in an error. When set to 0, no limit is applied.",
- "category": "Jobs",
- "category_slug": "jobs",
- "default": 200
- },
- "LOG_AGGREGATOR_HOST": {
- "type": "string",
- "required": false,
- "label": "Logging Aggregator",
- "help_text": "Hostname/IP where external logs will be sent to.",
- "category": "Logging",
- "category_slug": "logging",
- "default": null
- },
- "LOG_AGGREGATOR_PORT": {
- "type": "integer",
- "required": false,
- "label": "Logging Aggregator Port",
- "help_text": "Port on Logging Aggregator to send logs to (if required and not provided in Logging Aggregator).",
- "category": "Logging",
- "category_slug": "logging",
- "default": null
- },
- "LOG_AGGREGATOR_TYPE": {
- "type": "choice",
- "required": false,
- "label": "Logging Aggregator Type",
- "help_text": "Format messages for the chosen log aggregator.",
- "category": "Logging",
- "category_slug": "logging",
- "default": null,
- "choices": [
- [null, "---------"],
- ["logstash", "logstash"],
- ["splunk", "splunk"],
- ["loggly", "loggly"],
- ["sumologic", "sumologic"],
- ["other", "other"]
- ]
- },
- "LOG_AGGREGATOR_USERNAME": {
- "type": "string",
- "required": false,
- "label": "Logging Aggregator Username",
- "help_text": "Username for external log aggregator (if required; HTTP/s only).",
- "category": "Logging",
- "category_slug": "logging",
- "default": ""
- },
- "LOG_AGGREGATOR_PASSWORD": {
- "type": "string",
- "required": false,
- "label": "Logging Aggregator Password/Token",
- "help_text": "Password or authentication token for external log aggregator (if required; HTTP/s only).",
- "category": "Logging",
- "category_slug": "logging",
- "default": ""
- },
- "LOG_AGGREGATOR_LOGGERS": {
- "type": "list",
- "required": false,
- "label": "Loggers Sending Data to Log Aggregator Form",
- "help_text": "List of loggers that will send HTTP logs to the collector, these can include any or all of: \nawx - service logs\nactivity_stream - activity stream records\njob_events - callback data from Ansible job events\nsystem_tracking - facts gathered from scan jobs.",
- "category": "Logging",
- "category_slug": "logging",
- "default": ["awx", "activity_stream", "job_events", "system_tracking"],
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- },
- "LOG_AGGREGATOR_INDIVIDUAL_FACTS": {
- "type": "boolean",
- "required": false,
- "label": "Log System Tracking Facts Individually",
- "help_text": "If set, system tracking facts will be sent for each package, service, or other item found in a scan, allowing for greater search query granularity. If unset, facts will be sent as a single dictionary, allowing for greater efficiency in fact processing.",
- "category": "Logging",
- "category_slug": "logging",
- "default": false
- },
- "LOG_AGGREGATOR_ENABLED": {
- "type": "boolean",
- "required": false,
- "label": "Enable External Logging",
- "help_text": "Enable sending logs to external log aggregator.",
- "category": "Logging",
- "category_slug": "logging",
- "default": false
- },
- "LOG_AGGREGATOR_TOWER_UUID": {
- "type": "string",
- "required": false,
- "label": "Cluster-wide unique identifier.",
- "help_text": "Useful to uniquely identify instances.",
- "category": "Logging",
- "category_slug": "logging",
- "default": ""
- },
- "LOG_AGGREGATOR_PROTOCOL": {
- "type": "choice",
- "required": false,
- "label": "Logging Aggregator Protocol",
- "help_text": "Protocol used to communicate with log aggregator. HTTPS/HTTP assumes HTTPS unless http:// is explicitly used in the Logging Aggregator hostname.",
- "category": "Logging",
- "category_slug": "logging",
- "default": "https",
- "choices": [
- ["https", "HTTPS/HTTP"],
- ["tcp", "TCP"],
- ["udp", "UDP"]
- ]
- },
- "LOG_AGGREGATOR_TCP_TIMEOUT": {
- "type": "integer",
- "required": false,
- "label": "TCP Connection Timeout",
- "help_text": "Number of seconds for a TCP connection to external log aggregator to timeout. Applies to HTTPS and TCP log aggregator protocols.",
- "category": "Logging",
- "category_slug": "logging",
- "unit": "seconds",
- "default": 5
- },
- "LOG_AGGREGATOR_VERIFY_CERT": {
- "type": "boolean",
- "required": false,
- "label": "Enable/disable HTTPS certificate verification",
- "help_text": "Flag to control enable/disable of certificate verification when LOG_AGGREGATOR_PROTOCOL is \"https\". If enabled, the log handler will verify certificate sent by external log aggregator before establishing connection.",
- "category": "Logging",
- "category_slug": "logging",
- "default": true
- },
- "LOG_AGGREGATOR_LEVEL": {
- "type": "choice",
- "required": false,
- "label": "Logging Aggregator Level Threshold",
- "help_text": "Level threshold used by log handler. Severities from lowest to highest are DEBUG, INFO, WARNING, ERROR, CRITICAL. Messages less severe than the threshold will be ignored by log handler. (messages under category awx.anlytics ignore this setting)",
- "category": "Logging",
- "category_slug": "logging",
- "default": "INFO",
- "choices": [
- ["DEBUG", "DEBUG"],
- ["INFO", "INFO"],
- ["WARNING", "WARNING"],
- ["ERROR", "ERROR"],
- ["CRITICAL", "CRITICAL"]
- ]
- },
- "LOG_AGGREGATOR_MAX_DISK_USAGE_GB": {
- "type": "integer",
- "required": false,
- "label": "Maximum disk persistance for external log aggregation (in GB)",
- "help_text": "Amount of data to store (in gigabytes) during an outage of the external log aggregator (defaults to 1). Equivalent to the rsyslogd queue.maxdiskspace setting.",
- "min_value": 1,
- "category": "Logging",
- "category_slug": "logging",
- "default": 1
- },
- "LOG_AGGREGATOR_MAX_DISK_USAGE_PATH": {
- "type": "string",
- "required": false,
- "label": "File system location for rsyslogd disk persistence",
- "help_text": "Location to persist logs that should be retried after an outage of the external log aggregator (defaults to /var/lib/awx). Equivalent to the rsyslogd queue.spoolDirectory setting.",
- "category": "Logging",
- "category_slug": "logging",
- "default": "/var/lib/awx"
- },
- "LOG_AGGREGATOR_RSYSLOGD_DEBUG": {
- "type": "boolean",
- "required": false,
- "label": "Enable rsyslogd debugging",
- "help_text": "Enabled high verbosity debugging for rsyslogd. Useful for debugging connection issues for external log aggregation.",
- "category": "Logging",
- "category_slug": "logging",
- "default": false
- },
- "API_400_ERROR_LOG_FORMAT": {
- "type": "string",
- "required": false,
- "label": "Log Format For API 4XX Errors",
- "help_text": "The format of logged messages when an API 4XX error occurs, the following variables will be substituted: \nstatus_code - The HTTP status code of the error\nuser_name - The user name attempting to use the API\nurl_path - The URL path to the API endpoint called\nremote_addr - The remote address seen for the user\nerror - The error set by the api endpoint\nVariables need to be in the format {}.",
- "category": "Logging",
- "category_slug": "logging",
- "default": "status {status_code} received by user {user_name} attempting to access {url_path} from {remote_addr}"
- },
- "AUTOMATION_ANALYTICS_LAST_GATHER": {
- "type": "datetime",
- "required": true,
- "label": "Last gather date for Automation Analytics.",
- "category": "System",
- "category_slug": "system",
- "default": null
- },
- "AUTOMATION_ANALYTICS_LAST_ENTRIES": {
- "type": "string",
- "required": false,
- "label": "Last gathered entries from the data collection service of Automation Analytics",
- "category": "System",
- "category_slug": "system",
- "default": ""
- },
- "AUTOMATION_ANALYTICS_GATHER_INTERVAL": {
- "type": "integer",
- "required": false,
- "label": "Automation Analytics Gather Interval",
- "help_text": "Interval (in seconds) between data gathering.",
- "min_value": 1800,
- "category": "System",
- "category_slug": "system",
- "unit": "seconds",
- "default": 14400
- },
- "SESSION_COOKIE_AGE": {
- "type": "integer",
- "required": true,
- "label": "Idle Time Force Log Out",
- "help_text": "Number of seconds that a user is inactive before they will need to login again.",
- "min_value": 60,
- "max_value": 30000000000,
- "category": "Authentication",
- "category_slug": "authentication",
- "unit": "seconds",
- "default": 1800
- },
- "SESSIONS_PER_USER": {
- "type": "integer",
- "required": true,
- "label": "Maximum number of simultaneous logged in sessions",
- "help_text": "Maximum number of simultaneous logged in sessions a user may have. To disable enter -1.",
- "min_value": -1,
- "category": "Authentication",
- "category_slug": "authentication",
- "default": -1
- },
- "DISABLE_LOCAL_AUTH": {
- "type": "boolean",
- "required": true,
- "label": "Disable the built-in authentication system",
- "help_text": "Controls whether users are prevented from using the built-in authentication system. You probably want to do this if you are using an LDAP or SAML integration.",
- "category": "Authentication",
- "category_slug": "authentication",
- "default": false
- },
- "AUTH_BASIC_ENABLED": {
- "type": "boolean",
- "required": true,
- "label": "Enable HTTP Basic Auth",
- "help_text": "Enable HTTP Basic Auth for the API Browser.",
- "category": "Authentication",
- "category_slug": "authentication",
- "default": true
- },
- "OAUTH2_PROVIDER": {
- "type": "nested object",
- "required": false,
- "label": "OAuth 2 Timeout Settings",
- "help_text": "Dictionary for customizing OAuth 2 timeouts, available items are `ACCESS_TOKEN_EXPIRE_SECONDS`, the duration of access tokens in the number of seconds, `AUTHORIZATION_CODE_EXPIRE_SECONDS`, the duration of authorization codes in the number of seconds, and `REFRESH_TOKEN_EXPIRE_SECONDS`, the duration of refresh tokens, after expired access tokens, in the number of seconds.",
- "category": "Authentication",
- "category_slug": "authentication",
- "unit": "seconds",
- "default": {
- "ACCESS_TOKEN_EXPIRE_SECONDS": 31536000000,
- "AUTHORIZATION_CODE_EXPIRE_SECONDS": 600,
- "REFRESH_TOKEN_EXPIRE_SECONDS": 2628000
- },
- "child": {
- "type": "integer",
- "required": true,
- "read_only": false,
- "min_value": 1
- }
- },
- "ALLOW_OAUTH2_FOR_EXTERNAL_USERS": {
- "type": "boolean",
- "required": false,
- "label": "Allow External Users to Create OAuth2 Tokens",
- "help_text": "For security reasons, users from external auth providers (LDAP, SAML, SSO, Radius, and others) are not allowed to create OAuth2 tokens. To change this behavior, enable this setting. Existing tokens will not be deleted when this setting is toggled off.",
- "category": "Authentication",
- "category_slug": "authentication",
- "default": false
- },
- "LOGIN_REDIRECT_OVERRIDE": {
- "type": "string",
- "required": false,
- "label": "Login redirect override URL",
- "help_text": "URL to which unauthorized users will be redirected to log in. If blank, users will be sent to the login page.",
- "category": "Authentication",
- "category_slug": "authentication",
- "default": ""
- },
- "PENDO_TRACKING_STATE": {
- "type": "choice",
- "required": true,
- "label": "User Analytics Tracking State",
- "help_text": "Enable or Disable User Analytics Tracking.",
- "category": "UI",
- "category_slug": "ui",
- "default": "off",
- "choices": [
- ["off", "Off"],
- ["anonymous", "Anonymous"],
- ["detailed", "Detailed"]
- ]
- },
- "CUSTOM_LOGIN_INFO": {
- "type": "string",
- "required": false,
- "label": "Custom Login Info",
- "help_text": "If needed, you can add specific information (such as a legal notice or a disclaimer) to a text box in the login modal using this setting. Any content added must be in plain text or an HTML fragment, as other markup languages are not supported.",
- "category": "UI",
- "category_slug": "ui",
- "default": ""
- },
- "CUSTOM_LOGO": {
- "type": "string",
- "required": false,
- "label": "Custom Logo",
- "help_text": "To set up a custom logo, provide a file that you create. For the custom logo to look its best, use a .png file with a transparent background. GIF, PNG and JPEG formats are supported.",
- "category": "UI",
- "category_slug": "ui",
- "placeholder": "data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=",
- "default": ""
- },
- "MAX_UI_JOB_EVENTS": {
- "type": "integer",
- "required": true,
- "label": "Max Job Events Retrieved by UI",
- "help_text": "Maximum number of job events for the UI to retrieve within a single request.",
- "min_value": 100,
- "category": "UI",
- "category_slug": "ui",
- "default": 4000
- },
- "UI_LIVE_UPDATES_ENABLED": {
- "type": "boolean",
- "required": true,
- "label": "Enable Live Updates in the UI",
- "help_text": "If disabled, the page will not refresh when events are received. Reloading the page will be required to get the latest details.",
- "category": "UI",
- "category_slug": "ui",
- "default": true
- },
- "SOCIAL_AUTH_ORGANIZATION_MAP": {
- "type": "nested object",
- "required": false,
- "label": "Social Auth Organization Map",
- "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.",
- "category": "Authentication",
- "category_slug": "authentication",
- "placeholder": {
- "Default": {
- "users": true
- },
- "Test Org": {
- "admins": ["admin@example.com"],
- "auditors": ["auditor@example.com"],
- "users": true
- },
- "Test Org 2": {
- "admins": ["admin@example.com", "/^tower-[^@]+*?@.*$/"],
- "remove_admins": true,
- "users": "/^[^@].*?@example\\.com$/i",
- "remove_users": true
- }
- },
- "default": null,
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_TEAM_MAP": {
- "type": "nested object",
- "required": false,
- "label": "Social Auth Team Map",
- "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in the documentation.",
- "category": "Authentication",
- "category_slug": "authentication",
- "placeholder": {
- "My Team": {
- "organization": "Test Org",
- "users": ["/^[^@]+?@test\\.example\\.com$/"],
- "remove": true
- },
- "Other Team": {
- "organization": "Test Org 2",
- "users": "/^[^@]+?@test2\\.example\\.com$/i",
- "remove": false
- }
- },
- "default": null,
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_USER_FIELDS": {
- "type": "list",
- "required": false,
- "label": "Social Auth User Fields",
- "help_text": "When set to an empty list `[]`, this setting prevents new user accounts from being created. Only users who have previously logged in using social auth or have a user account with a matching email address will be able to login.",
- "category": "Authentication",
- "category_slug": "authentication",
- "placeholder": ["username", "email"],
- "default": null,
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- },
- "SOCIAL_AUTH_OIDC_KEY": {
- "type": "string",
- "label": "OIDC Key",
- "help_text": "The OIDC key (Client ID) from your IDP.",
- "category": "Generic OIDC",
- "category_slug": "oidc",
- "default": ""
- },
- "SOCIAL_AUTH_OIDC_SECRET": {
- "type": "string",
- "label": "OIDC Secret",
- "help_text": "The OIDC secret (Client Secret) from your IDP.",
- "category": "Generic OIDC",
- "category_slug": "oidc",
- "default": ""
- },
- "SOCIAL_AUTH_OIDC_OIDC_ENDPOINT": {
- "type": "string",
- "label": "OIDC Provider URL",
- "help_text": "The URL for your OIDC provider, e.g.: http(s)://hostname/.",
- "category": "Generic OIDC",
- "category_slug": "oidc",
- "default": ""
- },
- "SOCIAL_AUTH_OIDC_VERIFY_SSL": {
- "type": "boolean",
- "required": false,
- "label": "Verify OIDC Provider Certificate",
- "help_text": "Verify the OIDV provider ssl certificate.",
- "category": "Generic OIDC",
- "category_slug": "oidc",
- "default": true
- },
- "AUTH_LDAP_SERVER_URI": {
- "type": "string",
- "required": false,
- "label": "LDAP Server URI",
- "help_text": "URI to connect to LDAP server, such as \"ldap://ldap.example.com:389\" (non-SSL) or \"ldaps://ldap.example.com:636\" (SSL). Multiple LDAP servers may be specified by separating with spaces or commas. LDAP authentication is disabled if this parameter is empty.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "ldaps://ldap.example.com:636",
- "default": ""
- },
- "AUTH_LDAP_BIND_DN": {
- "type": "string",
- "required": false,
- "label": "LDAP Bind DN",
- "help_text": "DN (Distinguished Name) of user to bind for all search queries. This is the system user account we will use to login to query LDAP for other user information. Refer to the documentation for example syntax.",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": ""
- },
- "AUTH_LDAP_BIND_PASSWORD": {
- "type": "string",
- "required": false,
- "label": "LDAP Bind Password",
- "help_text": "Password used to bind LDAP user account.",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": ""
- },
- "AUTH_LDAP_START_TLS": {
- "type": "boolean",
- "required": false,
- "label": "LDAP Start TLS",
- "help_text": "Whether to enable TLS when the LDAP connection is not using SSL.",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": false
- },
- "AUTH_LDAP_CONNECTION_OPTIONS": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Connection Options",
- "help_text": "Additional options to set for the LDAP connection. LDAP referrals are disabled by default (to prevent certain LDAP queries from hanging with AD). Option names should be strings (e.g. \"OPT_REFERRALS\"). Refer to https://www.python-ldap.org/doc/html/ldap.html#options for possible options and values that can be set.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "OPT_REFERRALS": 0,
- "OPT_NETWORK_TIMEOUT": 30
- },
- "default": {
- "OPT_REFERRALS": 0,
- "OPT_NETWORK_TIMEOUT": 30
- },
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_USER_SEARCH": {
- "type": "list",
- "required": false,
- "label": "LDAP User Search",
- "help_text": "LDAP search query to find users. Any user that matches the given pattern will be able to login to the service. The user should also be mapped into an organization (as defined in the AUTH_LDAP_ORGANIZATION_MAP setting). If multiple search queries need to be supported use of \"LDAPUnion\" is possible. See the documentation for details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": [
- "OU=Users,DC=example,DC=com",
- "SCOPE_SUBTREE",
- "(sAMAccountName=%(user)s)"
- ],
- "default": [],
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_USER_DN_TEMPLATE": {
- "type": "string",
- "required": false,
- "label": "LDAP User DN Template",
- "help_text": "Alternative to user search, if user DNs are all of the same format. This approach is more efficient for user lookups than searching if it is usable in your organizational environment. If this setting has a value it will be used instead of AUTH_LDAP_USER_SEARCH.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "uid=%(user)s,OU=Users,DC=example,DC=com",
- "default": null
- },
- "AUTH_LDAP_USER_ATTR_MAP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP User Attribute Map",
- "help_text": "Mapping of LDAP user schema to API user attributes. The default setting is valid for ActiveDirectory but users with other LDAP configurations may need to change the values. Refer to the documentation for additional details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "first_name": "givenName",
- "last_name": "sn",
- "email": "mail"
- },
- "default": {},
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_GROUP_SEARCH": {
- "type": "list",
- "required": false,
- "label": "LDAP Group Search",
- "help_text": "Users are mapped to organizations based on their membership in LDAP groups. This setting defines the LDAP search query to find groups. Unlike the user search, group search does not support LDAPSearchUnion.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": [
- "DC=example,DC=com",
- "SCOPE_SUBTREE",
- "(objectClass=group)"
- ],
- "default": [],
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_GROUP_TYPE": {
- "type": "choice",
- "required": false,
- "label": "LDAP Group Type",
- "help_text": "The group type may need to be changed based on the type of the LDAP server. Values are listed at: https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": "MemberDNGroupType",
- "choices": [
- ["PosixGroupType", "PosixGroupType"],
- ["GroupOfNamesType", "GroupOfNamesType"],
- ["GroupOfUniqueNamesType", "GroupOfUniqueNamesType"],
- ["ActiveDirectoryGroupType", "ActiveDirectoryGroupType"],
- ["OrganizationalRoleGroupType", "OrganizationalRoleGroupType"],
- ["MemberDNGroupType", "MemberDNGroupType"],
- ["NestedGroupOfNamesType", "NestedGroupOfNamesType"],
- ["NestedGroupOfUniqueNamesType", "NestedGroupOfUniqueNamesType"],
- ["NestedActiveDirectoryGroupType", "NestedActiveDirectoryGroupType"],
- [
- "NestedOrganizationalRoleGroupType",
- "NestedOrganizationalRoleGroupType"
- ],
- ["NestedMemberDNGroupType", "NestedMemberDNGroupType"],
- ["PosixUIDGroupType", "PosixUIDGroupType"]
- ]
- },
- "AUTH_LDAP_GROUP_TYPE_PARAMS": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Group Type Parameters",
- "help_text": "Key value parameters to send the chosen group type init method.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "ldap_group_user_attr": "legacyuid",
- "member_attr": "member",
- "name_attr": "cn"
- },
- "default": {
- "member_attr": "member",
- "name_attr": "cn"
- },
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_REQUIRE_GROUP": {
- "type": "string",
- "required": false,
- "label": "LDAP Require Group",
- "help_text": "Group DN required to login. If specified, user must be a member of this group to login via LDAP. If not set, everyone in LDAP that matches the user search will be able to login to the service. Only one require group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "CN=Service Users,OU=Users,DC=example,DC=com",
- "default": null
- },
- "AUTH_LDAP_DENY_GROUP": {
- "type": "string",
- "required": false,
- "label": "LDAP Deny Group",
- "help_text": "Group DN denied from login. If specified, user will not be allowed to login if a member of this group. Only one deny group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "CN=Disabled Users,OU=Users,DC=example,DC=com",
- "default": null
- },
- "AUTH_LDAP_USER_FLAGS_BY_GROUP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP User Flags By Group",
- "help_text": "Retrieve users from a given group. At this time, superuser and system auditors are the only groups supported. Refer to the documentation for more detail.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "is_superuser": "CN=Domain Admins,CN=Users,DC=example,DC=com",
- "is_system_auditor": "CN=Domain Auditors,CN=Users,DC=example,DC=com"
- },
- "default": {},
- "child": {
- "type": "list",
- "required": true,
- "read_only": false,
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_ORGANIZATION_MAP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Organization Map",
- "help_text": "Mapping between organization admins/users and LDAP groups. This controls which users are placed into which organizations relative to their LDAP group memberships. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "Test Org": {
- "admins": "CN=Domain Admins,CN=Users,DC=example,DC=com",
- "auditors": "CN=Domain Auditors,CN=Users,DC=example,DC=com",
- "users": ["CN=Domain Users,CN=Users,DC=example,DC=com"],
- "remove_users": true,
- "remove_admins": true
- },
- "Test Org 2": {
- "admins": "CN=Administrators,CN=Builtin,DC=example,DC=com",
- "users": true,
- "remove_users": true,
- "remove_admins": true
- }
- },
- "default": {},
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_TEAM_MAP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Team Map",
- "help_text": "Mapping between team members (users) and LDAP groups. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "My Team": {
- "organization": "Test Org",
- "users": ["CN=Domain Users,CN=Users,DC=example,DC=com"],
- "remove": true
- },
- "Other Team": {
- "organization": "Test Org 2",
- "users": "CN=Other Users,CN=Users,DC=example,DC=com",
- "remove": false
- }
- },
- "default": {},
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_1_SERVER_URI": {
- "type": "string",
- "required": false,
- "label": "LDAP Server URI",
- "help_text": "URI to connect to LDAP server, such as \"ldap://ldap.example.com:389\" (non-SSL) or \"ldaps://ldap.example.com:636\" (SSL). Multiple LDAP servers may be specified by separating with spaces or commas. LDAP authentication is disabled if this parameter is empty.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "ldaps://ldap.example.com:636",
- "default": ""
- },
- "AUTH_LDAP_1_BIND_DN": {
- "type": "string",
- "required": false,
- "label": "LDAP Bind DN",
- "help_text": "DN (Distinguished Name) of user to bind for all search queries. This is the system user account we will use to login to query LDAP for other user information. Refer to the documentation for example syntax.",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": ""
- },
- "AUTH_LDAP_1_BIND_PASSWORD": {
- "type": "string",
- "required": false,
- "label": "LDAP Bind Password",
- "help_text": "Password used to bind LDAP user account.",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": ""
- },
- "AUTH_LDAP_1_START_TLS": {
- "type": "boolean",
- "required": false,
- "label": "LDAP Start TLS",
- "help_text": "Whether to enable TLS when the LDAP connection is not using SSL.",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": false
- },
- "AUTH_LDAP_1_CONNECTION_OPTIONS": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Connection Options",
- "help_text": "Additional options to set for the LDAP connection. LDAP referrals are disabled by default (to prevent certain LDAP queries from hanging with AD). Option names should be strings (e.g. \"OPT_REFERRALS\"). Refer to https://www.python-ldap.org/doc/html/ldap.html#options for possible options and values that can be set.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "OPT_REFERRALS": 0,
- "OPT_NETWORK_TIMEOUT": 30
- },
- "default": {
- "OPT_REFERRALS": 0,
- "OPT_NETWORK_TIMEOUT": 30
- },
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_1_USER_SEARCH": {
- "type": "list",
- "required": false,
- "label": "LDAP User Search",
- "help_text": "LDAP search query to find users. Any user that matches the given pattern will be able to login to the service. The user should also be mapped into an organization (as defined in the AUTH_LDAP_ORGANIZATION_MAP setting). If multiple search queries need to be supported use of \"LDAPUnion\" is possible. See the documentation for details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": [
- "OU=Users,DC=example,DC=com",
- "SCOPE_SUBTREE",
- "(sAMAccountName=%(user)s)"
- ],
- "default": [],
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_1_USER_DN_TEMPLATE": {
- "type": "string",
- "required": false,
- "label": "LDAP User DN Template",
- "help_text": "Alternative to user search, if user DNs are all of the same format. This approach is more efficient for user lookups than searching if it is usable in your organizational environment. If this setting has a value it will be used instead of AUTH_LDAP_USER_SEARCH.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "uid=%(user)s,OU=Users,DC=example,DC=com",
- "default": null
- },
- "AUTH_LDAP_1_USER_ATTR_MAP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP User Attribute Map",
- "help_text": "Mapping of LDAP user schema to API user attributes. The default setting is valid for ActiveDirectory but users with other LDAP configurations may need to change the values. Refer to the documentation for additional details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "first_name": "givenName",
- "last_name": "sn",
- "email": "mail"
- },
- "default": {},
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_1_GROUP_SEARCH": {
- "type": "list",
- "required": false,
- "label": "LDAP Group Search",
- "help_text": "Users are mapped to organizations based on their membership in LDAP groups. This setting defines the LDAP search query to find groups. Unlike the user search, group search does not support LDAPSearchUnion.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": [
- "DC=example,DC=com",
- "SCOPE_SUBTREE",
- "(objectClass=group)"
- ],
- "default": [],
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_1_GROUP_TYPE": {
- "type": "choice",
- "required": false,
- "label": "LDAP Group Type",
- "help_text": "The group type may need to be changed based on the type of the LDAP server. Values are listed at: https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": "MemberDNGroupType",
- "choices": [
- ["PosixGroupType", "PosixGroupType"],
- ["GroupOfNamesType", "GroupOfNamesType"],
- ["GroupOfUniqueNamesType", "GroupOfUniqueNamesType"],
- ["ActiveDirectoryGroupType", "ActiveDirectoryGroupType"],
- ["OrganizationalRoleGroupType", "OrganizationalRoleGroupType"],
- ["MemberDNGroupType", "MemberDNGroupType"],
- ["NestedGroupOfNamesType", "NestedGroupOfNamesType"],
- ["NestedGroupOfUniqueNamesType", "NestedGroupOfUniqueNamesType"],
- ["NestedActiveDirectoryGroupType", "NestedActiveDirectoryGroupType"],
- [
- "NestedOrganizationalRoleGroupType",
- "NestedOrganizationalRoleGroupType"
- ],
- ["NestedMemberDNGroupType", "NestedMemberDNGroupType"],
- ["PosixUIDGroupType", "PosixUIDGroupType"]
- ]
- },
- "AUTH_LDAP_1_GROUP_TYPE_PARAMS": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Group Type Parameters",
- "help_text": "Key value parameters to send the chosen group type init method.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "ldap_group_user_attr": "legacyuid",
- "member_attr": "member",
- "name_attr": "cn"
- },
- "default": {
- "member_attr": "member",
- "name_attr": "cn"
- },
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_1_REQUIRE_GROUP": {
- "type": "string",
- "required": false,
- "label": "LDAP Require Group",
- "help_text": "Group DN required to login. If specified, user must be a member of this group to login via LDAP. If not set, everyone in LDAP that matches the user search will be able to login to the service. Only one require group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "CN=Service Users,OU=Users,DC=example,DC=com",
- "default": null
- },
- "AUTH_LDAP_1_DENY_GROUP": {
- "type": "string",
- "required": false,
- "label": "LDAP Deny Group",
- "help_text": "Group DN denied from login. If specified, user will not be allowed to login if a member of this group. Only one deny group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "CN=Disabled Users,OU=Users,DC=example,DC=com",
- "default": null
- },
- "AUTH_LDAP_1_USER_FLAGS_BY_GROUP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP User Flags By Group",
- "help_text": "Retrieve users from a given group. At this time, superuser and system auditors are the only groups supported. Refer to the documentation for more detail.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "is_superuser": "CN=Domain Admins,CN=Users,DC=example,DC=com",
- "is_system_auditor": "CN=Domain Auditors,CN=Users,DC=example,DC=com"
- },
- "default": {},
- "child": {
- "type": "list",
- "required": true,
- "read_only": false,
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_1_ORGANIZATION_MAP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Organization Map",
- "help_text": "Mapping between organization admins/users and LDAP groups. This controls which users are placed into which organizations relative to their LDAP group memberships. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "Test Org": {
- "admins": "CN=Domain Admins,CN=Users,DC=example,DC=com",
- "auditors": "CN=Domain Auditors,CN=Users,DC=example,DC=com",
- "users": ["CN=Domain Users,CN=Users,DC=example,DC=com"],
- "remove_users": true,
- "remove_admins": true
- },
- "Test Org 2": {
- "admins": "CN=Administrators,CN=Builtin,DC=example,DC=com",
- "users": true,
- "remove_users": true,
- "remove_admins": true
- }
- },
- "default": {},
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_1_TEAM_MAP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Team Map",
- "help_text": "Mapping between team members (users) and LDAP groups. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "My Team": {
- "organization": "Test Org",
- "users": ["CN=Domain Users,CN=Users,DC=example,DC=com"],
- "remove": true
- },
- "Other Team": {
- "organization": "Test Org 2",
- "users": "CN=Other Users,CN=Users,DC=example,DC=com",
- "remove": false
- }
- },
- "default": {},
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_2_SERVER_URI": {
- "type": "string",
- "required": false,
- "label": "LDAP Server URI",
- "help_text": "URI to connect to LDAP server, such as \"ldap://ldap.example.com:389\" (non-SSL) or \"ldaps://ldap.example.com:636\" (SSL). Multiple LDAP servers may be specified by separating with spaces or commas. LDAP authentication is disabled if this parameter is empty.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "ldaps://ldap.example.com:636",
- "default": ""
- },
- "AUTH_LDAP_2_BIND_DN": {
- "type": "string",
- "required": false,
- "label": "LDAP Bind DN",
- "help_text": "DN (Distinguished Name) of user to bind for all search queries. This is the system user account we will use to login to query LDAP for other user information. Refer to the documentation for example syntax.",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": ""
- },
- "AUTH_LDAP_2_BIND_PASSWORD": {
- "type": "string",
- "required": false,
- "label": "LDAP Bind Password",
- "help_text": "Password used to bind LDAP user account.",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": ""
- },
- "AUTH_LDAP_2_START_TLS": {
- "type": "boolean",
- "required": false,
- "label": "LDAP Start TLS",
- "help_text": "Whether to enable TLS when the LDAP connection is not using SSL.",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": false
- },
- "AUTH_LDAP_2_CONNECTION_OPTIONS": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Connection Options",
- "help_text": "Additional options to set for the LDAP connection. LDAP referrals are disabled by default (to prevent certain LDAP queries from hanging with AD). Option names should be strings (e.g. \"OPT_REFERRALS\"). Refer to https://www.python-ldap.org/doc/html/ldap.html#options for possible options and values that can be set.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "OPT_REFERRALS": 0,
- "OPT_NETWORK_TIMEOUT": 30
- },
- "default": {
- "OPT_REFERRALS": 0,
- "OPT_NETWORK_TIMEOUT": 30
- },
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_2_USER_SEARCH": {
- "type": "list",
- "required": false,
- "label": "LDAP User Search",
- "help_text": "LDAP search query to find users. Any user that matches the given pattern will be able to login to the service. The user should also be mapped into an organization (as defined in the AUTH_LDAP_ORGANIZATION_MAP setting). If multiple search queries need to be supported use of \"LDAPUnion\" is possible. See the documentation for details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": [
- "OU=Users,DC=example,DC=com",
- "SCOPE_SUBTREE",
- "(sAMAccountName=%(user)s)"
- ],
- "default": [],
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_2_USER_DN_TEMPLATE": {
- "type": "string",
- "required": false,
- "label": "LDAP User DN Template",
- "help_text": "Alternative to user search, if user DNs are all of the same format. This approach is more efficient for user lookups than searching if it is usable in your organizational environment. If this setting has a value it will be used instead of AUTH_LDAP_USER_SEARCH.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "uid=%(user)s,OU=Users,DC=example,DC=com",
- "default": null
- },
- "AUTH_LDAP_2_USER_ATTR_MAP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP User Attribute Map",
- "help_text": "Mapping of LDAP user schema to API user attributes. The default setting is valid for ActiveDirectory but users with other LDAP configurations may need to change the values. Refer to the documentation for additional details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "first_name": "givenName",
- "last_name": "sn",
- "email": "mail"
- },
- "default": {},
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_2_GROUP_SEARCH": {
- "type": "list",
- "required": false,
- "label": "LDAP Group Search",
- "help_text": "Users are mapped to organizations based on their membership in LDAP groups. This setting defines the LDAP search query to find groups. Unlike the user search, group search does not support LDAPSearchUnion.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": [
- "DC=example,DC=com",
- "SCOPE_SUBTREE",
- "(objectClass=group)"
- ],
- "default": [],
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_2_GROUP_TYPE": {
- "type": "choice",
- "required": false,
- "label": "LDAP Group Type",
- "help_text": "The group type may need to be changed based on the type of the LDAP server. Values are listed at: https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": "MemberDNGroupType",
- "choices": [
- ["PosixGroupType", "PosixGroupType"],
- ["GroupOfNamesType", "GroupOfNamesType"],
- ["GroupOfUniqueNamesType", "GroupOfUniqueNamesType"],
- ["ActiveDirectoryGroupType", "ActiveDirectoryGroupType"],
- ["OrganizationalRoleGroupType", "OrganizationalRoleGroupType"],
- ["MemberDNGroupType", "MemberDNGroupType"],
- ["NestedGroupOfNamesType", "NestedGroupOfNamesType"],
- ["NestedGroupOfUniqueNamesType", "NestedGroupOfUniqueNamesType"],
- ["NestedActiveDirectoryGroupType", "NestedActiveDirectoryGroupType"],
- [
- "NestedOrganizationalRoleGroupType",
- "NestedOrganizationalRoleGroupType"
- ],
- ["NestedMemberDNGroupType", "NestedMemberDNGroupType"],
- ["PosixUIDGroupType", "PosixUIDGroupType"]
- ]
- },
- "AUTH_LDAP_2_GROUP_TYPE_PARAMS": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Group Type Parameters",
- "help_text": "Key value parameters to send the chosen group type init method.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "ldap_group_user_attr": "legacyuid",
- "member_attr": "member",
- "name_attr": "cn"
- },
- "default": {
- "member_attr": "member",
- "name_attr": "cn"
- },
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_2_REQUIRE_GROUP": {
- "type": "string",
- "required": false,
- "label": "LDAP Require Group",
- "help_text": "Group DN required to login. If specified, user must be a member of this group to login via LDAP. If not set, everyone in LDAP that matches the user search will be able to login to the service. Only one require group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "CN=Service Users,OU=Users,DC=example,DC=com",
- "default": null
- },
- "AUTH_LDAP_2_DENY_GROUP": {
- "type": "string",
- "required": false,
- "label": "LDAP Deny Group",
- "help_text": "Group DN denied from login. If specified, user will not be allowed to login if a member of this group. Only one deny group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "CN=Disabled Users,OU=Users,DC=example,DC=com",
- "default": null
- },
- "AUTH_LDAP_2_USER_FLAGS_BY_GROUP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP User Flags By Group",
- "help_text": "Retrieve users from a given group. At this time, superuser and system auditors are the only groups supported. Refer to the documentation for more detail.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "is_superuser": "CN=Domain Admins,CN=Users,DC=example,DC=com",
- "is_system_auditor": "CN=Domain Auditors,CN=Users,DC=example,DC=com"
- },
- "default": {},
- "child": {
- "type": "list",
- "required": true,
- "read_only": false,
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_2_ORGANIZATION_MAP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Organization Map",
- "help_text": "Mapping between organization admins/users and LDAP groups. This controls which users are placed into which organizations relative to their LDAP group memberships. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "Test Org": {
- "admins": "CN=Domain Admins,CN=Users,DC=example,DC=com",
- "auditors": "CN=Domain Auditors,CN=Users,DC=example,DC=com",
- "users": ["CN=Domain Users,CN=Users,DC=example,DC=com"],
- "remove_users": true,
- "remove_admins": true
- },
- "Test Org 2": {
- "admins": "CN=Administrators,CN=Builtin,DC=example,DC=com",
- "users": true,
- "remove_users": true,
- "remove_admins": true
- }
- },
- "default": {},
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_2_TEAM_MAP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Team Map",
- "help_text": "Mapping between team members (users) and LDAP groups. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "My Team": {
- "organization": "Test Org",
- "users": ["CN=Domain Users,CN=Users,DC=example,DC=com"],
- "remove": true
- },
- "Other Team": {
- "organization": "Test Org 2",
- "users": "CN=Other Users,CN=Users,DC=example,DC=com",
- "remove": false
- }
- },
- "default": {},
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_3_SERVER_URI": {
- "type": "string",
- "required": false,
- "label": "LDAP Server URI",
- "help_text": "URI to connect to LDAP server, such as \"ldap://ldap.example.com:389\" (non-SSL) or \"ldaps://ldap.example.com:636\" (SSL). Multiple LDAP servers may be specified by separating with spaces or commas. LDAP authentication is disabled if this parameter is empty.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "ldaps://ldap.example.com:636",
- "default": ""
- },
- "AUTH_LDAP_3_BIND_DN": {
- "type": "string",
- "required": false,
- "label": "LDAP Bind DN",
- "help_text": "DN (Distinguished Name) of user to bind for all search queries. This is the system user account we will use to login to query LDAP for other user information. Refer to the documentation for example syntax.",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": ""
- },
- "AUTH_LDAP_3_BIND_PASSWORD": {
- "type": "string",
- "required": false,
- "label": "LDAP Bind Password",
- "help_text": "Password used to bind LDAP user account.",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": ""
- },
- "AUTH_LDAP_3_START_TLS": {
- "type": "boolean",
- "required": false,
- "label": "LDAP Start TLS",
- "help_text": "Whether to enable TLS when the LDAP connection is not using SSL.",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": false
- },
- "AUTH_LDAP_3_CONNECTION_OPTIONS": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Connection Options",
- "help_text": "Additional options to set for the LDAP connection. LDAP referrals are disabled by default (to prevent certain LDAP queries from hanging with AD). Option names should be strings (e.g. \"OPT_REFERRALS\"). Refer to https://www.python-ldap.org/doc/html/ldap.html#options for possible options and values that can be set.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "OPT_REFERRALS": 0,
- "OPT_NETWORK_TIMEOUT": 30
- },
- "default": {
- "OPT_REFERRALS": 0,
- "OPT_NETWORK_TIMEOUT": 30
- },
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_3_USER_SEARCH": {
- "type": "list",
- "required": false,
- "label": "LDAP User Search",
- "help_text": "LDAP search query to find users. Any user that matches the given pattern will be able to login to the service. The user should also be mapped into an organization (as defined in the AUTH_LDAP_ORGANIZATION_MAP setting). If multiple search queries need to be supported use of \"LDAPUnion\" is possible. See the documentation for details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": [
- "OU=Users,DC=example,DC=com",
- "SCOPE_SUBTREE",
- "(sAMAccountName=%(user)s)"
- ],
- "default": [],
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_3_USER_DN_TEMPLATE": {
- "type": "string",
- "required": false,
- "label": "LDAP User DN Template",
- "help_text": "Alternative to user search, if user DNs are all of the same format. This approach is more efficient for user lookups than searching if it is usable in your organizational environment. If this setting has a value it will be used instead of AUTH_LDAP_USER_SEARCH.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "uid=%(user)s,OU=Users,DC=example,DC=com",
- "default": null
- },
- "AUTH_LDAP_3_USER_ATTR_MAP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP User Attribute Map",
- "help_text": "Mapping of LDAP user schema to API user attributes. The default setting is valid for ActiveDirectory but users with other LDAP configurations may need to change the values. Refer to the documentation for additional details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "first_name": "givenName",
- "last_name": "sn",
- "email": "mail"
- },
- "default": {},
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_3_GROUP_SEARCH": {
- "type": "list",
- "required": false,
- "label": "LDAP Group Search",
- "help_text": "Users are mapped to organizations based on their membership in LDAP groups. This setting defines the LDAP search query to find groups. Unlike the user search, group search does not support LDAPSearchUnion.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": [
- "DC=example,DC=com",
- "SCOPE_SUBTREE",
- "(objectClass=group)"
- ],
- "default": [],
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_3_GROUP_TYPE": {
- "type": "choice",
- "required": false,
- "label": "LDAP Group Type",
- "help_text": "The group type may need to be changed based on the type of the LDAP server. Values are listed at: https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": "MemberDNGroupType",
- "choices": [
- ["PosixGroupType", "PosixGroupType"],
- ["GroupOfNamesType", "GroupOfNamesType"],
- ["GroupOfUniqueNamesType", "GroupOfUniqueNamesType"],
- ["ActiveDirectoryGroupType", "ActiveDirectoryGroupType"],
- ["OrganizationalRoleGroupType", "OrganizationalRoleGroupType"],
- ["MemberDNGroupType", "MemberDNGroupType"],
- ["NestedGroupOfNamesType", "NestedGroupOfNamesType"],
- ["NestedGroupOfUniqueNamesType", "NestedGroupOfUniqueNamesType"],
- ["NestedActiveDirectoryGroupType", "NestedActiveDirectoryGroupType"],
- [
- "NestedOrganizationalRoleGroupType",
- "NestedOrganizationalRoleGroupType"
- ],
- ["NestedMemberDNGroupType", "NestedMemberDNGroupType"],
- ["PosixUIDGroupType", "PosixUIDGroupType"]
- ]
- },
- "AUTH_LDAP_3_GROUP_TYPE_PARAMS": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Group Type Parameters",
- "help_text": "Key value parameters to send the chosen group type init method.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "ldap_group_user_attr": "legacyuid",
- "member_attr": "member",
- "name_attr": "cn"
- },
- "default": {
- "member_attr": "member",
- "name_attr": "cn"
- },
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_3_REQUIRE_GROUP": {
- "type": "string",
- "required": false,
- "label": "LDAP Require Group",
- "help_text": "Group DN required to login. If specified, user must be a member of this group to login via LDAP. If not set, everyone in LDAP that matches the user search will be able to login to the service. Only one require group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "CN=Service Users,OU=Users,DC=example,DC=com",
- "default": null
- },
- "AUTH_LDAP_3_DENY_GROUP": {
- "type": "string",
- "required": false,
- "label": "LDAP Deny Group",
- "help_text": "Group DN denied from login. If specified, user will not be allowed to login if a member of this group. Only one deny group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "CN=Disabled Users,OU=Users,DC=example,DC=com",
- "default": null
- },
- "AUTH_LDAP_3_USER_FLAGS_BY_GROUP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP User Flags By Group",
- "help_text": "Retrieve users from a given group. At this time, superuser and system auditors are the only groups supported. Refer to the documentation for more detail.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "is_superuser": "CN=Domain Admins,CN=Users,DC=example,DC=com",
- "is_system_auditor": "CN=Domain Auditors,CN=Users,DC=example,DC=com"
- },
- "default": {},
- "child": {
- "type": "list",
- "required": true,
- "read_only": false,
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_3_ORGANIZATION_MAP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Organization Map",
- "help_text": "Mapping between organization admins/users and LDAP groups. This controls which users are placed into which organizations relative to their LDAP group memberships. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "Test Org": {
- "admins": "CN=Domain Admins,CN=Users,DC=example,DC=com",
- "auditors": "CN=Domain Auditors,CN=Users,DC=example,DC=com",
- "users": ["CN=Domain Users,CN=Users,DC=example,DC=com"],
- "remove_users": true,
- "remove_admins": true
- },
- "Test Org 2": {
- "admins": "CN=Administrators,CN=Builtin,DC=example,DC=com",
- "users": true,
- "remove_users": true,
- "remove_admins": true
- }
- },
- "default": {},
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_3_TEAM_MAP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Team Map",
- "help_text": "Mapping between team members (users) and LDAP groups. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "My Team": {
- "organization": "Test Org",
- "users": ["CN=Domain Users,CN=Users,DC=example,DC=com"],
- "remove": true
- },
- "Other Team": {
- "organization": "Test Org 2",
- "users": "CN=Other Users,CN=Users,DC=example,DC=com",
- "remove": false
- }
- },
- "default": {},
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_4_SERVER_URI": {
- "type": "string",
- "required": false,
- "label": "LDAP Server URI",
- "help_text": "URI to connect to LDAP server, such as \"ldap://ldap.example.com:389\" (non-SSL) or \"ldaps://ldap.example.com:636\" (SSL). Multiple LDAP servers may be specified by separating with spaces or commas. LDAP authentication is disabled if this parameter is empty.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "ldaps://ldap.example.com:636",
- "default": ""
- },
- "AUTH_LDAP_4_BIND_DN": {
- "type": "string",
- "required": false,
- "label": "LDAP Bind DN",
- "help_text": "DN (Distinguished Name) of user to bind for all search queries. This is the system user account we will use to login to query LDAP for other user information. Refer to the documentation for example syntax.",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": ""
- },
- "AUTH_LDAP_4_BIND_PASSWORD": {
- "type": "string",
- "required": false,
- "label": "LDAP Bind Password",
- "help_text": "Password used to bind LDAP user account.",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": ""
- },
- "AUTH_LDAP_4_START_TLS": {
- "type": "boolean",
- "required": false,
- "label": "LDAP Start TLS",
- "help_text": "Whether to enable TLS when the LDAP connection is not using SSL.",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": false
- },
- "AUTH_LDAP_4_CONNECTION_OPTIONS": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Connection Options",
- "help_text": "Additional options to set for the LDAP connection. LDAP referrals are disabled by default (to prevent certain LDAP queries from hanging with AD). Option names should be strings (e.g. \"OPT_REFERRALS\"). Refer to https://www.python-ldap.org/doc/html/ldap.html#options for possible options and values that can be set.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "OPT_REFERRALS": 0,
- "OPT_NETWORK_TIMEOUT": 30
- },
- "default": {
- "OPT_REFERRALS": 0,
- "OPT_NETWORK_TIMEOUT": 30
- },
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_4_USER_SEARCH": {
- "type": "list",
- "required": false,
- "label": "LDAP User Search",
- "help_text": "LDAP search query to find users. Any user that matches the given pattern will be able to login to the service. The user should also be mapped into an organization (as defined in the AUTH_LDAP_ORGANIZATION_MAP setting). If multiple search queries need to be supported use of \"LDAPUnion\" is possible. See the documentation for details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": [
- "OU=Users,DC=example,DC=com",
- "SCOPE_SUBTREE",
- "(sAMAccountName=%(user)s)"
- ],
- "default": [],
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_4_USER_DN_TEMPLATE": {
- "type": "string",
- "required": false,
- "label": "LDAP User DN Template",
- "help_text": "Alternative to user search, if user DNs are all of the same format. This approach is more efficient for user lookups than searching if it is usable in your organizational environment. If this setting has a value it will be used instead of AUTH_LDAP_USER_SEARCH.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "uid=%(user)s,OU=Users,DC=example,DC=com",
- "default": null
- },
- "AUTH_LDAP_4_USER_ATTR_MAP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP User Attribute Map",
- "help_text": "Mapping of LDAP user schema to API user attributes. The default setting is valid for ActiveDirectory but users with other LDAP configurations may need to change the values. Refer to the documentation for additional details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "first_name": "givenName",
- "last_name": "sn",
- "email": "mail"
- },
- "default": {},
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_4_GROUP_SEARCH": {
- "type": "list",
- "required": false,
- "label": "LDAP Group Search",
- "help_text": "Users are mapped to organizations based on their membership in LDAP groups. This setting defines the LDAP search query to find groups. Unlike the user search, group search does not support LDAPSearchUnion.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": [
- "DC=example,DC=com",
- "SCOPE_SUBTREE",
- "(objectClass=group)"
- ],
- "default": [],
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_4_GROUP_TYPE": {
- "type": "choice",
- "required": false,
- "label": "LDAP Group Type",
- "help_text": "The group type may need to be changed based on the type of the LDAP server. Values are listed at: https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": "MemberDNGroupType",
- "choices": [
- ["PosixGroupType", "PosixGroupType"],
- ["GroupOfNamesType", "GroupOfNamesType"],
- ["GroupOfUniqueNamesType", "GroupOfUniqueNamesType"],
- ["ActiveDirectoryGroupType", "ActiveDirectoryGroupType"],
- ["OrganizationalRoleGroupType", "OrganizationalRoleGroupType"],
- ["MemberDNGroupType", "MemberDNGroupType"],
- ["NestedGroupOfNamesType", "NestedGroupOfNamesType"],
- ["NestedGroupOfUniqueNamesType", "NestedGroupOfUniqueNamesType"],
- ["NestedActiveDirectoryGroupType", "NestedActiveDirectoryGroupType"],
- [
- "NestedOrganizationalRoleGroupType",
- "NestedOrganizationalRoleGroupType"
- ],
- ["NestedMemberDNGroupType", "NestedMemberDNGroupType"],
- ["PosixUIDGroupType", "PosixUIDGroupType"]
- ]
- },
- "AUTH_LDAP_4_GROUP_TYPE_PARAMS": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Group Type Parameters",
- "help_text": "Key value parameters to send the chosen group type init method.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "ldap_group_user_attr": "legacyuid",
- "member_attr": "member",
- "name_attr": "cn"
- },
- "default": {
- "member_attr": "member",
- "name_attr": "cn"
- },
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_4_REQUIRE_GROUP": {
- "type": "string",
- "required": false,
- "label": "LDAP Require Group",
- "help_text": "Group DN required to login. If specified, user must be a member of this group to login via LDAP. If not set, everyone in LDAP that matches the user search will be able to login to the service. Only one require group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "CN=Service Users,OU=Users,DC=example,DC=com",
- "default": null
- },
- "AUTH_LDAP_4_DENY_GROUP": {
- "type": "string",
- "required": false,
- "label": "LDAP Deny Group",
- "help_text": "Group DN denied from login. If specified, user will not be allowed to login if a member of this group. Only one deny group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "CN=Disabled Users,OU=Users,DC=example,DC=com",
- "default": null
- },
- "AUTH_LDAP_4_USER_FLAGS_BY_GROUP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP User Flags By Group",
- "help_text": "Retrieve users from a given group. At this time, superuser and system auditors are the only groups supported. Refer to the documentation for more detail.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "is_superuser": "CN=Domain Admins,CN=Users,DC=example,DC=com",
- "is_system_auditor": "CN=Domain Auditors,CN=Users,DC=example,DC=com"
- },
- "default": {},
- "child": {
- "type": "list",
- "required": true,
- "read_only": false,
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_4_ORGANIZATION_MAP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Organization Map",
- "help_text": "Mapping between organization admins/users and LDAP groups. This controls which users are placed into which organizations relative to their LDAP group memberships. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "Test Org": {
- "admins": "CN=Domain Admins,CN=Users,DC=example,DC=com",
- "auditors": "CN=Domain Auditors,CN=Users,DC=example,DC=com",
- "users": ["CN=Domain Users,CN=Users,DC=example,DC=com"],
- "remove_users": true,
- "remove_admins": true
- },
- "Test Org 2": {
- "admins": "CN=Administrators,CN=Builtin,DC=example,DC=com",
- "users": true,
- "remove_users": true,
- "remove_admins": true
- }
- },
- "default": {},
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_4_TEAM_MAP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Team Map",
- "help_text": "Mapping between team members (users) and LDAP groups. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "My Team": {
- "organization": "Test Org",
- "users": ["CN=Domain Users,CN=Users,DC=example,DC=com"],
- "remove": true
- },
- "Other Team": {
- "organization": "Test Org 2",
- "users": "CN=Other Users,CN=Users,DC=example,DC=com",
- "remove": false
- }
- },
- "default": {},
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_5_SERVER_URI": {
- "type": "string",
- "required": false,
- "label": "LDAP Server URI",
- "help_text": "URI to connect to LDAP server, such as \"ldap://ldap.example.com:389\" (non-SSL) or \"ldaps://ldap.example.com:636\" (SSL). Multiple LDAP servers may be specified by separating with spaces or commas. LDAP authentication is disabled if this parameter is empty.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "ldaps://ldap.example.com:636",
- "default": ""
- },
- "AUTH_LDAP_5_BIND_DN": {
- "type": "string",
- "required": false,
- "label": "LDAP Bind DN",
- "help_text": "DN (Distinguished Name) of user to bind for all search queries. This is the system user account we will use to login to query LDAP for other user information. Refer to the documentation for example syntax.",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": ""
- },
- "AUTH_LDAP_5_BIND_PASSWORD": {
- "type": "string",
- "required": false,
- "label": "LDAP Bind Password",
- "help_text": "Password used to bind LDAP user account.",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": ""
- },
- "AUTH_LDAP_5_START_TLS": {
- "type": "boolean",
- "required": false,
- "label": "LDAP Start TLS",
- "help_text": "Whether to enable TLS when the LDAP connection is not using SSL.",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": false
- },
- "AUTH_LDAP_5_CONNECTION_OPTIONS": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Connection Options",
- "help_text": "Additional options to set for the LDAP connection. LDAP referrals are disabled by default (to prevent certain LDAP queries from hanging with AD). Option names should be strings (e.g. \"OPT_REFERRALS\"). Refer to https://www.python-ldap.org/doc/html/ldap.html#options for possible options and values that can be set.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "OPT_REFERRALS": 0,
- "OPT_NETWORK_TIMEOUT": 30
- },
- "default": {
- "OPT_REFERRALS": 0,
- "OPT_NETWORK_TIMEOUT": 30
- },
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_5_USER_SEARCH": {
- "type": "list",
- "required": false,
- "label": "LDAP User Search",
- "help_text": "LDAP search query to find users. Any user that matches the given pattern will be able to login to the service. The user should also be mapped into an organization (as defined in the AUTH_LDAP_ORGANIZATION_MAP setting). If multiple search queries need to be supported use of \"LDAPUnion\" is possible. See the documentation for details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": [
- "OU=Users,DC=example,DC=com",
- "SCOPE_SUBTREE",
- "(sAMAccountName=%(user)s)"
- ],
- "default": [],
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_5_USER_DN_TEMPLATE": {
- "type": "string",
- "required": false,
- "label": "LDAP User DN Template",
- "help_text": "Alternative to user search, if user DNs are all of the same format. This approach is more efficient for user lookups than searching if it is usable in your organizational environment. If this setting has a value it will be used instead of AUTH_LDAP_USER_SEARCH.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "uid=%(user)s,OU=Users,DC=example,DC=com",
- "default": null
- },
- "AUTH_LDAP_5_USER_ATTR_MAP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP User Attribute Map",
- "help_text": "Mapping of LDAP user schema to API user attributes. The default setting is valid for ActiveDirectory but users with other LDAP configurations may need to change the values. Refer to the documentation for additional details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "first_name": "givenName",
- "last_name": "sn",
- "email": "mail"
- },
- "default": {},
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_5_GROUP_SEARCH": {
- "type": "list",
- "required": false,
- "label": "LDAP Group Search",
- "help_text": "Users are mapped to organizations based on their membership in LDAP groups. This setting defines the LDAP search query to find groups. Unlike the user search, group search does not support LDAPSearchUnion.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": [
- "DC=example,DC=com",
- "SCOPE_SUBTREE",
- "(objectClass=group)"
- ],
- "default": [],
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_5_GROUP_TYPE": {
- "type": "choice",
- "required": false,
- "label": "LDAP Group Type",
- "help_text": "The group type may need to be changed based on the type of the LDAP server. Values are listed at: https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups",
- "category": "LDAP",
- "category_slug": "ldap",
- "default": "MemberDNGroupType",
- "choices": [
- ["PosixGroupType", "PosixGroupType"],
- ["GroupOfNamesType", "GroupOfNamesType"],
- ["GroupOfUniqueNamesType", "GroupOfUniqueNamesType"],
- ["ActiveDirectoryGroupType", "ActiveDirectoryGroupType"],
- ["OrganizationalRoleGroupType", "OrganizationalRoleGroupType"],
- ["MemberDNGroupType", "MemberDNGroupType"],
- ["NestedGroupOfNamesType", "NestedGroupOfNamesType"],
- ["NestedGroupOfUniqueNamesType", "NestedGroupOfUniqueNamesType"],
- ["NestedActiveDirectoryGroupType", "NestedActiveDirectoryGroupType"],
- [
- "NestedOrganizationalRoleGroupType",
- "NestedOrganizationalRoleGroupType"
- ],
- ["NestedMemberDNGroupType", "NestedMemberDNGroupType"],
- ["PosixUIDGroupType", "PosixUIDGroupType"]
- ]
- },
- "AUTH_LDAP_5_GROUP_TYPE_PARAMS": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Group Type Parameters",
- "help_text": "Key value parameters to send the chosen group type init method.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "ldap_group_user_attr": "legacyuid",
- "member_attr": "member",
- "name_attr": "cn"
- },
- "default": {
- "member_attr": "member",
- "name_attr": "cn"
- },
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "AUTH_LDAP_5_REQUIRE_GROUP": {
- "type": "string",
- "required": false,
- "label": "LDAP Require Group",
- "help_text": "Group DN required to login. If specified, user must be a member of this group to login via LDAP. If not set, everyone in LDAP that matches the user search will be able to login to the service. Only one require group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "CN=Service Users,OU=Users,DC=example,DC=com",
- "default": null
- },
- "AUTH_LDAP_5_DENY_GROUP": {
- "type": "string",
- "required": false,
- "label": "LDAP Deny Group",
- "help_text": "Group DN denied from login. If specified, user will not be allowed to login if a member of this group. Only one deny group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": "CN=Disabled Users,OU=Users,DC=example,DC=com",
- "default": null
- },
- "AUTH_LDAP_5_USER_FLAGS_BY_GROUP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP User Flags By Group",
- "help_text": "Retrieve users from a given group. At this time, superuser and system auditors are the only groups supported. Refer to the documentation for more detail.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "is_superuser": "CN=Domain Admins,CN=Users,DC=example,DC=com",
- "is_system_auditor": "CN=Domain Auditors,CN=Users,DC=example,DC=com"
- },
- "default": {},
- "child": {
- "type": "list",
- "required": true,
- "read_only": false,
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_5_ORGANIZATION_MAP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Organization Map",
- "help_text": "Mapping between organization admins/users and LDAP groups. This controls which users are placed into which organizations relative to their LDAP group memberships. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "Test Org": {
- "admins": "CN=Domain Admins,CN=Users,DC=example,DC=com",
- "auditors": "CN=Domain Auditors,CN=Users,DC=example,DC=com",
- "users": ["CN=Domain Users,CN=Users,DC=example,DC=com"],
- "remove_users": true,
- "remove_admins": true
- },
- "Test Org 2": {
- "admins": "CN=Administrators,CN=Builtin,DC=example,DC=com",
- "users": true,
- "remove_users": true,
- "remove_admins": true
- }
- },
- "default": {},
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_5_TEAM_MAP": {
- "type": "nested object",
- "required": false,
- "label": "LDAP Team Map",
- "help_text": "Mapping between team members (users) and LDAP groups. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "placeholder": {
- "My Team": {
- "organization": "Test Org",
- "users": ["CN=Domain Users,CN=Users,DC=example,DC=com"],
- "remove": true
- },
- "Other Team": {
- "organization": "Test Org 2",
- "users": "CN=Other Users,CN=Users,DC=example,DC=com",
- "remove": false
- }
- },
- "default": {},
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "RADIUS_SERVER": {
- "type": "string",
- "required": false,
- "label": "RADIUS Server",
- "help_text": "Hostname/IP of RADIUS server. RADIUS authentication is disabled if this setting is empty.",
- "category": "RADIUS",
- "category_slug": "radius",
- "placeholder": "radius.example.com",
- "default": ""
- },
- "RADIUS_PORT": {
- "type": "integer",
- "required": false,
- "label": "RADIUS Port",
- "help_text": "Port of RADIUS server.",
- "min_value": 1,
- "max_value": 65535,
- "category": "RADIUS",
- "category_slug": "radius",
- "default": 1812
- },
- "RADIUS_SECRET": {
- "type": "string",
- "required": false,
- "label": "RADIUS Secret",
- "help_text": "Shared secret for authenticating to RADIUS server.",
- "category": "RADIUS",
- "category_slug": "radius",
- "default": ""
- },
- "TACACSPLUS_HOST": {
- "type": "string",
- "required": false,
- "label": "TACACS+ Server",
- "help_text": "Hostname of TACACS+ server.",
- "category": "TACACS+",
- "category_slug": "tacacsplus",
- "default": ""
- },
- "TACACSPLUS_PORT": {
- "type": "integer",
- "required": false,
- "label": "TACACS+ Port",
- "help_text": "Port number of TACACS+ server.",
- "min_value": 1,
- "max_value": 65535,
- "category": "TACACS+",
- "category_slug": "tacacsplus",
- "default": 49
- },
- "TACACSPLUS_SECRET": {
- "type": "string",
- "required": false,
- "label": "TACACS+ Secret",
- "help_text": "Shared secret for authenticating to TACACS+ server.",
- "category": "TACACS+",
- "category_slug": "tacacsplus",
- "default": ""
- },
- "TACACSPLUS_SESSION_TIMEOUT": {
- "type": "integer",
- "required": false,
- "label": "TACACS+ Auth Session Timeout",
- "help_text": "TACACS+ session timeout value in seconds, 0 disables timeout.",
- "min_value": 0,
- "category": "TACACS+",
- "category_slug": "tacacsplus",
- "unit": "seconds",
- "default": 5
- },
- "TACACSPLUS_AUTH_PROTOCOL": {
- "type": "choice",
- "required": false,
- "label": "TACACS+ Authentication Protocol",
- "help_text": "Choose the authentication protocol used by TACACS+ client.",
- "category": "TACACS+",
- "category_slug": "tacacsplus",
- "default": "ascii",
- "choices": [
- ["ascii", "ascii"],
- ["pap", "pap"]
- ]
- },
- "SOCIAL_AUTH_GOOGLE_OAUTH2_KEY": {
- "type": "string",
- "required": false,
- "label": "Google OAuth2 Key",
- "help_text": "The OAuth2 key from your web application.",
- "category": "Google OAuth2",
- "category_slug": "google-oauth2",
- "placeholder": "528620852399-gm2dt4hrl2tsj67fqamk09k1e0ad6gd8.apps.googleusercontent.com",
- "default": ""
- },
- "SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET": {
- "type": "string",
- "required": false,
- "label": "Google OAuth2 Secret",
- "help_text": "The OAuth2 secret from your web application.",
- "category": "Google OAuth2",
- "category_slug": "google-oauth2",
- "placeholder": "q2fMVCmEregbg-drvebPp8OW",
- "default": ""
- },
- "SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS": {
- "type": "list",
- "required": false,
- "label": "Google OAuth2 Allowed Domains",
- "help_text": "Update this setting to restrict the domains who are allowed to login using Google OAuth2.",
- "category": "Google OAuth2",
- "category_slug": "google-oauth2",
- "placeholder": ["example.com"],
- "default": [],
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- },
- "SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS": {
- "type": "nested object",
- "required": false,
- "label": "Google OAuth2 Extra Arguments",
- "help_text": "Extra arguments for Google OAuth2 login. You can restrict it to only allow a single domain to authenticate, even if the user is logged in with multple Google accounts. Refer to the documentation for more detail.",
- "category": "Google OAuth2",
- "category_slug": "google-oauth2",
- "placeholder": {
- "hd": "example.com"
- },
- "default": {},
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "SOCIAL_AUTH_GOOGLE_OAUTH2_ORGANIZATION_MAP": {
- "type": "nested object",
- "required": false,
- "label": "Google OAuth2 Organization Map",
- "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.",
- "category": "Google OAuth2",
- "category_slug": "google-oauth2",
- "placeholder": {
- "Default": {
- "users": true
- },
- "Test Org": {
- "admins": ["admin@example.com"],
- "auditors": ["auditor@example.com"],
- "users": true
- },
- "Test Org 2": {
- "admins": ["admin@example.com", "/^tower-[^@]+*?@.*$/"],
- "remove_admins": true,
- "users": "/^[^@].*?@example\\.com$/i",
- "remove_users": true
- }
- },
- "default": null,
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GOOGLE_OAUTH2_TEAM_MAP": {
- "type": "nested object",
- "required": false,
- "label": "Google OAuth2 Team Map",
- "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in the documentation.",
- "category": "Google OAuth2",
- "category_slug": "google-oauth2",
- "placeholder": {
- "My Team": {
- "organization": "Test Org",
- "users": ["/^[^@]+?@test\\.example\\.com$/"],
- "remove": true
- },
- "Other Team": {
- "organization": "Test Org 2",
- "users": "/^[^@]+?@test2\\.example\\.com$/i",
- "remove": false
- }
- },
- "default": null,
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_KEY": {
- "type": "string",
- "required": false,
- "label": "GitHub OAuth2 Key",
- "help_text": "The OAuth2 key (Client ID) from your GitHub developer application.",
- "category": "GitHub OAuth2",
- "category_slug": "github",
- "default": ""
- },
- "SOCIAL_AUTH_GITHUB_SECRET": {
- "type": "string",
- "required": false,
- "label": "GitHub OAuth2 Secret",
- "help_text": "The OAuth2 secret (Client Secret) from your GitHub developer application.",
- "category": "GitHub OAuth2",
- "category_slug": "github",
- "default": ""
- },
- "SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP": {
- "type": "nested object",
- "required": false,
- "label": "GitHub OAuth2 Organization Map",
- "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.",
- "category": "GitHub OAuth2",
- "category_slug": "github",
- "placeholder": {
- "Default": {
- "users": true
- },
- "Test Org": {
- "admins": ["admin@example.com"],
- "auditors": ["auditor@example.com"],
- "users": true
- },
- "Test Org 2": {
- "admins": ["admin@example.com", "/^tower-[^@]+*?@.*$/"],
- "remove_admins": true,
- "users": "/^[^@].*?@example\\.com$/i",
- "remove_users": true
- }
- },
- "default": null,
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_TEAM_MAP": {
- "type": "nested object",
- "required": false,
- "label": "GitHub OAuth2 Team Map",
- "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in the documentation.",
- "category": "GitHub OAuth2",
- "category_slug": "github",
- "placeholder": {
- "My Team": {
- "organization": "Test Org",
- "users": ["/^[^@]+?@test\\.example\\.com$/"],
- "remove": true
- },
- "Other Team": {
- "organization": "Test Org 2",
- "users": "/^[^@]+?@test2\\.example\\.com$/i",
- "remove": false
- }
- },
- "default": null,
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_ORG_KEY": {
- "type": "string",
- "required": false,
- "label": "GitHub Organization OAuth2 Key",
- "help_text": "The OAuth2 key (Client ID) from your GitHub organization application.",
- "category": "GitHub Organization OAuth2",
- "category_slug": "github-org",
- "default": ""
- },
- "SOCIAL_AUTH_GITHUB_ORG_SECRET": {
- "type": "string",
- "required": false,
- "label": "GitHub Organization OAuth2 Secret",
- "help_text": "The OAuth2 secret (Client Secret) from your GitHub organization application.",
- "category": "GitHub Organization OAuth2",
- "category_slug": "github-org",
- "default": ""
- },
- "SOCIAL_AUTH_GITHUB_ORG_NAME": {
- "type": "string",
- "required": false,
- "label": "GitHub Organization Name",
- "help_text": "The name of your GitHub organization, as used in your organization's URL: https://github.com//.",
- "category": "GitHub Organization OAuth2",
- "category_slug": "github-org",
- "default": ""
- },
- "SOCIAL_AUTH_GITHUB_ORG_ORGANIZATION_MAP": {
- "type": "nested object",
- "required": false,
- "label": "GitHub Organization OAuth2 Organization Map",
- "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.",
- "category": "GitHub Organization OAuth2",
- "category_slug": "github-org",
- "placeholder": {
- "Default": {
- "users": true
- },
- "Test Org": {
- "admins": ["admin@example.com"],
- "auditors": ["auditor@example.com"],
- "users": true
- },
- "Test Org 2": {
- "admins": ["admin@example.com", "/^tower-[^@]+*?@.*$/"],
- "remove_admins": true,
- "users": "/^[^@].*?@example\\.com$/i",
- "remove_users": true
- }
- },
- "default": null,
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_ORG_TEAM_MAP": {
- "type": "nested object",
- "required": false,
- "label": "GitHub Organization OAuth2 Team Map",
- "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in the documentation.",
- "category": "GitHub Organization OAuth2",
- "category_slug": "github-org",
- "placeholder": {
- "My Team": {
- "organization": "Test Org",
- "users": ["/^[^@]+?@test\\.example\\.com$/"],
- "remove": true
- },
- "Other Team": {
- "organization": "Test Org 2",
- "users": "/^[^@]+?@test2\\.example\\.com$/i",
- "remove": false
- }
- },
- "default": null,
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_TEAM_KEY": {
- "type": "string",
- "required": false,
- "label": "GitHub Team OAuth2 Key",
- "help_text": "The OAuth2 key (Client ID) from your GitHub organization application.",
- "category": "GitHub Team OAuth2",
- "category_slug": "github-team",
- "default": ""
- },
- "SOCIAL_AUTH_GITHUB_TEAM_SECRET": {
- "type": "string",
- "required": false,
- "label": "GitHub Team OAuth2 Secret",
- "help_text": "The OAuth2 secret (Client Secret) from your GitHub organization application.",
- "category": "GitHub Team OAuth2",
- "category_slug": "github-team",
- "default": ""
- },
- "SOCIAL_AUTH_GITHUB_TEAM_ID": {
- "type": "string",
- "required": false,
- "label": "GitHub Team ID",
- "help_text": "Find the numeric team ID using the Github API: http://fabian-kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/.",
- "category": "GitHub Team OAuth2",
- "category_slug": "github-team",
- "default": ""
- },
- "SOCIAL_AUTH_GITHUB_TEAM_ORGANIZATION_MAP": {
- "type": "nested object",
- "required": false,
- "label": "GitHub Team OAuth2 Organization Map",
- "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.",
- "category": "GitHub Team OAuth2",
- "category_slug": "github-team",
- "placeholder": {
- "Default": {
- "users": true
- },
- "Test Org": {
- "admins": ["admin@example.com"],
- "auditors": ["auditor@example.com"],
- "users": true
- },
- "Test Org 2": {
- "admins": ["admin@example.com", "/^tower-[^@]+*?@.*$/"],
- "remove_admins": true,
- "users": "/^[^@].*?@example\\.com$/i",
- "remove_users": true
- }
- },
- "default": null,
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_TEAM_TEAM_MAP": {
- "type": "nested object",
- "required": false,
- "label": "GitHub Team OAuth2 Team Map",
- "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in the documentation.",
- "category": "GitHub Team OAuth2",
- "category_slug": "github-team",
- "placeholder": {
- "My Team": {
- "organization": "Test Org",
- "users": ["/^[^@]+?@test\\.example\\.com$/"],
- "remove": true
- },
- "Other Team": {
- "organization": "Test Org 2",
- "users": "/^[^@]+?@test2\\.example\\.com$/i",
- "remove": false
- }
- },
- "default": null,
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_URL": {
- "type": "string",
- "required": false,
- "label": "GitHub Enterprise URL",
- "help_text": "The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise documentation for more details.",
- "category": "GitHub Enterprise OAuth2",
- "category_slug": "github-enterprise",
- "default": ""
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL": {
- "type": "string",
- "required": false,
- "label": "GitHub Enterprise API URL",
- "help_text": "The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github Enterprise documentation for more details.",
- "category": "GitHub Enterprise OAuth2",
- "category_slug": "github-enterprise",
- "default": ""
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY": {
- "type": "string",
- "required": false,
- "label": "GitHub Enterprise OAuth2 Key",
- "help_text": "The OAuth2 key (Client ID) from your GitHub Enterprise developer application.",
- "category": "GitHub Enterprise OAuth2",
- "category_slug": "github-enterprise",
- "default": ""
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET": {
- "type": "string",
- "required": false,
- "label": "GitHub Enterprise OAuth2 Secret",
- "help_text": "The OAuth2 secret (Client Secret) from your GitHub Enterprise developer application.",
- "category": "GitHub OAuth2",
- "category_slug": "github-enterprise",
- "default": ""
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORGANIZATION_MAP": {
- "type": "nested object",
- "required": false,
- "label": "GitHub Enterprise OAuth2 Organization Map",
- "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.",
- "category": "GitHub Enterprise OAuth2",
- "category_slug": "github-enterprise",
- "placeholder": {
- "Default": {
- "users": true
- },
- "Test Org": {
- "admins": ["admin@example.com"],
- "auditors": ["auditor@example.com"],
- "users": true
- },
- "Test Org 2": {
- "admins": ["admin@example.com", "/^tower-[^@]+*?@.*$/"],
- "remove_admins": true,
- "users": "/^[^@].*?@example\\.com$/i",
- "remove_users": true
- }
- },
- "default": null,
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_MAP": {
- "type": "nested object",
- "required": false,
- "label": "GitHub Enterprise OAuth2 Team Map",
- "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in the documentation.",
- "category": "GitHub Enterprise OAuth2",
- "category_slug": "github-enterprise",
- "placeholder": {
- "My Team": {
- "organization": "Test Org",
- "users": ["/^[^@]+?@test\\.example\\.com$/"],
- "remove": true
- },
- "Other Team": {
- "organization": "Test Org 2",
- "users": "/^[^@]+?@test2\\.example\\.com$/i",
- "remove": false
- }
- },
- "default": null,
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL": {
- "type": "string",
- "required": false,
- "label": "GitHub Enterprise Organization URL",
- "help_text": "The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise documentation for more details.",
- "category": "GitHub Enterprise OAuth2",
- "category_slug": "github-enterprise-org",
- "default": ""
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL": {
- "type": "string",
- "required": false,
- "label": "GitHub Enterprise Organization API URL",
- "help_text": "The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github Enterprise documentation for more details.",
- "category": "GitHub Enterprise OAuth2",
- "category_slug": "github-enterprise-org",
- "default": ""
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY": {
- "type": "string",
- "required": false,
- "label": "GitHub Enterprise Organization OAuth2 Key",
- "help_text": "The OAuth2 key (Client ID) from your GitHub Enterprise organization application.",
- "category": "GitHub Enterprise Organization OAuth2",
- "category_slug": "github-enterprise-org",
- "default": ""
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_SECRET": {
- "type": "string",
- "required": false,
- "label": "GitHub Enterprise Organization OAuth2 Secret",
- "help_text": "The OAuth2 secret (Client Secret) from your GitHub Enterprise organization application.",
- "category": "GitHub Enterprise Organization OAuth2",
- "category_slug": "github-enterprise-org",
- "default": ""
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME": {
- "type": "string",
- "required": false,
- "label": "GitHub Enterprise Organization Name",
- "help_text": "The name of your GitHub Enterprise organization, as used in your organization's URL: https://github.com//.",
- "category": "GitHub Enterprise Organization OAuth2",
- "category_slug": "github-enterprise-org",
- "default": ""
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_ORGANIZATION_MAP": {
- "type": "nested object",
- "required": false,
- "label": "GitHub Enterprise Organization OAuth2 Organization Map",
- "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.",
- "category": "GitHub Enterprise Organization OAuth2",
- "category_slug": "github-enterprise-org",
- "placeholder": {
- "Default": {
- "users": true
- },
- "Test Org": {
- "admins": ["admin@example.com"],
- "auditors": ["auditor@example.com"],
- "users": true
- },
- "Test Org 2": {
- "admins": ["admin@example.com", "/^tower-[^@]+*?@.*$/"],
- "remove_admins": true,
- "users": "/^[^@].*?@example\\.com$/i",
- "remove_users": true
- }
- },
- "default": null,
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_TEAM_MAP": {
- "type": "nested object",
- "required": false,
- "label": "GitHub Enterprise Organization OAuth2 Team Map",
- "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in the documentation.",
- "category": "GitHub Enterprise Organization OAuth2",
- "category_slug": "github-enterprise-org",
- "placeholder": {
- "My Team": {
- "organization": "Test Org",
- "users": ["/^[^@]+?@test\\.example\\.com$/"],
- "remove": true
- },
- "Other Team": {
- "organization": "Test Org 2",
- "users": "/^[^@]+?@test2\\.example\\.com$/i",
- "remove": false
- }
- },
- "default": null,
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL": {
- "type": "string",
- "required": false,
- "label": "GitHub Enterprise Team URL",
- "help_text": "The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise documentation for more details.",
- "category": "GitHub Enterprise OAuth2",
- "category_slug": "github-enterprise-team",
- "default": ""
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL": {
- "type": "string",
- "required": false,
- "label": "GitHub Enterprise Team API URL",
- "help_text": "The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github Enterprise documentation for more details.",
- "category": "GitHub Enterprise OAuth2",
- "category_slug": "github-enterprise-team",
- "default": ""
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY": {
- "type": "string",
- "required": false,
- "label": "GitHub Enterprise Team OAuth2 Key",
- "help_text": "The OAuth2 key (Client ID) from your GitHub Enterprise organization application.",
- "category": "GitHub Enterprise Team OAuth2",
- "category_slug": "github-enterprise-team",
- "default": ""
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_SECRET": {
- "type": "string",
- "required": false,
- "label": "GitHub Enterprise Team OAuth2 Secret",
- "help_text": "The OAuth2 secret (Client Secret) from your GitHub Enterprise organization application.",
- "category": "GitHub Enterprise Team OAuth2",
- "category_slug": "github-enterprise-team",
- "default": ""
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID": {
- "type": "string",
- "required": false,
- "label": "GitHub Enterprise Team ID",
- "help_text": "Find the numeric team ID using the Github Enterprise API: http://fabian-kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/.",
- "category": "GitHub Enterprise Team OAuth2",
- "category_slug": "github-enterprise-team",
- "default": ""
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ORGANIZATION_MAP": {
- "type": "nested object",
- "required": false,
- "label": "GitHub Enterprise Team OAuth2 Organization Map",
- "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.",
- "category": "GitHub Enterprise Team OAuth2",
- "category_slug": "github-enterprise-team",
- "placeholder": {
- "Default": {
- "users": true
- },
- "Test Org": {
- "admins": ["admin@example.com"],
- "auditors": ["auditor@example.com"],
- "users": true
- },
- "Test Org 2": {
- "admins": ["admin@example.com", "/^tower-[^@]+*?@.*$/"],
- "remove_admins": true,
- "users": "/^[^@].*?@example\\.com$/i",
- "remove_users": true
- }
- },
- "default": null,
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_TEAM_MAP": {
- "type": "nested object",
- "required": false,
- "label": "GitHub Enterprise Team OAuth2 Team Map",
- "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in the documentation.",
- "category": "GitHub Enterprise Team OAuth2",
- "category_slug": "github-enterprise-team",
- "placeholder": {
- "My Team": {
- "organization": "Test Org",
- "users": ["/^[^@]+?@test\\.example\\.com$/"],
- "remove": true
- },
- "Other Team": {
- "organization": "Test Org 2",
- "users": "/^[^@]+?@test2\\.example\\.com$/i",
- "remove": false
- }
- },
- "default": null,
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_AZUREAD_OAUTH2_KEY": {
- "type": "string",
- "required": false,
- "label": "Azure AD OAuth2 Key",
- "help_text": "The OAuth2 key (Client ID) from your Azure AD application.",
- "category": "Azure AD OAuth2",
- "category_slug": "azuread-oauth2",
- "default": ""
- },
- "SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET": {
- "type": "string",
- "required": false,
- "label": "Azure AD OAuth2 Secret",
- "help_text": "The OAuth2 secret (Client Secret) from your Azure AD application.",
- "category": "Azure AD OAuth2",
- "category_slug": "azuread-oauth2",
- "default": ""
- },
- "SOCIAL_AUTH_AZUREAD_OAUTH2_ORGANIZATION_MAP": {
- "type": "nested object",
- "required": false,
- "label": "Azure AD OAuth2 Organization Map",
- "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.",
- "category": "Azure AD OAuth2",
- "category_slug": "azuread-oauth2",
- "placeholder": {
- "Default": {
- "users": true
- },
- "Test Org": {
- "admins": ["admin@example.com"],
- "auditors": ["auditor@example.com"],
- "users": true
- },
- "Test Org 2": {
- "admins": ["admin@example.com", "/^tower-[^@]+*?@.*$/"],
- "remove_admins": true,
- "users": "/^[^@].*?@example\\.com$/i",
- "remove_users": true
- }
- },
- "default": null,
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_AZUREAD_OAUTH2_TEAM_MAP": {
- "type": "nested object",
- "required": false,
- "label": "Azure AD OAuth2 Team Map",
- "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in the documentation.",
- "category": "Azure AD OAuth2",
- "category_slug": "azuread-oauth2",
- "placeholder": {
- "My Team": {
- "organization": "Test Org",
- "users": ["/^[^@]+?@test\\.example\\.com$/"],
- "remove": true
- },
- "Other Team": {
- "organization": "Test Org 2",
- "users": "/^[^@]+?@test2\\.example\\.com$/i",
- "remove": false
- }
- },
- "default": null,
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SAML_AUTO_CREATE_OBJECTS": {
- "type": "boolean",
- "required": false,
- "label": "Automatically Create Organizations and Teams on SAML Login",
- "help_text": "When enabled (the default), mapped Organizations and Teams will be created automatically on successful SAML login.",
- "category": "SAML",
- "category_slug": "saml",
- "default": true
- },
- "SOCIAL_AUTH_SAML_SP_ENTITY_ID": {
- "type": "string",
- "required": false,
- "label": "SAML Service Provider Entity ID",
- "help_text": "The application-defined unique identifier used as the audience of the SAML service provider (SP) configuration. This is usually the URL for the service.",
- "category": "SAML",
- "category_slug": "saml",
- "default": ""
- },
- "SOCIAL_AUTH_SAML_SP_PUBLIC_CERT": {
- "type": "string",
- "required": true,
- "label": "SAML Service Provider Public Certificate",
- "help_text": "Create a keypair to use as a service provider (SP) and include the certificate content here.",
- "category": "SAML",
- "category_slug": "saml",
- "default": ""
- },
- "SOCIAL_AUTH_SAML_SP_PRIVATE_KEY": {
- "type": "string",
- "required": true,
- "label": "SAML Service Provider Private Key",
- "help_text": "Create a keypair to use as a service provider (SP) and include the private key content here.",
- "category": "SAML",
- "category_slug": "saml",
- "default": ""
- },
- "SOCIAL_AUTH_SAML_ORG_INFO": {
- "type": "nested object",
- "required": true,
- "label": "SAML Service Provider Organization Info",
- "help_text": "Provide the URL, display name, and the name of your app. Refer to the documentation for example syntax.",
- "category": "SAML",
- "category_slug": "saml",
- "placeholder": {
- "en-US": {
- "name": "example",
- "displayname": "Example",
- "url": "http://www.example.com"
- }
- },
- "default": {},
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_SAML_TECHNICAL_CONTACT": {
- "type": "nested object",
- "required": true,
- "label": "SAML Service Provider Technical Contact",
- "help_text": "Provide the name and email address of the technical contact for your service provider. Refer to the documentation for example syntax.",
- "category": "SAML",
- "category_slug": "saml",
- "placeholder": {
- "givenName": "Technical Contact",
- "emailAddress": "techsup@example.com"
- },
- "default": {},
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "SOCIAL_AUTH_SAML_SUPPORT_CONTACT": {
- "type": "nested object",
- "required": true,
- "label": "SAML Service Provider Support Contact",
- "help_text": "Provide the name and email address of the support contact for your service provider. Refer to the documentation for example syntax.",
- "category": "SAML",
- "category_slug": "saml",
- "placeholder": {
- "givenName": "Support Contact",
- "emailAddress": "support@example.com"
- },
- "default": {},
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "SOCIAL_AUTH_SAML_ENABLED_IDPS": {
- "type": "nested object",
- "required": false,
- "label": "SAML Enabled Identity Providers",
- "help_text": "Configure the Entity ID, SSO URL and certificate for each identity provider (IdP) in use. Multiple SAML IdPs are supported. Some IdPs may provide user data using attribute names that differ from the default OIDs. Attribute names may be overridden for each IdP. Refer to the Ansible documentation for additional details and syntax.",
- "category": "SAML",
- "category_slug": "saml",
- "placeholder": {
- "Okta": {
- "entity_id": "http://www.okta.com/HHniyLkaxk9e76wD0Thh",
- "url": "https://dev-123456.oktapreview.com/app/ansibletower/HHniyLkaxk9e76wD0Thh/sso/saml",
- "x509cert": "MIIDpDCCAoygAwIBAgIGAVVZ4rPzMA0GCSqGSIb3...",
- "attr_user_permanent_id": "username",
- "attr_first_name": "first_name",
- "attr_last_name": "last_name",
- "attr_username": "username",
- "attr_email": "email"
- },
- "OneLogin": {
- "entity_id": "https://app.onelogin.com/saml/metadata/123456",
- "url": "https://example.onelogin.com/trust/saml2/http-post/sso/123456",
- "x509cert": "MIIEJjCCAw6gAwIBAgIUfuSD54OPSBhndDHh3gZo...",
- "attr_user_permanent_id": "name_id",
- "attr_first_name": "User.FirstName",
- "attr_last_name": "User.LastName",
- "attr_username": "User.email",
- "attr_email": "User.email"
- }
- },
- "default": {},
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_SAML_SECURITY_CONFIG": {
- "type": "nested object",
- "required": false,
- "label": "SAML Security Config",
- "help_text": "A dict of key value pairs that are passed to the underlying python-saml security setting https://github.com/onelogin/python-saml#settings",
- "category": "SAML",
- "category_slug": "saml",
- "placeholder": {
- "nameIdEncrypted": false,
- "authnRequestsSigned": false,
- "logoutRequestSigned": false,
- "logoutResponseSigned": false,
- "signMetadata": false,
- "wantMessagesSigned": false,
- "wantAssertionsSigned": false,
- "wantAssertionsEncrypted": false,
- "wantNameId": true,
- "wantNameIdEncrypted": false,
- "wantAttributeStatement": true,
- "requestedAuthnContext": true,
- "requestedAuthnContextComparison": "exact",
- "metadataValidUntil": "2015-06-26T20:00:00Z",
- "metadataCacheDuration": "PT518400S",
- "signatureAlgorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
- "digestAlgorithm": "http://www.w3.org/2000/09/xmldsig#sha1"
- },
- "default": {
- "requestedAuthnContext": false
- },
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "SOCIAL_AUTH_SAML_SP_EXTRA": {
- "type": "nested object",
- "required": false,
- "label": "SAML Service Provider extra configuration data",
- "help_text": "A dict of key value pairs to be passed to the underlying python-saml Service Provider configuration setting.",
- "category": "SAML",
- "category_slug": "saml",
- "placeholder": {},
- "default": null,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "SOCIAL_AUTH_SAML_EXTRA_DATA": {
- "type": "list",
- "required": false,
- "label": "SAML IDP to extra_data attribute mapping",
- "help_text": "A list of tuples that maps IDP attributes to extra_attributes. Each attribute will be a list of values, even if only 1 value.",
- "category": "SAML",
- "category_slug": "saml",
- "placeholder": [
- ["attribute_name", "extra_data_name_for_attribute"],
- ["department", "department"],
- ["manager_full_name", "manager_full_name"]
- ],
- "default": null,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "SOCIAL_AUTH_SAML_ORGANIZATION_MAP": {
- "type": "nested object",
- "required": false,
- "label": "SAML Organization Map",
- "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.",
- "category": "SAML",
- "category_slug": "saml",
- "placeholder": {
- "Default": {
- "users": true
- },
- "Test Org": {
- "admins": ["admin@example.com"],
- "auditors": ["auditor@example.com"],
- "users": true
- },
- "Test Org 2": {
- "admins": ["admin@example.com", "/^tower-[^@]+*?@.*$/"],
- "remove_admins": true,
- "users": "/^[^@].*?@example\\.com$/i",
- "remove_users": true
- }
- },
- "default": null,
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_SAML_TEAM_MAP": {
- "type": "nested object",
- "required": false,
- "label": "SAML Team Map",
- "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in the documentation.",
- "category": "SAML",
- "category_slug": "saml",
- "placeholder": {
- "My Team": {
- "organization": "Test Org",
- "users": ["/^[^@]+?@test\\.example\\.com$/"],
- "remove": true
- },
- "Other Team": {
- "organization": "Test Org 2",
- "users": "/^[^@]+?@test2\\.example\\.com$/i",
- "remove": false
- }
- },
- "default": null,
- "child": {
- "type": "nested object",
- "required": true,
- "read_only": false,
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_SAML_ORGANIZATION_ATTR": {
- "type": "nested object",
- "required": false,
- "label": "SAML Organization Attribute Mapping",
- "help_text": "Used to translate user organization membership.",
- "category": "SAML",
- "category_slug": "saml",
- "placeholder": {
- "saml_attr": "organization",
- "saml_admin_attr": "organization_admin",
- "saml_auditor_attr": "organization_auditor",
- "remove": true,
- "remove_admins": true,
- "remove_auditors": true
- },
- "default": {},
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "SOCIAL_AUTH_SAML_TEAM_ATTR": {
- "type": "nested object",
- "required": false,
- "label": "SAML Team Attribute Mapping",
- "help_text": "Used to translate user team membership.",
- "category": "SAML",
- "category_slug": "saml",
- "placeholder": {
- "saml_attr": "team",
- "remove": true,
- "team_org_map": [
- {
- "team": "Marketing",
- "organization": "Red Hat"
- },
- {
- "team": "Human Resources",
- "organization": "Red Hat"
- },
- {
- "team": "Engineering",
- "organization": "Red Hat"
- },
- {
- "team": "Engineering",
- "organization": "Ansible"
- },
- {
- "team": "Quality Engineering",
- "organization": "Ansible"
- },
- {
- "team": "Sales",
- "organization": "Ansible"
- }
- ]
- },
- "default": {},
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- },
- "SOCIAL_AUTH_SAML_USER_FLAGS_BY_ATTR": {
- "type": "nested object",
- "required": false,
- "label": "SAML User Flags Attribute Mapping",
- "help_text": "Used to map super users and system auditors from SAML.",
- "category": "SAML",
- "category_slug": "saml",
- "placeholder": {
- "is_superuser_attr": "saml_attr",
- "is_superuser_value": "value",
- "is_superuser_role": "saml_role",
- "is_system_auditor_attr": "saml_attr",
- "is_system_auditor_value": "value",
- "is_system_auditor_role": "saml_role"
- },
- "default": {},
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "GET": {
- "ACTIVITY_STREAM_ENABLED": {
- "type": "boolean",
- "label": "Enable Activity Stream",
- "help_text": "Enable capturing activity for the activity stream.",
- "category": "System",
- "category_slug": "system",
- "defined_in_file": false
- },
- "ACTIVITY_STREAM_ENABLED_FOR_INVENTORY_SYNC": {
- "type": "boolean",
- "label": "Enable Activity Stream for Inventory Sync",
- "help_text": "Enable capturing activity for the activity stream when running inventory sync.",
- "category": "System",
- "category_slug": "system",
- "defined_in_file": false
- },
- "ORG_ADMINS_CAN_SEE_ALL_USERS": {
- "type": "boolean",
- "label": "All Users Visible to Organization Admins",
- "help_text": "Controls whether any Organization Admin can view all users and teams, even those not associated with their Organization.",
- "category": "System",
- "category_slug": "system",
- "defined_in_file": false
- },
- "MANAGE_ORGANIZATION_AUTH": {
- "type": "boolean",
- "label": "Organization Admins Can Manage Users and Teams",
- "help_text": "Controls whether any Organization Admin has the privileges to create and manage users and teams. You may want to disable this ability if you are using an LDAP or SAML integration.",
- "category": "System",
- "category_slug": "system",
- "defined_in_file": false
- },
- "TOWER_URL_BASE": {
- "type": "string",
- "label": "Base URL of the service",
- "help_text": "This setting is used by services like notifications to render a valid url to the service.",
- "category": "System",
- "category_slug": "system",
- "defined_in_file": false
- },
- "REMOTE_HOST_HEADERS": {
- "type": "list",
- "label": "Remote Host Headers",
- "help_text": "HTTP headers and meta keys to search to determine remote host name or IP. Add additional items to this list, such as \"HTTP_X_FORWARDED_FOR\", if behind a reverse proxy. See the \"Proxy Support\" section of the Adminstrator guide for more details.",
- "category": "System",
- "category_slug": "system",
- "defined_in_file": false,
- "child": {
- "type": "string"
- }
- },
- "PROXY_IP_ALLOWED_LIST": {
- "type": "list",
- "label": "Proxy IP Allowed List",
- "help_text": "If the service is behind a reverse proxy/load balancer, use this setting to configure the proxy IP addresses from which the service should trust custom REMOTE_HOST_HEADERS header values. If this setting is an empty list (the default), the headers specified by REMOTE_HOST_HEADERS will be trusted unconditionally')",
- "category": "System",
- "category_slug": "system",
- "defined_in_file": false,
- "child": {
- "type": "string"
- }
- },
- "LICENSE": {
- "type": "nested object",
- "label": "License",
- "help_text": "The license controls which features and functionality are enabled. Use /api/v2/config/ to update or change the license.",
- "category": "System",
- "category_slug": "system",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "REDHAT_USERNAME": {
- "type": "string",
- "label": "Red Hat customer username",
- "help_text": "This username is used to send data to Automation Analytics",
- "category": "System",
- "category_slug": "system",
- "defined_in_file": false
- },
- "REDHAT_PASSWORD": {
- "type": "string",
- "label": "Red Hat customer password",
- "help_text": "This password is used to send data to Automation Analytics",
- "category": "System",
- "category_slug": "system",
- "defined_in_file": false
- },
- "SUBSCRIPTIONS_USERNAME": {
- "type": "string",
- "label": "Red Hat or Satellite username",
- "help_text": "This username is used to retrieve subscription and content information",
- "category": "System",
- "category_slug": "system",
- "defined_in_file": false
- },
- "SUBSCRIPTIONS_PASSWORD": {
- "type": "string",
- "label": "Red Hat or Satellite password",
- "help_text": "This password is used to retrieve subscription and content information",
- "category": "System",
- "category_slug": "system",
- "defined_in_file": false
- },
- "AUTOMATION_ANALYTICS_URL": {
- "type": "string",
- "label": "Automation Analytics upload URL",
- "help_text": "This setting is used to to configure the upload URL for data collection for Automation Analytics.",
- "category": "System",
- "category_slug": "system",
- "defined_in_file": false
- },
- "INSTALL_UUID": {
- "type": "string",
- "label": "Unique identifier for an installation",
- "category": "System",
- "category_slug": "system",
- "defined_in_file": false
- },
- "DEFAULT_EXECUTION_ENVIRONMENT": {
- "type": "field",
- "label": "Global default execution environment",
- "help_text": "The Execution Environment to be used when one has not been configured for a job template.",
- "category": "System",
- "category_slug": "system",
- "defined_in_file": false
- },
- "CUSTOM_VENV_PATHS": {
- "type": "list",
- "label": "Custom virtual environment paths",
- "help_text": "Paths where Tower will look for custom virtual environments (in addition to /var/lib/awx/venv/). Enter one path per line.",
- "category": "System",
- "category_slug": "system",
- "defined_in_file": false,
- "child": {
- "type": "string"
- }
- },
- "AD_HOC_COMMANDS": {
- "type": "list",
- "label": "Ansible Modules Allowed for Ad Hoc Jobs",
- "help_text": "List of modules allowed to be used by ad-hoc jobs.",
- "category": "Jobs",
- "category_slug": "jobs",
- "defined_in_file": false,
- "child": {
- "type": "string"
- }
- },
- "ALLOW_JINJA_IN_EXTRA_VARS": {
- "type": "choice",
- "label": "When can extra variables contain Jinja templates?",
- "help_text": "Ansible allows variable substitution via the Jinja2 templating language for --extra-vars. This poses a potential security risk where users with the ability to specify extra vars at job launch time can use Jinja2 templates to run arbitrary Python. It is recommended that this value be set to \"template\" or \"never\".",
- "category": "Jobs",
- "category_slug": "jobs",
- "defined_in_file": false,
- "choices": [
- ["always", "Always"],
- ["never", "Never"],
- ["template", "Only On Job Template Definitions"]
- ]
- },
- "AWX_ISOLATION_BASE_PATH": {
- "type": "string",
- "label": "Job execution path",
- "help_text": "The directory in which the service will create new temporary directories for job execution and isolation (such as credential files).",
- "category": "Jobs",
- "category_slug": "jobs",
- "defined_in_file": false
- },
- "AWX_ISOLATION_SHOW_PATHS": {
- "type": "list",
- "label": "Paths to expose to isolated jobs",
- "help_text": "List of paths that would otherwise be hidden to expose to isolated jobs. Enter one path per line.",
- "category": "Jobs",
- "category_slug": "jobs",
- "defined_in_file": false,
- "child": {
- "type": "string"
- }
- },
- "AWX_TASK_ENV": {
- "type": "nested object",
- "label": "Extra Environment Variables",
- "help_text": "Additional environment variables set for playbook runs, inventory updates, project updates, and notification sending.",
- "category": "Jobs",
- "category_slug": "jobs",
- "defined_in_file": false,
- "child": {
- "type": "string"
- }
- },
- "GALAXY_TASK_ENV": {
- "type": "nested object",
- "required": true,
- "label": "Environment Variables for Galaxy Commands",
- "help_text": "Additional environment variables set for invocations of ansible-galaxy within project updates. Useful if you must use a proxy server for ansible-galaxy but not git.",
- "category": "Jobs",
- "category_slug": "jobs",
- "placeholder": {
- "HTTP_PROXY": "myproxy.local:8080"
- },
- "default": {
- "ANSIBLE_FORCE_COLOR": "false",
- "GIT_SSH_COMMAND": "ssh -o StrictHostKeyChecking=no"
- },
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- },
- "INSIGHTS_TRACKING_STATE": {
- "type": "boolean",
- "label": "Gather data for Automation Analytics",
- "help_text": "Enables the service to gather data on automation and send it to Automation Analytics.",
- "category": "System",
- "category_slug": "system",
- "defined_in_file": false
- },
- "PROJECT_UPDATE_VVV": {
- "type": "boolean",
- "label": "Run Project Updates With Higher Verbosity",
- "help_text": "Adds the CLI -vvv flag to ansible-playbook runs of project_update.yml used for project updates.",
- "category": "Jobs",
- "category_slug": "jobs",
- "defined_in_file": false
- },
- "AWX_ROLES_ENABLED": {
- "type": "boolean",
- "label": "Enable Role Download",
- "help_text": "Allows roles to be dynamically downloaded from a requirements.yml file for SCM projects.",
- "category": "Jobs",
- "category_slug": "jobs",
- "defined_in_file": false
- },
- "AWX_COLLECTIONS_ENABLED": {
- "type": "boolean",
- "label": "Enable Collection(s) Download",
- "help_text": "Allows collections to be dynamically downloaded from a requirements.yml file for SCM projects.",
- "category": "Jobs",
- "category_slug": "jobs",
- "defined_in_file": false
- },
- "AWX_SHOW_PLAYBOOK_LINKS": {
- "type": "boolean",
- "label": "Follow symlinks",
- "help_text": "Follow symbolic links when scanning for playbooks. Be aware that setting this to True can lead to infinite recursion if a link points to a parent directory of itself.",
- "category": "Jobs",
- "category_slug": "jobs",
- "defined_in_file": false
- },
- "AWX_MOUNT_ISOLATED_PATHS_ON_K8S": {
- "type": "boolean",
- "label": "Expose host paths for Container Groups",
- "help_text": "Expose paths via hostPath for the Pods created by a Container Group. HostPath volumes present many security risks, and it is a best practice to avoid the use of HostPaths when possible. ",
- "category": "Jobs",
- "category_slug": "jobs",
- "defined_in_file": false
- },
- "GALAXY_IGNORE_CERTS": {
- "type": "boolean",
- "label": "Ignore Ansible Galaxy SSL Certificate Verification",
- "help_text": "If set to true, certificate validation will not be done when installing content from any Galaxy server.",
- "category": "Jobs",
- "category_slug": "jobs",
- "defined_in_file": false
- },
- "STDOUT_MAX_BYTES_DISPLAY": {
- "type": "integer",
- "label": "Standard Output Maximum Display Size",
- "help_text": "Maximum Size of Standard Output in bytes to display before requiring the output be downloaded.",
- "min_value": 0,
- "category": "Jobs",
- "category_slug": "jobs",
- "defined_in_file": false
- },
- "EVENT_STDOUT_MAX_BYTES_DISPLAY": {
- "type": "integer",
- "label": "Job Event Standard Output Maximum Display Size",
- "help_text": "Maximum Size of Standard Output in bytes to display for a single job or ad hoc command event. `stdout` will end with `…` when truncated.",
- "min_value": 0,
- "category": "Jobs",
- "category_slug": "jobs",
- "defined_in_file": false
- },
- "SCHEDULE_MAX_JOBS": {
- "type": "integer",
- "label": "Maximum Scheduled Jobs",
- "help_text": "Maximum number of the same job template that can be waiting to run when launching from a schedule before no more are created.",
- "min_value": 1,
- "category": "Jobs",
- "category_slug": "jobs",
- "defined_in_file": false
- },
- "AWX_ANSIBLE_CALLBACK_PLUGINS": {
- "type": "list",
- "label": "Ansible Callback Plugins",
- "help_text": "List of paths to search for extra callback plugins to be used when running jobs. Enter one path per line.",
- "category": "Jobs",
- "category_slug": "jobs",
- "defined_in_file": false,
- "child": {
- "type": "string"
- }
- },
- "DEFAULT_JOB_TIMEOUT": {
- "type": "integer",
- "label": "Default Job Timeout",
- "help_text": "Maximum time in seconds to allow jobs to run. Use value of 0 to indicate that no timeout should be imposed. A timeout set on an individual job template will override this.",
- "min_value": 0,
- "category": "Jobs",
- "category_slug": "jobs",
- "defined_in_file": false,
- "unit": "seconds"
- },
- "DEFAULT_JOB_IDLE_TIMEOUT": {
- "type": "integer",
- "label": "Default Job Idle Timeout",
- "help_text": "If no output is detected from ansible in this number of seconds the execution will be terminated. Use value of 0 to used default idle_timeout is 600s.",
- "min_value": 0,
- "category": "Jobs",
- "category_slug": "jobs",
- "defined_in_file": false,
- "unit": "seconds"
- },
- "DEFAULT_INVENTORY_UPDATE_TIMEOUT": {
- "type": "integer",
- "label": "Default Inventory Update Timeout",
- "help_text": "Maximum time in seconds to allow inventory updates to run. Use value of 0 to indicate that no timeout should be imposed. A timeout set on an individual inventory source will override this.",
- "min_value": 0,
- "category": "Jobs",
- "category_slug": "jobs",
- "defined_in_file": false,
- "unit": "seconds"
- },
- "DEFAULT_PROJECT_UPDATE_TIMEOUT": {
- "type": "integer",
- "label": "Default Project Update Timeout",
- "help_text": "Maximum time in seconds to allow project updates to run. Use value of 0 to indicate that no timeout should be imposed. A timeout set on an individual project will override this.",
- "min_value": 0,
- "category": "Jobs",
- "category_slug": "jobs",
- "defined_in_file": false,
- "unit": "seconds"
- },
- "ANSIBLE_FACT_CACHE_TIMEOUT": {
- "type": "integer",
- "label": "Per-Host Ansible Fact Cache Timeout",
- "help_text": "Maximum time, in seconds, that stored Ansible facts are considered valid since the last time they were modified. Only valid, non-stale, facts will be accessible by a playbook. Note, this does not influence the deletion of ansible_facts from the database. Use a value of 0 to indicate that no timeout should be imposed.",
- "min_value": 0,
- "category": "Jobs",
- "category_slug": "jobs",
- "defined_in_file": false,
- "unit": "seconds"
- },
- "MAX_FORKS": {
- "type": "integer",
- "label": "Maximum number of forks per job",
- "help_text": "Saving a Job Template with more than this number of forks will result in an error. When set to 0, no limit is applied.",
- "category": "Jobs",
- "category_slug": "jobs",
- "defined_in_file": false
- },
- "LOG_AGGREGATOR_HOST": {
- "type": "string",
- "label": "Logging Aggregator",
- "help_text": "Hostname/IP where external logs will be sent to.",
- "category": "Logging",
- "category_slug": "logging",
- "defined_in_file": false
- },
- "LOG_AGGREGATOR_PORT": {
- "type": "integer",
- "label": "Logging Aggregator Port",
- "help_text": "Port on Logging Aggregator to send logs to (if required and not provided in Logging Aggregator).",
- "category": "Logging",
- "category_slug": "logging",
- "defined_in_file": false
- },
- "LOG_AGGREGATOR_TYPE": {
- "type": "choice",
- "label": "Logging Aggregator Type",
- "help_text": "Format messages for the chosen log aggregator.",
- "category": "Logging",
- "category_slug": "logging",
- "defined_in_file": false,
- "choices": [
- [null, "---------"],
- ["logstash", "logstash"],
- ["splunk", "splunk"],
- ["loggly", "loggly"],
- ["sumologic", "sumologic"],
- ["other", "other"]
- ]
- },
- "LOG_AGGREGATOR_USERNAME": {
- "type": "string",
- "label": "Logging Aggregator Username",
- "help_text": "Username for external log aggregator (if required; HTTP/s only).",
- "category": "Logging",
- "category_slug": "logging",
- "defined_in_file": false
- },
- "LOG_AGGREGATOR_PASSWORD": {
- "type": "string",
- "label": "Logging Aggregator Password/Token",
- "help_text": "Password or authentication token for external log aggregator (if required; HTTP/s only).",
- "category": "Logging",
- "category_slug": "logging",
- "defined_in_file": false
- },
- "LOG_AGGREGATOR_LOGGERS": {
- "type": "list",
- "label": "Loggers Sending Data to Log Aggregator Form",
- "help_text": "List of loggers that will send HTTP logs to the collector, these can include any or all of: \nawx - service logs\nactivity_stream - activity stream records\njob_events - callback data from Ansible job events\nsystem_tracking - facts gathered from scan jobs.",
- "category": "Logging",
- "category_slug": "logging",
- "defined_in_file": false,
- "child": {
- "type": "string"
- }
- },
- "LOG_AGGREGATOR_INDIVIDUAL_FACTS": {
- "type": "boolean",
- "label": "Log System Tracking Facts Individually",
- "help_text": "If set, system tracking facts will be sent for each package, service, or other item found in a scan, allowing for greater search query granularity. If unset, facts will be sent as a single dictionary, allowing for greater efficiency in fact processing.",
- "category": "Logging",
- "category_slug": "logging",
- "defined_in_file": false
- },
- "LOG_AGGREGATOR_ENABLED": {
- "type": "boolean",
- "label": "Enable External Logging",
- "help_text": "Enable sending logs to external log aggregator.",
- "category": "Logging",
- "category_slug": "logging",
- "defined_in_file": false
- },
- "LOG_AGGREGATOR_TOWER_UUID": {
- "type": "string",
- "label": "Cluster-wide unique identifier.",
- "help_text": "Useful to uniquely identify instances.",
- "category": "Logging",
- "category_slug": "logging",
- "defined_in_file": false
- },
- "LOG_AGGREGATOR_PROTOCOL": {
- "type": "choice",
- "label": "Logging Aggregator Protocol",
- "help_text": "Protocol used to communicate with log aggregator. HTTPS/HTTP assumes HTTPS unless http:// is explicitly used in the Logging Aggregator hostname.",
- "category": "Logging",
- "category_slug": "logging",
- "defined_in_file": false,
- "choices": [
- ["https", "HTTPS/HTTP"],
- ["tcp", "TCP"],
- ["udp", "UDP"]
- ]
- },
- "LOG_AGGREGATOR_TCP_TIMEOUT": {
- "type": "integer",
- "label": "TCP Connection Timeout",
- "help_text": "Number of seconds for a TCP connection to external log aggregator to timeout. Applies to HTTPS and TCP log aggregator protocols.",
- "category": "Logging",
- "category_slug": "logging",
- "defined_in_file": false,
- "unit": "seconds"
- },
- "LOG_AGGREGATOR_VERIFY_CERT": {
- "type": "boolean",
- "label": "Enable/disable HTTPS certificate verification",
- "help_text": "Flag to control enable/disable of certificate verification when LOG_AGGREGATOR_PROTOCOL is \"https\". If enabled, the log handler will verify certificate sent by external log aggregator before establishing connection.",
- "category": "Logging",
- "category_slug": "logging",
- "defined_in_file": false
- },
- "LOG_AGGREGATOR_LEVEL": {
- "type": "choice",
- "label": "Logging Aggregator Level Threshold",
- "help_text": "Level threshold used by log handler. Severities from lowest to highest are DEBUG, INFO, WARNING, ERROR, CRITICAL. Messages less severe than the threshold will be ignored by log handler. (messages under category awx.anlytics ignore this setting)",
- "category": "Logging",
- "category_slug": "logging",
- "defined_in_file": false,
- "choices": [
- ["DEBUG", "DEBUG"],
- ["INFO", "INFO"],
- ["WARNING", "WARNING"],
- ["ERROR", "ERROR"],
- ["CRITICAL", "CRITICAL"]
- ]
- },
- "LOG_AGGREGATOR_MAX_DISK_USAGE_GB": {
- "type": "integer",
- "label": "Maximum disk persistance for external log aggregation (in GB)",
- "help_text": "Amount of data to store (in gigabytes) during an outage of the external log aggregator (defaults to 1). Equivalent to the rsyslogd queue.maxdiskspace setting.",
- "min_value": 1,
- "category": "Logging",
- "category_slug": "logging",
- "defined_in_file": false
- },
- "LOG_AGGREGATOR_MAX_DISK_USAGE_PATH": {
- "type": "string",
- "label": "File system location for rsyslogd disk persistence",
- "help_text": "Location to persist logs that should be retried after an outage of the external log aggregator (defaults to /var/lib/awx). Equivalent to the rsyslogd queue.spoolDirectory setting.",
- "category": "Logging",
- "category_slug": "logging",
- "defined_in_file": false
- },
- "LOG_AGGREGATOR_RSYSLOGD_DEBUG": {
- "type": "boolean",
- "label": "Enable rsyslogd debugging",
- "help_text": "Enabled high verbosity debugging for rsyslogd. Useful for debugging connection issues for external log aggregation.",
- "category": "Logging",
- "category_slug": "logging",
- "defined_in_file": false
- },
- "API_400_ERROR_LOG_FORMAT": {
- "type": "string",
- "required": false,
- "label": "Log Format For API 4XX Errors",
- "help_text": "The format of logged messages when an API 4XX error occurs, the following variables will be substituted: \nstatus_code - The HTTP status code of the error\nuser_name - The user name attempting to use the API\nurl_path - The URL path to the API endpoint called\nremote_addr - The remote address seen for the user\nerror - The error set by the api endpoint\nVariables need to be in the format {}.",
- "category": "Logging",
- "category_slug": "logging",
- "default": "status {status_code} received by user {user_name} attempting to access {url_path} from {remote_addr}"
- },
- "AUTOMATION_ANALYTICS_LAST_GATHER": {
- "type": "datetime",
- "label": "Last gather date for Automation Analytics.",
- "category": "System",
- "category_slug": "system",
- "defined_in_file": false
- },
- "AUTOMATION_ANALYTICS_LAST_ENTRIES": {
- "type": "string",
- "label": "Last gathered entries from the data collection service of Automation Analytics",
- "category": "System",
- "category_slug": "system",
- "defined_in_file": false
- },
- "AUTOMATION_ANALYTICS_GATHER_INTERVAL": {
- "type": "integer",
- "label": "Automation Analytics Gather Interval",
- "help_text": "Interval (in seconds) between data gathering.",
- "min_value": 1800,
- "category": "System",
- "category_slug": "system",
- "defined_in_file": false,
- "unit": "seconds"
- },
- "SESSION_COOKIE_AGE": {
- "type": "integer",
- "label": "Idle Time Force Log Out",
- "help_text": "Number of seconds that a user is inactive before they will need to login again.",
- "min_value": 60,
- "max_value": 30000000000,
- "category": "Authentication",
- "category_slug": "authentication",
- "defined_in_file": false,
- "unit": "seconds"
- },
- "SESSIONS_PER_USER": {
- "type": "integer",
- "label": "Maximum number of simultaneous logged in sessions",
- "help_text": "Maximum number of simultaneous logged in sessions a user may have. To disable enter -1.",
- "min_value": -1,
- "category": "Authentication",
- "category_slug": "authentication",
- "defined_in_file": false
- },
- "DISABLE_LOCAL_AUTH": {
- "type": "boolean",
- "label": "Disable the built-in authentication system",
- "help_text": "Controls whether users are prevented from using the built-in authentication system. You probably want to do this if you are using an LDAP or SAML integration.",
- "category": "Authentication",
- "category_slug": "authentication",
- "defined_in_file": false
- },
- "AUTH_BASIC_ENABLED": {
- "type": "boolean",
- "label": "Enable HTTP Basic Auth",
- "help_text": "Enable HTTP Basic Auth for the API Browser.",
- "category": "Authentication",
- "category_slug": "authentication",
- "defined_in_file": false
- },
- "OAUTH2_PROVIDER": {
- "type": "nested object",
- "label": "OAuth 2 Timeout Settings",
- "help_text": "Dictionary for customizing OAuth 2 timeouts, available items are `ACCESS_TOKEN_EXPIRE_SECONDS`, the duration of access tokens in the number of seconds, `AUTHORIZATION_CODE_EXPIRE_SECONDS`, the duration of authorization codes in the number of seconds, and `REFRESH_TOKEN_EXPIRE_SECONDS`, the duration of refresh tokens, after expired access tokens, in the number of seconds.",
- "category": "Authentication",
- "category_slug": "authentication",
- "defined_in_file": false,
- "unit": "seconds",
- "child": {
- "type": "integer",
- "min_value": 1
- }
- },
- "ALLOW_OAUTH2_FOR_EXTERNAL_USERS": {
- "type": "boolean",
- "label": "Allow External Users to Create OAuth2 Tokens",
- "help_text": "For security reasons, users from external auth providers (LDAP, SAML, SSO, Radius, and others) are not allowed to create OAuth2 tokens. To change this behavior, enable this setting. Existing tokens will not be deleted when this setting is toggled off.",
- "category": "Authentication",
- "category_slug": "authentication",
- "defined_in_file": false
- },
- "LOGIN_REDIRECT_OVERRIDE": {
- "type": "string",
- "label": "Login redirect override URL",
- "help_text": "URL to which unauthorized users will be redirected to log in. If blank, users will be sent to the login page.",
- "category": "Authentication",
- "category_slug": "authentication",
- "defined_in_file": false
- },
- "PENDO_TRACKING_STATE": {
- "type": "choice",
- "label": "User Analytics Tracking State",
- "help_text": "Enable or Disable User Analytics Tracking.",
- "category": "UI",
- "category_slug": "ui",
- "defined_in_file": false,
- "choices": [
- ["off", "Off"],
- ["anonymous", "Anonymous"],
- ["detailed", "Detailed"]
- ]
- },
- "CUSTOM_LOGIN_INFO": {
- "type": "string",
- "label": "Custom Login Info",
- "help_text": "If needed, you can add specific information (such as a legal notice or a disclaimer) to a text box in the login modal using this setting. Any content added must be in plain text or an HTML fragment, as other markup languages are not supported.",
- "category": "UI",
- "category_slug": "ui",
- "defined_in_file": false
- },
- "CUSTOM_LOGO": {
- "type": "string",
- "label": "Custom Logo",
- "help_text": "To set up a custom logo, provide a file that you create. For the custom logo to look its best, use a .png file with a transparent background. GIF, PNG and JPEG formats are supported.",
- "category": "UI",
- "category_slug": "ui",
- "defined_in_file": false
- },
- "MAX_UI_JOB_EVENTS": {
- "type": "integer",
- "label": "Max Job Events Retrieved by UI",
- "help_text": "Maximum number of job events for the UI to retrieve within a single request.",
- "min_value": 100,
- "category": "UI",
- "category_slug": "ui",
- "defined_in_file": false
- },
- "UI_LIVE_UPDATES_ENABLED": {
- "type": "boolean",
- "label": "Enable Live Updates in the UI",
- "help_text": "If disabled, the page will not refresh when events are received. Reloading the page will be required to get the latest details.",
- "category": "UI",
- "category_slug": "ui",
- "defined_in_file": false
- },
- "AUTHENTICATION_BACKENDS": {
- "type": "list",
- "label": "Authentication Backends",
- "help_text": "List of authentication backends that are enabled based on license features and other authentication settings.",
- "category": "Authentication",
- "category_slug": "authentication",
- "defined_in_file": false,
- "child": {
- "type": "string"
- }
- },
- "SOCIAL_AUTH_ORGANIZATION_MAP": {
- "type": "nested object",
- "label": "Social Auth Organization Map",
- "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.",
- "category": "Authentication",
- "category_slug": "authentication",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_TEAM_MAP": {
- "type": "nested object",
- "label": "Social Auth Team Map",
- "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in the documentation.",
- "category": "Authentication",
- "category_slug": "authentication",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_USER_FIELDS": {
- "type": "list",
- "label": "Social Auth User Fields",
- "help_text": "When set to an empty list `[]`, this setting prevents new user accounts from being created. Only users who have previously logged in using social auth or have a user account with a matching email address will be able to login.",
- "category": "Authentication",
- "category_slug": "authentication",
- "defined_in_file": false,
- "child": {
- "type": "string"
- }
- },
- "SOCIAL_AUTH_OIDC_KEY": {
- "type": "string",
- "label": "OIDC Key",
- "help_text": "The OIDC key (Client ID) from your IDP.",
- "category": "Generic OIDC",
- "category_slug": "oidc",
- "default": ""
- },
- "SOCIAL_AUTH_OIDC_SECRET": {
- "type": "string",
- "label": "OIDC Secret",
- "help_text": "The OIDC secret (Client Secret) from your IDP.",
- "category": "Generic OIDC",
- "category_slug": "oidc",
- "default": ""
- },
- "SOCIAL_AUTH_OIDC_OIDC_ENDPOINT": {
- "type": "string",
- "label": "OIDC Provider URL",
- "help_text": "The URL for your OIDC provider, e.g.: http(s)://hostname/.",
- "category": "Generic OIDC",
- "category_slug": "oidc",
- "default": ""
- },
- "SOCIAL_AUTH_OIDC_VERIFY_SSL": {
- "type": "boolean",
- "label": "Verify OIDC Provider Certificate",
- "help_text": "Verify the OIDV provider ssl certificate.",
- "category": "Generic OIDC",
- "category_slug": "oidc",
- "default": true
- },
- "AUTH_LDAP_SERVER_URI": {
- "type": "string",
- "label": "LDAP Server URI",
- "help_text": "URI to connect to LDAP server, such as \"ldap://ldap.example.com:389\" (non-SSL) or \"ldaps://ldap.example.com:636\" (SSL). Multiple LDAP servers may be specified by separating with spaces or commas. LDAP authentication is disabled if this parameter is empty.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_BIND_DN": {
- "type": "string",
- "label": "LDAP Bind DN",
- "help_text": "DN (Distinguished Name) of user to bind for all search queries. This is the system user account we will use to login to query LDAP for other user information. Refer to the documentation for example syntax.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_BIND_PASSWORD": {
- "type": "string",
- "label": "LDAP Bind Password",
- "help_text": "Password used to bind LDAP user account.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_START_TLS": {
- "type": "boolean",
- "label": "LDAP Start TLS",
- "help_text": "Whether to enable TLS when the LDAP connection is not using SSL.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_CONNECTION_OPTIONS": {
- "type": "nested object",
- "label": "LDAP Connection Options",
- "help_text": "Additional options to set for the LDAP connection. LDAP referrals are disabled by default (to prevent certain LDAP queries from hanging with AD). Option names should be strings (e.g. \"OPT_REFERRALS\"). Refer to https://www.python-ldap.org/doc/html/ldap.html#options for possible options and values that can be set.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_USER_SEARCH": {
- "type": "list",
- "label": "LDAP User Search",
- "help_text": "LDAP search query to find users. Any user that matches the given pattern will be able to login to the service. The user should also be mapped into an organization (as defined in the AUTH_LDAP_ORGANIZATION_MAP setting). If multiple search queries need to be supported use of \"LDAPUnion\" is possible. See the documentation for details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_USER_DN_TEMPLATE": {
- "type": "string",
- "label": "LDAP User DN Template",
- "help_text": "Alternative to user search, if user DNs are all of the same format. This approach is more efficient for user lookups than searching if it is usable in your organizational environment. If this setting has a value it will be used instead of AUTH_LDAP_USER_SEARCH.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_USER_ATTR_MAP": {
- "type": "nested object",
- "label": "LDAP User Attribute Map",
- "help_text": "Mapping of LDAP user schema to API user attributes. The default setting is valid for ActiveDirectory but users with other LDAP configurations may need to change the values. Refer to the documentation for additional details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "string"
- }
- },
- "AUTH_LDAP_GROUP_SEARCH": {
- "type": "list",
- "label": "LDAP Group Search",
- "help_text": "Users are mapped to organizations based on their membership in LDAP groups. This setting defines the LDAP search query to find groups. Unlike the user search, group search does not support LDAPSearchUnion.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_GROUP_TYPE": {
- "type": "choice",
- "label": "LDAP Group Type",
- "help_text": "The group type may need to be changed based on the type of the LDAP server. Values are listed at: https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "choices": [
- ["PosixGroupType", "PosixGroupType"],
- ["GroupOfNamesType", "GroupOfNamesType"],
- ["GroupOfUniqueNamesType", "GroupOfUniqueNamesType"],
- ["ActiveDirectoryGroupType", "ActiveDirectoryGroupType"],
- ["OrganizationalRoleGroupType", "OrganizationalRoleGroupType"],
- ["MemberDNGroupType", "MemberDNGroupType"],
- ["NestedGroupOfNamesType", "NestedGroupOfNamesType"],
- ["NestedGroupOfUniqueNamesType", "NestedGroupOfUniqueNamesType"],
- ["NestedActiveDirectoryGroupType", "NestedActiveDirectoryGroupType"],
- [
- "NestedOrganizationalRoleGroupType",
- "NestedOrganizationalRoleGroupType"
- ],
- ["NestedMemberDNGroupType", "NestedMemberDNGroupType"],
- ["PosixUIDGroupType", "PosixUIDGroupType"]
- ]
- },
- "AUTH_LDAP_GROUP_TYPE_PARAMS": {
- "type": "nested object",
- "label": "LDAP Group Type Parameters",
- "help_text": "Key value parameters to send the chosen group type init method.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_REQUIRE_GROUP": {
- "type": "string",
- "label": "LDAP Require Group",
- "help_text": "Group DN required to login. If specified, user must be a member of this group to login via LDAP. If not set, everyone in LDAP that matches the user search will be able to login to the service. Only one require group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_DENY_GROUP": {
- "type": "string",
- "label": "LDAP Deny Group",
- "help_text": "Group DN denied from login. If specified, user will not be allowed to login if a member of this group. Only one deny group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_USER_FLAGS_BY_GROUP": {
- "type": "nested object",
- "label": "LDAP User Flags By Group",
- "help_text": "Retrieve users from a given group. At this time, superuser and system auditors are the only groups supported. Refer to the documentation for more detail.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "list",
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_ORGANIZATION_MAP": {
- "type": "nested object",
- "label": "LDAP Organization Map",
- "help_text": "Mapping between organization admins/users and LDAP groups. This controls which users are placed into which organizations relative to their LDAP group memberships. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_TEAM_MAP": {
- "type": "nested object",
- "label": "LDAP Team Map",
- "help_text": "Mapping between team members (users) and LDAP groups. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_1_SERVER_URI": {
- "type": "string",
- "label": "LDAP Server URI",
- "help_text": "URI to connect to LDAP server, such as \"ldap://ldap.example.com:389\" (non-SSL) or \"ldaps://ldap.example.com:636\" (SSL). Multiple LDAP servers may be specified by separating with spaces or commas. LDAP authentication is disabled if this parameter is empty.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_1_BIND_DN": {
- "type": "string",
- "label": "LDAP Bind DN",
- "help_text": "DN (Distinguished Name) of user to bind for all search queries. This is the system user account we will use to login to query LDAP for other user information. Refer to the documentation for example syntax.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_1_BIND_PASSWORD": {
- "type": "string",
- "label": "LDAP Bind Password",
- "help_text": "Password used to bind LDAP user account.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_1_START_TLS": {
- "type": "boolean",
- "label": "LDAP Start TLS",
- "help_text": "Whether to enable TLS when the LDAP connection is not using SSL.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_1_CONNECTION_OPTIONS": {
- "type": "nested object",
- "label": "LDAP Connection Options",
- "help_text": "Additional options to set for the LDAP connection. LDAP referrals are disabled by default (to prevent certain LDAP queries from hanging with AD). Option names should be strings (e.g. \"OPT_REFERRALS\"). Refer to https://www.python-ldap.org/doc/html/ldap.html#options for possible options and values that can be set.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_1_USER_SEARCH": {
- "type": "list",
- "label": "LDAP User Search",
- "help_text": "LDAP search query to find users. Any user that matches the given pattern will be able to login to the service. The user should also be mapped into an organization (as defined in the AUTH_LDAP_ORGANIZATION_MAP setting). If multiple search queries need to be supported use of \"LDAPUnion\" is possible. See the documentation for details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_1_USER_DN_TEMPLATE": {
- "type": "string",
- "label": "LDAP User DN Template",
- "help_text": "Alternative to user search, if user DNs are all of the same format. This approach is more efficient for user lookups than searching if it is usable in your organizational environment. If this setting has a value it will be used instead of AUTH_LDAP_USER_SEARCH.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_1_USER_ATTR_MAP": {
- "type": "nested object",
- "label": "LDAP User Attribute Map",
- "help_text": "Mapping of LDAP user schema to API user attributes. The default setting is valid for ActiveDirectory but users with other LDAP configurations may need to change the values. Refer to the documentation for additional details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "string"
- }
- },
- "AUTH_LDAP_1_GROUP_SEARCH": {
- "type": "list",
- "label": "LDAP Group Search",
- "help_text": "Users are mapped to organizations based on their membership in LDAP groups. This setting defines the LDAP search query to find groups. Unlike the user search, group search does not support LDAPSearchUnion.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_1_GROUP_TYPE": {
- "type": "choice",
- "label": "LDAP Group Type",
- "help_text": "The group type may need to be changed based on the type of the LDAP server. Values are listed at: https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "choices": [
- ["PosixGroupType", "PosixGroupType"],
- ["GroupOfNamesType", "GroupOfNamesType"],
- ["GroupOfUniqueNamesType", "GroupOfUniqueNamesType"],
- ["ActiveDirectoryGroupType", "ActiveDirectoryGroupType"],
- ["OrganizationalRoleGroupType", "OrganizationalRoleGroupType"],
- ["MemberDNGroupType", "MemberDNGroupType"],
- ["NestedGroupOfNamesType", "NestedGroupOfNamesType"],
- ["NestedGroupOfUniqueNamesType", "NestedGroupOfUniqueNamesType"],
- ["NestedActiveDirectoryGroupType", "NestedActiveDirectoryGroupType"],
- [
- "NestedOrganizationalRoleGroupType",
- "NestedOrganizationalRoleGroupType"
- ],
- ["NestedMemberDNGroupType", "NestedMemberDNGroupType"],
- ["PosixUIDGroupType", "PosixUIDGroupType"]
- ]
- },
- "AUTH_LDAP_1_GROUP_TYPE_PARAMS": {
- "type": "nested object",
- "label": "LDAP Group Type Parameters",
- "help_text": "Key value parameters to send the chosen group type init method.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_1_REQUIRE_GROUP": {
- "type": "string",
- "label": "LDAP Require Group",
- "help_text": "Group DN required to login. If specified, user must be a member of this group to login via LDAP. If not set, everyone in LDAP that matches the user search will be able to login to the service. Only one require group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_1_DENY_GROUP": {
- "type": "string",
- "label": "LDAP Deny Group",
- "help_text": "Group DN denied from login. If specified, user will not be allowed to login if a member of this group. Only one deny group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_1_USER_FLAGS_BY_GROUP": {
- "type": "nested object",
- "label": "LDAP User Flags By Group",
- "help_text": "Retrieve users from a given group. At this time, superuser and system auditors are the only groups supported. Refer to the documentation for more detail.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "list",
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_1_ORGANIZATION_MAP": {
- "type": "nested object",
- "label": "LDAP Organization Map",
- "help_text": "Mapping between organization admins/users and LDAP groups. This controls which users are placed into which organizations relative to their LDAP group memberships. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_1_TEAM_MAP": {
- "type": "nested object",
- "label": "LDAP Team Map",
- "help_text": "Mapping between team members (users) and LDAP groups. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_2_SERVER_URI": {
- "type": "string",
- "label": "LDAP Server URI",
- "help_text": "URI to connect to LDAP server, such as \"ldap://ldap.example.com:389\" (non-SSL) or \"ldaps://ldap.example.com:636\" (SSL). Multiple LDAP servers may be specified by separating with spaces or commas. LDAP authentication is disabled if this parameter is empty.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_2_BIND_DN": {
- "type": "string",
- "label": "LDAP Bind DN",
- "help_text": "DN (Distinguished Name) of user to bind for all search queries. This is the system user account we will use to login to query LDAP for other user information. Refer to the documentation for example syntax.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_2_BIND_PASSWORD": {
- "type": "string",
- "label": "LDAP Bind Password",
- "help_text": "Password used to bind LDAP user account.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_2_START_TLS": {
- "type": "boolean",
- "label": "LDAP Start TLS",
- "help_text": "Whether to enable TLS when the LDAP connection is not using SSL.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_2_CONNECTION_OPTIONS": {
- "type": "nested object",
- "label": "LDAP Connection Options",
- "help_text": "Additional options to set for the LDAP connection. LDAP referrals are disabled by default (to prevent certain LDAP queries from hanging with AD). Option names should be strings (e.g. \"OPT_REFERRALS\"). Refer to https://www.python-ldap.org/doc/html/ldap.html#options for possible options and values that can be set.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_2_USER_SEARCH": {
- "type": "list",
- "label": "LDAP User Search",
- "help_text": "LDAP search query to find users. Any user that matches the given pattern will be able to login to the service. The user should also be mapped into an organization (as defined in the AUTH_LDAP_ORGANIZATION_MAP setting). If multiple search queries need to be supported use of \"LDAPUnion\" is possible. See the documentation for details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_2_USER_DN_TEMPLATE": {
- "type": "string",
- "label": "LDAP User DN Template",
- "help_text": "Alternative to user search, if user DNs are all of the same format. This approach is more efficient for user lookups than searching if it is usable in your organizational environment. If this setting has a value it will be used instead of AUTH_LDAP_USER_SEARCH.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_2_USER_ATTR_MAP": {
- "type": "nested object",
- "label": "LDAP User Attribute Map",
- "help_text": "Mapping of LDAP user schema to API user attributes. The default setting is valid for ActiveDirectory but users with other LDAP configurations may need to change the values. Refer to the documentation for additional details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "string"
- }
- },
- "AUTH_LDAP_2_GROUP_SEARCH": {
- "type": "list",
- "label": "LDAP Group Search",
- "help_text": "Users are mapped to organizations based on their membership in LDAP groups. This setting defines the LDAP search query to find groups. Unlike the user search, group search does not support LDAPSearchUnion.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_2_GROUP_TYPE": {
- "type": "choice",
- "label": "LDAP Group Type",
- "help_text": "The group type may need to be changed based on the type of the LDAP server. Values are listed at: https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "choices": [
- ["PosixGroupType", "PosixGroupType"],
- ["GroupOfNamesType", "GroupOfNamesType"],
- ["GroupOfUniqueNamesType", "GroupOfUniqueNamesType"],
- ["ActiveDirectoryGroupType", "ActiveDirectoryGroupType"],
- ["OrganizationalRoleGroupType", "OrganizationalRoleGroupType"],
- ["MemberDNGroupType", "MemberDNGroupType"],
- ["NestedGroupOfNamesType", "NestedGroupOfNamesType"],
- ["NestedGroupOfUniqueNamesType", "NestedGroupOfUniqueNamesType"],
- ["NestedActiveDirectoryGroupType", "NestedActiveDirectoryGroupType"],
- [
- "NestedOrganizationalRoleGroupType",
- "NestedOrganizationalRoleGroupType"
- ],
- ["NestedMemberDNGroupType", "NestedMemberDNGroupType"],
- ["PosixUIDGroupType", "PosixUIDGroupType"]
- ]
- },
- "AUTH_LDAP_2_GROUP_TYPE_PARAMS": {
- "type": "nested object",
- "label": "LDAP Group Type Parameters",
- "help_text": "Key value parameters to send the chosen group type init method.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_2_REQUIRE_GROUP": {
- "type": "string",
- "label": "LDAP Require Group",
- "help_text": "Group DN required to login. If specified, user must be a member of this group to login via LDAP. If not set, everyone in LDAP that matches the user search will be able to login to the service. Only one require group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_2_DENY_GROUP": {
- "type": "string",
- "label": "LDAP Deny Group",
- "help_text": "Group DN denied from login. If specified, user will not be allowed to login if a member of this group. Only one deny group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_2_USER_FLAGS_BY_GROUP": {
- "type": "nested object",
- "label": "LDAP User Flags By Group",
- "help_text": "Retrieve users from a given group. At this time, superuser and system auditors are the only groups supported. Refer to the documentation for more detail.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "list",
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_2_ORGANIZATION_MAP": {
- "type": "nested object",
- "label": "LDAP Organization Map",
- "help_text": "Mapping between organization admins/users and LDAP groups. This controls which users are placed into which organizations relative to their LDAP group memberships. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_2_TEAM_MAP": {
- "type": "nested object",
- "label": "LDAP Team Map",
- "help_text": "Mapping between team members (users) and LDAP groups. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_3_SERVER_URI": {
- "type": "string",
- "label": "LDAP Server URI",
- "help_text": "URI to connect to LDAP server, such as \"ldap://ldap.example.com:389\" (non-SSL) or \"ldaps://ldap.example.com:636\" (SSL). Multiple LDAP servers may be specified by separating with spaces or commas. LDAP authentication is disabled if this parameter is empty.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_3_BIND_DN": {
- "type": "string",
- "label": "LDAP Bind DN",
- "help_text": "DN (Distinguished Name) of user to bind for all search queries. This is the system user account we will use to login to query LDAP for other user information. Refer to the documentation for example syntax.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_3_BIND_PASSWORD": {
- "type": "string",
- "label": "LDAP Bind Password",
- "help_text": "Password used to bind LDAP user account.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_3_START_TLS": {
- "type": "boolean",
- "label": "LDAP Start TLS",
- "help_text": "Whether to enable TLS when the LDAP connection is not using SSL.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_3_CONNECTION_OPTIONS": {
- "type": "nested object",
- "label": "LDAP Connection Options",
- "help_text": "Additional options to set for the LDAP connection. LDAP referrals are disabled by default (to prevent certain LDAP queries from hanging with AD). Option names should be strings (e.g. \"OPT_REFERRALS\"). Refer to https://www.python-ldap.org/doc/html/ldap.html#options for possible options and values that can be set.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_3_USER_SEARCH": {
- "type": "list",
- "label": "LDAP User Search",
- "help_text": "LDAP search query to find users. Any user that matches the given pattern will be able to login to the service. The user should also be mapped into an organization (as defined in the AUTH_LDAP_ORGANIZATION_MAP setting). If multiple search queries need to be supported use of \"LDAPUnion\" is possible. See the documentation for details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_3_USER_DN_TEMPLATE": {
- "type": "string",
- "label": "LDAP User DN Template",
- "help_text": "Alternative to user search, if user DNs are all of the same format. This approach is more efficient for user lookups than searching if it is usable in your organizational environment. If this setting has a value it will be used instead of AUTH_LDAP_USER_SEARCH.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_3_USER_ATTR_MAP": {
- "type": "nested object",
- "label": "LDAP User Attribute Map",
- "help_text": "Mapping of LDAP user schema to API user attributes. The default setting is valid for ActiveDirectory but users with other LDAP configurations may need to change the values. Refer to the documentation for additional details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "string"
- }
- },
- "AUTH_LDAP_3_GROUP_SEARCH": {
- "type": "list",
- "label": "LDAP Group Search",
- "help_text": "Users are mapped to organizations based on their membership in LDAP groups. This setting defines the LDAP search query to find groups. Unlike the user search, group search does not support LDAPSearchUnion.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_3_GROUP_TYPE": {
- "type": "choice",
- "label": "LDAP Group Type",
- "help_text": "The group type may need to be changed based on the type of the LDAP server. Values are listed at: https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "choices": [
- ["PosixGroupType", "PosixGroupType"],
- ["GroupOfNamesType", "GroupOfNamesType"],
- ["GroupOfUniqueNamesType", "GroupOfUniqueNamesType"],
- ["ActiveDirectoryGroupType", "ActiveDirectoryGroupType"],
- ["OrganizationalRoleGroupType", "OrganizationalRoleGroupType"],
- ["MemberDNGroupType", "MemberDNGroupType"],
- ["NestedGroupOfNamesType", "NestedGroupOfNamesType"],
- ["NestedGroupOfUniqueNamesType", "NestedGroupOfUniqueNamesType"],
- ["NestedActiveDirectoryGroupType", "NestedActiveDirectoryGroupType"],
- [
- "NestedOrganizationalRoleGroupType",
- "NestedOrganizationalRoleGroupType"
- ],
- ["NestedMemberDNGroupType", "NestedMemberDNGroupType"],
- ["PosixUIDGroupType", "PosixUIDGroupType"]
- ]
- },
- "AUTH_LDAP_3_GROUP_TYPE_PARAMS": {
- "type": "nested object",
- "label": "LDAP Group Type Parameters",
- "help_text": "Key value parameters to send the chosen group type init method.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_3_REQUIRE_GROUP": {
- "type": "string",
- "label": "LDAP Require Group",
- "help_text": "Group DN required to login. If specified, user must be a member of this group to login via LDAP. If not set, everyone in LDAP that matches the user search will be able to login to the service. Only one require group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_3_DENY_GROUP": {
- "type": "string",
- "label": "LDAP Deny Group",
- "help_text": "Group DN denied from login. If specified, user will not be allowed to login if a member of this group. Only one deny group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_3_USER_FLAGS_BY_GROUP": {
- "type": "nested object",
- "label": "LDAP User Flags By Group",
- "help_text": "Retrieve users from a given group. At this time, superuser and system auditors are the only groups supported. Refer to the documentation for more detail.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "list",
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_3_ORGANIZATION_MAP": {
- "type": "nested object",
- "label": "LDAP Organization Map",
- "help_text": "Mapping between organization admins/users and LDAP groups. This controls which users are placed into which organizations relative to their LDAP group memberships. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_3_TEAM_MAP": {
- "type": "nested object",
- "label": "LDAP Team Map",
- "help_text": "Mapping between team members (users) and LDAP groups. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_4_SERVER_URI": {
- "type": "string",
- "label": "LDAP Server URI",
- "help_text": "URI to connect to LDAP server, such as \"ldap://ldap.example.com:389\" (non-SSL) or \"ldaps://ldap.example.com:636\" (SSL). Multiple LDAP servers may be specified by separating with spaces or commas. LDAP authentication is disabled if this parameter is empty.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_4_BIND_DN": {
- "type": "string",
- "label": "LDAP Bind DN",
- "help_text": "DN (Distinguished Name) of user to bind for all search queries. This is the system user account we will use to login to query LDAP for other user information. Refer to the documentation for example syntax.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_4_BIND_PASSWORD": {
- "type": "string",
- "label": "LDAP Bind Password",
- "help_text": "Password used to bind LDAP user account.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_4_START_TLS": {
- "type": "boolean",
- "label": "LDAP Start TLS",
- "help_text": "Whether to enable TLS when the LDAP connection is not using SSL.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_4_CONNECTION_OPTIONS": {
- "type": "nested object",
- "label": "LDAP Connection Options",
- "help_text": "Additional options to set for the LDAP connection. LDAP referrals are disabled by default (to prevent certain LDAP queries from hanging with AD). Option names should be strings (e.g. \"OPT_REFERRALS\"). Refer to https://www.python-ldap.org/doc/html/ldap.html#options for possible options and values that can be set.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_4_USER_SEARCH": {
- "type": "list",
- "label": "LDAP User Search",
- "help_text": "LDAP search query to find users. Any user that matches the given pattern will be able to login to the service. The user should also be mapped into an organization (as defined in the AUTH_LDAP_ORGANIZATION_MAP setting). If multiple search queries need to be supported use of \"LDAPUnion\" is possible. See the documentation for details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_4_USER_DN_TEMPLATE": {
- "type": "string",
- "label": "LDAP User DN Template",
- "help_text": "Alternative to user search, if user DNs are all of the same format. This approach is more efficient for user lookups than searching if it is usable in your organizational environment. If this setting has a value it will be used instead of AUTH_LDAP_USER_SEARCH.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_4_USER_ATTR_MAP": {
- "type": "nested object",
- "label": "LDAP User Attribute Map",
- "help_text": "Mapping of LDAP user schema to API user attributes. The default setting is valid for ActiveDirectory but users with other LDAP configurations may need to change the values. Refer to the documentation for additional details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "string"
- }
- },
- "AUTH_LDAP_4_GROUP_SEARCH": {
- "type": "list",
- "label": "LDAP Group Search",
- "help_text": "Users are mapped to organizations based on their membership in LDAP groups. This setting defines the LDAP search query to find groups. Unlike the user search, group search does not support LDAPSearchUnion.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_4_GROUP_TYPE": {
- "type": "choice",
- "label": "LDAP Group Type",
- "help_text": "The group type may need to be changed based on the type of the LDAP server. Values are listed at: https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "choices": [
- ["PosixGroupType", "PosixGroupType"],
- ["GroupOfNamesType", "GroupOfNamesType"],
- ["GroupOfUniqueNamesType", "GroupOfUniqueNamesType"],
- ["ActiveDirectoryGroupType", "ActiveDirectoryGroupType"],
- ["OrganizationalRoleGroupType", "OrganizationalRoleGroupType"],
- ["MemberDNGroupType", "MemberDNGroupType"],
- ["NestedGroupOfNamesType", "NestedGroupOfNamesType"],
- ["NestedGroupOfUniqueNamesType", "NestedGroupOfUniqueNamesType"],
- ["NestedActiveDirectoryGroupType", "NestedActiveDirectoryGroupType"],
- [
- "NestedOrganizationalRoleGroupType",
- "NestedOrganizationalRoleGroupType"
- ],
- ["NestedMemberDNGroupType", "NestedMemberDNGroupType"],
- ["PosixUIDGroupType", "PosixUIDGroupType"]
- ]
- },
- "AUTH_LDAP_4_GROUP_TYPE_PARAMS": {
- "type": "nested object",
- "label": "LDAP Group Type Parameters",
- "help_text": "Key value parameters to send the chosen group type init method.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_4_REQUIRE_GROUP": {
- "type": "string",
- "label": "LDAP Require Group",
- "help_text": "Group DN required to login. If specified, user must be a member of this group to login via LDAP. If not set, everyone in LDAP that matches the user search will be able to login to the service. Only one require group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_4_DENY_GROUP": {
- "type": "string",
- "label": "LDAP Deny Group",
- "help_text": "Group DN denied from login. If specified, user will not be allowed to login if a member of this group. Only one deny group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_4_USER_FLAGS_BY_GROUP": {
- "type": "nested object",
- "label": "LDAP User Flags By Group",
- "help_text": "Retrieve users from a given group. At this time, superuser and system auditors are the only groups supported. Refer to the documentation for more detail.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "list",
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_4_ORGANIZATION_MAP": {
- "type": "nested object",
- "label": "LDAP Organization Map",
- "help_text": "Mapping between organization admins/users and LDAP groups. This controls which users are placed into which organizations relative to their LDAP group memberships. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_4_TEAM_MAP": {
- "type": "nested object",
- "label": "LDAP Team Map",
- "help_text": "Mapping between team members (users) and LDAP groups. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_5_SERVER_URI": {
- "type": "string",
- "label": "LDAP Server URI",
- "help_text": "URI to connect to LDAP server, such as \"ldap://ldap.example.com:389\" (non-SSL) or \"ldaps://ldap.example.com:636\" (SSL). Multiple LDAP servers may be specified by separating with spaces or commas. LDAP authentication is disabled if this parameter is empty.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_5_BIND_DN": {
- "type": "string",
- "label": "LDAP Bind DN",
- "help_text": "DN (Distinguished Name) of user to bind for all search queries. This is the system user account we will use to login to query LDAP for other user information. Refer to the documentation for example syntax.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_5_BIND_PASSWORD": {
- "type": "string",
- "label": "LDAP Bind Password",
- "help_text": "Password used to bind LDAP user account.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_5_START_TLS": {
- "type": "boolean",
- "label": "LDAP Start TLS",
- "help_text": "Whether to enable TLS when the LDAP connection is not using SSL.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_5_CONNECTION_OPTIONS": {
- "type": "nested object",
- "label": "LDAP Connection Options",
- "help_text": "Additional options to set for the LDAP connection. LDAP referrals are disabled by default (to prevent certain LDAP queries from hanging with AD). Option names should be strings (e.g. \"OPT_REFERRALS\"). Refer to https://www.python-ldap.org/doc/html/ldap.html#options for possible options and values that can be set.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_5_USER_SEARCH": {
- "type": "list",
- "label": "LDAP User Search",
- "help_text": "LDAP search query to find users. Any user that matches the given pattern will be able to login to the service. The user should also be mapped into an organization (as defined in the AUTH_LDAP_ORGANIZATION_MAP setting). If multiple search queries need to be supported use of \"LDAPUnion\" is possible. See the documentation for details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_5_USER_DN_TEMPLATE": {
- "type": "string",
- "label": "LDAP User DN Template",
- "help_text": "Alternative to user search, if user DNs are all of the same format. This approach is more efficient for user lookups than searching if it is usable in your organizational environment. If this setting has a value it will be used instead of AUTH_LDAP_USER_SEARCH.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_5_USER_ATTR_MAP": {
- "type": "nested object",
- "label": "LDAP User Attribute Map",
- "help_text": "Mapping of LDAP user schema to API user attributes. The default setting is valid for ActiveDirectory but users with other LDAP configurations may need to change the values. Refer to the documentation for additional details.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "string"
- }
- },
- "AUTH_LDAP_5_GROUP_SEARCH": {
- "type": "list",
- "label": "LDAP Group Search",
- "help_text": "Users are mapped to organizations based on their membership in LDAP groups. This setting defines the LDAP search query to find groups. Unlike the user search, group search does not support LDAPSearchUnion.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_5_GROUP_TYPE": {
- "type": "choice",
- "label": "LDAP Group Type",
- "help_text": "The group type may need to be changed based on the type of the LDAP server. Values are listed at: https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "choices": [
- ["PosixGroupType", "PosixGroupType"],
- ["GroupOfNamesType", "GroupOfNamesType"],
- ["GroupOfUniqueNamesType", "GroupOfUniqueNamesType"],
- ["ActiveDirectoryGroupType", "ActiveDirectoryGroupType"],
- ["OrganizationalRoleGroupType", "OrganizationalRoleGroupType"],
- ["MemberDNGroupType", "MemberDNGroupType"],
- ["NestedGroupOfNamesType", "NestedGroupOfNamesType"],
- ["NestedGroupOfUniqueNamesType", "NestedGroupOfUniqueNamesType"],
- ["NestedActiveDirectoryGroupType", "NestedActiveDirectoryGroupType"],
- [
- "NestedOrganizationalRoleGroupType",
- "NestedOrganizationalRoleGroupType"
- ],
- ["NestedMemberDNGroupType", "NestedMemberDNGroupType"],
- ["PosixUIDGroupType", "PosixUIDGroupType"]
- ]
- },
- "AUTH_LDAP_5_GROUP_TYPE_PARAMS": {
- "type": "nested object",
- "label": "LDAP Group Type Parameters",
- "help_text": "Key value parameters to send the chosen group type init method.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "AUTH_LDAP_5_REQUIRE_GROUP": {
- "type": "string",
- "label": "LDAP Require Group",
- "help_text": "Group DN required to login. If specified, user must be a member of this group to login via LDAP. If not set, everyone in LDAP that matches the user search will be able to login to the service. Only one require group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_5_DENY_GROUP": {
- "type": "string",
- "label": "LDAP Deny Group",
- "help_text": "Group DN denied from login. If specified, user will not be allowed to login if a member of this group. Only one deny group is supported.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false
- },
- "AUTH_LDAP_5_USER_FLAGS_BY_GROUP": {
- "type": "nested object",
- "label": "LDAP User Flags By Group",
- "help_text": "Retrieve users from a given group. At this time, superuser and system auditors are the only groups supported. Refer to the documentation for more detail.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "list",
- "child": {
- "type": "string",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_5_ORGANIZATION_MAP": {
- "type": "nested object",
- "label": "LDAP Organization Map",
- "help_text": "Mapping between organization admins/users and LDAP groups. This controls which users are placed into which organizations relative to their LDAP group memberships. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "AUTH_LDAP_5_TEAM_MAP": {
- "type": "nested object",
- "label": "LDAP Team Map",
- "help_text": "Mapping between team members (users) and LDAP groups. Configuration details are available in the documentation.",
- "category": "LDAP",
- "category_slug": "ldap",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "RADIUS_SERVER": {
- "type": "string",
- "label": "RADIUS Server",
- "help_text": "Hostname/IP of RADIUS server. RADIUS authentication is disabled if this setting is empty.",
- "category": "RADIUS",
- "category_slug": "radius",
- "defined_in_file": false
- },
- "RADIUS_PORT": {
- "type": "integer",
- "label": "RADIUS Port",
- "help_text": "Port of RADIUS server.",
- "min_value": 1,
- "max_value": 65535,
- "category": "RADIUS",
- "category_slug": "radius",
- "defined_in_file": false
- },
- "RADIUS_SECRET": {
- "type": "string",
- "label": "RADIUS Secret",
- "help_text": "Shared secret for authenticating to RADIUS server.",
- "category": "RADIUS",
- "category_slug": "radius",
- "defined_in_file": false
- },
- "TACACSPLUS_HOST": {
- "type": "string",
- "label": "TACACS+ Server",
- "help_text": "Hostname of TACACS+ server.",
- "category": "TACACS+",
- "category_slug": "tacacsplus",
- "defined_in_file": false
- },
- "TACACSPLUS_PORT": {
- "type": "integer",
- "label": "TACACS+ Port",
- "help_text": "Port number of TACACS+ server.",
- "min_value": 1,
- "max_value": 65535,
- "category": "TACACS+",
- "category_slug": "tacacsplus",
- "defined_in_file": false
- },
- "TACACSPLUS_SECRET": {
- "type": "string",
- "label": "TACACS+ Secret",
- "help_text": "Shared secret for authenticating to TACACS+ server.",
- "category": "TACACS+",
- "category_slug": "tacacsplus",
- "defined_in_file": false
- },
- "TACACSPLUS_SESSION_TIMEOUT": {
- "type": "integer",
- "label": "TACACS+ Auth Session Timeout",
- "help_text": "TACACS+ session timeout value in seconds, 0 disables timeout.",
- "min_value": 0,
- "category": "TACACS+",
- "category_slug": "tacacsplus",
- "defined_in_file": false,
- "unit": "seconds"
- },
- "TACACSPLUS_AUTH_PROTOCOL": {
- "type": "choice",
- "label": "TACACS+ Authentication Protocol",
- "help_text": "Choose the authentication protocol used by TACACS+ client.",
- "category": "TACACS+",
- "category_slug": "tacacsplus",
- "defined_in_file": false,
- "choices": [
- ["ascii", "ascii"],
- ["pap", "pap"]
- ]
- },
- "SOCIAL_AUTH_GOOGLE_OAUTH2_CALLBACK_URL": {
- "type": "string",
- "label": "Google OAuth2 Callback URL",
- "help_text": "Provide this URL as the callback URL for your application as part of your registration process. Refer to the documentation for more detail.",
- "category": "Google OAuth2",
- "category_slug": "google-oauth2",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GOOGLE_OAUTH2_KEY": {
- "type": "string",
- "label": "Google OAuth2 Key",
- "help_text": "The OAuth2 key from your web application.",
- "category": "Google OAuth2",
- "category_slug": "google-oauth2",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET": {
- "type": "string",
- "label": "Google OAuth2 Secret",
- "help_text": "The OAuth2 secret from your web application.",
- "category": "Google OAuth2",
- "category_slug": "google-oauth2",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS": {
- "type": "list",
- "label": "Google OAuth2 Allowed Domains",
- "help_text": "Update this setting to restrict the domains who are allowed to login using Google OAuth2.",
- "category": "Google OAuth2",
- "category_slug": "google-oauth2",
- "defined_in_file": false,
- "child": {
- "type": "string"
- }
- },
- "SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS": {
- "type": "nested object",
- "label": "Google OAuth2 Extra Arguments",
- "help_text": "Extra arguments for Google OAuth2 login. You can restrict it to only allow a single domain to authenticate, even if the user is logged in with multple Google accounts. Refer to the documentation for more detail.",
- "category": "Google OAuth2",
- "category_slug": "google-oauth2",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "SOCIAL_AUTH_GOOGLE_OAUTH2_ORGANIZATION_MAP": {
- "type": "nested object",
- "label": "Google OAuth2 Organization Map",
- "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.",
- "category": "Google OAuth2",
- "category_slug": "google-oauth2",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GOOGLE_OAUTH2_TEAM_MAP": {
- "type": "nested object",
- "label": "Google OAuth2 Team Map",
- "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in the documentation.",
- "category": "Google OAuth2",
- "category_slug": "google-oauth2",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_CALLBACK_URL": {
- "type": "string",
- "label": "GitHub OAuth2 Callback URL",
- "help_text": "Provide this URL as the callback URL for your application as part of your registration process. Refer to the documentation for more detail.",
- "category": "GitHub OAuth2",
- "category_slug": "github",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_KEY": {
- "type": "string",
- "label": "GitHub OAuth2 Key",
- "help_text": "The OAuth2 key (Client ID) from your GitHub developer application.",
- "category": "GitHub OAuth2",
- "category_slug": "github",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_SECRET": {
- "type": "string",
- "label": "GitHub OAuth2 Secret",
- "help_text": "The OAuth2 secret (Client Secret) from your GitHub developer application.",
- "category": "GitHub OAuth2",
- "category_slug": "github",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP": {
- "type": "nested object",
- "label": "GitHub OAuth2 Organization Map",
- "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.",
- "category": "GitHub OAuth2",
- "category_slug": "github",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_TEAM_MAP": {
- "type": "nested object",
- "label": "GitHub OAuth2 Team Map",
- "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in the documentation.",
- "category": "GitHub OAuth2",
- "category_slug": "github",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_ORG_CALLBACK_URL": {
- "type": "string",
- "label": "GitHub Organization OAuth2 Callback URL",
- "help_text": "Provide this URL as the callback URL for your application as part of your registration process. Refer to the documentation for more detail.",
- "category": "GitHub Organization OAuth2",
- "category_slug": "github-org",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_ORG_KEY": {
- "type": "string",
- "label": "GitHub Organization OAuth2 Key",
- "help_text": "The OAuth2 key (Client ID) from your GitHub organization application.",
- "category": "GitHub Organization OAuth2",
- "category_slug": "github-org",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_ORG_SECRET": {
- "type": "string",
- "label": "GitHub Organization OAuth2 Secret",
- "help_text": "The OAuth2 secret (Client Secret) from your GitHub organization application.",
- "category": "GitHub Organization OAuth2",
- "category_slug": "github-org",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_ORG_NAME": {
- "type": "string",
- "label": "GitHub Organization Name",
- "help_text": "The name of your GitHub organization, as used in your organization's URL: https://github.com//.",
- "category": "GitHub Organization OAuth2",
- "category_slug": "github-org",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_ORG_ORGANIZATION_MAP": {
- "type": "nested object",
- "label": "GitHub Organization OAuth2 Organization Map",
- "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.",
- "category": "GitHub Organization OAuth2",
- "category_slug": "github-org",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_ORG_TEAM_MAP": {
- "type": "nested object",
- "label": "GitHub Organization OAuth2 Team Map",
- "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in the documentation.",
- "category": "GitHub Organization OAuth2",
- "category_slug": "github-org",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_TEAM_CALLBACK_URL": {
- "type": "string",
- "label": "GitHub Team OAuth2 Callback URL",
- "help_text": "Create an organization-owned application at https://github.com/organizations//settings/applications and obtain an OAuth2 key (Client ID) and secret (Client Secret). Provide this URL as the callback URL for your application.",
- "category": "GitHub Team OAuth2",
- "category_slug": "github-team",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_TEAM_KEY": {
- "type": "string",
- "label": "GitHub Team OAuth2 Key",
- "help_text": "The OAuth2 key (Client ID) from your GitHub organization application.",
- "category": "GitHub Team OAuth2",
- "category_slug": "github-team",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_TEAM_SECRET": {
- "type": "string",
- "label": "GitHub Team OAuth2 Secret",
- "help_text": "The OAuth2 secret (Client Secret) from your GitHub organization application.",
- "category": "GitHub Team OAuth2",
- "category_slug": "github-team",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_TEAM_ID": {
- "type": "string",
- "label": "GitHub Team ID",
- "help_text": "Find the numeric team ID using the Github API: http://fabian-kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/.",
- "category": "GitHub Team OAuth2",
- "category_slug": "github-team",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_TEAM_ORGANIZATION_MAP": {
- "type": "nested object",
- "label": "GitHub Team OAuth2 Organization Map",
- "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.",
- "category": "GitHub Team OAuth2",
- "category_slug": "github-team",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_TEAM_TEAM_MAP": {
- "type": "nested object",
- "label": "GitHub Team OAuth2 Team Map",
- "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in the documentation.",
- "category": "GitHub Team OAuth2",
- "category_slug": "github-team",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_CALLBACK_URL": {
- "type": "string",
- "label": "GitHub Enterprise OAuth2 Callback URL",
- "help_text": "Provide this URL as the callback URL for your application as part of your registration process. Refer to the documentation for more detail.",
- "category": "GitHub Enterprise OAuth2",
- "category_slug": "github-enterprise",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_URL": {
- "type": "string",
- "label": "GitHub Enterprise URL",
- "help_text": "The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise documentation for more details.",
- "category": "GitHub Enterprise OAuth2",
- "category_slug": "github-enterprise",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL": {
- "type": "string",
- "label": "GitHub Enterprise API URL",
- "help_text": "The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github Enterprise documentation for more details.",
- "category": "GitHub Enterprise OAuth2",
- "category_slug": "github-enterprise",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY": {
- "type": "string",
- "label": "GitHub Enterprise OAuth2 Key",
- "help_text": "The OAuth2 key (Client ID) from your GitHub Enterprise developer application.",
- "category": "GitHub Enterprise OAuth2",
- "category_slug": "github-enterprise",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET": {
- "type": "string",
- "label": "GitHub Enterprise OAuth2 Secret",
- "help_text": "The OAuth2 secret (Client Secret) from your GitHub Enterprise developer application.",
- "category": "GitHub OAuth2",
- "category_slug": "github-enterprise",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORGANIZATION_MAP": {
- "type": "nested object",
- "label": "GitHub Enterprise OAuth2 Organization Map",
- "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.",
- "category": "GitHub Enterprise OAuth2",
- "category_slug": "github-enterprise",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_MAP": {
- "type": "nested object",
- "label": "GitHub Enterprise OAuth2 Team Map",
- "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in the documentation.",
- "category": "GitHub Enterprise OAuth2",
- "category_slug": "github-enterprise",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_CALLBACK_URL": {
- "type": "string",
- "label": "GitHub Enterprise Organization OAuth2 Callback URL",
- "help_text": "Provide this URL as the callback URL for your application as part of your registration process. Refer to the documentation for more detail.",
- "category": "GitHub Enterprise Organization OAuth2",
- "category_slug": "github-enterprise-org",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL": {
- "type": "string",
- "label": "GitHub Enterprise Organization URL",
- "help_text": "The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise documentation for more details.",
- "category": "GitHub Enterprise OAuth2",
- "category_slug": "github-enterprise-org",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL": {
- "type": "string",
- "label": "GitHub Enterprise Organization API URL",
- "help_text": "The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github Enterprise documentation for more details.",
- "category": "GitHub Enterprise OAuth2",
- "category_slug": "github-enterprise-org",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY": {
- "type": "string",
- "label": "GitHub Enterprise Organization OAuth2 Key",
- "help_text": "The OAuth2 key (Client ID) from your GitHub Enterprise organization application.",
- "category": "GitHub Enterprise Organization OAuth2",
- "category_slug": "github-enterprise-org",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_SECRET": {
- "type": "string",
- "label": "GitHub Enterprise Organization OAuth2 Secret",
- "help_text": "The OAuth2 secret (Client Secret) from your GitHub Enterprise organization application.",
- "category": "GitHub Enterprise Organization OAuth2",
- "category_slug": "github-enterprise-org",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME": {
- "type": "string",
- "label": "GitHub Enterprise Organization Name",
- "help_text": "The name of your GitHub Enterprise organization, as used in your organization's URL: https://github.com//.",
- "category": "GitHub Enterprise Organization OAuth2",
- "category_slug": "github-enterprise-org",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_ORGANIZATION_MAP": {
- "type": "nested object",
- "label": "GitHub Enterprise Organization OAuth2 Organization Map",
- "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.",
- "category": "GitHub Enterprise Organization OAuth2",
- "category_slug": "github-enterprise-org",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_TEAM_MAP": {
- "type": "nested object",
- "label": "GitHub Enterprise Organization OAuth2 Team Map",
- "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in the documentation.",
- "category": "GitHub Enterprise Organization OAuth2",
- "category_slug": "github-enterprise-org",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_CALLBACK_URL": {
- "type": "string",
- "label": "GitHub Enterprise Team OAuth2 Callback URL",
- "help_text": "Create an organization-owned application at https://github.com/organizations//settings/applications and obtain an OAuth2 key (Client ID) and secret (Client Secret). Provide this URL as the callback URL for your application.",
- "category": "GitHub Enterprise Team OAuth2",
- "category_slug": "github-enterprise-team",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL": {
- "type": "string",
- "label": "GitHub Enterprise Team URL",
- "help_text": "The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise documentation for more details.",
- "category": "GitHub Enterprise OAuth2",
- "category_slug": "github-enterprise-team",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL": {
- "type": "string",
- "label": "GitHub Enterprise Team API URL",
- "help_text": "The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github Enterprise documentation for more details.",
- "category": "GitHub Enterprise OAuth2",
- "category_slug": "github-enterprise-team",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY": {
- "type": "string",
- "label": "GitHub Enterprise Team OAuth2 Key",
- "help_text": "The OAuth2 key (Client ID) from your GitHub Enterprise organization application.",
- "category": "GitHub Enterprise Team OAuth2",
- "category_slug": "github-enterprise-team",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_SECRET": {
- "type": "string",
- "label": "GitHub Enterprise Team OAuth2 Secret",
- "help_text": "The OAuth2 secret (Client Secret) from your GitHub Enterprise organization application.",
- "category": "GitHub Enterprise Team OAuth2",
- "category_slug": "github-enterprise-team",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID": {
- "type": "string",
- "label": "GitHub Enterprise Team ID",
- "help_text": "Find the numeric team ID using the Github Enterprise API: http://fabian-kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/.",
- "category": "GitHub Enterprise Team OAuth2",
- "category_slug": "github-enterprise-team",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ORGANIZATION_MAP": {
- "type": "nested object",
- "label": "GitHub Enterprise Team OAuth2 Organization Map",
- "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.",
- "category": "GitHub Enterprise Team OAuth2",
- "category_slug": "github-enterprise-team",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_TEAM_MAP": {
- "type": "nested object",
- "label": "GitHub Enterprise Team OAuth2 Team Map",
- "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in the documentation.",
- "category": "GitHub Enterprise Team OAuth2",
- "category_slug": "github-enterprise-team",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_AZUREAD_OAUTH2_CALLBACK_URL": {
- "type": "string",
- "label": "Azure AD OAuth2 Callback URL",
- "help_text": "Provide this URL as the callback URL for your application as part of your registration process. Refer to the documentation for more detail. ",
- "category": "Azure AD OAuth2",
- "category_slug": "azuread-oauth2",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_AZUREAD_OAUTH2_KEY": {
- "type": "string",
- "label": "Azure AD OAuth2 Key",
- "help_text": "The OAuth2 key (Client ID) from your Azure AD application.",
- "category": "Azure AD OAuth2",
- "category_slug": "azuread-oauth2",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET": {
- "type": "string",
- "label": "Azure AD OAuth2 Secret",
- "help_text": "The OAuth2 secret (Client Secret) from your Azure AD application.",
- "category": "Azure AD OAuth2",
- "category_slug": "azuread-oauth2",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_AZUREAD_OAUTH2_ORGANIZATION_MAP": {
- "type": "nested object",
- "label": "Azure AD OAuth2 Organization Map",
- "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.",
- "category": "Azure AD OAuth2",
- "category_slug": "azuread-oauth2",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_AZUREAD_OAUTH2_TEAM_MAP": {
- "type": "nested object",
- "label": "Azure AD OAuth2 Team Map",
- "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in the documentation.",
- "category": "Azure AD OAuth2",
- "category_slug": "azuread-oauth2",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SAML_AUTO_CREATE_OBJECTS": {
- "type": "boolean",
- "label": "Automatically Create Organizations and Teams on SAML Login",
- "help_text": "When enabled (the default), mapped Organizations and Teams will be created automatically on successful SAML login.",
- "category": "SAML",
- "category_slug": "saml",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_SAML_CALLBACK_URL": {
- "type": "string",
- "label": "SAML Assertion Consumer Service (ACS) URL",
- "help_text": "Register the service as a service provider (SP) with each identity provider (IdP) you have configured. Provide your SP Entity ID and this ACS URL for your application.",
- "category": "SAML",
- "category_slug": "saml",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_SAML_METADATA_URL": {
- "type": "string",
- "label": "SAML Service Provider Metadata URL",
- "help_text": "If your identity provider (IdP) allows uploading an XML metadata file, you can download one from this URL.",
- "category": "SAML",
- "category_slug": "saml",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_SAML_SP_ENTITY_ID": {
- "type": "string",
- "label": "SAML Service Provider Entity ID",
- "help_text": "The application-defined unique identifier used as the audience of the SAML service provider (SP) configuration. This is usually the URL for the service.",
- "category": "SAML",
- "category_slug": "saml",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_SAML_SP_PUBLIC_CERT": {
- "type": "string",
- "label": "SAML Service Provider Public Certificate",
- "help_text": "Create a keypair to use as a service provider (SP) and include the certificate content here.",
- "category": "SAML",
- "category_slug": "saml",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_SAML_SP_PRIVATE_KEY": {
- "type": "string",
- "label": "SAML Service Provider Private Key",
- "help_text": "Create a keypair to use as a service provider (SP) and include the private key content here.",
- "category": "SAML",
- "category_slug": "saml",
- "defined_in_file": false
- },
- "SOCIAL_AUTH_SAML_ORG_INFO": {
- "type": "nested object",
- "label": "SAML Service Provider Organization Info",
- "help_text": "Provide the URL, display name, and the name of your app. Refer to the documentation for example syntax.",
- "category": "SAML",
- "category_slug": "saml",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_SAML_TECHNICAL_CONTACT": {
- "type": "nested object",
- "label": "SAML Service Provider Technical Contact",
- "help_text": "Provide the name and email address of the technical contact for your service provider. Refer to the documentation for example syntax.",
- "category": "SAML",
- "category_slug": "saml",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "SOCIAL_AUTH_SAML_SUPPORT_CONTACT": {
- "type": "nested object",
- "label": "SAML Service Provider Support Contact",
- "help_text": "Provide the name and email address of the support contact for your service provider. Refer to the documentation for example syntax.",
- "category": "SAML",
- "category_slug": "saml",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "SOCIAL_AUTH_SAML_ENABLED_IDPS": {
- "type": "nested object",
- "label": "SAML Enabled Identity Providers",
- "help_text": "Configure the Entity ID, SSO URL and certificate for each identity provider (IdP) in use. Multiple SAML IdPs are supported. Some IdPs may provide user data using attribute names that differ from the default OIDs. Attribute names may be overridden for each IdP. Refer to the Ansible documentation for additional details and syntax.",
- "category": "SAML",
- "category_slug": "saml",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_SAML_SECURITY_CONFIG": {
- "type": "nested object",
- "label": "SAML Security Config",
- "help_text": "A dict of key value pairs that are passed to the underlying python-saml security setting https://github.com/onelogin/python-saml#settings",
- "category": "SAML",
- "category_slug": "saml",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "SOCIAL_AUTH_SAML_SP_EXTRA": {
- "type": "nested object",
- "label": "SAML Service Provider extra configuration data",
- "help_text": "A dict of key value pairs to be passed to the underlying python-saml Service Provider configuration setting.",
- "category": "SAML",
- "category_slug": "saml",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "SOCIAL_AUTH_SAML_EXTRA_DATA": {
- "type": "list",
- "label": "SAML IDP to extra_data attribute mapping",
- "help_text": "A list of tuples that maps IDP attributes to extra_attributes. Each attribute will be a list of values, even if only 1 value.",
- "category": "SAML",
- "category_slug": "saml",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "SOCIAL_AUTH_SAML_ORGANIZATION_MAP": {
- "type": "nested object",
- "label": "SAML Organization Map",
- "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.",
- "category": "SAML",
- "category_slug": "saml",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_SAML_TEAM_MAP": {
- "type": "nested object",
- "label": "SAML Team Map",
- "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in the documentation.",
- "category": "SAML",
- "category_slug": "saml",
- "defined_in_file": false,
- "child": {
- "type": "nested object",
- "child": {
- "type": "field",
- "required": true,
- "read_only": false
- }
- }
- },
- "SOCIAL_AUTH_SAML_ORGANIZATION_ATTR": {
- "type": "nested object",
- "label": "SAML Organization Attribute Mapping",
- "help_text": "Used to translate user organization membership.",
- "category": "SAML",
- "category_slug": "saml",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "SOCIAL_AUTH_SAML_TEAM_ATTR": {
- "type": "nested object",
- "label": "SAML Team Attribute Mapping",
- "help_text": "Used to translate user team membership.",
- "category": "SAML",
- "category_slug": "saml",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "SOCIAL_AUTH_SAML_USER_FLAGS_BY_ATTR": {
- "type": "nested object",
- "label": "SAML User Flags Attribute Mapping",
- "help_text": "Used to map super users and system auditors from SAML.",
- "category": "SAML",
- "category_slug": "saml",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "NAMED_URL_FORMATS": {
- "type": "nested object",
- "label": "Formats of all available named urls",
- "help_text": "Read-only list of key-value pairs that shows the standard format of all available named URLs.",
- "category": "Named URL",
- "category_slug": "named-url",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- },
- "NAMED_URL_GRAPH_NODES": {
- "type": "nested object",
- "label": "List of all named url graph nodes.",
- "help_text": "Read-only list of key-value pairs that exposes named URL graph topology. Use this list to programmatically generate named URLs for resources",
- "category": "Named URL",
- "category_slug": "named-url",
- "defined_in_file": false,
- "child": {
- "type": "field"
- }
- }
- }
- }
-}
diff --git a/awx/ui/src/screens/Setting/shared/data.allSettings.json b/awx/ui/src/screens/Setting/shared/data.allSettings.json
deleted file mode 100644
index e5136f4b5842..000000000000
--- a/awx/ui/src/screens/Setting/shared/data.allSettings.json
+++ /dev/null
@@ -1,310 +0,0 @@
-{
- "ACTIVITY_STREAM_ENABLED":true,
- "ACTIVITY_STREAM_ENABLED_FOR_INVENTORY_SYNC":false,
- "ORG_ADMINS_CAN_SEE_ALL_USERS":true,
- "MANAGE_ORGANIZATION_AUTH":true,
- "DISABLE_LOCAL_AUTH":false,
- "TOWER_URL_BASE":"https://localhost:3000",
- "REMOTE_HOST_HEADERS":["REMOTE_ADDR","REMOTE_HOST"],
- "PROXY_IP_ALLOWED_LIST":[],
- "LICENSE":{},
- "REDHAT_USERNAME":"",
- "REDHAT_PASSWORD":"",
- "AUTOMATION_ANALYTICS_URL":"https://example.com",
- "INSTALL_UUID":"3f5a4d68-3a94-474c-a3c0-f23a33122ce6",
- "CUSTOM_VENV_PATHS":[],
- "AD_HOC_COMMANDS":[
- "command",
- "shell",
- "yum",
- "apt",
- "apt_key",
- "apt_repository",
- "apt_rpm",
- "service",
- "group",
- "user",
- "mount",
- "ping",
- "selinux",
- "setup",
- "win_ping",
- "win_service",
- "win_updates",
- "win_group",
- "win_user"
- ],
- "ALLOW_JINJA_IN_EXTRA_VARS":"template",
- "AWX_ISOLATION_BASE_PATH":"/tmp",
- "AWX_ISOLATION_SHOW_PATHS":[],
- "AWX_TASK_ENV":{},
- "GALAXY_TASK_ENV": {
- "ANSIBLE_FORCE_COLOR": "false",
- "GIT_SSH_COMMAND": "ssh -o StrictHostKeyChecking=no"
- },
- "INSIGHTS_TRACKING_STATE":false,
- "PROJECT_UPDATE_VVV":false,
- "AWX_ROLES_ENABLED":true,
- "AWX_COLLECTIONS_ENABLED":true,
- "AWX_SHOW_PLAYBOOK_LINKS":false,
- "GALAXY_IGNORE_CERTS":false,
- "STDOUT_MAX_BYTES_DISPLAY":1048576,
- "EVENT_STDOUT_MAX_BYTES_DISPLAY":1024,
- "SCHEDULE_MAX_JOBS":10,
- "AWX_ANSIBLE_CALLBACK_PLUGINS":[],
- "DEFAULT_JOB_TIMEOUT":0,
- "DEFAULT_JOB_IDLE_TIMEOUT":0,
- "DEFAULT_INVENTORY_UPDATE_TIMEOUT":0,
- "DEFAULT_PROJECT_UPDATE_TIMEOUT":0,
- "ANSIBLE_FACT_CACHE_TIMEOUT":0,
- "MAX_FORKS":200,
- "LOG_AGGREGATOR_HOST":null,
- "LOG_AGGREGATOR_PORT":null,
- "LOG_AGGREGATOR_TYPE":null,
- "LOG_AGGREGATOR_USERNAME":"",
- "LOG_AGGREGATOR_PASSWORD":"",
- "LOG_AGGREGATOR_LOGGERS":["awx","activity_stream","job_events","system_tracking"],
- "LOG_AGGREGATOR_INDIVIDUAL_FACTS":false,
- "LOG_AGGREGATOR_ENABLED":true,
- "LOG_AGGREGATOR_TOWER_UUID":"",
- "LOG_AGGREGATOR_PROTOCOL":"https",
- "LOG_AGGREGATOR_TCP_TIMEOUT":5,
- "LOG_AGGREGATOR_VERIFY_CERT":true,
- "LOG_AGGREGATOR_LEVEL":"INFO",
- "LOG_AGGREGATOR_MAX_DISK_USAGE_GB":1,
- "LOG_AGGREGATOR_MAX_DISK_USAGE_PATH":"/var/lib/awx",
- "LOG_AGGREGATOR_RSYSLOGD_DEBUG":false,
- "API_400_ERROR_LOG_FORMAT":"status {status_code} received by user {user_name} attempting to access {url_path} from {remote_addr}",
- "AUTOMATION_ANALYTICS_LAST_GATHER":null,
- "AUTOMATION_ANALYTICS_GATHER_INTERVAL":14400,
- "SESSION_COOKIE_AGE":1800,
- "SESSIONS_PER_USER":-1,
- "AUTH_BASIC_ENABLED":true,
- "OAUTH2_PROVIDER":{
- "ACCESS_TOKEN_EXPIRE_SECONDS":31536000000,
- "REFRESH_TOKEN_EXPIRE_SECONDS":2628000,
- "AUTHORIZATION_CODE_EXPIRE_SECONDS":600
- },
- "ALLOW_OAUTH2_FOR_EXTERNAL_USERS":false,
- "LOGIN_REDIRECT_OVERRIDE":"",
- "PENDO_TRACKING_STATE":"off",
- "CUSTOM_LOGIN_INFO":"",
- "CUSTOM_LOGO":"",
- "MAX_UI_JOB_EVENTS":4000,
- "UI_LIVE_UPDATES_ENABLED":true,
- "AUTHENTICATION_BACKENDS":[
- "awx.sso.backends.LDAPBackend",
- "awx.sso.backends.RADIUSBackend",
- "awx.sso.backends.TACACSPlusBackend",
- "social_core.backends.github.GithubTeamOAuth2",
- "django.contrib.auth.backends.ModelBackend"
- ],
- "SOCIAL_AUTH_ORGANIZATION_MAP":null,
- "SOCIAL_AUTH_TEAM_MAP":null,
- "SOCIAL_AUTH_USER_FIELDS":null,
- "AUTH_LDAP_SERVER_URI":"ldap://ldap.example.com",
- "AUTH_LDAP_BIND_DN":"cn=eng_user1",
- "AUTH_LDAP_BIND_PASSWORD":"$encrypted$",
- "AUTH_LDAP_START_TLS":false,
- "AUTH_LDAP_CONNECTION_OPTIONS":{"OPT_REFERRALS":0,"OPT_NETWORK_TIMEOUT":30},
- "AUTH_LDAP_USER_SEARCH":[],
- "AUTH_LDAP_USER_DN_TEMPLATE":"uid=%(user)s,OU=Users,DC=example,DC=com",
- "AUTH_LDAP_USER_ATTR_MAP":{},
- "AUTH_LDAP_GROUP_SEARCH":["DC=example,DC=com","SCOPE_SUBTREE","(objectClass=group)"],
- "AUTH_LDAP_GROUP_TYPE":"MemberDNGroupType",
- "AUTH_LDAP_GROUP_TYPE_PARAMS":{"name_attr":"cn","member_attr":"member"},
- "AUTH_LDAP_REQUIRE_GROUP":"CN=Service Users,OU=Users,DC=example,DC=com",
- "AUTH_LDAP_DENY_GROUP":null,
- "AUTH_LDAP_USER_FLAGS_BY_GROUP":{"is_superuser":["cn=superusers"]},
- "AUTH_LDAP_ORGANIZATION_MAP":{},
- "AUTH_LDAP_TEAM_MAP":{},
- "AUTH_LDAP_1_SERVER_URI":"",
- "AUTH_LDAP_1_BIND_DN":"",
- "AUTH_LDAP_1_BIND_PASSWORD":"",
- "AUTH_LDAP_1_START_TLS":true,
- "AUTH_LDAP_1_CONNECTION_OPTIONS":{"OPT_REFERRALS":0,"OPT_NETWORK_TIMEOUT":30},
- "AUTH_LDAP_1_USER_SEARCH":[],
- "AUTH_LDAP_1_USER_DN_TEMPLATE":null,
- "AUTH_LDAP_1_USER_ATTR_MAP":{},
- "AUTH_LDAP_1_GROUP_SEARCH":[],
- "AUTH_LDAP_1_GROUP_TYPE":"MemberDNGroupType",
- "AUTH_LDAP_1_GROUP_TYPE_PARAMS":{"member_attr":"member","name_attr":"cn"},
- "AUTH_LDAP_1_REQUIRE_GROUP":null,
- "AUTH_LDAP_1_DENY_GROUP":"CN=Disabled1",
- "AUTH_LDAP_1_USER_FLAGS_BY_GROUP":{},
- "AUTH_LDAP_1_ORGANIZATION_MAP":{},
- "AUTH_LDAP_1_TEAM_MAP":{},
- "AUTH_LDAP_2_SERVER_URI":"",
- "AUTH_LDAP_2_BIND_DN":"",
- "AUTH_LDAP_2_BIND_PASSWORD":"",
- "AUTH_LDAP_2_START_TLS":false,
- "AUTH_LDAP_2_CONNECTION_OPTIONS":{"OPT_REFERRALS":0,"OPT_NETWORK_TIMEOUT":30},
- "AUTH_LDAP_2_USER_SEARCH":[],
- "AUTH_LDAP_2_USER_DN_TEMPLATE":null,
- "AUTH_LDAP_2_USER_ATTR_MAP":{},
- "AUTH_LDAP_2_GROUP_SEARCH":[],
- "AUTH_LDAP_2_GROUP_TYPE":"MemberDNGroupType",
- "AUTH_LDAP_2_GROUP_TYPE_PARAMS":{"member_attr":"member","name_attr":"cn"},
- "AUTH_LDAP_2_REQUIRE_GROUP":null,
- "AUTH_LDAP_2_DENY_GROUP":"CN=Disabled2",
- "AUTH_LDAP_2_USER_FLAGS_BY_GROUP":{},
- "AUTH_LDAP_2_ORGANIZATION_MAP":{},
- "AUTH_LDAP_2_TEAM_MAP":{},
- "AUTH_LDAP_3_SERVER_URI":"",
- "AUTH_LDAP_3_BIND_DN":"",
- "AUTH_LDAP_3_BIND_PASSWORD":"",
- "AUTH_LDAP_3_START_TLS":false,
- "AUTH_LDAP_3_CONNECTION_OPTIONS":{"OPT_REFERRALS":0,"OPT_NETWORK_TIMEOUT":30},
- "AUTH_LDAP_3_USER_SEARCH":[],
- "AUTH_LDAP_3_USER_DN_TEMPLATE":null,
- "AUTH_LDAP_3_USER_ATTR_MAP":{},
- "AUTH_LDAP_3_GROUP_SEARCH":[],
- "AUTH_LDAP_3_GROUP_TYPE":"MemberDNGroupType",
- "AUTH_LDAP_3_GROUP_TYPE_PARAMS":{"member_attr":"member","name_attr":"cn"},
- "AUTH_LDAP_3_REQUIRE_GROUP":null,
- "AUTH_LDAP_3_DENY_GROUP":null,
- "AUTH_LDAP_3_USER_FLAGS_BY_GROUP":{},
- "AUTH_LDAP_3_ORGANIZATION_MAP":{},
- "AUTH_LDAP_3_TEAM_MAP":{},
- "AUTH_LDAP_4_SERVER_URI":"",
- "AUTH_LDAP_4_BIND_DN":"",
- "AUTH_LDAP_4_BIND_PASSWORD":"",
- "AUTH_LDAP_4_START_TLS":false,
- "AUTH_LDAP_4_CONNECTION_OPTIONS":{"OPT_REFERRALS":0,"OPT_NETWORK_TIMEOUT":30},
- "AUTH_LDAP_4_USER_SEARCH":[],
- "AUTH_LDAP_4_USER_DN_TEMPLATE":null,
- "AUTH_LDAP_4_USER_ATTR_MAP":{},
- "AUTH_LDAP_4_GROUP_SEARCH":[],
- "AUTH_LDAP_4_GROUP_TYPE":"MemberDNGroupType",
- "AUTH_LDAP_4_GROUP_TYPE_PARAMS":{"member_attr":"member","name_attr":"cn"},
- "AUTH_LDAP_4_REQUIRE_GROUP":null,
- "AUTH_LDAP_4_DENY_GROUP":null,
- "AUTH_LDAP_4_USER_FLAGS_BY_GROUP":{},
- "AUTH_LDAP_4_ORGANIZATION_MAP":{},
- "AUTH_LDAP_4_TEAM_MAP":{},
- "AUTH_LDAP_5_SERVER_URI":"",
- "AUTH_LDAP_5_BIND_DN":"",
- "AUTH_LDAP_5_BIND_PASSWORD":"",
- "AUTH_LDAP_5_START_TLS":false,
- "AUTH_LDAP_5_CONNECTION_OPTIONS":{"OPT_REFERRALS":0,"OPT_NETWORK_TIMEOUT":30},
- "AUTH_LDAP_5_USER_SEARCH":[],
- "AUTH_LDAP_5_USER_DN_TEMPLATE":null,
- "AUTH_LDAP_5_USER_ATTR_MAP":{},
- "AUTH_LDAP_5_GROUP_SEARCH":[],
- "AUTH_LDAP_5_GROUP_TYPE":"MemberDNGroupType",
- "AUTH_LDAP_5_GROUP_TYPE_PARAMS":{"member_attr":"member","name_attr":"cn"},
- "AUTH_LDAP_5_REQUIRE_GROUP":null,
- "AUTH_LDAP_5_DENY_GROUP":null,
- "AUTH_LDAP_5_USER_FLAGS_BY_GROUP":{},
- "AUTH_LDAP_5_ORGANIZATION_MAP":{},
- "AUTH_LDAP_5_TEAM_MAP":{},
- "RADIUS_SERVER":"example.org",
- "RADIUS_PORT":1812,
- "RADIUS_SECRET":"$encrypted$",
- "TACACSPLUS_HOST":"",
- "TACACSPLUS_PORT":49,
- "TACACSPLUS_SECRET":"",
- "TACACSPLUS_SESSION_TIMEOUT":5,
- "TACACSPLUS_AUTH_PROTOCOL":"ascii",
- "SOCIAL_AUTH_GOOGLE_OAUTH2_CALLBACK_URL":"https://localhost:3000/sso/complete/google-oauth2/",
- "SOCIAL_AUTH_GOOGLE_OAUTH2_KEY":"",
- "SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET":"",
- "SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS":[],
- "SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS":{},
- "SOCIAL_AUTH_GOOGLE_OAUTH2_ORGANIZATION_MAP":null,
- "SOCIAL_AUTH_GOOGLE_OAUTH2_TEAM_MAP":null,
- "SOCIAL_AUTH_GITHUB_CALLBACK_URL":"https://localhost:3000/sso/complete/github/",
- "SOCIAL_AUTH_GITHUB_KEY":"",
- "SOCIAL_AUTH_GITHUB_SECRET":"",
- "SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP":null,
- "SOCIAL_AUTH_GITHUB_TEAM_MAP":null,
- "SOCIAL_AUTH_GITHUB_ORG_CALLBACK_URL":"https://localhost:3000/sso/complete/github-org/",
- "SOCIAL_AUTH_GITHUB_ORG_KEY":"",
- "SOCIAL_AUTH_GITHUB_ORG_SECRET":"",
- "SOCIAL_AUTH_GITHUB_ORG_NAME":"",
- "SOCIAL_AUTH_GITHUB_ORG_ORGANIZATION_MAP":null,
- "SOCIAL_AUTH_GITHUB_ORG_TEAM_MAP":null,
- "SOCIAL_AUTH_GITHUB_TEAM_CALLBACK_URL":"https://localhost:3000/sso/complete/github-team/",
- "SOCIAL_AUTH_GITHUB_TEAM_KEY":"OAuth2 key (Client ID)",
- "SOCIAL_AUTH_GITHUB_TEAM_SECRET":"$encrypted$",
- "SOCIAL_AUTH_GITHUB_TEAM_ID":"team_id",
- "SOCIAL_AUTH_GITHUB_TEAM_ORGANIZATION_MAP":{},
- "SOCIAL_AUTH_GITHUB_TEAM_TEAM_MAP":{},
- "SOCIAL_AUTH_AZUREAD_OAUTH2_CALLBACK_URL":"https://localhost:3000/sso/complete/azuread-oauth2/",
- "SOCIAL_AUTH_AZUREAD_OAUTH2_KEY":"",
- "SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET":"",
- "SOCIAL_AUTH_AZUREAD_OAUTH2_ORGANIZATION_MAP":null,
- "SOCIAL_AUTH_AZUREAD_OAUTH2_TEAM_MAP":null,
- "SAML_AUTO_CREATE_OBJECTS":true,
- "SOCIAL_AUTH_SAML_CALLBACK_URL":"https://localhost:3000/sso/complete/saml/",
- "SOCIAL_AUTH_SAML_METADATA_URL":"https://localhost:3000/sso/metadata/saml/",
- "SOCIAL_AUTH_SAML_SP_ENTITY_ID":"",
- "SOCIAL_AUTH_SAML_SP_PUBLIC_CERT":"",
- "SOCIAL_AUTH_SAML_SP_PRIVATE_KEY":"",
- "SOCIAL_AUTH_SAML_ORG_INFO":{},
- "SOCIAL_AUTH_SAML_TECHNICAL_CONTACT":{},
- "SOCIAL_AUTH_SAML_SUPPORT_CONTACT":{},
- "SOCIAL_AUTH_SAML_ENABLED_IDPS":{},
- "SOCIAL_AUTH_SAML_SECURITY_CONFIG":{"requestedAuthnContext":false},
- "SOCIAL_AUTH_SAML_SP_EXTRA":null,
- "SOCIAL_AUTH_SAML_EXTRA_DATA":null,
- "SOCIAL_AUTH_SAML_ORGANIZATION_MAP":null,
- "SOCIAL_AUTH_SAML_TEAM_MAP":null,
- "SOCIAL_AUTH_SAML_ORGANIZATION_ATTR":{},
- "SOCIAL_AUTH_SAML_TEAM_ATTR":{},
- "SOCIAL_AUTH_SAML_USER_FLAGS_BY_ATTR":{},
- "SOCIAL_AUTH_OIDC_KEY":"",
- "SOCIAL_AUTH_OIDC_SECRET":"",
- "SOCIAL_AUTH_OIDC_OIDC_ENDPOINT":"",
- "SOCIAL_AUTH_OIDC_VERIFY_SSL":true,
- "NAMED_URL_FORMATS":{
- "organizations":"",
- "teams":"++",
- "credential_types":"+",
- "credentials":"+++++",
- "notification_templates":"++",
- "job_templates":"++",
- "projects":"++",
- "inventories":"++",
- "hosts":"++